Postgres9.5で実装されたUPSERT
。以下を参照してください。
Postgres9.4以前
これは難しい問題です。この制限に直面しています(ドキュメントごと):
VALUES
でINSERT
のトップレベルに表示されるリスト 、式はDEFAULT
に置き換えることができます destinationcolumnのデフォルト値を挿入する必要があることを示します。DEFAULT
VALUES
の場合は使用できません 他のコンテキストで表示されます。
大胆な強調鉱山。デフォルト値は、挿入するテーブルがないと定義されません。したがって、直接はありません あなたの質問に対する解決策ですが、正確な要件に応じて、いくつかの可能な代替ルートがあります 。
システムカタログからデフォルト値を取得しますか?
あなたはできた システムカタログからそれらをフェッチしますpg_attrdef
@Patrickがコメントしたように、またはinformation_schema.columns
から 。ここで完全な手順:
- Postgresのテーブル列のデフォルト値を取得しますか?
しかし、あなたはまだ 行のリストのみがあります デフォルト値をクックするための式のテキスト表現を使用します。使用する値を取得するには、ステートメントを動的に作成して実行する必要があります。退屈で厄介です。代わりに、組み込みのPostgres機能にそれを行わせることができます :
簡単なショートカット
ダミーの行を挿入し、生成されたデフォルトを使用するように戻します:
INSERT INTO playlist_items DEFAULT VALUES RETURNING *;
問題/ソリューションの範囲
- これは、
STABLE
でのみ機能することが保証されています またはIMMUTABLE
デフォルトの式 。ほとんどのVOLATILE
関数も同様に機能しますが、保証はありません。current_timestamp
関数のファミリーは、トランザクション内で値が変更されないため、安定していると見なされます。
特に、これはserial
に副作用があります。 列(またはシーケンスから描画されるその他のデフォルト)。ただし、通常はserial
に書き込むことはないため、これは問題にはなりません。 列を直接。それらはINSERT
にリストされるべきではありません ステートメントすべて。serial
の残りの欠陥 列:シーケンスは、デフォルトの行を取得するための1回の呼び出しによって引き続き進められ、番号付けにギャップが生じます。繰り返しになりますが、ギャップは一般的に予想されるため、これは問題にはなりません。serial
列。
さらに2つの問題を解決できます:
-
列を定義している場合
NOT NULL
、ダミー値を挿入してNULL
に置き換える必要があります 結果に。 -
実際にはダミー行を挿入したくありません 。後で(同じトランザクションで)削除することもできますが、トリガー
ON DELETE
など、より多くの副作用が発生する可能性があります。 。より良い方法があります:
ダミー行を避ける
一時テーブルのクローンを作成します 列のデフォルトを含め、 thatに挿入します :
BEGIN;
CREATE TEMP TABLE tmp_playlist_items (LIKE playlist_items INCLUDING DEFAULTS)
ON COMMIT DROP; -- drop at end of transaction
INSERT INTO tmp_playlist_items DEFAULT VALUES RETURNING *;
...
同じ結果、より少ない副作用。デフォルトの式は逐語的にコピーされるため、クローンは同じシーケンスがあればそれから描画します。ただし、不要な行またはトリガーによる他の副作用は完全に回避されます。
アイデアに対するIgorの功績:
- Postgresql、「偽の」行を選択します
NOT NULL
を削除します 制約
NOT NULL
にはダミー値を指定する必要があります 列、理由(ドキュメントごと):
null以外の制約は、常に新しいテーブルにコピーされます。
INSERT
のいずれかに対応します ステートメントまたは(より良い)制約を排除します:
ALTER TABLE tmp_playlist_items
ALTER COLUMN foo DROP NOT NULL
, ALTER COLUMN bar DROP NOT NULL;
迅速で汚い方法があります スーパーユーザー権限を持つ場合:
UPDATE pg_attribute
SET attnotnull = FALSE
WHERE attrelid = 'tmp_playlist_items'::regclass
AND attnotnull
AND attnum > 0;
これは、データやその他の目的のない一時的なテーブルであり、トランザクションの終了時に削除されます。したがって、ショートカットは魅力的です。それでも、基本的なルールは、システムカタログを直接改ざんしないことです。
それでは、クリーンな方法を調べてみましょう。 :DO
で動的SQLを使用して自動化する 声明。必要なのは通常の特権だけです 同じ役割で一時テーブルが作成されたので、必ず持っているはずです。
DO $$BEGIN
EXECUTE (
SELECT 'ALTER TABLE tmp_playlist_items ALTER '
|| string_agg(quote_ident(attname), ' DROP NOT NULL, ALTER ')
|| ' DROP NOT NULL'
FROM pg_catalog.pg_attribute
WHERE attrelid = 'tmp_playlist_items'::regclass
AND attnotnull
AND attnum > 0
);
END$$
はるかにクリーンで、それでも非常に高速です。動的コマンドで注意を払い、SQLインジェクションに注意してください。このステートメントは安全です。私はより多くの説明とともにいくつかの関連する答えを投稿しました。
一般的なソリューション(9.4以前)
BEGIN;
CREATE TEMP TABLE tmp_playlist_items
(LIKE playlist_items INCLUDING DEFAULTS) ON COMMIT DROP;
DO $$BEGIN
EXECUTE (
SELECT 'ALTER TABLE tmp_playlist_items ALTER '
|| string_agg(quote_ident(attname), ' DROP NOT NULL, ALTER ')
|| ' DROP NOT NULL'
FROM pg_catalog.pg_attribute
WHERE attrelid = 'tmp_playlist_items'::regclass
AND attnotnull
AND attnum > 0
);
END$$;
LOCK TABLE playlist_items IN EXCLUSIVE MODE; -- forbid concurrent writes
WITH default_row AS (
INSERT INTO tmp_playlist_items DEFAULT VALUES RETURNING *
)
, new_values (id, playlist, item, group_name, duration, sort, legacy) AS (
VALUES
(651, 21, 30012, 'a', 30, 1, FALSE)
, (NULL, 21, 1, 'b', 34, 2, NULL)
, (668, 21, 30012, 'c', 30, 3, FALSE)
, (7428, 21, 23068, 'd', 0, 4, FALSE)
)
, upsert AS ( -- *not* replacing existing values in UPDATE (?)
UPDATE playlist_items m
SET ( playlist, item, group_name, duration, sort, legacy)
= (n.playlist, n.item, n.group_name, n.duration, n.sort, n.legacy)
-- ..., COALESCE(n.legacy, m.legacy) -- see below
FROM new_values n
WHERE n.id = m.id
RETURNING m.id
)
INSERT INTO playlist_items
(playlist, item, group_name, duration, sort, legacy)
SELECT n.playlist, n.item, n.group_name, n.duration, n.sort
, COALESCE(n.legacy, d.legacy)
FROM new_values n, default_row d -- single row can be cross-joined
WHERE NOT EXISTS (SELECT 1 FROM upsert u WHERE u.id = n.id)
RETURNING id;
COMMIT;
必要なのはLOCK
だけです 同じテーブルに書き込もうとしている同時トランザクションがある場合。
要求に応じて、これは列legacy
のNULL値のみを置き換えます INSERT
の入力行 場合。他の列またはUPDATE
で機能するように簡単に拡張できます ケースも。たとえば、UPDATE
条件付きでも:入力値がNOT NULL
の場合のみ 。 UPDATE
にコメント行を追加しました 上記。
余談:キャストする必要はありません VALUES
の最初の行以外の任意の行の値 型は最初のから派生するため、式 行。
Postgres 9.5
UPSERTを実装します INSERT .. ON CONFLICT .. DO NOTHING | UPDATE
。これにより、操作が大幅に簡素化されます。
INSERT INTO playlist_items AS m (id, playlist, item, group_name, duration, sort, legacy)
VALUES (651, 21, 30012, 'a', 30, 1, FALSE)
, (DEFAULT, 21, 1, 'b', 34, 2, DEFAULT) -- !
, (668, 21, 30012, 'c', 30, 3, FALSE)
, (7428, 21, 23068, 'd', 0, 4, FALSE)
ON CONFLICT (id) DO UPDATE
SET (playlist, item, group_name, duration, sort, legacy)
= (EXCLUDED.playlist, EXCLUDED.item, EXCLUDED.group_name
, EXCLUDED.duration, EXCLUDED.sort, EXCLUDED.legacy)
-- (..., COALESCE(l.legacy, EXCLUDED.legacy)) -- see below
RETURNING m.id;
VALUES
を添付できます INSERT
への句 直接、DEFAULT
を許可します キーワード。 (id)
での固有の違反の場合 、代わりにPostgresが更新されます。 UPDATE
で除外された行を使用できます 。マニュアル:
SET
およびWHERE
ON CONFLICT DO UPDATE
の句 テーブルの名前(またはエイリアス)を使用して既存の行にアクセスし、特別なexcluded
を使用して挿入を提案された行にアクセスできます。 テーブル。
そして:
行ごとのすべての
BEFORE INSERT
の影響に注意してください トリガーは除外された値に反映されます。これらの影響により、行が挿入から除外された可能性があるためです。
残りのコーナーケース
UPDATE
にはさまざまなオプションがあります :できます...
- ...まったく更新されない:
WHERE
を追加しますUPDATE
の句 選択した行にのみ書き込む。 - ...選択した列のみを更新します。
- ...列が現在NULLの場合にのみ更新:
COALESCE(l.legacy, EXCLUDED.legacy)
- ...新しい値が
NOT NULL
の場合にのみ更新します :COALESCE(EXCLUDED.legacy, l.legacy)
ただし、DEFAULT
を識別する方法はありません。 値とINSERT
で実際に提供される値 。結果のEXCLUDED
のみ 行が表示されます。区別が必要な場合は、前のソリューションに戻ってください。両方を自由に使用できます。