発生するエラー:
ON CONFLICTDOUPDATEコマンドは行に2回影響を与えることはできません
1つのコマンドで同じ行を複数回アップサートしようとしていることを示します。言い換えれば、(name, url, email)
に重複があります VALUES
で リスト。複製を折りたたむと(それがオプションの場合)、機能するはずです。ただし、デュープの各セットからどの行を選択するかを決定する必要があります。
INSERT INTO feeds_person (created, modified, name, url, email)
SELECT DISTINCT ON (name, url, email) *
FROM (
VALUES
('blah', 'blah', 'blah', 'blah', 'blah')
-- ... more
) v(created, modified, name, url, email) -- match column list
ON CONFLICT (name, url, email) DO UPDATE
SET url = feeds_person.url
RETURNING id;
自立型のVALUES
を使用しているため ここで、式をデフォルト以外の型に明示的な型キャストを追加する必要があります。いいね:
VALUES
(timestamptz '2016-03-12 02:47:56+01'
, timestamptz '2016-03-12 02:47:56+01'
, 'n3', 'u3', 'e3')
...
あなたのtimestamptz
列には明示的な型キャストが必要ですが、文字列型はデフォルトのtext
で動作できます 。 (引き続きvarchar(n)
にキャストできます すぐに。)
複製の各セットからどの行を選択するかを決定する方法があります:
- 各GROUPBYグループの最初の行を選択しますか?
そうです、(現在)除外する方法はありません RETURNING
の行 句。 Postgres Wikiを引用します:
RETURNING
に注意してください 「EXCLUDED.*
」は表示されません "UPDATE
からのエイリアス (一般的な「TARGET.*
「エイリアスはそこに表示されます。そうすることで、単純で一般的なケース[30]に厄介なあいまいさが生じ、ほとんどまたはまったくメリットがないと考えられます。将来のある時点で、ifRETURNING
を公開する方法を追求する可能性があります。 -投影されたタプルが挿入および更新されましたが、これはおそらく、機能の最初のコミットされた反復に含める必要はありません[31]。
ただし 、更新されるはずのない行を更新するべきではありません。空の更新は通常の更新とほぼ同じくらい費用がかかり、意図しない副作用が発生する可能性があります。そもそもUPSERTは厳密には必要ありません。ケースは、「SELECTまたはINSERT」のように見えます。関連:
- 関数内のSELECTまたはINSERTは競合状態になりやすいですか?
1つ 行のセットを挿入するよりクリーンな方法は、データ変更CTEを使用することです:
WITH val AS (
SELECT DISTINCT ON (name, url, email) *
FROM (
VALUES
(timestamptz '2016-1-1 0:0+1', timestamptz '2016-1-1 0:0+1', 'n', 'u', 'e')
, ('2016-03-12 02:47:56+01', '2016-03-12 02:47:56+01', 'n1', 'u3', 'e3')
-- more (type cast only needed in 1st row)
) v(created, modified, name, url, email)
)
, ins AS (
INSERT INTO feeds_person (created, modified, name, url, email)
SELECT created, modified, name, url, email FROM val
ON CONFLICT (name, url, email) DO NOTHING
RETURNING id, name, url, email
)
SELECT 'inserted' AS how, id FROM ins -- inserted
UNION ALL
SELECT 'selected' AS how, f.id -- not inserted
FROM val v
JOIN feeds_person f USING (name, url, email);
追加された複雑さは、INSERT
がある大きなテーブルに支払う必要があります ルールであり、SELECT
例外。
もともと、私はNOT EXISTS
を追加していました 最後のSELECT
の述語 結果の重複を防ぐため。しかし、それは冗長でした。 単一のクエリのすべてのCTEは、テーブルの同じスナップショットを参照します。 ON CONFLICT (name, url, email) DO NOTHING
で返されたセット INNER JOIN
の後に返されるセットに対して相互に排他的です 同じ列にあります。
残念ながら、これは競合状態の小さなウィンドウも開きます 。もし...
- 同時トランザクションにより、競合する行が挿入されます
- まだコミットしていません
- しかし最終的にはコミットします
...一部の行が失われる可能性があります。
INSERT .. ON CONFLICT DO NOTHING
、その後に別のSELECT
これを克服するために、同じトランザクション内ですべての行をクエリします。これにより、競合状態の別の小さなウィンドウが開きます。 並行トランザクションがINSERT
の間にテーブルへの書き込みをコミットできるかどうか およびSELECT
(デフォルトではREAD COMMITTED
分離レベル)。 REPEATABLE READ
で回避できます トランザクション分離(またはより厳密)。または、テーブル全体に(おそらく高価または許容できない)書き込みロックを使用します。あなたはあなたが必要とするどんな行動も得ることができます、しかし支払うべき代償があるかもしれません。
関連:
- PostgreSQLでONCONFLICTを使用してRETURNINGを使用するにはどうすればよいですか?
- 更新せずに、ONCONFLICTを使用してINSERTから行を返します