sql >> データベース >  >> RDS >> PostgreSQL

INSERT ... ON CONFLICT ...ですべての列を更新する方法は?

    UPDATE 構文必須 ターゲット列に明示的に名前を付けるため。それを回避するための考えられる理由:

    • 列がたくさんあり、構文を短くしたいだけです。
    • あなたは知らない 一意の列を除く列名。

    "All columns" 「ターゲットテーブルのすべての列」を意味する必要があります (または少なくとも「テーブルの先頭の列」 )一致する順序と一致するデータ型。それ以外の場合は、とにかくターゲット列名のリストを提供する必要があります。

    テストテーブル:

    CREATE TABLE tbl (
       id    int PRIMARY KEY
     , text  text
     , extra text
    );
    
    INSERT INTO tbl AS t
    VALUES (1, 'foo')
         , (2, 'bar');
    

    1。 DELETEINSERT 代わりに単一のクエリで

    id以外の列名を知らなくても 。

    「ターゲットテーブルのすべての列」でのみ機能します 。構文は主要なサブセットでも機能しますが、ターゲットテーブルの余分な列はDELETEでNULLにリセットされます。 およびINSERT

    UPSERT(INSERT ... ON CONFLICT ... )は、同時書き込みロードでの同時実行/ロックの問題を回避するために必要です。これは、Postgresにまだ存在しない行をロックする一般的な方法がないためです(値のロック

    特別な要件は、UPDATEにのみ影響します 部。 既存の場合、起こりうる合併症は当てはまりません。 行が影響を受けます。それらは適切にロックされています。さらに単純化すると、ケースをDELETEに減らすことができます およびINSERT

    WITH data(id) AS (              -- Only 1st column gets explicit name!
       VALUES
          (1, 'foo_upd', 'a')       -- changed
        , (2, 'bar', 'b')           -- unchanged
        , (3, 'baz', 'c')           -- new
       )
    , del AS (
       DELETE FROM tbl AS t
       USING  data d
       WHERE  t.id = d.id
       -- AND    t <> d              -- optional, to avoid empty updates
       )                             -- only works for complete rows
    INSERT INTO tbl AS t
    TABLE  data                      -- short for: SELECT * FROM data
    ON     CONFLICT (id) DO NOTHING
    RETURNING t.id;
    

    Postgres MVCCモデルでは、UPDATE DELETEとほぼ同じです およびINSERT とにかく(並行性、HOT更新、および行外に格納された大きな列値を伴う一部のコーナーケースを除く)。とにかくすべての行を置き換えたいので、INSERTの前に競合する行を削除するだけです 。削除された行は、トランザクションがコミットされるまでロックされたままになります。 INSERT 同時トランザクションでそれらが同時に挿入された場合(DELETEの後)にのみ、以前は存在しなかったキー値の競合する行が見つかる可能性があります 、ただしINSERTの前 。

    この特殊なケースでは、影響を受ける行の追加の列値が失われます。例外は発生しません。ただし、競合するクエリの優先度が同じである場合、それはほとんど問題になりません。他のクエリが一部で勝ちます。 行。また、他のクエリが同様のUPSERTである場合、その代替手段は、このトランザクションがコミットされるのを待ってからすぐに更新することです。 「勝利」はピュロスの勝利かもしれません。

    「空の更新」について:

    • 複数の列でDISTINCTを選択するにはどうすればよいですか(またはできますか?

    いいえ、クエリが勝つ必要があります!

    OK、あなたはそれを求めました:

    WITH data(id) AS (                   -- Only 1st column gets explicit name!
       VALUES                            -- rest gets default names "column2", etc.
         (1, 'foo_upd', NULL)              -- changed
       , (2, 'bar', NULL)                  -- unchanged
       , (3, 'baz', NULL)                  -- new
       , (4, 'baz', NULL)                  -- new
       )
    , ups AS (
       INSERT INTO tbl AS t
       TABLE  data                       -- short for: SELECT * FROM data
       ON     CONFLICT (id) DO UPDATE
       SET    id = t.id
       WHERE  false                      -- never executed, but locks the row!
       RETURNING t.id
       )
    , del AS (
       DELETE FROM tbl AS t
       USING  data     d
       LEFT   JOIN ups u USING (id)
       WHERE  u.id IS NULL               -- not inserted !
       AND    t.id = d.id
       -- AND    t <> d                  -- avoid empty updates - only for full rows
       RETURNING t.id
       )
    , ins AS (
       INSERT INTO tbl AS t
       SELECT *
       FROM   data
       JOIN   del USING (id)             -- conflict impossible!
       RETURNING id
       )
    SELECT ARRAY(TABLE ups) AS inserted  -- with UPSERT
         , ARRAY(TABLE ins) AS updated   -- with DELETE & INSERT;
    

    どうやって?

    • 最初のCTEdata データを提供するだけです。代わりにテーブルにすることもできます。
    • 2番目のCTEups :UPSERT。 idが競合する行 変更されませんが、ロックされます
    • 3番目のCTEdel 競合する行を削除します。ロックされたままです。
    • 4番目のCTEins 行全体を挿入します 。同じトランザクションでのみ許可されます
    • 最後のSELECTは、デモで何が起こったかを示すためだけのものです。

    空の更新テスト(前後)を確認するには:

    SELECT ctid, * FROM tbl; -- did the ctid change?
    

    (コメントアウトされた)行の変更をチェックしますAND t <> d マニュアルに従って2つの型指定された行の値を比較しているため、NULL値でも機能します。

    2つのNULLフィールド値は等しいと見なされ、NULLは非NULLよりも大きいと見なされます

    2。動的SQL

    これは先頭の列のサブセットでも機能し、既存の値を保持します。

    秘訣は、Postgresにシステムカタログの列名を使用してクエリ文字列を動的に構築させ、それを実行させることです。

    コードの関連する回答を参照してください:

    • plpgsqlのトリガー関数の複数の列を更新します

    • すべての列の一括更新

    • 別のテーブルのフィールドから1つのテーブルのSQL更新フィールド



    1. Oracleでテーブル定義を取得する方法は?

    2. MySQLで日付を比較する

    3. HaproxyとKeepalivedをインストールする方法

    4. 2つの日付の間の営業日または営業時間を取得する方法