web-dev-qa-db-ja.com

SQL Serverの条件付きのLag()

私はこのようなテーブルを持っています:

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

ただし、2つ以上の行が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 

この場合、リード/ラグを使用できますか?そうでない場合、サブクエリを使用する必要がありましたか?

12
user3292586

あなたの質問(そしてアノンの優れた答え)は 島とギャップの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は、2つのrow_number()を計算します。 1つ目は、テーブルの各行の_1 2 3_です。 2番目のrow_number()は、一時停止のアナウンスを1つのリストに配置し、他のイベントを2番目のリストに配置します。 2つの違いである_rn1 - rn2_は、ゲームのセクションごとに異なります。例の出力の違いを確認すると便利です。それは_group_nr_列にあります。各値がゲームの1つのセクションに対応していることがわかります。
  • 「order_in_group」と呼ばれる2番目のCTEは、アイランドまたはギャップ内の現在の行の位置を決定します。 3行の島の場合、位置は昇順の場合は_1 2 3_、降順の場合は_3 2 1_です。
  • 最後に、lag()lead()にジャンプする距離を指示するのに十分な知識があります。前のセクションの最後の行を見つけるには、_rank_asc_行を遅らせる必要があります。次のセクションの最初の行を見つけるには、_rank_desc_行をリードする必要があります。

これがギャップと島の「魔法」を明らかにするのに役立つことを願っています。 これはSQL Fiddleでの実例です。

10
Andomar

はい、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
5
Anon

同じ結果を達成するさらに別の方法がありますが、序数に対してウィンドウ処理された条件付き最大/最小関数を使用します。序数は、目的に合った列に基づいて設定できますが、この場合、OPはそれらをDateおよび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の最初の2つの連続する行によって導入されたあいまいさにより、最大値が選択されます。 Priceを含めるように序数を設定することは、この動作を変更するための可能な方法です。

SQLFiddleはここにあります: http://sqlfiddle.com/#!18/85117/1

0
Lars Rönnbäck