简体   繁体   English

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

[英]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, ...记录:

  • if alias exists in aliases , update the corresponding password in users 如果alias存在aliases ,请更新users的相应password
  • if 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 : 注意事项

  • I used the crypt() function form the pgcrypto module to generate password_hash from plain passwords. 我用crypt()函数形式pgcrypto模块生成password_hash从普通的密码。 I hope you're doing something similar. 我希望你在做类似的事情。
  • This may cause gaps in the 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()部分将这种情况的发生率降到了最低)。
  • You can leave the 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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM