我有一个包含单个主键的表。当我尝试插入时,尝试插入具有现有密钥的行可能会导致冲突。我想允许插入更新所有列?这有什么简单的语法吗?我试图让它“插入”所有列。
我正在使用PostgreSQL 9.5.5。
答案 0 :(得分:9)
UPDATE
syntax 要求明确命名目标列。
避免这种情况的可能原因:
"All columns"
必须表示“目标表的所有列”(或至少“表格的前导列”)匹配顺序和匹配数据类型。否则,您无论如何都必须提供目标列名称列表。
测试表:
CREATE TABLE tbl (
id int PRIMARY KEY
, text text
, extra text
);
INSERT INTO tbl AS t
VALUES (1, 'foo')
, (2, 'bar');
DELETE
&而是在单个查询中使用INSERT
不知道除id
之外的任何列名。
仅适用于“目标表的所有列”。虽然语法甚至适用于前导子集,但目标表中的多余列将使用DELETE
和INSERT
重置为NULL。
需要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更新和大列值的一些极端情况)线)。由于您仍想要替换所有行,只需删除INSERT
之前的冲突行。在提交事务之前,已删除的行将保持锁定状态。 INSERT
可能只发现以前不存在的键值存在冲突的行,如果并发事务碰巧同时插入它们(在DELETE
之后但在INSERT
之前)。
在这种特殊情况下,您会丢失受影响行的其他列值。没有例外。但是,如果竞争查询具有相同的优先级,那么这几乎不是问题:另一个查询赢得某些行。此外,如果另一个查询是类似的UPSERT,它的替代方法是等待此事务提交然后立即更新。 “胜利”可能是一场惨淡的胜利。
关于“空更新”:
好的,你问过它:
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;
如何?
data
只提供数据。可能是一张桌子。ups
:UPSERT。有冲突id
的行不会更改,但已锁定。del
删除有冲突的行。他们仍然被锁定。ins
插入整行。仅允许进行相同的交易使用以下方法检查空更新测试(之前和之后):
SELECT ctid, * FROM tbl; -- did the ctid change?
这也适用于前导列的子集,保留现有值。
诀窍是让Postgres动态地用系统目录中的列名构建查询字符串,然后执行它。
查看代码的相关答案:
答案 1 :(得分:0)
由于我缺乏评论的名声:当id列不在第一列时,Erwin Brandstetter的答案似乎失败了。
以下内容来自他的另一本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 - only for full-row updates where all columns are comparable (e.g. jsonb not json)
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$;