PostgreSQL v9.1を使用します。次の表があります。
CREATE TABLE foo
(
id BIGSERIAL NOT NULL UNIQUE PRIMARY KEY,
type VARCHAR(60) NOT NULL UNIQUE
);
CREATE TABLE bar
(
id BIGSERIAL NOT NULL UNIQUE PRIMARY KEY,
description VARCHAR(40) NOT NULL UNIQUE,
foo_id BIGINT NOT NULL REFERENCES foo ON DELETE RESTRICT
);
最初のテーブルfoo
に次のように入力するとします。
INSERT INTO foo (type) VALUES
( 'red' ),
( 'green' ),
( 'blue' );
bar
テーブルを参照して簡単にfoo
に行を挿入する方法はありますか?または、最初に必要なfoo
タイプを検索し、次にbar
に新しい行を挿入するという2つの手順で実行する必要がありますか?
これが私が望んでいたことを示す疑似コードの例です:
INSERT INTO bar (description, foo_id) VALUES
( 'testing', SELECT id from foo WHERE type='blue' ),
( 'another row', SELECT id from foo WHERE type='red' );
あなたの構文はほぼ良好です、サブクエリの周りにいくつかの括弧が必要であり、それは動作します:
INSERT INTO bar (description, foo_id) VALUES
( 'testing', (SELECT id from foo WHERE type='blue') ),
( 'another row', (SELECT id from foo WHERE type='red' ) );
SQL-Fiddle でテスト
別の方法として、挿入する値が多い場合は構文を短くします。
WITH ins (description, type) AS
( VALUES
( 'more testing', 'blue') ,
( 'yet another row', 'green' )
)
INSERT INTO bar
(description, foo_id)
SELECT
ins.description, foo.id
FROM
foo JOIN ins
ON ins.type = foo.type ;
_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's CTEと同じです。 共通テーブル式 は追加の機能を提供し、大きなクエリで読みやすくなりますが、最適化の障壁としても機能します。したがって、上記のいずれも必要としない場合、サブクエリは通常少し高速です。
id
as column name is a広く普及しているアンチパターン。 _foo_id
_および_bar_id
_、または説明的なものにする必要があります。一連のテーブルを結合すると、すべてid
という名前の複数の列ができます...
varchar(n)
ではなく、単純なtext
またはvarchar
を検討してください。長さ制限を課す必要がある場合は、CHECK
制約を追加します。
明示的な型キャストを追加する必要がある場合があります。 VALUES
式は直接テーブルにアタッチされないため(_INSERT ... VALUES ...
_のように)、型を導出できず、明示的な型宣言なしにデフォルトのデータ型が使用されますが、すべての場合で機能するとは限りません。最初の行でそれを行うだけで十分です。残りは整列します。
存在しないエントリをその場でfoo
に作成する場合は、単一の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つの新しいダミー行に注意してください。どちらもpurpleですが、foo
にはまだ存在しません。 最初のDISTINCT
ステートメントでのINSERT
の必要性を示す2つの行。
最初のCTE sel
は、入力データの複数の行を提供します。 val
式を持つサブクエリVALUES
は、ソースとしてテーブルまたはサブクエリに置き換えることができます。すぐに_LEFT JOIN
_をfoo
に追加して、既存のtype
行の_foo_id
_を追加します。他のすべての行は、この方法で_foo_id IS NULL
_を取得します。
2番目のCTE ins
は、distinct新しいタイプ(_foo_id IS NULL
_)をfoo
に挿入し、新しく生成された_foo_id
_を返します-type
と一緒に結合して行を挿入します。
最後の外側のINSERT
は、すべての行にfoo.idを挿入できるようになりました。既存の型か、手順2で挿入された型のどちらかです。
厳密に言えば、両方の挿入は「並行して」行われますが、これはsingleステートメントであるため、デフォルトの_FOREIGN KEY
_制約は問題になりません。参照整合性は、デフォルトでステートメントの最後に適用されます。
SQL Fiddlefor Postgres 9.3。 (9.1でも同じように機能します。)
これらのクエリを同時に複数実行すると、小さな競合状態が発生します。関連する質問 ここ および ここ および ここ で詳細をお読みください。実際には、同時負荷が重い場合にのみ発生します。別の回答で宣伝されているようなキャッシングソリューションと比較すると、チャンスはsuper-tinyです。
繰り返し使用する場合は、パラメーターとしてレコードの配列を取り、VALUES
式の代わりにunnest(param)
を使用する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;
_
パラメータを渡すためのカスタム行タイプを作成します。それなしでも可能ですが、もっと簡単です:
_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
);
_
同時トランザクションが存在する環境では高速かつ強固です。
上記のクエリに加えて、これは...
... SELECT
またはINSERT
をfoo
に適用:FKテーブルにまだ存在しないtype
が挿入されます。ほとんどのタイプが事前に存在すると仮定します。確実に競合状態を排除するために、必要な既存の行はロックされます(同時トランザクションが干渉できないようにするため)。それがあなたのケースにとってあまりにも偏執的であるなら、あなたは置き換えることができます:
_ 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
またはbar
(true "UPSERT")を適用します: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パラメータに注意してください!比較:
複数の行を渡す方法は他にもたくさんあります...
関連:
調べる。それらをbarに挿入するには、基本的にfoo IDが必要です。
ちなみに、postgres固有ではありません。 (そしてあなたはそのようにタグ付けしていませんでした)-これは一般的にSQLが機能する方法です。ここにショートカットはありません。
ただし、アプリケーションについては、メモリ内にfooアイテムのキャッシュがある場合があります。多くの場合、テーブルには最大3つの一意のフィールドがあります。
例:
明らかに、何かをアカウントにリンクしたい場合-最初に、技術的にはIDを取得する必要があります-が、IDとコードの両方が存在すると変更されないため、メモリカンのポジティブキャッシュは、ほとんどのルックアップがデータベースにアクセスするのを防ぎます。