SQLのみを使用してpre-UPDATEカラム値を返す - PostgreSQLのバージョン

I have a related question, but this is another part of MY puzzle.

トリガー(ストアドプロシージャーやその他のSQL /クエリー以外の追加のエンティティーもありません)を使用せずに、UPDATEdされた行からOLD VALUEを取得したいとします。

私が持っている質問は次のようなものです:

   UPDATE my_table
      SET processing_by = our_id_info -- unique to this worker
    WHERE trans_nbr IN (
                        SELECT trans_nbr
                          FROM my_table
                         GROUP BY trans_nbr
                        HAVING COUNT(trans_nbr) > 1
                         LIMIT our_limit_to_have_single_process_grab
                       )
RETURNING row_id;

サブクエリの最後に "FOR UPDATE ON my_table"を実行できれば、それはdevineになります(私の他の質問/問題を修正します)。しかし、それはうまくいかないでしょう:これと "GROUP BY"(これは、trans_nbrのCOUNTを計算するために必要です)を持つことはできません。それから私は、trans_nbrのものをとり、最初に(すぐになるべく)以前のprocessing_by値を取得するためのクエリを実行できます。

私は次のようにしてみました:

   UPDATE my_table
      SET processing_by = our_id_info -- unique to this worker
     FROM my_table old_my_table
     JOIN (
             SELECT trans_nbr
               FROM my_table
           GROUP BY trans_nbr
             HAVING COUNT(trans_nbr) > 1
              LIMIT our_limit_to_have_single_process_grab
          ) sub_my_table
       ON old_my_table.trans_nbr = sub_my_table.trans_nbr
    WHERE     my_table.trans_nbr = sub_my_table.trans_nbr
      AND my_table.processing_by = old_my_table.processing_by
RETURNING my_table.row_id, my_table.processing_by, old_my_table.processing_by

しかし、それはうまくいかない。 old_my_table は結合の外側には表示されません。 RETURNING 節はそれを知ることができません。

私は長い間、私が作ったすべての試みを失ってしまった。私は文字通り数時間このことを研究してきました。

私のサブクエリーの行をロックするための弾丸に耐えられる方法を見つけることができれば - そしてそれらの行だけと、サブクエリが起こるとき - 私が避けようとしているすべての並行性の問題は消えます。


UPDATE: [WIPES EGG OFF FACE] Okay, so I had a typo in the non-generic code of the above that I wrote "doesn't work"; it does... thanks to Erwin Brandstetter, below, who stated it would, I re-did it (after a night's sleep, refreshed eyes, and a banana for bfast). Since it took me so long/hard to find this sort of solution, perhaps my embarrassment is worth it? At least this is on SO for posterity now... :>

私が今持っていることは、次のようなものです:

   UPDATE my_table
      SET processing_by = our_id_info -- unique to this worker
     FROM my_table AS old_my_table
    WHERE trans_nbr IN (
                          SELECT trans_nbr
                            FROM my_table
                        GROUP BY trans_nbr
                          HAVING COUNT(*) > 1
                           LIMIT our_limit_to_have_single_process_grab
                       )
      AND my_table.row_id = old_my_table.row_id
RETURNING my_table.row_id, my_table.processing_by, old_my_table.processing_by AS old_processing_by

カウント(*)は、他の(上にリンクされている)質問のコメントの Flimzy の提案に基づいています。 (私は必要以上に具体的でした[この例では])

Please see my other question for correctly implementing concurrency and even a non-blocking version; THIS query merely shows how to get the old and new values from an update, ignore the bad/wrong concurrency bits.

31
なぜルールやトリガーを使用できないのですか?
追加された 著者 Flimzy,
最善の方法IMHOは、明示的なSQLか、書き換えルールまたはトリガーのどちらかで古い行を歴史的にすることです。
追加された 著者 wildplasser,
@wildplasser:目標は(1)変更されたものと(2)変更の前にあったもの(少なくとも)を取り戻すことです。これは変更されるものが明示的に前もって知られていないジョブにとっては便利ですが、プログラムは古いバリューが処理されたものを知る必要があります。 (トラブルシューティングの前/後に出力する)履歴行は不要であり、ルールやトリガはクルフト(このユースケースの場合)だけでなく、「より多く」(セキュリティ、アクセスなど)を必要とします。これらを持たない/望む/必要としない人には、この解決法が最適です。
追加された 著者 pythonlarry,
@Flimzy:1.純粋にSQL /単一のクエリで行うことができるのであれば、そのようなものへのアクセス権がない場合(私はします)、2.純粋にSQL /単一クエリで行うことができる場合2.ルール/ 'nother debug enchilada。 3.それをシンプルで真っ直ぐなSQLにしておき、1つのクエリーを実行することはすべてK.I.S.S.を行います。うまく。ありがとう、もう一度、カウント(*)リマインダーのために、tho! :>
追加された 著者 pythonlarry,

4 答え

問題

The manual explains:

オプションの RETURNING 句は、 UPDATE を計算して返します   実際に更新された各行に基づく値。使用して任意の式   表の列、および/または FROM に記載されている他の表の列   計算される。表の列の新しい(更新後の)値は次のとおりです。   使用されたRETURNING リストの構文は、    SELECT の出力リスト。

強調する私。 RETURNING 句で古い行にアクセスする方法はありません。トランザクションを@Flimzyと@wildplasserでコメントしたように、 UPDATE を別の SELECT before @MattDiPasqualeが投稿したCTEでラップされています。

溶液

しかし、 FROM 句でテーブルの別のインスタンスに参加すると、完全にうまくいくという目的を達成しようとしています:

UPDATE tbl x
SET    tbl_id = 23
     , name = 'New Guy'
FROM   tbl y                -- using the FROM clause
WHERE  x.tbl_id = y.tbl_id  -- must be UNIQUE NOT NULL
AND    x.tbl_id = 3
RETURNING y.tbl_id AS old_id, y.name AS old_name
        , x.tbl_id          , x.name;

戻り値:

 old_id | old_name | tbl_id |  name
--------+----------+--------+---------
  3     | Old Guy  | 23     | New Guy

SQL Fiddle。

私は8.4から9.6までのPostgreSQLバージョンでこれをテストしました。

INSERT とは異なります:

同時書き込み負荷の処理

並行書き込み操作で競合状態を回避するには、いくつかの方法があります。単純で、遅く、確かな(しかし高価な)方法は、 SERIALIZABLE 分離レベルでトランザクションを実行することです。

BEGIN ISOLATION LEVEL SERIALIZABLE;
UPDATE ..;
COMMIT;

しかし、それはおそらく過剰です。シリアル化に失敗した場合は、操作を繰り返す準備が必要です。
より簡単で高速な(同時書き込みロードでも信頼性が高い)更新する 1つの行の明示的なロックです。

UPDATE tbl x
SET    tbl_id = 24
     , name = 'New Gal'
FROM  (SELECT tbl_id, name FROM tbl WHERE tbl_id = 4 FOR UPDATE) y 
WHERE  x.tbl_id = y.tbl_id
RETURNING y.tbl_id AS old_id, y.name AS old_name, x.tbl_id, x.name;

この関連する質問の下の説明、例、リンク

53
追加された
@ErwinBrandstetter私が示唆しているようにサブクエリを使うのはどうですか? stackoverflow.com/a/22734004/242933
追加された 著者 ma11hew28,
@ErwinBrandstetterなぜ WHERE x.tbl_id = y.tbl_id が必要ですか?
追加された 著者 ma11hew28,
かなりクールなトリック。
追加された 著者 a_horse_with_no_name,
ちょうど同様に9.1で働いている(私は非常にしなかった場合は驚いただろう)
追加された 著者 a_horse_with_no_name,
非常に素晴らしい。ありがとう!
追加された 著者 Daniel Shin,
それはうまくいきません。私にとっては、合理的に小さな遅延(約10ミリ秒)で異なるパラメータで同じ要求を実行することが起こります。この場合、後続の要求は、実行中の要求ではなく、後続の要求(「異なる鍵を持つオブジェクト」として読み取られる)のいずれかから古い値を持ちます。 負荷が重い場合は注意して使用してください!
追加された 著者 JLarky,
@a_horse_with_no_name:この小さなテストを実行できるところに9.1クラスターがありますか?物事を完了するために.. :)
追加された 著者 Erwin Brandstetter,
@a_horse_with_no_name:ありがとう!ここも同じですが、>>を前提にしています。 :)
追加された 著者 Erwin Brandstetter,
@PythonLarry:この編集は、私たちの間の悲観論者を助けるはずです。 :)
追加された 著者 Erwin Brandstetter,
@MattDiPasquale:別の別​​名で同じテーブルの2番目のインスタンスに参加しています。結合条件が必要な場合、またはクロス結合(デカルト積)になります。
追加された 著者 Erwin Brandstetter,
@ジャーリー:良い点。私はそのための解決策を追加しました。
追加された 著者 Erwin Brandstetter,
乾杯@ Errwin Brandstetter。次のステップは、この美しさの再帰的なバージョンを作成することです;-)再帰的なCTEに副作用を持つことは許されません。
追加された 著者 wildplasser,
ニードを拾ってはいけませんが、通訳者が一番上の部分(「できない完全にロッキンな証拠です!どちらにせよ、ありがとう、もう一度! :o)
追加された 著者 pythonlarry,

SELECT サブクエリを使用できます。

例:ユーザーのメールアドレスを更新する RETURNING 古い値。

  1. RETURNING Subquery

    UPDATE users SET email = '[email protected]' WHERE id = 1
    RETURNING (SELECT email FROM users WHERE id = 1);
    
  2. PostgreSQL WITH Query (Common Table Expressions)

    WITH u AS (
        SELECT email FROM users WHERE id = 1
    )
    UPDATE users SET email = '[email protected]' WHERE id = 1
    RETURNING (SELECT email FROM u);
    

    This has worked several times on my local database without fail, but I'm not sure if the SELECT in WITH is guaranteed to consistently execute before the UPDATE since "the sub-statements in WITH are executed concurrently with each other and with the main query."

6
追加された
両方のソリューションが重い負荷で一貫して動作しますか?はいの場合は、それを証明してください。もしそうでなければ、どのようにして変更することができますか?
追加された 著者 ma11hew28,
そのような音はむしろ質問であるべきです。あなたはいつもこのコンテキストにリンクすることができます...
追加された 著者 Erwin Brandstetter,
@ 2: "すべてのステートメントは同じスナップショット(第13章を参照)で実行されるため、互いの影響がターゲットテーブルに「見える」ことはありません。これにより、行の更新の実際の順序の予測不可能性の影響が緩和されます。 RETURNINGデータは、異なるWITHサブステートメントとメインクエリ間の変更を伝達する唯一の方法です。 - postgresql.org/docs/9.3/static/queries-with。 html
追加された 著者 moi,

@MattDiPasquale が提案したCTEの変形も有効です。
CTEの快適な手段で、私はより明示的になります。

WITH sel AS (
   SELECT tbl_id, name FROM tbl WHERE tbl_id = 3  -- assuming unique tbl_id
   )
, upd AS (
   UPDATE tbl SET name = 'New Guy' WHERE tbl_id = 3
   RETURNING tbl_id, name
   )
SELECT s.tbl_id AS old_id, s.name As old_name
     , u.tbl_id, u.name
FROM   sel s, upd u;

テストしないと、私はこれがうまく動作すると主張します: SELECTUPDATE はデータベースの同じスナップショットを見ます。 UPDATE は( UPDATE でCTEの後にCTEを置いても)古い値を返すように SELECT 定義によって新しい値。 Voilá。

しかし、それは私の最初の答えより遅くなります。

4
追加された
@calebboyd:唯一の直接の「問題」:CTEの変種よりも高速です。また、他の答えで説明されている可能性のある並行性の問題に対して防御的なバージョンを検討してください。
追加された 著者 Erwin Brandstetter,
サブクエリメソッドを使用すると直ちに問題が発生しますか? (パフォーマンスなど)
追加された 著者 calebboyd,
私はそれが私が疑問に思っていたかもしれないと思います - サブクエリが同様の並行性の問題を呈するかどうか知っていますか? - 別の質問かもしれない...
追加された 著者 calebboyd,

このジレンマに直面すると、私はテーブルに迷惑メールを追加し、古い値を迷惑メールの列にコピーします。テーブルを少し盛り上げますが、結合の必要はありません。

0
追加された