[英]How to conditionally insert a record in table B during insert into table A in PostgreSQL?
Given the following structure: 给出以下结构:
Table A ( aliases
): 表A( aliases
):
user_id | alias
---------------
1 john
2 peter
user_id
references id
in users
. user_id
引用users
id
。
Table B ( users
): 表B( users
):
id | password_hash | ...
---------------------------
1 ... ...
2 ... ...
(the idea is that users can have multiple aliases all of which point to the same main user account record) (想法是用户可以具有多个别名,所有别名都指向同一主用户帐户记录)
I would like to do the following operation: given an alias, password, ...
record: 我想执行以下操作:给定alias, password, ...
记录:
alias
exists in aliases
, update the corresponding password
in users
如果alias
存在aliases
,请更新users
的相应password
alias
does not exist, create a new user in users
with the given password and insert a row into aliases
pointing to this new record. 如果alias
不存在,请使用给定的密码在users
创建一个新用户,并将一行插入指向该新记录的aliases
。 How can I do that in a single query in Postgres? 如何在Postgres中的单个查询中做到这一点?
Something along the lines of 遵循以下原则
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}
Note : I'll assume that alias
is the primary key (but at least, it's a unique key) of aliases
. 注 :我假设alias
是主键(但至少,它是一个独特的密钥)的aliases
。
Unfortunately, because the unique column ( alias
) is not on the target table (of the UPSERT
), you cannot do this with a single INSERT ... ON CONFLICT ...
statement. 不幸的是,由于唯一列( alias
)不在目标表( UPSERT
的目标表)上,因此无法使用单个INSERT ... ON CONFLICT ...
语句来执行此操作。
First, you'll need to define the foreign key on aliases.user_id
(which refers to the users.id
column) to be DEFERRABLE
(it can be INITIALLY IMMEDIATE
though). 首先,您需要将aliases.user_id
(指的是users.id
列)上的外键定义为DEFERRABLE
(尽管可以INITIALLY IMMEDIATE
users.id
)。
After that, these statements should be able to run (despite any concurrent modifications to these tables): 之后,这些语句应能够运行(尽管对这些表进行了任何并发修改):
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;
Notes : 注意事项 :
crypt()
function form the pgcrypto
module to generate password_hash
from plain passwords. 我用crypt()
函数形式pgcrypto
模块生成password_hash
从普通的密码。 I hope you're doing something similar. 我希望你在做类似的事情。 users_id_seq
when the concurrency is high, but should always succeed (and I minimized the chances for that with the coalesce()
part of the first insert). 当并发性很高时,这可能会导致users_id_seq
出现间隙,但应该总是成功(并且我使用第一个插入的coalesce()
部分将这种情况的发生率降到了最低)。 set constraints
statements, if your foreign key is INITIALLY DEFERRED
. 如果您的外键是INITIALLY DEFERRED
,则可以保留set constraints
语句。 http://rextester.com/YDY89070 http://rextester.com/YDY89070
Your other option is to use PL/pgSQL and a retry loop (what was the official recommendation before the ON CONFLICT
support was added). 您的另一个选择是使用PL / pgSQL和重试循环(添加ON CONFLICT
支持之前的官方建议 )。
Edit : it seems immediate constraints are not checked between CTE boundaries (however, I have not found any evidence for this in the docs, yet), so the set constraints
statements & to make the foreign key deferrable is not needed. 编辑 :似乎没有检查CTE边界之间的立即约束(但是,我还没有在文档中找到任何证据),因此不需要set constraints
语句和使外键可延期。
http://rextester.com/IUSM65192 http://rextester.com/IUSM65192
This assumes that users_id_seq
is the sequence used for users.id
and that there is a UNIQUE
constraint on aliases.alias
: 假设users_id_seq
是用于users.id
的序列,并且对aliases.alias
有一个UNIQUE
约束:
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;
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.