web-dev-qa-db-ja.com

SQLのみを使用して更新前の列値を返す-PostgreSQLバージョン

関連する質問があります ですが、これは私のパズルの別の部分です。

更新された行から列の古い値を取得したい-トリガーを使用せずに(ストアドプロシージャ、またはその他の追加の非SQL /クエリエンティティ)。

私が持っているクエリは次のようなものです:

   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」を実行できた場合、それはすばらしいことです(そして、他の質問/問題を修正します)。しかし、これは機能しません。これと「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]さて、上記の非ジェネリックコードに「誤動作」と書いたタイプミスがありました。 ";それはそうです...Erwin Brandstetterのおかげで、それを述べたので、私はそれをやり直しました(夜の睡眠後、目をリフレッシュし、 bfastのバナナ)。この種の解決策を見つけるのにmeとても長く/難しいので、おそらく私の恥ずかしさはそれだけの価値がありますか?少なくともこれはSO後世のために今...です...:>

私が今持っているもの(それは機能します)は次のとおりです:

   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

COUNT(*)は、コメントのFlimzyからの提案に基づいています私の他の(上にリンクされた)質問に。 (私は必要以上に具体的だった。[この場合]。)

私の別の質問を参照してください 同時実行性を正しく実装するため、さらには非ブロッキングバージョンについても。このクエリは、更新から古い値と新しい値を取得する方法を示すだけであり、不良/間違った同時実行ビットは無視します。

43
pythonlarry

問題

マニュアルで説明しています

オプションのRETURNING句を使用すると、UPDATEは実際に更新された各行に基づいて値を計算して返します。テーブルの列、またはFROMで言及されている他のテーブルの列、あるいはその両方を使用する式はすべて計算できます。テーブルの列の新しい(更新後)値が使用されますRETURNINGリストの構文は、SELECTの出力リストの構文と同じです。

鉱山を強調します。 RETURNING句の古い行にアクセスする方法はありません。これは、トリガーで、またはSELECTの前に別のUPDATEbeforeを使用して行うことができます。

解決

ただし、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フィドル

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

INSERTの場合は異なります。

同時書き込み負荷の処理

同時書き込み操作で競合状態を回避する方法はいくつかあります。シンプルで遅くて確実な(しかし高価な)メソッドは、SERIALIZABLE分離レベルでトランザクションを実行することです。

BEGIN ISOLATION LEVEL SERIALIZABLE;
UPDATE ..;
COMMIT;

しかし、それはおそらくやり過ぎです。また、シリアル化の失敗が発生した場合は、操作を繰り返す準備をする必要があります。
よりシンプルで高速(そして同時書き込み負荷と同じくらい信頼できる)は、更新されるone行の明示的なロックです。

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;

この関連質問の下の詳細な説明、例、リンク:

67

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

例: pdate ユーザーのメールRETURNING古い値。

  1. RETURNINGサブクエリ

    UPDATE users SET email = '[email protected]' WHERE id = 1
    RETURNING (SELECT email FROM users WHERE id = 1);
    
  2. PostgreSQL WITHクエリ(共通テーブル式)

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

    これは間違いなく私のローカルデータベースで数回動作しましたが、SELECTWITHUPDATEの前に一貫して実行されることが保証されているかどうかはわかりません。 -WITHのステートメントは、互いに、およびメインクエリと同時に実行されます。

10
ma11hew28

@ 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はデータベースの同じスナップショットを参照してください。 SELECTは古い値を返すようにバインドされています(たとえUPDATEを使用してCTEの後にCTEを配置した場合でも)。一方、UPDATEは定義により新しい値を返します。ボイラ。

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

7

このジレンマに直面したとき、テーブルにジャンク列を追加し、レコードを更新するときに古い値をジャンク列(次に返す)にコピーします。これはテーブルを少し膨らませますが、結合の必要性を回避します。

1
Jasen