【问题标题】:Lag() with condition in sql serverLag() 与 sql server 中的条件
【发布时间】:2014-03-14 02:22:52
【问题描述】:

我有一张这样的桌子:

Number   Price    Type       Date         Time
------   -----    ----    ----------    ---------
23456    0,665     SV     2014/02/02     08:00:02
23457    1,3       EC     2014/02/02     07:50:45
23460    0,668     SV     2014/02/02     07:36:34

对于每个 EC,我需要上一个/下一个 SV 价格。在这种情况下,查询很简单。

Select Lag(price, 1, price) over (order by date desc, time desc),
Lead(price, 1, price) over (order by date desc, time desc)
from ITEMS

但是,有一些特殊情况,两行或多行是 EC 类型:

Number   Price    Type       Date         Time
------   -----    ----    ----------    ---------
23456    0,665     SV     2014/02/02     08:00:02
23457    1,3       EC     2014/02/02     07:50:45
23658    2,4       EC     2014/02/02     07:50:45
23660    2,4       EC     2014/02/02     07:50:48
23465    0,668     SV     2014/02/02     07:36:34 

在这种情况下我可以使用领先/滞后吗?如果没有,我是否必须使用子查询?

【问题讨论】:

    标签: sql-server lag lead


    【解决方案1】:

    您的问题(以及 Anon 的出色回答)是 the SQL of islands and gaps 的一部分。在这个答案中,我将尝试详细检查“row_number() 魔法”。

    我根据球赛中的事件做了一个简单的例子。对于每个事件,我们希望打印上一季度和下一季度的相关消息:

    create table TestTable (id int identity, event varchar(64));
    insert TestTable values
        ('Start of Q1'),
        ('Free kick'),
        ('Goal'),
        ('End of Q1'),
        ('Start of Q2'),
        ('Penalty'),
        ('Miss'),
        ('Yellow card'),
        ('End of Q2');
    

    这是一个展示“row_number() 魔术”方法的查询:

    ; with  grouped as
            (
            select  *
            ,       row_number() over (order by id) as rn1
            ,       row_number() over (
                        partition by case when event like '%of Q[1-4]' then 1 end 
                        order by id) as rn2
            from    TestTable
            )
    ,       order_in_group as
            (
            select  *
            ,       rn1-rn2 as group_nr
            ,       row_number() over (partition by rn1-rn2 order by id) as rank_asc
            ,       row_number() over (partition by rn1-rn2 order by id desc)
                        as rank_desc
            from    grouped
            )
    select  *
    ,       lag(event, rank_asc) over (order by id) as last_event_of_prev_group
    ,       lead(event, rank_desc) over (order by id) as first_event_of_next_group
    from    order_in_group
    order by
            id
    
    • 称为“分组”的第一个 CTE 计算两个 row_number()s。第一个是表中每一行的1 2 3。第二个row_number() 将暂停通知放在一个列表中,将其他事件放在第二个列表中。两者之间的区别rn1 - rn2,对于游戏的每个部分都是独一无二的。检查示例输出中的差异很有帮助:它位于 group_nr 列中。您会看到每个值对应于游戏的一个部分。
    • 称为“order_in_group”的第二个 CTE 确定当前行在其岛或间隙内的位置。对于 3 行的岛,位置为1 2 3 为升序,3 2 1 为降序。
    • 最后,我们知道足以告诉lag()lead() 跳多远。我们必须滞后rank_asc 行才能找到上一节的最后一行。要找到下一部分的第一行,我们必须引导rank_desc 行。

    希望这有助于阐明“峡谷”和“岛屿”的“魔力”。 Here is a working example at SQL Fiddle.

    【讨论】:

      【解决方案2】:

      是的,您可以使用 LEAD/LAG。您只需要使用一点 ROW_NUMBER() 魔法预先计算跳多远。

      DECLARE @a TABLE ( number int, price money, type varchar(2),
                         date date, time time)
      INSERT @a VALUES
      (23456,0.665,'SV','2014/02/02','08:00:02'),
      (23457,1.3  ,'EC','2014/02/02','07:50:45'),
      (23658,2.4  ,'EC','2014/02/02','07:50:45'),
      (23660,2.4  ,'EC','2014/02/02','07:50:48'),
      (23465,0.668,'SV','2014/02/02','07:36:34');
      
      ; WITH a AS (
           SELECT *,
                  ROW_NUMBER() OVER(ORDER BY [date] DESC, [time] DESC) x, 
                  ROW_NUMBER() OVER(PARTITION BY 
                     CASE [type] WHEN 'SV' THEN 1 ELSE 0 END 
                     ORDER BY [date] DESC, [time] DESC) y 
           FROM @a)
       , b AS (
           SELECT *,
                  ROW_NUMBER() OVER(PARTITION BY x-y ORDER BY x ASC) z1,
                  ROW_NUMBER() OVER(PARTITION BY x-y ORDER BY x DESC) z2 
           FROM a)
      SELECT *,
             CASE [type] WHEN 'SV' THEN 
                 LAG(price,z1,price) OVER(PARTITION BY [type] ORDER BY x) 
                 ELSE LAG(price,z1,price) OVER(ORDER BY x) 
                 END,
             CASE [type] WHEN 'SV' THEN 
                 LEAD(price,z2,price) OVER(PARTITION BY [type] ORDER BY x) 
                 ELSE LEAD(price,z2,price) OVER(ORDER BY x) 
                 END
      FROM b
      ORDER BY x
      

      【讨论】:

      • 很好的解决方案,但我的 RDBMS (Netezza) 抱怨前导函数的偏移量必须是“正整数常量”。
      【解决方案3】:

      这是实现相同结果的另一种方法,但使用对序数进行窗口化的条件 max/min 函数。可以根据适合目的的任何列设置序号,但在这种情况下,我相信 OP 打算将它们设置为 DateTime

      DROP TABLE IF EXISTS #t;
      CREATE TABLE #t (
          Number INT, 
          Price MONEY, 
          Type CHAR(2),
          Date DATE,
          Time TIME(0)
      );
      INSERT INTO #t VALUES 
      (23456, 0.666, 'SV', '2014/02/02', '10:00:02'),
      (23457, 1.4  , 'EC', '2014/02/02', '09:50:45'),
      (23658, 2.5  , 'EC', '2014/02/02', '09:50:45'),
      (23660, 2.5  , 'EC', '2014/02/02', '09:50:48'),
      (23465, 0.669, 'SV', '2014/02/02', '09:36:34'),
      (23456, 0.665, 'SV', '2014/02/02', '08:00:02'),
      (23457, 1.3  , 'EC', '2014/02/02', '07:50:45'),
      (23658, 2.4  , 'EC', '2014/02/02', '07:50:45'),
      (23660, 2.4  , 'EC', '2014/02/02', '07:50:48'),
      (23465, 0.668, 'SV', '2014/02/02', '07:36:34'), -- which one of these?
      (23465, 0.670, 'SV', '2014/02/02', '07:36:34'); -- 
      
      WITH time_ordered AS (
          SELECT *, DENSE_RANK() OVER (ORDER BY Date, Time) AS ordinal FROM #t
      )
      SELECT
          *,
          CASE WHEN Type = 'EC' 
          THEN MAX(CASE WHEN ordinal = preceding_non_EC_ordinal THEN Price END) 
          OVER (PARTITION BY preceding_non_EC_ordinal ORDER BY ordinal ASC) END AS preceding_price,
          CASE WHEN Type = 'EC'
          THEN MIN(CASE WHEN ordinal = following_non_EC_ordinal THEN Price END) 
          OVER (PARTITION BY following_non_EC_ordinal ORDER BY ordinal DESC) END AS following_price
      FROM (
          SELECT
              *,
              MAX(CASE WHEN Type <> 'EC' THEN ordinal END) 
              OVER (ORDER BY ordinal ASC) AS preceding_non_EC_ordinal,
              MIN(CASE WHEN Type <> 'EC' THEN ordinal END) 
              OVER (ORDER BY ordinal DESC) AS following_non_EC_ordinal
          FROM time_ordered
      ) t
      ORDER BY Date, Time
      

      请注意,OP 给出的示例已被扩展,以表明EC 的散布序列产生了预期的结果。在这种情况下,由 SV 类型的最早的两个连续行引入的歧义将导致选择最大值。设置序号以包含 Price 是改变这种行为的一种可能方法。

      可以在此处找到 SQLFiddle:http://sqlfiddle.com/#!18/85117/1

      【讨论】:

        猜你喜欢
        • 2018-12-12
        • 1970-01-01
        • 2014-04-09
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2020-05-23
        相关资源
        最近更新 更多