我有一种情况,我经常需要从具有唯一约束的表中获取一行,如果不存在,则创建它并返回。 例如,我的表可能是:
CREATE TABLE names(
id SERIAL PRIMARY KEY,
name TEXT,
CONSTRAINT names_name_key UNIQUE (name)
);
它包含:
id | name
1 | bob
2 | alice
然后我想:
INSERT INTO names(name) VALUES ('bob')
ON CONFLICT DO NOTHING RETURNING id;
或者也许:
INSERT INTO names(name) VALUES ('bob')
ON CONFLICT (name) DO NOTHING RETURNING id
让它返回bob的id 1
。但是,RETURNING
仅返回插入或更新的行。所以,在上面的例子中,它不会返回任何东西。为了让它按照需要运行,我实际上需要:
INSERT INTO names(name) VALUES ('bob')
ON CONFLICT ON CONSTRAINT names_name_key DO UPDATE
SET name = 'bob'
RETURNING id;
这看起来有点麻烦。我想我的问题是:
不允许(我)期望行为的原因是什么?
有更优雅的方法吗?
答案 0 :(得分: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;
这样,您实际上不需要编写新的行版本。
我假设您知道在Postgres中,每个UPDATE
由于其MVCC model而写入该行的新版本 - 即使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;
(来自其他会话),则无需锁定行并简化:
string input = "ok, here is369 and777, and 20k0 10+1.any word.";