鉴于以下结构:
表A(aliases
):
user_id | alias
---------------
1 john
2 peter
user_id
在id
中引用users
。
表B(users
):
id | password_hash | ...
---------------------------
1 ... ...
2 ... ...
(想法是用户可以拥有多个别名,所有别名都指向相同的主用户帐户记录)
我想执行以下操作:给出alias, password, ...
记录:
alias
中存在aliases
,请更新password
中相应的users
alias
不存在,请使用给定的密码在users
中创建一个新用户,并在指向此新记录的aliases
中插入一行。如何在Postgres的单个查询中执行此操作?
的内容
WITH (
INSERT INTO users(id, password, ...) VALUES(DEFAULT, password, ...) RETURNING id
)
INSERT INTO aliases(user_id, alias) VALUES(id, alias)
ON CONFLICT {delete the temp row in users and update the one with the
known user_id instead}
答案 0 :(得分:2)
注意:我假设alias
是aliases
的主键(但至少是它的唯一键)。
不幸的是,由于唯一列(alias
)不在目标表(UPSERT
)上,因此您无法使用单个INSERT ... ON CONFLICT ...
语句执行此操作。
首先,您需要将aliases.user_id
(引用users.id
列)上的外键定义为DEFERRABLE
(它可以是INITIALLY IMMEDIATE
虽然)。
之后,这些语句应该能够运行(尽管对这些表进行了任何并发修改):
set constraints fk_aliases_user_id deferred;
with params(alias, pwd) as (
values ('john', 'pass3'),
('jane', 'pass4')
),
inserted_alias as (
insert into aliases(alias, user_id)
select alias, coalesce((select user_id
from aliases a
where a.alias = p.alias),
nextval('users_id_seq'))
from params p
on conflict (alias) do nothing
returning *
)
insert into users(id, password_hash)
select coalesce(i.user_id, a.user_id),
crypt(p.pwd, gen_salt('bf'))
from params p
left join inserted_alias i using (alias)
left join aliases a using (alias)
on conflict (id) do update
set password_hash = excluded.password_hash;
set constraints fk_aliases_user_id immediate;
备注:
pgcrypto
模块的crypt()
函数从普通密码生成password_hash
。我希望你能做类似的事情。users_id_seq
中出现空白,但应该总是成功(并且我使用第一次插入的coalesce()
部分的可能性最小化。)set constraints
,则可以保留INITIALLY DEFERRED
语句。您的另一个选择是使用PL / pgSQL和重试循环(添加ON CONFLICT
支持之前的official recommendation)。
编辑:似乎没有在CTE边界之间检查直接约束(但是,我还没有在文档中找到任何证据),所以set constraints
语句&不需要使外键可延迟。
答案 1 :(得分:2)
这假设users_id_seq
是用于users.id
的序列,UNIQUE
上有aliases.alias
约束:
WITH a AS (INSERT INTO aliases (user_id, alias)
VALUES (nextval('users_id_seq'), p_alias)
ON CONFLICT (alias)
/* this does nothing, but is needed for RETURNING */
DO UPDATE
SET user_id = aliases.user_id
RETURNING user_id
)
INSERT INTO users (id, password_hash, ...)
SELECT user_id, p_password, ...
FROM a
ON CONFLICT (id)
DO UPDATE
SET password_hash = EXCLUDED.password_hash;