如何使用 INSERT ... ON CONFLICT ... 更新所有列?
How to update all columns with INSERT ... ON CONFLICT ...?
我有一个只有一个主键的 table。当我尝试执行插入操作时,可能会因尝试使用现有键插入一行而导致冲突。我想让插入更新所有列?这有什么简单的语法吗?我试图让它 "upsert" 所有列。
我正在使用 PostgreSQL 9.5.5。
UPDATE
syntax要求明确命名目标列。
避免这种情况的可能原因:
- 您有很多列,只想缩短语法。
- 您不知道 列名称,唯一列除外。
"All columns"
必须表示 “目标的所有列 table”(或至少 " table") 的前导列以匹配顺序和匹配数据类型。否则您无论如何都必须提供目标列名列表。
测试table:
CREATE TABLE tbl (
id int PRIMARY KEY
, text text
, extra text
);
INSERT INTO tbl VALUES
(1, 'foo')
, (2, 'bar')
;
1。 DELETE
& INSERT
在单个查询中
不知道除id
以外的任何列名。
仅适用于 “目标的所有列 table”。虽然该语法甚至适用于前导子集,但目标 table 中的多余列将重置为其各自的列默认值(默认为 NULL),其中 DELETE
和 INSERT
.
需要 UPSERT (INSERT ... ON CONFLICT ...
) 来避免并发写入负载下的并发/锁定问题,这只是因为没有通用的方法来锁定 Postgres 中尚不存在的行 (value locking ).
您的特殊要求只影响UPDATE
部分。可能的并发症不适用于 现有 行受到影响的情况。那些被正确锁定。进一步简化,您可以将案例减少到 DELETE
和 INSERT
:
WITH data(id) AS ( -- Only 1st column gets explicit name
VALUES
(1, 'foo_upd', 'a') -- changed
, (2, 'bar', 'b') -- unchanged
, (3, 'baz', 'c') -- new
)
, del AS (
DELETE FROM tbl AS t
USING data d
WHERE t.id = d.id
-- AND t <> d -- optional, to avoid empty updates
) -- only works for complete rows
INSERT INTO tbl AS t
TABLE data -- short for: SELECT * FROM data
ON CONFLICT (id) DO NOTHING
RETURNING t.id;
在 Postgres MVCC 模型中,UPDATE
与 DELETE
和 INSERT
大致相同 - 除了一些具有并发、触发器、HOT 更新和大列的极端情况存储的值越界,“TOASTed”值。由于您无论如何都想替换所有行,只需删除 INSERT
之前的冲突行即可。删除的行保持锁定状态,直到提交事务。如果并发事务碰巧同时插入它们(在 [=16= 之后,但在 INSERT
之前),INSERT
可能只会找到以前不存在的键值的冲突行。
在这种特殊情况下,您会丢失受影响行的额外列值。没有例外。但是,如果竞争查询具有相同的优先级,那几乎不是问题:另一个查询赢得了 some 行。此外,如果另一个查询是一个类似的 UPSERT,它的备选方案是等待此事务提交,然后立即更新。 “胜利”可能是代价不菲的胜利。
关于“空更新”:
- How do I (or can I) SELECT DISTINCT on multiple columns?
不,我的查询必须赢!
好的,你自找的:
WITH data(id) AS ( -- Only 1st column gets explicit name
VALUES -- rest gets default names "column2", etc.
(1, 'foo_upd', NULL) -- changed
, (2, 'bar', NULL) -- unchanged
, (3, 'baz', NULL) -- new
, (4, 'baz', NULL) -- new
)
, ups AS (
INSERT INTO tbl AS t
TABLE data -- short for: SELECT * FROM data
ON CONFLICT (id) DO UPDATE
SET id = t.id
WHERE false -- never executed, but locks the row!
RETURNING t.id
)
, del AS (
DELETE FROM tbl AS t
USING data d
LEFT JOIN ups u USING (id)
WHERE u.id IS NULL -- not inserted!
AND t.id = d.id
-- AND t <> d -- avoid empty updates - only for full rows
RETURNING t.id
)
, ins AS (
INSERT INTO tbl AS t
SELECT *
FROM data
JOIN del USING (id) -- conflict impossible!
RETURNING id
)
SELECT ARRAY(TABLE ups) AS inserted -- with UPSERT
, ARRAY(TABLE ins) AS updated; -- with DELETE & INSERT
如何?
- 第一个 CTE
data
只是提供数据。可以是 table。
- 第二个 CTE
ups
:UPSERT。有冲突的行 id
没有改变,但也 locked.
- 第三个 CTE
del
删除了冲突的行。他们保持锁定状态。
- 第 4 个 CTE
ins
插入 整行 。只允许同一笔交易
- 最后的
SELECT
是可选的,以显示发生了什么。
检查空更新测试(之前和之后):
SELECT ctid, * FROM tbl; -- did the ctid change?
(注释掉)检查行中的任何更改 AND t <> d
即使使用 NULL 值也能正常工作,因为我们正在比较两个类型化的行值 according to the manual:
two NULL field values are considered equal, and a NULL is considered larger than a non-NULL
但是所有列都必须支持 =
/ <>
运算符才能进行行比较。参见:
- How to query a json column for empty objects?
2。动态 SQL
这也适用于前导列的子集,保留现有值。
诀窍是让 Postgres 使用系统目录中的列名动态构建查询字符串,然后执行它。
代码见相关答案:
Update multiple columns in a trigger function in plpgsql
-
SQL update fields of one table from fields of another one
当 id
列不是第一列时,Erwin Brandstetter 的回答似乎失败了。
以下使用 one of his other answers 中的片段来重现我的 'return ins/ups' 功能:
DO
$do$
BEGIN
EXECUTE (
SELECT
'DROP TABLE IF EXISTS res_tbl; CREATE TABLE res_tbl AS
WITH ins AS (
INSERT INTO dest
TABLE src -- short for: SELECT * FROM data
ON CONFLICT (id) DO UPDATE
SET id = dest.id
WHERE false -- never executed, but locks the row!
RETURNING id
),
repl AS (
UPDATE dest
SET (' || string_agg( quote_ident(column_name), ',') || ')
= (' || string_agg('src.' || quote_ident(column_name), ',') || ')
FROM src
WHERE src.id = dest.id
AND src <> dest -- avoids empty updates ¹
RETURNING dest.id
)
SELECT ARRAY(TABLE ins) AS inserted -- with UPSERT
, ARRAY(TABLE repl) AS updated -- with DYNAMIC UPDATE
;'
FROM information_schema.columns
WHERE table_name = 'src' -- table name, case sensitive
AND table_schema = 'public' -- schema name, case sensitive
AND column_name <> 'id' -- all columns except id)
);
END
$do$;
¹ 仅适用于所有列都具有可比性的全行更新(例如 jsonb
而不是 json
)。
我有一个只有一个主键的 table。当我尝试执行插入操作时,可能会因尝试使用现有键插入一行而导致冲突。我想让插入更新所有列?这有什么简单的语法吗?我试图让它 "upsert" 所有列。
我正在使用 PostgreSQL 9.5.5。
UPDATE
syntax要求明确命名目标列。
避免这种情况的可能原因:
- 您有很多列,只想缩短语法。
- 您不知道 列名称,唯一列除外。
"All columns"
必须表示 “目标的所有列 table”(或至少 " table") 的前导列以匹配顺序和匹配数据类型。否则您无论如何都必须提供目标列名列表。
测试table:
CREATE TABLE tbl (
id int PRIMARY KEY
, text text
, extra text
);
INSERT INTO tbl VALUES
(1, 'foo')
, (2, 'bar')
;
1。 DELETE
& INSERT
在单个查询中
不知道除id
以外的任何列名。
仅适用于 “目标的所有列 table”。虽然该语法甚至适用于前导子集,但目标 table 中的多余列将重置为其各自的列默认值(默认为 NULL),其中 DELETE
和 INSERT
.
UPSERT (INSERT ... ON CONFLICT ...
) 来避免并发写入负载下的并发/锁定问题,这只是因为没有通用的方法来锁定 Postgres 中尚不存在的行 (value locking ).
您的特殊要求只影响UPDATE
部分。可能的并发症不适用于 现有 行受到影响的情况。那些被正确锁定。进一步简化,您可以将案例减少到 DELETE
和 INSERT
:
WITH data(id) AS ( -- Only 1st column gets explicit name
VALUES
(1, 'foo_upd', 'a') -- changed
, (2, 'bar', 'b') -- unchanged
, (3, 'baz', 'c') -- new
)
, del AS (
DELETE FROM tbl AS t
USING data d
WHERE t.id = d.id
-- AND t <> d -- optional, to avoid empty updates
) -- only works for complete rows
INSERT INTO tbl AS t
TABLE data -- short for: SELECT * FROM data
ON CONFLICT (id) DO NOTHING
RETURNING t.id;
在 Postgres MVCC 模型中,UPDATE
与 DELETE
和 INSERT
大致相同 - 除了一些具有并发、触发器、HOT 更新和大列的极端情况存储的值越界,“TOASTed”值。由于您无论如何都想替换所有行,只需删除 INSERT
之前的冲突行即可。删除的行保持锁定状态,直到提交事务。如果并发事务碰巧同时插入它们(在 [=16= 之后,但在 INSERT
之前),INSERT
可能只会找到以前不存在的键值的冲突行。
在这种特殊情况下,您会丢失受影响行的额外列值。没有例外。但是,如果竞争查询具有相同的优先级,那几乎不是问题:另一个查询赢得了 some 行。此外,如果另一个查询是一个类似的 UPSERT,它的备选方案是等待此事务提交,然后立即更新。 “胜利”可能是代价不菲的胜利。
关于“空更新”:
- How do I (or can I) SELECT DISTINCT on multiple columns?
不,我的查询必须赢!
好的,你自找的:
WITH data(id) AS ( -- Only 1st column gets explicit name
VALUES -- rest gets default names "column2", etc.
(1, 'foo_upd', NULL) -- changed
, (2, 'bar', NULL) -- unchanged
, (3, 'baz', NULL) -- new
, (4, 'baz', NULL) -- new
)
, ups AS (
INSERT INTO tbl AS t
TABLE data -- short for: SELECT * FROM data
ON CONFLICT (id) DO UPDATE
SET id = t.id
WHERE false -- never executed, but locks the row!
RETURNING t.id
)
, del AS (
DELETE FROM tbl AS t
USING data d
LEFT JOIN ups u USING (id)
WHERE u.id IS NULL -- not inserted!
AND t.id = d.id
-- AND t <> d -- avoid empty updates - only for full rows
RETURNING t.id
)
, ins AS (
INSERT INTO tbl AS t
SELECT *
FROM data
JOIN del USING (id) -- conflict impossible!
RETURNING id
)
SELECT ARRAY(TABLE ups) AS inserted -- with UPSERT
, ARRAY(TABLE ins) AS updated; -- with DELETE & INSERT
如何?
- 第一个 CTE
data
只是提供数据。可以是 table。 - 第二个 CTE
ups
:UPSERT。有冲突的行id
没有改变,但也 locked. - 第三个 CTE
del
删除了冲突的行。他们保持锁定状态。 - 第 4 个 CTE
ins
插入 整行 。只允许同一笔交易 - 最后的
SELECT
是可选的,以显示发生了什么。
检查空更新测试(之前和之后):
SELECT ctid, * FROM tbl; -- did the ctid change?
(注释掉)检查行中的任何更改 AND t <> d
即使使用 NULL 值也能正常工作,因为我们正在比较两个类型化的行值 according to the manual:
two NULL field values are considered equal, and a NULL is considered larger than a non-NULL
但是所有列都必须支持 =
/ <>
运算符才能进行行比较。参见:
- How to query a json column for empty objects?
2。动态 SQL
这也适用于前导列的子集,保留现有值。
诀窍是让 Postgres 使用系统目录中的列名动态构建查询字符串,然后执行它。
代码见相关答案:
Update multiple columns in a trigger function in plpgsql
SQL update fields of one table from fields of another one
当 id
列不是第一列时,Erwin Brandstetter 的回答似乎失败了。
以下使用 one of his other answers 中的片段来重现我的 'return ins/ups' 功能:
DO
$do$
BEGIN
EXECUTE (
SELECT
'DROP TABLE IF EXISTS res_tbl; CREATE TABLE res_tbl AS
WITH ins AS (
INSERT INTO dest
TABLE src -- short for: SELECT * FROM data
ON CONFLICT (id) DO UPDATE
SET id = dest.id
WHERE false -- never executed, but locks the row!
RETURNING id
),
repl AS (
UPDATE dest
SET (' || string_agg( quote_ident(column_name), ',') || ')
= (' || string_agg('src.' || quote_ident(column_name), ',') || ')
FROM src
WHERE src.id = dest.id
AND src <> dest -- avoids empty updates ¹
RETURNING dest.id
)
SELECT ARRAY(TABLE ins) AS inserted -- with UPSERT
, ARRAY(TABLE repl) AS updated -- with DYNAMIC UPDATE
;'
FROM information_schema.columns
WHERE table_name = 'src' -- table name, case sensitive
AND table_schema = 'public' -- schema name, case sensitive
AND column_name <> 'id' -- all columns except id)
);
END
$do$;
¹ 仅适用于所有列都具有可比性的全行更新(例如 jsonb
而不是 json
)。