5

PostgreSQL クエリの WINDOW 関数の PARTITION BY 句で現在の行と比較する方法を見つけようとしています。

次のクエリに、この 5 つの要素の短いリストがあるとします (実際には、数千または数百万の行があります)。各行、次の異なる要素 (イベント列) の ID、および前の異なる要素の ID を取得しようとしています。

WITH events AS(
  SELECT 1 as id, 12 as event, '2014-03-19 08:00:00'::timestamp as date
  UNION SELECT 2 as id, 12 as event, '2014-03-19 08:30:00'::timestamp as date
  UNION SELECT 3 as id, 13 as event, '2014-03-19 09:00:00'::timestamp as date
  UNION SELECT 4 as id, 13 as event, '2014-03-19 09:30:00'::timestamp as date
  UNION SELECT 5 as id, 12 as event, '2014-03-19 10:00:00'::timestamp as date
)
SELECT lag(id)  over w as previous_different, event
     , lead(id) over w as next_different
FROM events ev
WINDOW w AS (PARTITION BY event!=ev.event ORDER BY date ASC);

比較event!=ev.eventが正しくないことはわかっていますが、それが私が到達したいポイントです。

得られる結果は次のとおりです (PARTITION BY 句を削除した場合と同じです)。

 |12|2
1|12|3
2|13|4
3|13|5
4|12|

そして、私が得たい結果は次のとおりです。

 |12|3
 |12|3
2|13|5
2|13|5
4|12|

それが可能かどうか、そしてその方法を知っている人はいますか?どうもありがとうございました!

編集: aと aの 2 つJOINの s で実行できることはわかっていますが、実際には数百万行の場合、非常に非効率的です。ORDER BYDISTINCT ON

WITH events AS(
  SELECT 1 as id, 12 as event, '2014-03-19 08:00:00'::timestamp as date
  UNION SELECT 2 as id, 12 as event, '2014-03-19 08:30:00'::timestamp as date
  UNION SELECT 3 as id, 13 as event, '2014-03-19 09:00:00'::timestamp as date
  UNION SELECT 4 as id, 13 as event, '2014-03-19 09:30:00'::timestamp as date
  UNION SELECT 5 as id, 12 as event, '2014-03-19 10:00:00'::timestamp as date
)
SELECT DISTINCT ON (e.id, e.date) e1.id, e.event, e2.id
FROM events e
LEFT JOIN events e1 ON (e1.date<=e.date AND e1.id!=e.id AND e1.event!=e.event) 
LEFT JOIN events e2 ON (e2.date>=e.date AND e2.id!=e.id AND e2.event!=e.event) 
ORDER BY e.date ASC, e.id ASC, e1.date DESC, e1.id DESC, e2.date ASC, e2.id ASC
4

1 に答える 1

12

いくつかの異なるウィンドウ関数と 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します(一意の制約により自動的に行われます)。

50,000 行の実際のテーブルを使用したテストでは、1 回のインデックス スキャンのみが必要でした。したがって、大きなテーブルでもかなり高速になるはずです。比較すると、結合/個別のクエリは 1 分後に終了しませんでした (予想どおり)。
最適化されたバージョンでさえ、一度に 1 つのクロス結合 (制限条件がほとんどない左側の結合は実質的に制限されたクロス結合) を処理しても、1 分後には終了しませんでした。

大きなテーブルで最高のパフォーマンスを得るには、メモリ設定を調整します。特にwork_mem(大きな並べ替え操作の場合)。RAM を節約できる場合は、一時的にセッションの値を (はるかに) 高く設定することを検討してください。詳細はこちらこちらをご覧ください。

どのように?

  1. サブクエリsub1では、前の行のイベントを見て、変更された場合にのみそれを保持し、新しいグループの最初の要素をマークします。同時に、id前後の行(pre_idpost_id)の を取得します。

  2. subquerysub2では、count()null 以外の値のみをカウントします。結果のgrpマークは、連続した同じイベントのブロックでピアリングします。

  3. finalでは、各行のグループごと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 フィドル。

于 2014-03-20T01:08:32.020 に答える