web-dev-qa-db-ja.com

SQLでWITH句を使用するためのガイダンス

再帰クエリ(!!)にWITH句を使用する方法は理解していますが、その一般的な使用法/能力を理解するのに問題があります。

たとえば、次のクエリは、タイムスタンプで最初のレコードのIDを返すサブクエリを使用してIDが決定される1つのレコードを更新します。

update global.prospect psp
set    status=status||'*'
where  psp.psp_id=(
           select  p2.psp_id
           from    global.prospect p2
           where   p2.status='new' or p2.status='reset'
           order   by p2.request_ts
           limit   1 )
returning psp.*;

これは、比較的醜いサブクエリの代わりにWITHラッパーを使用するのに適した候補でしょうか?もしそうなら、なぜですか?

14
cc young

関連するテーブルへの同時書き込みアクセスが存在する可能性がある場合、上記の次のクエリには競合状態があります。考えてみましょう:


あなたの例can CTE(共通テーブル式)を使用しますが、サブクエリで実行できなかったものは何もありません。

WITH x AS (
   SELECT  psp_id
   FROM    global.prospect
   WHERE   status IN ('new', 'reset')
   ORDER   BY request_ts
   LIMIT   1
   )
UPDATE global.prospect psp
SET    status = status || '*'
FROM   x
WHERE  psp.psp_id = x.psp_id
RETURNING psp.*;

ところで、返される行はpdatedバージョンになります。


返された行を別のテーブルに挿入したい場合、ここでWITH句が不可欠になります。

WITH x AS (
   SELECT  psp_id
   FROM    global.prospect
   WHERE   status IN ('new', 'reset')
   ORDER   BY request_ts
   LIMIT   1
   ), y AS (
   UPDATE global.prospect psp
   SET    status = status || '*'
   FROM   x
   WHERE  psp.psp_id = x.psp_id
   RETURNING psp.*
   )
INSERT INTO z
SELECT *
FROM   y

CTEを使用したデータ変更クエリは、PostgreSQL9.1以降で可能です。
読む 優れたマニュアルの詳細

20

WITHを使用すると、SELECTクエリで使用する「一時テーブル」を定義できます。たとえば、最近、次のようなクエリを作成して、2つのセット間の変化を計算しました。

_-- Let o be the set of old things, and n be the set of new things.
WITH o AS (SELECT * FROM things(OLD)),
     n AS (SELECT * FROM things(NEW))

-- Select both the set of things whose value changed,
-- and the set of things in the old set but not in the new set.
SELECT o.key, n.value
    FROM o
    LEFT JOIN n ON o.key = n.key
    WHERE o.value IS DISTINCT FROM n.value

UNION ALL

-- Select the set of things in the new set but not in the old set.
SELECT n.key, n.value
    FROM o
    RIGHT JOIN n ON o.key = n.key
    WHERE o.key IS NULL;
_

上部に「テーブル」onを定義することで、式things(OLD)things(NEW)の繰り返しを回避することができました。

確かに、_UNION ALL_を使用して_FULL JOIN_を削除することはできますが、特定のケースではそれを実行できませんでした。


私があなたの質問を正しく理解しているなら、それはこれをします:

  • ステータスが「new」または「reset」であるglobal.prospectの最も古い行を検索します。

  • ステータスにアスタリスクを追加してマークします

  • 行を返します(statusへの微調整を含む)。

WITHがあなたのケースで何かを単純化するとは思わない。ただし、FROM句を使用する方が少しエレガントかもしれません。

_update global.prospect psp
set    status = status || '*'
from   ( select psp_id
         from   global.prospect
         where  status = 'new' or status = 'reset'
         order  by request_ts
         limit  1
       ) p2
where  psp.psp_id = p2.psp_id
returning psp.*;
_

テストされていません。動作するかどうか教えてください。

以下を除いて、それはあなたがすでに持っているものとほとんど同じです。

  • これを簡単に拡張して、複数の行を更新できます。サブクエリ式を使用するバージョンでは、サブクエリが複数の行を生成するように変更された場合、クエリは失敗します。

  • サブクエリで_global.prospect_のエイリアスを作成しなかったため、少し読みやすくなりました。これはFROM句を使用するため、更新中のテーブルを誤って参照するとエラーが発生します。

  • ご使用のバージョンでは、サブクエリ式はすべてのアイテムで検出されます。 PostgreSQLはこれを最適化し、式を1回だけ評価する必要がありますが、誤ってpspの列を参照したり、揮発性の式を追加したりすると、この最適化はなくなります。

10
Joey Adams