Условный Row_Number () для минимальной и максимальной даты - PullRequest
0 голосов
/ 15 ноября 2018

У меня есть таблица с данными, которая выглядит следующим образом:

Таблица T1

+----+------------+------------+

| ID |   Udate    | last_code  | 
+----+------------+------------+
|  1 | 05/11/2018 | ATTEMPT    |
|  1 | 03/11/2018 | ATTEMPT    |
|  1 | 01/11/2017 | INFO       |
|  1 | 25/10/2016 | ARRIVED    |
|  1 | 22/9/2016  | ARRIVED    |
|  1 | 14/9/2016  |   SENT     | 
|  1 | 1/9/2016   |   SENT     |
+----+------------+------------+
|  2 | 26/10/2016 | RECEIVED   | 
|  2 | 19/10/2016 | ARRIVED    | 
|  2 | 18/10/2016 | ARRIVED    |
|  2 | 14/10/2016 | ANNOUNCED  | 
|  2 | 23/9/2016  | INFO       | 
|  2 | 14/9/2016  | DAMAGE     |
|  2 | 2/9/2016   | SCHEDULED  | 
+----+------------+------------+

У каждого идентификатора есть несколько кодов в разные даты, и для них нет шаблона.

В общем, я пытаюсь получить последнюю дату и код, но , если есть код «ATTEMPT», мне нужно получить первую дату и этот код для каждого отдельного идентификатора.Исходя из приведенной выше таблицы, я получу:

+----+------------+------------+

| ID |   Udate    | last_code  | 
|  1 | 03/11/2018 | ATTEMPT    |
|  2 | 26/10/2016 | RECEIVED   | 
+----+------------+------------+

Я пытался

ROW_NUMBER() OVER (PARTITION BY ID
                                ORDER BY 
                                    (CASE WHEN code = 'ATTEMPT' THEN u_date END) ASC,
                                    (CASE WHEN code_key <> 'ATTEMPT' THEN u_date END) DESC
                                ) as RN

И в тот момент, когда я застрял после того, как я дважды использовал ROW_NUMBER (), но могуЯ не могу придумать, как привести их все в одну таблицу.

,ROW_NUMBER() OVER (PARTITION BY id, code order by udate asc) as RN1
,ROW_NUMBER() OVER (PARTITION BY id order by udate desc) AS RN2

Я не очень знаком с CTE, и я думаю, что это один из тех запросов, который требует один, возможно ..

Спасибо.

1 Ответ

0 голосов
/ 15 ноября 2018

Я думаю, у вас есть несколько вариантов, прежде чем пытаться CTE.

Попробуйте, примеры ниже:

DECLARE @TestData TABLE
    (
        [ID] INT
      , [Udate] DATE
      , [last_code] NVARCHAR(100)
    );

INSERT INTO @TestData (
                          [ID]
                        , [Udate]
                        , [last_code]
                      )
VALUES ( 1, '11/05/2018', 'ATTEMPT  ' )
     , ( 1, '11/03/2018', 'ATTEMPT' )
     , ( 1, '11/01/2017', 'INFO' )
     , ( 1, '10/25/2016', 'ARRIVED' )
     , ( 1, '9/22/2016 ', 'ARRIVED' )
     , ( 1, '9/14/2016 ', 'SENT' )
     , ( 1, '9/1/2016  ', 'SENT' )
     , ( 2, '10/26/2016', 'RECEIVED' )
     , ( 2, '10/19/2016', 'ARRIVED' )
     , ( 2, '10/18/2016', 'ARRIVED' )
     , ( 2, '10/14/2016', 'ANNOUNCED' )
     , ( 2, '9/23/2016 ', 'INFO' )
     , ( 2, '9/14/2016 ', 'DAMAGE' )
     , ( 2, '9/2/2016  ', 'SCHEDULED' );

--option 1
--couple of outer apply
--1 - to get the min date for attempt
--2 - to get the max date regardless of the the code
--where clause, using coalesce will pick what date.  Use the date if I have one for code ='ATTEMPT', if not use the max date.
SELECT      [a].*
FROM        @TestData [a]
OUTER APPLY (
                SELECT   [b].[ID]
                       , MIN([b].[Udate]) AS [AttemptUdate]
                FROM     @TestData [b]
                WHERE    [b].[ID] = [a].[ID]
                         AND [b].[last_code] = 'ATTEMPT'
                GROUP BY [b].[ID]
            ) AS [aa]
OUTER APPLY (
                SELECT   [c].[ID]
                       , MAX([c].[Udate]) AS [MaxUdate]
                FROM     @TestData [c]
                WHERE    [c].[ID] = [a].[ID]
                GROUP BY [c].[ID]
            ) AS [cc]
WHERE       [a].[ID] = COALESCE([aa].[ID], [cc].[ID])
            AND [a].[Udate] = COALESCE([aa].[AttemptUdate], [cc].[MaxUdate]);


--use window functions
--Similiar in that we are finding the max Udate and also min Udate when last_code='ATTEMPT'
--Then using COALESCE in the where clause to evaluate which one to use.
--Maybe a little cleaner
SELECT [td].[ID]
     , [td].[Udate]
     , [td].[last_code]
FROM   (
           SELECT [ID]
                , [last_code]
                , [Udate]
                , MAX([Udate]) OVER ( PARTITION BY [ID] ) AS [MaxUdate]
                , MIN(   CASE WHEN [last_code] = 'ATTEMPT' THEN [Udate]
                              ELSE NULL
                         END
                     ) OVER ( PARTITION BY [ID] ) AS [AttemptUdate]
           FROM   @TestData
       ) AS [td]
WHERE  [td].[Udate] = COALESCE([td].[AttemptUdate], [td].[MaxUdate]);

Чтобы объяснить, как я туда попал, это было в основном основано на вашем требовании:

В целом я пытаюсь получить последнюю дату и код, но если есть Код «ПОПЫТКА», мне нужно получить первую дату и этот код для каждого индивидуальный идентификатор.

Так что для каждого удостоверения личности мне нужен был способ получить:

  • Минимальное Udate для last_code = 'ATTEMPT' для идентификатора - если не было ATTEMPT, мы получим ноль
  • Максимальное время обновления для всех записей для каждого идентификатора

Если бы я мог определить вышеупомянутое для каждой записи на основе идентификатора, то мой окончательный набор результатов в основном те, в которых Udate равен моему Maximum Udate, если Minimum был нулевым. Если Минимум не был нулевым, используйте его вместо этого.

Первый вариант с использованием 2 внешних применений - это выполнение каждого из пунктов выше.

Минимальное Udate для last_code = 'ATTEMPT' для идентификатора - если не было ATTEMPT, мы получим ноль:

OUTER APPLY (
                SELECT   [b].[ID]
                       , MIN([b].[Udate]) AS [AttemptUdate]
                FROM     @TestData [b]
                WHERE    [b].[ID] = [a].[ID]
                         AND [b].[last_code] = 'ATTEMPT'
                GROUP BY [b].[ID]
            ) AS [aa]

Outer Apply, поскольку у меня может не быть записи ATTEMPT для данного идентификатора, поэтому в этих ситуациях возвращается NULL.

Максимальное время обновления для всех записей на один идентификатор:

OUTER APPLY (
                SELECT   [c].[ID]
                       , MAX([c].[Udate]) AS [MaxUdate]
                FROM     @TestData [c]
                WHERE    [c].[ID] = [a].[ID]
                GROUP BY [c].[ID]
            ) AS [cc]

Затем предложение where сравнивает то, что было возвращено теми, кто возвращает только те записи, которые я хочу:

    [a].[Udate] = COALESCE([aa].[AttemptUdate], [cc].[MaxUdate]);

Я использую COALESCE для обработки и оценки NULL. COALESCE оценит поля слева направо и будет использовать / возвращать первое ненулевое значение.

Таким образом, используя это с Udate, мы можем оценить, какое значение Udate я должен использовать в своем фильтре для удовлетворения требования.

Потому что, если бы у меня было поле записи ATTEMPT, AttemptUdate имел бы значение и использовался бы сначала в фильтре. Если у меня не будет записи ATTEMPT, AttemptUdate будет иметь значение NULL, поэтому будет использоваться MaxUdate.

Для варианта 2 аналогично, просто после него немного по-другому.

Минимальное Udate для last_code = 'ATTEMPT' для идентификатора - если не было ATTEMPT, мы получим ноль:

        MIN(   CASE WHEN [last_code] = 'ATTEMPT' THEN [Udate]
                      ELSE NULL
                 END
             ) OVER ( PARTITION BY [ID] ) AS [AttemptUdate]

Мин. На Udate, но я использую оператор case, чтобы оценить, является ли эта запись попыткой или нет. использование OVER PARTITION сделает это в зависимости от того, как я скажу разделить данные по идентификатору.

Максимальное время обновления для всех записей на один идентификатор:

MAX([Udate]) OVER ( PARTITION BY [ID] ) AS [MaxUdate]

Иди и принеси мне максимальное количество обновлений на основе идентификатора, поскольку я так и сказал разделить его.

Я делаю все это в подзапросе, чтобы облегчить работу с предложением where. Тогда то же самое, что и при фильтрации:

[td].[Udate] = COALESCE([td].[AttemptUdate], [td].[MaxUdate]);

Использование COALESCE, чтобы определить, какую дату мне следует использовать, и вернуть только те записи, которые мне нужны.

Со вторым вариантом, пойдите немного глубже. Если вы выполните только подзапрос, вы увидите, что для каждой отдельной записи вы получаете 2 основных движущих момента требования:

  • Какое максимальное количество обновлений для идентификатора
  • Что такое Mint Udate из last_code = ATTEMPT для идентификатора

enter image description here

Оттуда я могу просто фильтровать те записи, которые удовлетворяют тому, что я искал изначально, используя COALESCE для упрощения моего фильтра.

[td].[Udate] = COALESCE([td].[AttemptUdate], [td].[MaxUdate]);

Используйте AttemptUdate, если оно не равно NULL, затем используйте MaxUdate.

...