邮政编码 |在单个查询中插入批量更新,1:n 到 1:1

postgresql | batch update with insert in single query, 1:n to 1:1

我需要将 1:n 关系转换为 1:1 关系,同时数据保持不变。 我想知道是否可以使用 单一纯 sql(没有 plpgsql,没有外部语言)来实现这一点。 下面有更多详细信息、MWE 和一些额外的上下文。


为了说明,如果我有

+------+--------+     +------+----------+--------+  
| id   | name   |     | id   | foo_id   | name   |  
|------+--------|     |------+----------+--------|  
| 1    | foo1   |     | 1    | 1        | baz1   |  
| 2    | foo2   |     | 2    | 1        | baz2   |  
| 3    | foo3   |     | 3    | 2        | baz3   |  
+------+--------+     | 4    | 2        | baz4   |  
                      | 5    | 3        | baz5   |
                      +------+----------+--------+

我想去

+------+--------+     +------+----------+--------+    
| id   | name   |     | id   | foo_id   | name   |    
|------+--------|     |------+----------+--------|    
| 4    | foo1   |     | 1    | 4        | baz1   |    
| 5    | foo1   |     | 2    | 5        | baz2   |    
| 6    | foo2   |     | 3    | 6        | baz3   |    
| 7    | foo2   |     | 4    | 7        | baz4   |    
| 8    | foo3   |     | 5    | 8        | baz5   |    
+------+--------+     +------+----------+--------+    


如果需要,这里有一些设置表格的代码:

drop table if exists baz;
drop table if exists foo;
create table foo(
  id   serial primary key,
  name varchar
);
insert into foo (name) values
  ('foo1'),
  ('foo2'),
  ('foo3');

create table baz(
  id     serial primary key,
  foo_id integer references foo (id),
  name   varchar
);
insert into baz (foo_id, name) values
  (1, 'baz1'),
  (1, 'baz2'),
  (2, 'baz3'),
  (2, 'baz4'),
  (3, 'baz5');

我设法计算出以下仅更新一个条目的查询(即 必须提供一对 <baz id, foo id>):

with
existing_foo_values as (
  select name from foo where id = 1
),
new_id as (
  insert into foo(name)
  select name from existing_foo_values
  returning id
)
update baz
set foo_id = (select id from new_id)
where id = 1;

真实案例场景(在nodejs环境中的数据库迁移)解决了使用 类似于

const existingPairs = await runQuery(`
  select id, foo_id from baz
`);
await Promise.all(existingPairs.map(({
  id, foo_id
}) => runQuery(`
  with
  existing_foo_values as (
    select name from foo where id = ${foo_id}
  ),
  new_id as (
    insert into foo(name)
    select name from existing_foo_values
    returning id
  )
  update baz
  set foo_id = (select id from new_id)
  where id = ${id};
`)));

// Then delete all the orphan entries from `foo`

这是一个解决方案,首先将我们想要的 foo 放在一起(使用序列中的值),然后基于此对两个表进行必要的更改。

WITH new_ids AS (
    SELECT nextval('foo_id_seq') as foo_id, baz.id as baz_id, foo.name as foo_name
    FROM foo 
    JOIN baz ON (foo.id = baz.foo_id)
), 
inserts AS (
    INSERT INTO foo (id, name)
    SELECT foo_id, foo_name 
    FROM new_ids
), 
updates AS (
    UPDATE baz 
    SET foo_id = new_ids.foo_id 
    FROM new_ids 
    WHERE new_ids.baz_id = baz.id
) 
DELETE FROM foo 
WHERE id < (SELECT min(foo_id) FROM new_ids);