在PostgreSQL中插入表a的过程中,如何有条件地插入表B中的记录?

在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

鉴于以下结构:

表A(
别名
):

user\u id
users
中引用
id

表B(
用户
):

(想法是用户可以有多个别名,所有别名都指向同一个主用户帐户记录)

我要执行以下操作:给定
别名、密码,…
记录:

  • 如果
    别名中存在
    别名
    ,请更新
    用户中相应的
    密码
  • 如果
    别名
    不存在,请使用给定密码在
    用户
    中创建一个新用户,并在
    别名
    中插入一行,指向此新记录
如何在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}
注意:我假设
别名
别名
的主键(但至少是唯一键)

不幸的是,由于唯一列(
别名
)不在目标表上(属于
插入
),因此无法使用单个
插入来执行此操作。。。关于冲突…
语句

首先,您需要将
别名.user\u id
(指
users.id
列)上的外键定义为
可延迟的
(但可以是
初始立即的

之后,这些语句应该能够运行(尽管对这些表进行了任何并发修改):

注:

  • 我使用模块中的
    crypt()
    函数从普通密码生成
    password\u hash
    。我希望你也在做类似的事情
  • 当并发性很高时,这可能会导致
    用户id_seq
    中出现漏洞,但应该总是成功的(我使用第一次插入的
    coalesce()
    部分将这种可能性降至最低)
  • 如果您的外键最初被延迟,则可以保留
    设置约束
    语句

您的另一个选项是使用PL/pgSQL和重试循环(在添加
ON CONFLICT
支持之前是什么)

编辑:似乎没有在CTE边界之间检查即时约束(但是,我还没有在文档中找到任何证据),因此不需要使用
设置约束
语句&使外键可延迟

注意:我假设
别名
别名
的主键(但至少是唯一键)

不幸的是,由于唯一列(
别名
)不在目标表上(属于
插入
),因此无法使用单个
插入来执行此操作。。。关于冲突…
语句

首先,您需要将
别名.user\u id
(指
users.id
列)上的外键定义为
可延迟的
(但可以是
初始立即的

之后,这些语句应该能够运行(尽管对这些表进行了任何并发修改):

注:

  • 我使用模块中的
    crypt()
    函数从普通密码生成
    password\u hash
    。我希望你也在做类似的事情
  • 当并发性很高时,这可能会导致
    用户id_seq
    中出现漏洞,但应该总是成功的(我使用第一次插入的
    coalesce()
    部分将这种可能性降至最低)
  • 如果您的外键最初被延迟,则可以保留
    设置约束
    语句

您的另一个选项是使用PL/pgSQL和重试循环(在添加
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;