我有一个表,其中的数据看起来像这样:
表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 |
+----+------------+------------+
每个id在不同的日期都有多个代码,并且没有模式。
总的来说,我正在尝试获取最后的日期和代码,但如果有一个“ ATTEMPT”代码,我需要获取每个个人ID的第一个日期和该代码。根据上表,我将得到:
+----+------------+------------+
| 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不太熟悉,我认为这是可能需要其中一项的查询之一。
谢谢。
答案 0 :(得分:1)
我认为在尝试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]);
要解释我如何到达那里,这主要取决于您的要求:
总的来说,我正在尝试获取最后的日期和代码,但是如果有 “ ATTEMPT”代码,我需要获取第一个日期以及每个代码的日期 个人ID。
因此,对于每个ID,我都需要一种获取方法:
如果我可以基于ID确定每个记录的上述条件,那么我的最终结果集基本上就是那些在最小值为空的情况下Udate等于我的Maximum Udate的结果集。如果Minimum不为null,请改用该值。
使用2个外部套用的第一个选项是完成上述每个要点。
每个ID的last_code ='ATTEMPT'的最小Udate-如果没有ATTEMPT,我们将得到一个null:
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]
外部申请,因为我可能没有给定ID的ATTEMPT记录,因此在这种情况下,它返回NULL。
每个ID的所有记录的最大Udate:
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将从左到右评估这些字段,并使用/返回第一个非NULL值。
因此,将其与Udate一起使用,我们可以评估应在过滤器中使用哪个Udate值以满足要求。
因为如果我有一个ATTEMPT记录字段,则AttemptUdate将具有一个值,并首先在过滤器中使用。如果我没有ATTEMPT记录,则AttemptUdate将为NULL,因此将使用MaxUdate。
对于选项2,相似之处只是有所不同。
每个ID的last_code ='ATTEMPT'的最小Udate-如果没有ATTEMPT,我们将得到一个null:
MIN( CASE WHEN [last_code] = 'ATTEMPT' THEN [Udate]
ELSE NULL
END
) OVER ( PARTITION BY [ID] ) AS [AttemptUdate]
关于Udate的最小值,但是我使用了一条case语句来评估该记录是否为ATTEMPT。使用OVER PARTITION会根据我告诉它按ID对数据进行分区的方式来做到这一点。
每个ID的所有记录的最大Udate:
MAX([Udate]) OVER ( PARTITION BY [ID] ) AS [MaxUdate]
根据ID告诉我最大的Udate,因为这就是我告诉它进行分区的方式。
我在子查询中做了所有这些操作,以使where子句更易于使用。然后与以前的过滤相同:
[td].[Udate] = COALESCE([td].[AttemptUdate], [td].[MaxUdate]);
使用COALESCE确定我应该使用的日期,并且只返回我想要的记录。
使用第二个选项,可以进行更深入的研究。如果仅运行子查询,您将看到为每个记录获取需求的两个主要驱动点:
从那里,我可以使用COALESCE简化我的过滤器,从而对那些符合我最初寻找条件的记录进行过滤。
[td].[Udate] = COALESCE([td].[AttemptUdate], [td].[MaxUdate]);
使用AttemptUdate,除非它为NULL,然后使用MaxUdate。