サンプルテーブルとデータ
CREATE TABLE dupes(col1 int primary key, col2 int, col3 text,
CONSTRAINT col2_unique UNIQUE (col2)
);
INSERT INTO dupes values(1,1,'a'),(2,2,'b');
問題の再現
INSERT INTO dupes values(3,2,'c')
ON CONFLICT (col1) DO UPDATE SET col3 = 'c', col2 = 2
これをQ1と呼びましょう。結果は
ERROR: duplicate key value violates unique constraint "col2_unique"
DETAIL: Key (col2)=(2) already exists.
コンフリクトターゲットは、一意のインデックス推論を実行できます。推論を実行する場合、1つ以上のindex_column_name列やindex_expression式、およびオプションのindex_predicateで構成されます。順序に関係なく、conflict_targetで指定された列/式を正確に含むすべてのtable_name一意のインデックスは、アービターインデックスとして推測(選択)されます。index_predicateが指定されている場合、推論の追加要件として、アービターインデックスを満たす必要があります。
これは、次のクエリが機能するはずであるという印象を与えますが、実際にはcol1とcol2に一緒に一意のインデックスが必要になるため、機能しません。ただし、このようなインデックスは、col1とcol2が個別に一意になることを保証するものではありません。これはOPの要件の1つです。
INSERT INTO dupes values(3,2,'c')
ON CONFLICT (col1,col2) DO UPDATE SET col3 = 'c', col2 = 2
このクエリをQ2と呼びましょう(これは構文エラーで失敗します)
どうして?
Postgresqlがこのように動作するのは、2番目の列で競合が発生したときに何が起こるかが明確に定義されていないためです。いくつかの可能性があります。たとえば、上記のQ1クエリでcol1
は、col2
?で競合が発生した場合、postgresqlを更新する必要があります。しかし、それが別の紛争につながる場合はどうなりcol1
ますか?postgresqlはそれをどのように処理することが期待されていますか?
解決策
解決策は、ONCONFLICTを昔ながらのUPSERTと組み合わせることです。
CREATE OR REPLACE FUNCTION merge_db(key1 INT, key2 INT, data TEXT) RETURNS VOID AS
$$
BEGIN
LOOP
UPDATE dupes SET col3 = data WHERE col1 = key1 and col2 = key2;
IF found THEN
RETURN;
END IF;
BEGIN
INSERT INTO dupes VALUES (key1, key2, data) ON CONFLICT (col1) DO UPDATE SET col3 = data;
RETURN;
EXCEPTION WHEN unique_violation THEN
BEGIN
INSERT INTO dupes VALUES (key1, key2, data) ON CONFLICT (col2) DO UPDATE SET col3 = data;
RETURN;
EXCEPTION WHEN unique_violation THEN
END;
END;
END LOOP;
END;
$$
LANGUAGE plpgsql;
このストアド関数のロジックを変更して、列を希望どおりに正確に更新する必要があります。次のように呼び出します
SELECT merge_db(3,2,'c');
SELECT merge_db(1,2,'d');