如何使用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中新插入的行在基础表中不可见。 (相同命令的所有部分都可以看到基础表的相同快照。)

由于VALUESexpression式是独立的(不直接附加到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的模板

对于独立式VALUESexpression式中第一行数据的显式types转换可能不方便。 有办法绕过它。 您可以使用任何现有的关系(表,视图,…)作为行模板。 目标表是用例的明显select。 input数据被自动强制转换为适当的types,例如INSERTVALUES子句中:

 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)