【问题标题】:Conditional Row_Number() for min and maximum date最小和最大日期的条件 Row_Number()
【发布时间】:2019-04-17 22:32:24
【问题描述】:

我有一个表格,其中包含如下所示的数据:

表 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 不是很熟悉,我认为这是可能需要一个的查询之一..

谢谢。

【问题讨论】:

  • 我不确定 ROW_NUMBER() 与您正在尝试的东西在哪里发挥作用。如果需要并且您澄清,如果这不能满足您的需要,我可以在下面调整我的答案。
  • 我使用的是 ROW_NUMBER () 因为起初我没有意识到可能有多个“ATTEMPT”代码。它比最大和最小日期的内部连接要快得多。

标签: tsql date conditional common-table-expression row-number


【解决方案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]);

为了稍微解释一下我是如何到达那里的,主要是根据您的要求:

总的来说,我正在尝试获取最后的日期和代码,但如果有 “尝试”代码,我需要为每个日期获取第一个日期和该代码 个人身份证。

因此,对于每个 ID,我都需要一种获取方式:

  • 每个 ID 的 last_code = 'ATTEMPT' 的最小 Udate - 如果没有 ATTEMPT,我们将得到一个空值
  • 每个 ID 的所有记录的最大 Udate

如果我可以根据 ID 确定每条记录的上述内容,那么我的最终结果集基本上是 Udate 等于我的最大 Udate(如果最小值为空)的结果集。如果最小值不为空,请改用它。

第一个选项,使用 2 个外部应用是执行上述每个点。

每个 ID 的 last_code 的最小 Udate = '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]

外部应用,因为我可能没有给定 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 的最小 Udate = 'ATTEMPT' - 如果没有 ATTEMPT,我们将得到一个空值:

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

Udate 上的最小值,但我使用案例语句来评估该记录是否为 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 来确定我应该使用哪个日期并只返回我想要的记录。

使用第二个选项,再深入一点,如果您只运行子查询,您会看到为每个单独的记录获得需求的 2 个主要驱动点:

  • 每个 ID 的最大 Udate 是多少
  • last_code=ATTEMPT per ID 的 mint Udate 是多少

从那里我可以过滤那些满足我最初寻找的记录,使用 COALESCE 来简化我的过滤器。

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

使用 AttemptUdate,除非它为 NULL,然后使用 MaxUdate。

【讨论】:

  • 效果很好!!!我有一些重复,但这是我的数据,而不是您的查询。谢谢+1。
  • 另外,如果你能简要解释一下这个的思考过程,我将不胜感激,我想看看我错过了什么。谢谢。
  • @madlicksxxx 你打赌!我更新了答案并添加了我的思考过程以及我的工作方式。希望对您有所帮助。
猜你喜欢
  • 1970-01-01
  • 2020-08-29
  • 2012-07-16
  • 1970-01-01
  • 1970-01-01
  • 2013-11-29
  • 2010-12-27
  • 1970-01-01
  • 2012-09-05
相关资源
最近更新 更多