使用ON CONFLICT从INSERT返回行而无需更新

ira*_*ira 16 sql postgresql upsert postgresql-9.5

我有一种情况,我经常需要从具有唯一约束的表中获取一行,如果不存在,则创建它并返回.例如我的表可能是:

CREATE TABLE names(
    id SERIAL PRIMARY KEY,
    name TEXT,
    CONSTRAINT names_name_key UNIQUE (name)
);
Run Code Online (Sandbox Code Playgroud)

它包含:

id | name
 1 | bob 
 2 | alice
Run Code Online (Sandbox Code Playgroud)

然后我想:

 INSERT INTO names(name) VALUES ('bob')
 ON CONFLICT DO NOTHING RETURNING id;
Run Code Online (Sandbox Code Playgroud)

也许:

 INSERT INTO names(name) VALUES ('bob')
 ON CONFLICT (name) DO NOTHING RETURNING id
Run Code Online (Sandbox Code Playgroud)

让它返回bob的id 1.但是,RETURNING只返回插入或更新的行.所以,在上面的例子中,它不会返回任何东西.为了让它按照需要运行,我实际上需要:

INSERT INTO names(name) VALUES ('bob') 
ON CONFLICT ON CONSTRAINT names_name_key DO UPDATE
SET name = 'bob'
RETURNING id;
Run Code Online (Sandbox Code Playgroud)

这看起来有点麻烦.我想我的问题是:

  1. 不允许(我)期望行为的原因是什么?

  2. 有没有更优雅的方式来做到这一点?

Erw*_*ter 16

这是一个反复出现的问题SELECT or INSERT,与UPSERT相关(但不同).Postgres 9.5中新的UPSERT功能仍然有用.

WITH ins AS (
   INSERT INTO names(name)
   VALUES ('bob')
   ON     CONFLICT ON CONSTRAINT names_name_key DO UPDATE
   SET    name = NULL
   WHERE  FALSE      -- never executed, but locks the row
   RETURNING id
   )
SELECT id FROM ins
UNION  ALL
SELECT id FROM names
WHERE  name = 'bob'  -- only executed if no INSERT
LIMIT  1;
Run Code Online (Sandbox Code Playgroud)

这样,您实际上不需要编写新的行版本.

我假设您知道在Postgres UPDATE中,由于其MVCC模型,每个人都会写一个新版本的行- 即使name设置为与之前相同的值.这会使操作更加昂贵,在某些情况下增加可能的并发问题/锁定争用,并使表格膨胀.

详细解释以及如何将其包装到函数中:

为什么"排除"行不包括在RETURNING条款中?

如果无法并发UPDATE或DELETE(来自不同的会话)您不需要锁定行并可以简化:

WITH ins AS (
   INSERT INTO names(name)
   VALUES ('bob')
   ON     CONFLICT ON CONSTRAINT names_name_key DO NOTHING  -- no lock needed
   RETURNING id
   )
SELECT id FROM ins
UNION  ALL
SELECT id FROM names
WHERE  name = 'bob'  -- only executed if no INSERT
LIMIT  1;
Run Code Online (Sandbox Code Playgroud)