如何使用PostgreSQL中的ON CONFLICT返回?
我在PostgreSQL 9.5中有以下的UPSERT:
INSERT INTO chats ("user", "contact", "name") VALUES ($1, $2, $3), ($2, $1, NULL) ON CONFLICT("user", "contact") DO NOTHING RETURNING id;
如果没有冲突,则返回如下所示的内容:
---------- | id | ---------- 1 | 50 | ---------- 2 | 51 | ----------
但是如果有冲突,它不会返回任何行:
---------- | id | ----------
我想返回新的id
列,如果没有冲突或返回冲突列的现有id
列。
可以这样做吗? 如果是这样, 怎么样?
我有完全相同的问题,我用“做更新”而不是“无所事事”来解决问题,尽pipe我没有更新。 在你的情况下,会是这样的:
INSERT INTO chats ("user", "contact", "name") VALUES ($1, $2, $3), ($2, $1, NULL) ON CONFLICT("user", "contact") DO UPDATE SET name=EXCLUDED.name RETURNING id;
这个查询将返回所有的行,无论它们是刚被插入还是以前存在。
目前接受的答案似乎很less重复,小元组,没有触发器,没有或很less的并发负载在桌子上。 简单的解决scheme有其吸引力,副作用可能不那么重要。
不过,对于所有其他情况,不要无需更新相同的行。 即使你看不到表面上的差异,也会有各种副作用 :
-
它可能触发不应该被触发的触发器。
-
它locking“无辜”的行,可能会导致并发交易成本。
-
这可能会使行看起来很新,虽然它是旧的(事务时间戳)。
-
最重要的是在PostgreSQL的MVCC模型中 ,无论行数据是否相同,都可以使用新的行版本。 这会导致UPSERT本身的性能损失,表膨胀,索引膨胀,表中所有后续操作的性能损失,
VACUUM
成本。 less数重复的影响很小,但大部分都是重复的。
您可以实现相同的没有空的更新和副作用:
WITH input_rows("user", "contact", "name") AS ( VALUES (text 'foo', text 'bar', text 'bob') -- type casts in first row , ('foo1', 'bar1', 'barb') -- more? ) , ins AS ( INSERT INTO chats ("user", "contact", "name") SELECT * FROM input_rows ON CONFLICT ("user", "contact") DO NOTHING RETURNING id ) SELECT 'i' AS source, id FROM ins -- 'i' for 'inserted' UNION ALL SELECT 's' AS source, c.id -- 's' for 'selected' FROM input_rows JOIN chats c USING ("user", "contact"); -- columns of unique index
source
列是一个可选的添加,以演示如何工作。 您可能实际上需要它来说明这两种情况之间的区别(与空写入相比的另一个优点)。
最终的JOIN chats
工作,因为从附加的数据修改CTE中新插入的行在基础表中不可见。 (相同命令的所有部分都可以看到基础表的相同快照。)
由于VALUES
expression式是独立的(不直接附加到INSERT
)Postgres不能从目标列派生数据types,您可能需要添加明确的types转换。 手册:
在
INSERT
使用VALUES
,这些值全部自动强制为相应目标列的数据types。 在其他情况下使用时,可能需要指定正确的数据types。 如果条目都是引用文字常量,强制第一个足以确定所有的假定types。
由于CTE和附加SELECT
的开销(由于定义完美的索引是一个唯一的约束是用一个索引来实现的,所以它应该是便宜的),查询本身对于less数模式可能会更贵一些 。
对许多副本可能(更快)。 额外写入的有效成本取决于许多因素。
但无论如何, 副作用和隐藏成本都较低 。 (附加序列仍然是先进的,因为在testing冲突之前填入默认值。)总体来说,这可能更便宜。
如果并发事务可以写入受影响行的涉及列,并且您必须确保在事务的后续阶段中find的行仍然存在,则可以使用以下命令便宜地locking行:
... ON CONFLICT ("user", "contact") DO UPDATE SET name = name WHERE FALSE -- never executed, only locks rows ...
更多细节和解释:
- 如何在INSERT … ON CONFLICT的RETURNING中包含排除的行
- SELECT或INSERT在一个容易出现竞争条件的函数中?
另外:不要使用引用的保留字如"user"
作为标识符。 这是一个装载的猎枪。 只能使用合法的,小写的,不加引号的标识符。
现有表格作为数据types的模板
对于独立式VALUES
expression式中第一行数据的显式types转换可能不方便。 有办法绕过它。 您可以使用任何现有的关系(表,视图,…)作为行模板。 目标表是用例的明显select。 input数据被自动强制转换为适当的types,例如INSERT
的VALUES
子句中:
WITH input_rows AS ( (SELECT "user", contact, name FROM chats LIMIT 0) -- only copies column names and types UNION ALL VALUES ('foo' , 'bar' , 'bob') -- no type casts needed , ('foo1', 'bar1', 'barb') ) ...
…和名字
如果插入整行,也可以省略列名 – 如果需要的话。 假设示例中的表chats
只有3列使用:
WITH input_rows AS ( (TABLE chats LIMIT 0) -- copy whole row structure UNION ALL VALUES ('foo' , 'bar' , 'bob') -- no type casts needed , ('foo1', 'bar1', 'barb') ) ...
TABLE
只是SELECT * FROM
的语法缩写。
- 在psql中有SELECT * FROM的快捷方式吗?
详细的解释和替代:
- 在更新多行时投射NULLtypes
根据http://michael.otacoo.com/postgresql-2/postgres-9-5-feature-highlight-upsert/ :
Upsert作为INSERT
查询的扩展,在约束冲突的情况下可以用两种不同的行为来定义:不要做任何事情或做DO UPDATE
。
INSERT INTO upsert_table VALUES (2, 6, 'upserted') ON CONFLICT DO NOTHING RETURNING *; id | sub_id | status ----+--------+-------- (0 rows)
还要注意, RETURNING
什么都不返回,因为没有插入元组 。 现在用DO UPDATE
,有可能在元组上执行操作就有冲突了。 首先要注意的是,定义一个将用来定义冲突的约束是很重要的。
INSERT INTO upsert_table VALUES (2, 2, 'inserted') ON CONFLICT ON CONSTRAINT upsert_table_sub_id_key DO UPDATE SET status = 'upserted' RETURNING *; id | sub_id | status ----+--------+---------- 2 | 2 | upserted (1 row)