私はこのテーブルを持っています(Djangoが生成):
CREATE TABLE feeds_person (
id serial PRIMARY KEY,
created timestamp with time zone NOT NULL,
modified timestamp with time zone NOT NULL,
name character varying(4000) NOT NULL,
url character varying(1000) NOT NULL,
email character varying(254) NOT NULL,
CONSTRAINT feeds_person_name_ad8c7469_uniq UNIQUE (name, url, email)
);
ON CONFLICT
句とINSERT
を使用して大量のデータを一括挿入しようとしています。
しわは、行が既に存在するかどうかに関係なく、行のallに対してid
を取り戻す必要があることです。
他の場合では、私は次のようなことをします:
INSERT INTO feeds_person (created, modified, name, url, email)
VALUES blah blah blah
ON CONFLICT (name, url, email) DO UPDATE SET url = feeds_person.url
RETURNING id
UPDATE
を実行すると、ステートメントはその行のid
を返します。ただし、このテーブルでは機能しません。私はthink複数の一意のフィールドを一緒に持っているので機能しませんが、他の例ではこの方法を使用しましたが、一意のフィールドが1つしかありませんでした。
Djangoのカーソルを介してSQLを実行しようとすると、このエラーが発生します。
Django.db.utils.ProgrammingError: ON CONFLICT DO UPDATE command cannot affect row a second time HINT: Ensure that no rows proposed for insertion within the same command have duplicate constrained values.
このテーブルで一括挿入を行い、挿入されたIDと既存のIDを取得するにはどうすればよいですか?
あなたが得るエラー:
ON CONFLICT DO UPDATEコマンドは行に2回影響を与えることはできません
単一のコマンドで同じ行を複数回アップサートしようとしていることを示します。つまり、VALUES
リストの_(name, url, email)
_に重複があります。複製を折りたたみ(オプションの場合)、機能するはずです。ただし、各デュープセットから選択する行を決定する必要があります。
_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)
にすぐにキャストできます。)
デュープの各セットから選択する行を決定する方法があります。
あなたが正しい、(現在)RETURNING
句。 Postgres Wiki を引用します:
RETURNING
はUPDATE
からの「_EXCLUDED.*
_」エイリアスを表示しないことに注意してください(一般的な「_TARGET.*
_」エイリアスのみが表示されます)。そうすることは、単純で一般的なケース [30] のメリットをほとんどまたはまったくもたらさずに、迷惑なあいまいさを生み出すと考えられています。将来のある時点で、RETURNING
- projectedタプルが挿入および更新されたかどうかを公開する方法を追求する可能性がありますが、これはおそらく機能の最初のコミット済みイテレーションにする必要はありません [31] 。
ただし、更新されるはずのない行は更新しないでください。空の更新は通常の更新とほぼ同じくらい高価であり、意図しない副作用が生じる可能性があります。 UPSERTを厳密に必要とするわけではありません。ケースは「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
が例外である大きなテーブルに支払うべきです。
元々、結果の重複を防ぐために、最後のSELECT
に_NOT EXISTS
_述語を追加していました。しかし、それは冗長でした。 単一のクエリのすべてのCTEは、テーブルの同じスナップショットを参照します。ON CONFLICT (name, url, email) DO NOTHING
は、同じ列の_INNER JOIN
_の後に返されるセットと相互に排他的です。
残念ながら、これは競合状態の小さなウィンドウも開きます。もし...
...一部の行が失われる可能性があります。
同じトランザクション内で、_INSERT .. ON CONFLICT DO NOTHING
_に続けて、すべての行に対して個別のSELECT
クエリを実行すると、これを克服できます。同時トランザクションがテーブルへの書き込みをコミットできる場合、競合状態の別の小さなウィンドウを順に開きますINSERT
とSELECT
の間(デフォルトでは _READ COMMITTED
_分離レベル )。 _REPEATABLE READ
_トランザクション分離 (またはより厳密)で回避できます。または、テーブル全体に対する(おそらく高価であるか、または許容できない)書き込みロックを使用します。あなたはあなたが必要とするあらゆる振る舞いを得ることができますが、支払う代償があるかもしれません。
関連: