いくつかの異なるウィンドウ関数と2つのサブクエリを使用すると、これはまともな速度で動作するはずです:
WITH events(id, event, ts) AS (
VALUES
(1, 12, '2014-03-19 08:00:00'::timestamp)
,(2, 12, '2014-03-19 08:30:00')
,(3, 13, '2014-03-19 09:00:00')
,(4, 13, '2014-03-19 09:30:00')
,(5, 12, '2014-03-19 10:00:00')
)
SELECT first_value(pre_id) OVER (PARTITION BY grp ORDER BY ts) AS pre_id
, id, ts
, first_value(post_id) OVER (PARTITION BY grp ORDER BY ts DESC) AS post_id
FROM (
SELECT *, count(step) OVER w AS grp
FROM (
SELECT id, ts
, NULLIF(lag(event) OVER w, event) AS step
, lag(id) OVER w AS pre_id
, lead(id) OVER w AS post_id
FROM events
WINDOW w AS (ORDER BY ts)
) sub1
WINDOW w AS (ORDER BY ts)
) sub2
ORDER BY ts;
ts
の使用 タイムスタンプ列の名前として。ts
を想定 一意である-そしてインデックス付き (一意の制約が自動的にそれを行います)。
5万行の生命表を使用したテストでは、単一のインデックススキャンのみが必要でした。 。したがって、大きなテーブルでもかなり高速である必要があります。比較すると、join /distinctを使用したクエリは1分後に終了しませんでした(予想どおり)。
最適化されたバージョンでも、一度に1つのクロス結合を処理します(制限条件がほとんどない左側の結合は事実上制限されています)クロスジョイン)は1分後に終了しませんでした。
大きなテーブルで最高のパフォーマンスを得るには、特に work_mem
のメモリ設定を調整してください (大規模なソート操作の場合)。 RAMを節約できる場合は、セッション用に一時的に(はるかに)高く設定することを検討してください。詳しくはこちらとこちらをご覧ください。
どのように?
-
サブクエリ
sub1
前の行のイベントを確認し、それが変更された場合にのみ保持して、新しいグループの最初の要素をマークします。同時に、id
を取得します 前の行と次の行の(pre_id
、post_id
。 -
サブクエリ
sub2
、count()
null以外の値のみをカウントします。結果のgrp
連続する同じイベントのブロックでピアをマークします。 -
最後の
SELECT
、最初のpre_id
を取得します 最後のpost_id
各行のグループごとに、目的の結果に到達します。
実際には、これは外側のSELECT
でさらに高速になるはずです。 :last_value(post_id) OVER (PARTITION BY grp ORDER BY ts RANGE BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING) AS post_id
...ウィンドウの並べ替え順序が
pre_id
のウィンドウと一致するため 、したがって、必要なソートは1つだけです。簡単なテストで確認できるようです。このフレーム定義の詳細。
SQLフィドル。