16

我有一张这样的桌子:

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 

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

4

3 回答 3

13

您的问题(以及 Anon 的出色回答)是 island and gaps 的 SQL 的一部分。在这个答案中,我将尝试详细检查“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行。

希望这有助于澄清差距和岛屿的“魔力”。 这是 SQL Fiddle 的一个工作示例。

于 2014-02-14T18:04:07.377 回答
8

Yes, you can use LEAD/LAG. You just need to precalculate how far to jump with a little ROW_NUMBER() magic.

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
于 2014-02-14T17:19:32.517 回答
1

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

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

于 2019-05-28T09:12:10.240 回答