基本的に、私はpostgresqlの単一のコマンドで複数の挿入を行おうとしていますが、私の_ON CONFLICT
_には挿入ごとのパラメーターがあります。
以下はクエリです(_psycopg2
_の形式)。
_INSERT INTO
web_pages
(url, starturl, netloc, distance, is_text,
priority, type, addtime, state)
VALUES
(%(url)s, %(starturl)s, %(netloc)s, %(distance)s, %(is_text)s,
%(priority)s, %(type)s, %(addtime)s, %(state)s)
ON CONFLICT (url) DO
UPDATE
SET
state = EXCLUDED.state,
starturl = EXCLUDED.starturl,
netloc = EXCLUDED.netloc,
is_text = EXCLUDED.is_text,
distance = LEAST(EXCLUDED.distance, web_pages.distance),
priority = GREATEST(EXCLUDED.priority, web_pages.priority),
addtime = LEAST(EXCLUDED.addtime, web_pages.addtime)
WHERE
(
web_pages.ignoreuntiltime < %(ignoreuntiltime)s
AND
web_pages.url = EXCLUDED.url
AND
(web_pages.state = 'complete' OR web_pages.state = 'error')
)
;
_
複数のVALUES
タプルを渡せるようにしたいのですが、%(ignoreuntiltime)s
は値のタプルごとに異なる場合があります。
各行のvalues
タプルなどの追加パラメーターとして指定できる方法はありますか?
私が欲しいのは一種です:
_INSERT INTO
web_pages
(url, starturl, netloc, distance, is_text,
priority, type, addtime, state, not_a_column)
VALUES
(%(url)s, %(starturl)s, %(netloc)s, %(distance)s, %(is_text)s,
%(priority)s, %(type)s, %(addtime)s, %(state)s, %(ignoreuntiltime)s)
....(more value tuples here)....
ON CONFLICT (url) DO
UPDATE
SET
state = EXCLUDED.state,
starturl = EXCLUDED.starturl,
netloc = EXCLUDED.netloc,
is_text = EXCLUDED.is_text,
distance = LEAST(EXCLUDED.distance, web_pages.distance),
priority = GREATEST(EXCLUDED.priority, web_pages.priority),
addtime = LEAST(EXCLUDED.addtime, web_pages.addtime)
WHERE
(
web_pages.ignoreuntiltime < EXCLUDED.not_a_column
AND
web_pages.url = EXCLUDED.url
AND
(web_pages.state = 'complete' OR web_pages.state = 'error')
)
;
_
現在、Postgres9.6には直接的な方法はありません。
UPDATE
では、(更新された行に加えて)特別なEXCLUDED
行のみが表示されます。追加のテーブルに結合することを許可されているFROM
句はありません。
EXCLUDED
行は、競合により拒否された挿入予定の行の状態です。正確に挿入されるテーブルの列。これ以上何もない。
したがって、UPSERTのINSERT
ブランチのUPDATE
に記載されていない追加の列を配置する余地はありません。将来のバージョンではさらに多くのものが可能になるかもしれません。さまざまな追加について説明されていますが、まだ実装されていません。 複雑な問題です。
CTEを使用してこの回避策を提案します。
独立したVALUES
式で必要なすべての列を含む行を提供します。 (以下の例ではCTE data
)。
INSERT ... ON CONFLICT ... DO UPDATE
を実行しますが、実際には行(WHERE FALSE
)を更新しないでください。望ましい副作用:除外された行は、を同時に使用しても安全になるようにロックされます。ON CONFLICT ... DO NOTHING
だけでは、競合する行はロックされず、同時トランザクションの公平なゲームになります。 If同じテーブルの同じ行への同時書き込みはできないため、この安価なバリアントを使用します。
同じコマンドの一部として別のUPDATE
を実行します。 CTE data
からのすべての列-必要に応じて、任意の追加テーブルにも結合します。
デモ表:
CREATE TEMP TABLE tbl (tbl_id int PRIMARY KEY, note text);
INSERT INTO tbl VALUES
(1, 'old1')
, (2, 'old2');
デモクエリ:
WITH data (tbl_id, note) AS (
VALUES
(int '1', text 'update') -- explicit type declarations for free standing VALUES
, (2, 'no update') -- going to exclude row in WHERE of UPDATE
, (3, 'insert') -- not going to INSERT the "note"
)
, ins AS (
INSERT INTO tbl AS t (tbl_id) -- "note" not inserted
SELECT tbl_id FROM data
ON CONFLICT (tbl_id) DO UPDATE
SET note = NULL
WHERE FALSE -- never executed, but locks row.
RETURNING t.tbl_id
)
UPDATE tbl t
SET note = d.note -- now we can!
FROM data d
LEFT JOIN ins i USING (tbl_id)
WHERE i.tbl_id IS NULL
AND t.tbl_id = d.tbl_id
AND d.note <> 'no update'; -- just to demonstrate use in WHERE
結果:
TABLE tbl ORDER BY tbl_id;
tbl_id | note --------+-------- 1 | update -- updated 2 | old2 -- not updated because of WHERE in UPDATE 3 | -- inserted without "note"
関連: