zam*_*6ak 5 postgresql insert cte
想象一下具有 1:M 关系的多个父子表。我想“级联” - 根据根父表行选择插入重复行。每个表都有IDENTITY
主键,每个子表都有其父 ID 的 FK(上一级)。
给定根父表 ID,为其及其所有子表插入重复行。
我尝试了“级联”插入 CTE,但遇到了RETURNING
仅限返回插入数据的问题,而我需要额外的信息来连接下一个INSERT
.
我通过添加额外的列 ( ) 来完成此任务copied_from_id
。
有没有办法在没有额外列的情况下完成相同的任务?
我确实看到了@Erwin Brandstetter 的这个答案,但他的例子只有 1 个父母和孩子,我不知道如何将其扩展到多个级别
这是示例 DDL 和 DML 来说明问题
--DROP TABLE IF EXISTS lvl_one,lvl_two,lvl_three CASCADE;
CREATE TABLE IF NOT EXISTS public.lvl_one (
id bigint NOT NULL GENERATED ALWAYS AS IDENTITY,
name text,
CONSTRAINT lvl_one_pk PRIMARY KEY (id)
);
CREATE TABLE IF NOT EXISTS public.lvl_two (
id bigint NOT NULL GENERATED ALWAYS AS IDENTITY,
lvl_one_id bigint NOT NULL,
name text,
CONSTRAINT lvl_two_pk PRIMARY KEY (id),
CONSTRAINT lvl_two_lvl_one_id_fk FOREIGN KEY (lvl_one_id)
REFERENCES public.lvl_one (id)
);
CREATE TABLE IF NOT EXISTS public.lvl_three (
id bigint NOT NULL GENERATED ALWAYS AS IDENTITY,
lvl_two_id bigint NOT NULL,
name text,
CONSTRAINT lvl_three_pk PRIMARY KEY (id),
CONSTRAINT lvl_three_lvl_two_id_fk FOREIGN KEY (lvl_two_id)
REFERENCES public.lvl_two (id)
);
Run Code Online (Sandbox Code Playgroud)
-- initial data
INSERT INTO lvl_one(name) VALUES ('Honda'), ('Ford'), ('Toyota');
INSERT INTO lvl_two(lvl_one_id, name) VALUES (1,'Civic'), (1,'Passport'), (3,'Prius');
INSERT INTO lvl_three(lvl_two_id, name) VALUES (1,'door'), (1,'window'), (3,'trunk');
SELECT * FROM lvl_one ORDER BY id;
-- id, name
-- 1, "Honda"
-- 2. "Ford"
-- 3, "Toyota"
SELECT * FROM lvl_two ORDER BY id;
-- id, lvl_one_id, name
-- 1, 1, "Civic"
-- 2, 1, "Passport"
-- 3, 3, "Prius"
SELECT * FROM lvl_three ORDER BY id;
-- id, lvl_two_id, name
-- 1, 1, "door"
-- 2, 1, "window"
-- 3, 3, "trunk"
SELECT
one.id AS one_id, one.name AS one_name
, two.id AS two_id, two.name AS two_name
, three.id AS three_id, three.name AS three_name
FROM lvl_one AS one
LEFT OUTER JOIN lvl_two AS two ON one.id = two.lvl_one_id
LEFT OUTER JOIN lvl_three AS three ON two.id = three.lvl_two_id
ORDER BY one.id, two.id, three.id;
--1 "Honda" 1 "Civic" 1 "door"
--1 "Honda" 1 "Civic" 2 "window"
--1 "Honda" 2 "Passport" NULL NULL
--2 "Ford" NULL NULL NULL NULL
--3 "Toyota" 3 "Prius" 3 "trunk"
Run Code Online (Sandbox Code Playgroud)
ALTER TABLE lvl_one ADD COLUMN copied_from_id bigint;
ALTER TABLE lvl_two ADD COLUMN copied_from_id bigint;
ALTER TABLE lvl_three ADD COLUMN copied_from_id bigint;
-- copy row id=1 from lvl_one and all its child tables
WITH source_one AS (
SELECT id,name
FROM lvl_one
WHERE id=1
)
, copy_one AS (
INSERT INTO lvl_one(name,copied_from_id)
SELECT name,id AS copied_from_id
FROM source_one
RETURNING id AS new_one_id, copied_from_id
)
, copy_two AS (
INSERT INTO lvl_two(lvl_one_id,name,copied_from_id)
SELECT new_one_id, lvl_two.name,lvl_two.id AS copied_from_id
FROM copy_one
INNER JOIN lvl_one ON lvl_one.id = copy_one.copied_from_id
INNER JOIN lvl_two ON lvl_two.lvl_one_id = lvl_one.id
RETURNING id AS new_two_id, copied_from_id
)
, copy_three AS (
INSERT INTO lvl_three(lvl_two_id,name,copied_from_id)
SELECT new_two_id, lvl_three.name, lvl_three.id AS copied_from_id
FROM copy_two
INNER JOIN lvl_two ON lvl_two.id = copy_two.copied_from_id
INNER JOIN lvl_three ON lvl_three.lvl_two_id = lvl_two.id
RETURNING id AS new_three_id, copied_from_id
)
SELECT * FROM copy_one, copy_two, copy_three;
Run Code Online (Sandbox Code Playgroud)
lvl_one.id=1
由于“复制”lvl_one.id=1
行,将在所有 3 个表中创建以下行。
-- lvl_one
-- 4,Honda,1
-- lvl_two
--4,4,Civic,1
--5,4,Passport,2
-- lvl_three
--4,4,door,1
--5,4,window,2
Run Code Online (Sandbox Code Playgroud)
不幸的是,RETURNING
an 的子句INSERT
只能处理插入行中的列。由子句添加的列FROM
在那里不可见。
看:
为了解决这个限制,我建议在SELECT
每个之前INSERT
使用 提前生成预期的新序列 ID nextval()
。然后,您将旧 ID 和新 ID 放在同一行中以进行必要的连接。
此方法的一个额外的小问题是您的IDENTITY
列带有GENERATE ALWAYS
. 所以无论如何我们都需要OVERRIDING SYSTEM VALUE
写入INSERT
这些列。(或者您使用 创建IDENTITY
列GENERATED BY DEFAULT
):
WITH ins1 AS (
INSERT INTO lvl_one(name)
SELECT name
FROM lvl_one
WHERE id = 1 -- $1 here
RETURNING id AS new_parent_id, name -- just the one
)
, sel2 AS (
SELECT ins1.new_parent_id, t2.id, t2.name, nextval(pg_get_serial_sequence('lvl_two', 'id')) AS new_id
FROM ins1
JOIN lvl_two t2 ON t2.lvl_one_id = 1 -- and $1 here
)
, ins2 AS (
INSERT INTO lvl_two(id, lvl_one_id, name) OVERRIDING SYSTEM VALUE
SELECT new_id, new_parent_id, name
FROM sel2
)
, sel3 AS (
SELECT sel2.new_id AS new_parent_id, t3.id, t3.name, nextval(pg_get_serial_sequence('lvl_three', 'id')) AS new_id
FROM sel2
JOIN lvl_three t3 ON t3.lvl_two_id = sel2.id -- old parent ID
)
, ins3 AS (
INSERT INTO lvl_three(id, lvl_two_id, name) OVERRIDING SYSTEM VALUE
SELECT new_id, new_parent_id, name
FROM sel3
)
SELECT ins1.new_parent_id AS lvl1_id, ins1.name AS lvl1_name
, sel2.new_id AS lvl2_id, sel2.name AS lvl2_name
, sel3.new_id AS lvl3_id, sel3.name AS lvl3_name
FROM ins1
LEFT JOIN sel2 USING (new_parent_id)
LEFT JOIN sel3 ON sel3.new_parent_id = sel2.new_id
ORDER BY lvl1_id, lvl2_id, lvl3_id;
Run Code Online (Sandbox Code Playgroud)
db<>在这里摆弄
性能应该非常相似。主要好处是我们不需要按要求添加额外的表列。
第一个INSERT
很简单,因为根据定义它只能影响单行,所以我没有SELECT
在那里添加另一个。以下步骤遵循相同的模式,并且可以根据需要下降任意多个级别。
SELECT
另请注意,原始解决方案中的外部会产生不正确的结果(不影响实际插入的行):
...
SELECT * FROM copy_one, copy_two, copy_three;
Run Code Online (Sandbox Code Playgroud)
CROSS JOIN
表之间的行将合并不应合并的行并排除不应排除的行。
归档时间: |
|
查看次数: |
1206 次 |
最近记录: |