Я думаю, у вас есть несколько вариантов, прежде чем пытаться 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 для идентификатора
Оттуда я могу просто фильтровать те записи, которые удовлетворяют тому, что я искал изначально, используя COALESCE для упрощения моего фильтра.
[td].[Udate] = COALESCE([td].[AttemptUdate], [td].[MaxUdate]);
Используйте AttemptUdate, если оно не равно NULL, затем используйте MaxUdate.