プレーンINSERT
INSERT INTO bar (description, foo_id)
SELECT val.description, f.id
FROM (
VALUES
(text 'testing', text 'blue') -- explicit type declaration; see below
, ('another row', 'red' )
, ('new row1' , 'purple') -- purple does not exist in foo, yet
, ('new row2' , 'purple')
) val (description, type)
LEFT JOIN foo f USING (type);
のLEFT [OUTER] JOIN
代わりにを使用すると、で一致するものが見つからない場合に[INNER] JOIN
からの行val
は削除されませんfoo
。代わりに、NULL
に入力されfoo_id
ます。
VALUES
サブクエリの式は、@ ypercubeの CTE と同じです。共通テーブル式は追加機能を提供し、大きなクエリで読みやすくなりますが、最適化の障壁にもなります。したがって、上記のいずれも必要ない場合、通常、サブクエリは少し高速です。
id
列名は広く普及しているアンチパターンであるため。説明的なものである必要がfoo_id
ありbar_id
ます。多数のテーブルを結合すると、すべての名前が複数の列になりid
ます...
プレーンtext
またはのvarchar
代わりに検討してくださいvarchar(n)
。長さ制限を本当に課す必要がある場合は、CHECK
制約を追加します。
明示的な型キャストを追加する必要がある場合があります。VALUES
式は(のようにINSERT ... VALUES ...
)テーブルに直接関連付けられないため、明示的な型宣言なしでは型を導出できず、デフォルトのデータ型が使用されますが、これはすべての場合に機能するとは限りません。最初の行でそれを行うだけで十分で、残りは整列します。
欠落しているFK行を同時に挿入する
存在しないエントリをfoo
オンザフライで1 つのSQLステートメントで作成する場合、CTEが役立ちます。
WITH sel AS (
SELECT val.description, val.type, f.id AS foo_id
FROM (
VALUES
(text 'testing', text 'blue')
, ('another row', 'red' )
, ('new row1' , 'purple')
, ('new row2' , 'purple')
) val (description, type)
LEFT JOIN foo f USING (type)
)
, ins AS (
INSERT INTO foo (type)
SELECT DISTINCT type FROM sel WHERE foo_id IS NULL
RETURNING id AS foo_id, type
)
INSERT INTO bar (description, foo_id)
SELECT sel.description, COALESCE(sel.foo_id, ins.foo_id)
FROM sel
LEFT JOIN ins USING (type);
挿入する2つの新しいダミー行に注意してください。どちらも紫色でfoo
、まだ存在しません。最初のステートメントでの必要性を示す2行。DISTINCT
INSERT
段階的な説明
最初のCTE sel
は、入力データの複数の行を提供します。サブクエリval
とのVALUES
表現は、ソースとして、テーブルまたはサブクエリに置き換えることができます。すぐLEFT JOIN
に既存の行foo
に追加します。他のすべての行はこのようになります。foo_id
type
foo_id IS NULL
2番目のCTEは、別個の新しいタイプ()をにins
挿入し、新しく生成された-を結合して、行を挿入するために結合します。foo_id IS NULL
foo
foo_id
type
最後のアウターINSERT
は、すべての行にfoo.idを挿入できるようになりました。既存のタイプ、またはステップ2で挿入されたタイプです。
厳密に言えば、両方の挿入は「並行して」行われますが、これは単一のステートメントなので、デフォルトのFOREIGN KEY
制約は文句を言いません。デフォルトでは、ステートメントの最後に参照整合性が適用されます。
Postgres 9.3用のSQL Fiddle。(9.1でも同じように機能します。)
これらのクエリを複数同時に実行すると、小さな競合状態が発生します。関連する質問については、こちらとこちら、こちらをご覧ください。実際には、重い同時ロードが発生した場合にのみ発生します。別の回答で宣伝されているようなキャッシュソリューションと比較すると、チャンスは非常に小さいです。
繰り返し使用するための機能
繰り返し使用する場合は、レコードの配列をパラメーターとして受け取りunnest(param)
、VALUES
式の代わりに使用するSQL関数を作成します。
または、レコードの配列の構文が面倒な場合は、パラメーターとしてコンマ区切りの文字列を使用します_param
。フォームの例:
'description1,type1;description2,type2;description3,type3'
次に、これを使用VALUES
して上記のステートメントの式を置き換えます。
SELECT split_part(x, ',', 1) AS description
split_part(x, ',', 2) AS type
FROM unnest(string_to_array(_param, ';')) x;
Postgres 9.5でUPSERTを使用する機能
パラメーターを渡すためのカスタム行タイプを作成します。それなしでもできますが、もっと簡単です:
CREATE TYPE foobar AS (description text, type text);
関数:
CREATE OR REPLACE FUNCTION f_insert_foobar(VARIADIC _val foobar[])
RETURNS void AS
$func$
WITH val AS (SELECT * FROM unnest(_val)) -- well-known row type
, ins AS (
INSERT INTO foo AS f (type)
SELECT DISTINCT v.type -- DISTINCT!
FROM val v
ON CONFLICT(type) DO UPDATE -- type already exists
SET type = excluded.type WHERE FALSE -- never executed, but lock rows
RETURNING f.type, f.id
)
INSERT INTO bar AS b (description, foo_id)
SELECT v.description, COALESCE(f.id, i.id) -- assuming most types pre-exist
FROM val v
LEFT JOIN foo f USING (type) -- already existed
LEFT JOIN ins i USING (type) -- newly inserted
ON CONFLICT (description) DO UPDATE -- description already exists
SET foo_id = excluded.foo_id -- real UPSERT this time
WHERE b.foo_id IS DISTINCT FROM excluded.foo_id -- only if actually changed
$func$ LANGUAGE sql;
コール:
SELECT f_insert_foobar(
'(testing,blue)'
, '(another row,red)'
, '(new row1,purple)'
, '(new row2,purple)'
, '("with,comma",green)' -- added to demonstrate row syntax
);
同時トランザクションを伴う環境向けの高速で堅実な。
上記のクエリに加えて、これは...
... apply SELECT
またはINSERT
on foo
:type
FKテーブルに存在しないが、まだ挿入されているもの。ほとんどのタイプが既に存在すると仮定します。競合状態を完全に確認して除外するには、必要な既存の行をロックします(同時トランザクションが干渉しないようにするため)。それがあなたの場合にはあまりにも妄想的である場合、あなたは置き換えることができます:
ON CONFLICT(type) DO UPDATE -- type already exists
SET type = excluded.type WHERE FALSE -- never executed, but lock rows
と
ON CONFLICT(type) DO NOTHING
...適用INSERT
またはUPDATE
(true "UPSERT")on bar
:description
既に存在する場合type
は更新されます:
ON CONFLICT (description) DO UPDATE -- description already exists
SET foo_id = excluded.foo_id -- real UPSERT this time
WHERE b.foo_id IS DISTINCT FROM excluded.foo_id -- only if actually changed
ただし、type
実際に変更された場合のみ:
... VARIADIC
パラメータ付きの既知の行タイプとして値を渡します。デフォルトの最大100個のパラメーターに注意してください!比較:
複数の行を渡す方法は他にもたくさんあります...
関連する: