如何在插入到PostgreSQL中的表A期间有条件地在表B中插入记录?

时间:2017-03-31 11:45:58

标签: sql postgresql upsert

鉴于以下结构:

表A(aliases):

user_id | alias 
---------------
   1      john
   2      peter

user_idid中引用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}

2 个答案:

答案 0 :(得分:2)

注意:我假设aliasaliases的主键(但至少是它的唯一键)。

不幸的是,由于唯一列(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语句。

http://rextester.com/YDY89070

您的另一个选择是使用PL / pgSQL和重试循环(添加ON CONFLICT支持之前的official recommendation)。

编辑:似乎没有在CTE边界之间检查直接约束(但是,我还没有在文档中找到任何证据),所以set constraints语句&不需要使外键可延迟。

http://rextester.com/IUSM65192

答案 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;