Как выбрать ближайшее значение даты в SQL, используя выражения CASE WHEN? - PullRequest
1 голос
/ 07 апреля 2020

Допустим, у меня есть две таблицы, в которых я пытаюсь сравнить значения дат. По сути, я хотел бы написать набор выражений CASE WHEN, которые будут просеиваться через два столбца даты (обычный столбец даты и столбец оценочной даты) для каждого идентификатора пользователя. Сравнивая эти два столбца даты, я хочу установить sh условия, при которых, если значение даты в столбце обычной даты не равно значению даты оценки, выберите ближайшее значение даты оценки, предшествующее столбцу обычной даты. Второе условие заключается в том, что значения обычной даты и значения оценочной даты равны равному числу дней (например, если одно значение оценочной даты находится за два дня до значения обычной даты, а другое значение оценочной даты - через два дня после обычной даты). выберите значение даты оценки, предшествующее значению обычной даты).

В настоящее время настроено так:

SELECT t1.userID,

--if dates are equal, select the date that matches
CASE WHEN t1.ASSESS_DATE = t2.REG_DATE
THEN (SELECT TOP 1 ASSESS_DATE FROM DB_1.dbo.Table_A -- only want to return one record for each date value
      WHERE userID = t2.userID
      AND ASSESS_DATE = t2.REG_DATE
      ORDER BY REG_DATE ASC) -- not sure if this is needed

   --if ASSESS_DATE value is less than or greater than the REG_DATE
   --choose nearest ASSESS_DATE value that is before the REG_DATE
   ELSE CASE WHEN t1.ASSESS_DATE <= t2.REG_DATE OR t1.ASSESS_DATE >= t2.REG_DATE
   THEN (SELECT TOP 1 ASSESS_DATE FROM DB_1.dbo.Table_A
         WHERE userID = t2.userID
         AND ASSESS_DATE <= t2.REG_DATE
         ORDER BY ASSESS_DATE ASC)

         -- if two ASSESS_DATES are an equal number of days apart from the REG_DATE, 
         --choose the ASSESS_DATE value that comes before the REG_DATE
         ELSE CASE WHEN DATEDIFF(DAY, t2.REG_DATE, t1.ASSESS_DATE) = ABS(DATEDIFF(DAY, t1.ASSESS_DATE, t2.REG_DATE)) 
         THEN (SELECT TOP 1 ASSESS_DATE FROM DB_1.dbo.Table_A
               WHERE userID = t2.userID
               ORDER BY ASSESS_DATE ASC)
         END
    END
END AS ASSESS_DATE, t2.REG_DATE
FROM 
(SELECT userID, ASSESS_DATE 
 FROM DB_1.dbo.Table_A) t1 

INNER JOIN
(SELECT userID, REG_DATE
 FROM DB_2.dbo.Table_B) t2
 ON (t1.userID = t2.userID)

- Примеры таблиц

Таблица A:

 userID    ASSESS_DATE
---------|-------------
    1      2017-01-04
    2      2017-03-14
    3      2018-05-23
    4      2016-07-03
    4      2016-07-09
    5      2019-04-28
    6      2016-10-10
    7      2018-11-19

Таблица B:

 userID    REG_DATE
---------|-------------
    1      2017-01-04
    2      2017-03-14
    3      2018-05-28
    4      2016-07-06
    5      2019-05-03
    6      2016-10-04
    7      2018-12-05

Как показано выше, userID's 1 и 2 имеют одинаковые значения ASSESS_DATE и REG_DATE, поэтому их легко сопоставить. userID 3 имеет значение ASSESS_DATE до значения REG_DATE. Значение ASSESS_DATE должно быть связано со значением REG_DATE. userID 4 имеет два значения ASSESS_DATE, оба из которых равны количеству дней, кроме REG_DATE (три дня до и три дня после). Я хочу связать значение ASSESS_DATE, которое приходит ДО значения REG_DATE. userID 5 имеет значение ASSESS_DATE, которое предшествует значению REG_DATE. userID 6 имеет значение ASSESS_DATE, которое следует после значения REG_DATE. Поскольку это единственное значение ASSESS_DATE, записанное для этого идентификатора пользователя, запрос должен выбрать это значение ASSESS_DATE. Наконец, userID 7 имеет значение ASSESS_DATE до REG_DATE, поэтому соедините это значение даты со значением REG_DATE.

Мне бы хотелось, чтобы финальный стол выглядел следующим образом:

 userID   ASSESS_DATE    REG_DATE
--------|-------------|-------------|
   1      2017-01-04    2017-01-04 -- Values are equal
   2      2017-03-14    2017-03-14
   3      2018-05-23    2018-05-28 -- ASSESS_DATE is prior to REG_DATE
   4      2016-07-03    2016-07-06 -- Choose the ASSESS_DATE value that comes before REG_DATE
   5      2019-04-28    2019-05-03
   6      2016-10-10    2016-10-04
   7      2018-11-19    2018-12-05

В этом его суть. Я надеюсь, что за запросом легко следовать с учетом комментариев. Спасибо за любую предоставленную помощь.

1 Ответ

1 голос
/ 07 апреля 2020

В такой ситуации оконная функция может быть вашим лучшим другом. Множество поддерживаемых функций в сочетании с умным использованием разбиения и упорядочения могут дать вам необходимые результаты без сложных логик c или длинного запроса.

В приведенном ниже примере создается CTE, который объединяет два таблиц, а затем добавляет пару дополнительных значений, которые будут использоваться в оконной функции. Эти значения представляют собой разность дат в днях от REG_DATE до ASSESS_DATE и абсолютное значение разницы. Затем в основном запросе используется функция FIRST_VALUE, которая разбивает все результаты по userId и REG_DATE и сортирует их по абсолютной разнице, а затем по нормальной разнице. Сортировка гарантирует, что результатом будет дата с наименьшей разницей, а при совпадении нескольких результатов будет возвращена та, что была в прошлом. Наконец, используется отличный, потому что, в отличие от обычного агрегата, для каждого результата будет один результат.

/** SETUP **/
DECLARE @Table_A TABLE ( [userID] int NOT NULL, [ASSESS_DATE] DATE NOT NULL );
DECLARE @Table_B TABLE ( [userID] int NOT NULL, [REG_DATE] DATE NOT NULL );
INSERT INTO @Table_A ( [userID],[ASSESS_DATE] )
VALUES (1, '2017-01-04'), (2, '2017-03-14'), (3, '2018-05-23'), (4, '2016-07-03'), (4, '2016-07-09'),   (5, '2019-04-28'),  (5, '2019-05-10'),  (6, '2016-10-10'),  (7, '2018-11-19');
INSERT INTO @Table_B ( [userID],[REG_DATE] )
VALUES (1, N'2017-01-04'), (2, N'2017-03-14'), (3, N'2018-05-28'), (4, N'2016-07-06'), (5, N'2019-05-03'), (6, N'2016-10-04'), (7, N'2018-12-05');

/** ANSWER **/
WITH src AS (
    SELECT b.*, a.ASSESS_DATE, diff, absDiff
    FROM @Table_B b
    INNER JOIN @Table_A a ON a.userID = b.userID
    CROSS APPLY (SELECT DATEDIFF(DAY, b.REG_DATE, a.ASSESS_DATE) diff) forDiff
    CROSS APPLY (SELECT ABS(forDiff.diff) absDiff) forAbsDiff
)
SELECT DISTINCT userID
    , FIRST_VALUE(ASSESS_DATE) OVER 
         (PARTITION BY userId, REG_DATE ORDER BY absDiff, diff) BestAssessDate, REG_DATE
FROM src
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...