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。 DELETE
&INSERT
代わりに単一のクエリで
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;
どうやって?
- 最初のCTE
data
データを提供するだけです。代わりにテーブルにすることもできます。 - 2番目のCTE
ups
:UPSERT。id
が競合する行 変更されませんが、ロックされます 。 - 3番目のCTE
del
競合する行を削除します。ロックされたままです。 - 4番目のCTE
ins
行全体を挿入します 。同じトランザクションでのみ許可されます - 最後のSELECTは、デモで何が起こったかを示すためだけのものです。
空の更新テスト(前後)を確認するには:
SELECT ctid, * FROM tbl; -- did the ctid change?
(コメントアウトされた)行の変更をチェックしますAND t <> d
マニュアルに従って2つの型指定された行の値を比較しているため、NULL値でも機能します。
2つのNULLフィールド値は等しいと見なされ、NULLは非NULLよりも大きいと見なされます
2。動的SQL
これは先頭の列のサブセットでも機能し、既存の値を保持します。
秘訣は、Postgresにシステムカタログの列名を使用してクエリ文字列を動的に構築させ、それを実行させることです。
コードの関連する回答を参照してください:
-
plpgsqlのトリガー関数の複数の列を更新します
-
すべての列の一括更新
-
別のテーブルのフィールドから1つのテーブルのSQL更新フィールド