在PostgreSQL中插入表a的过程中,如何有条件地插入表B中的记录?
鉴于以下结构: 表A(在PostgreSQL中插入表a的过程中,如何有条件地插入表B中的记录?,sql,postgresql,upsert,Sql,Postgresql,Upsert,鉴于以下结构: 表A(别名): user\u id在users中引用id 表B(用户): (想法是用户可以有多个别名,所有别名都指向同一个主用户帐户记录) 我要执行以下操作:给定别名、密码,…记录: 如果别名中存在别名,请更新用户中相应的密码 如果别名不存在,请使用给定密码在用户中创建一个新用户,并在别名中插入一行,指向此新记录 如何在Postgres中的单个查询中做到这一点 类似于 WITH ( INSERT INTO users(id, password, ...) VALUES(D
别名
):
user\u id
在users
中引用id
表B(用户
):
(想法是用户可以有多个别名,所有别名都指向同一个主用户帐户记录)
我要执行以下操作:给定别名、密码,…
记录:
- 如果
别名中存在
,请更新别名
用户中相应的
密码
- 如果
不存在,请使用给定密码在别名
中创建一个新用户,并在用户
中插入一行,指向此新记录别名
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}
注意:我假设别名
是别名
的主键(但至少是唯一键)
不幸的是,由于唯一列(别名
)不在目标表上(属于插入
),因此无法使用单个插入来执行此操作。。。关于冲突…
语句
首先,您需要将别名.user\u id
(指users.id
列)上的外键定义为可延迟的
(但可以是初始立即的
)
之后,这些语句应该能够运行(尽管对这些表进行了任何并发修改):
注:
- 我使用模块中的
函数从普通密码生成crypt()
。我希望你也在做类似的事情password\u hash
- 当并发性很高时,这可能会导致
中出现漏洞,但应该总是成功的(我使用第一次插入的用户id_seq
部分将这种可能性降至最低)coalesce()
- 如果您的外键最初被延迟,则可以保留
语句设置约束
ON CONFLICT
支持之前是什么)
编辑:似乎没有在CTE边界之间检查即时约束(但是,我还没有在文档中找到任何证据),因此不需要使用设置约束
语句&使外键可延迟
注意:我假设别名
是别名
的主键(但至少是唯一键)
不幸的是,由于唯一列(别名
)不在目标表上(属于插入
),因此无法使用单个插入来执行此操作。。。关于冲突…
语句
首先,您需要将别名.user\u id
(指users.id
列)上的外键定义为可延迟的
(但可以是初始立即的
)
之后,这些语句应该能够运行(尽管对这些表进行了任何并发修改):
注:
- 我使用模块中的
函数从普通密码生成crypt()
。我希望你也在做类似的事情password\u hash
- 当并发性很高时,这可能会导致
中出现漏洞,但应该总是成功的(我使用第一次插入的用户id_seq
部分将这种可能性降至最低)coalesce()
- 如果您的外键最初被延迟,则可以保留
语句设置约束
ON CONFLICT
支持之前是什么)
编辑:似乎没有在CTE边界之间检查即时约束(但是,我还没有在文档中找到任何证据),因此不需要使用设置约束
语句&使外键可延迟
这假设
用户id\u seq
是用户id
使用的序列,并且别名上存在唯一的约束。别名
:
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;
这假设users\u id\u seq
是用于users.id
的序列,并且别名上存在唯一的约束。别名
:
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;
那么,您的意思是在CTE边界处不检查直接约束?在这种情况下会很方便。我不明白-哪种约束?是的。我假定别名.user\u id
上有一个外键(应该指向用户.id
)。至少在我的示例中是这样的。有什么原因不能在CTE部分中仅对冲突执行“不执行任何操作”返回用户id吗?是的,因为这样就不会有返回
数据。这不漂亮,但不应该是个问题。那么,你是说直接约束在CTE边界没有检查?在这种情况下会很方便。我不明白-哪种约束?是的。我假定别名.user\u id
上有一个外键(应该指向用户.id
)。至少在我的示例中是这样的。有什么原因不能在CTE部分中仅对冲突执行“不执行任何操作”返回用户id吗?是的,因为这样就不会有返回
数据。它不漂亮,但不应该是个问题。下面的答案更简洁,所以我会接受,但延迟约束检查是一个我不知道存在的非常有趣的功能。@IvanPoliakov用更简洁的另一个答案回答,因为它会在序列中留下空白。而且,它也不会总是成功的,因为插入到。。。在重复执行更新时。。。返回…
当有更新(而不是插入)时,实际上不会返回。瞧:——加上这些,你就可以得到我的answe了
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;
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;