将数据修改 CTE 中的 INSERT 语句与 CASE 表达式相结合
Combining INSERT statements in a data-modifying CTE with a CASE expression
我的问题是对 Erwin Brandstetter 在 this thread 中关于正确使用 WITH
.
的出色回答的某种扩展
我的旧查询如下所示:
WITH x AS (
INSERT INTO d (dm_id)
SELECT dm_id
FROM dm, import i
WHERE dm.dm_name = i.dm_name
RETURNING d_id
), y AS (
INSERT INTO z (d_id)
SELECT d_id
FROM x
RETURNING z_id
)
INSERT INTO port (z_id)
SELECT z_id
FROM y;
这很有魅力。但是现在,添加了另一个 table (r
)(与 table d
相同的结构)并且可能 d_id
或 r_id
必须添加到 table z
。这个,取决于dm_name
或rm_name
在tableimport
中是否为空。所以我的理论方法是这样的:
SELECT dm_name, rm_name
,CASE WHEN dm_name != '' THEN
WITH x AS (
INSERT INTO d (dm_id)
SELECT dm_id
FROM dm, import i
WHERE dm.dm_name = i.dm_name
RETURNING d_id
), y AS (
INSERT INTO z (d_id)
SELECT d_id
FROM x
RETURNING z_id
)
INSERT INTO port (z_id)
SELECT z_id
FROM y
END
,CASE WHEN rm_name != '' THEN
WITH x AS (
INSERT INTO r (rm_id)
SELECT rm_id
FROM rm, import i
WHERE rm.rm_name = i.rm_name
RETURNING r_id
), y AS (
INSERT INTO z (r_id)
SELECT r_id
FROM x
RETURNING z_id
)
INSERT INTO port (z_id)
SELECT z_id
FROM y
END
FROM import;
但是 PostgreSQL 告诉我:
syntax error at or near "INSERT INTO port (z_id)"
虽然查询的那部分应该是正确的,因为它已经有效了。
我希望你能帮我解决这个问题。 :)
为了更好地理解 - 这是 table 结构:
CREATE TABLE import (
dm_name character varying,
rm_name character varying
-- many other columns which are not relevant
);
CREATE TABLE dm (
dm_id integer NOT NULL, -- serial
dm_name character varying
-- plus more columns
);
CREATE TABLE d (
d_id integer NOT NULL, -- serial
dm_id integer -- references dm.dm_id
-- plus more columns
);
CREATE TABLE rm (
rm_id integer NOT NULL, -- serial
rm_name character varying
-- plus more columns
);
CREATE TABLE r (
r_id integer NOT NULL, -- serial
rm_id integer -- references rm.rm_id
-- plus more columns
);
CREATE TABLE z (
z_id integer NOT NULL, -- serial
r_id integer, -- references r.r_id
d_id integer -- references d.d_id
-- plus more columns
);
CREATE TABLE port (
p_id integer NOT NULL, -- serial
z_id integer, -- references z.z_id
-- plus more columns
);
导入 table 不知道 id,因为它们是在原子化过程中生成的。 dm 和 rm tables 用于已从导入 table 中提取的设备模型。 d 和 r tables 用于实际设备。由于端口只能有一个 r-device 或 d-device 或 none,z-table 被引入以在 port-table 中只有一个字段代表所有可能性。 d/r 和 dm/rm table 不能合并,因为它们根据设备类型有不同的特殊列。
您不能在 CASE
表达式中嵌套 INSERT
语句。从我所看到的情况来看,这种完全不同的方法应该可以做到:
假设
你实际上并不需要外层 SELECT
.
dm_name
/ rm_name
在 dm
/ rm
中定义唯一且不为空 (<> ''
)。你应该有一个 CHECK
约束来确保。
z
中 d_id
和 r_id
的列默认值为 NULL(默认)。
dm_name
和rm_name
互斥
如果两者从未同时存在。
WITH d1 AS (
INSERT INTO d (dm_id)
SELECT dm.dm_id
FROM import
JOIN dm USING (dm_name)
RETURNING d_id
)
, r1 AS (
INSERT INTO r (rm_id)
SELECT rm.rm_id
FROM import
JOIN rm USING (rm_name)
RETURNING r_id
)
, z1 AS (
INSERT INTO z (d_id, r_id)
SELECT d_id, r_id
FROM d1 FULL JOIN r1 ON FALSE
RETURNING z_id
)
INSERT INTO port (z_id)
SELECT z_id
FROM z1;
FULL JOIN .. ON FALSE
生成一个派生的 table,其中所有来自 d1
和 r1
的行都为相应的其他列附加了 NULL(两者之间没有重叠)。所以我们只需要一个 INSERT
而不是两个。小优化。
dm_name
和rm_name
可以共存
WITH i AS (
SELECT dm.dm_id, rm.rm_id
FROM import
LEFT JOIN dm USING (dm_name)
LEFT JOIN rm USING (rm_name)
)
, d1 AS (
INSERT INTO d (dm_id)
SELECT dm_id FROM i WHERE dm_id IS NOT NULL
RETURNING dm_id, d_id
)
, r1 AS (
INSERT INTO r (rm_id)
SELECT rm_id FROM i WHERE rm_id IS NOT NULL
RETURNING rm_id, r_id
)
, z1 AS (
INSERT INTO z (d_id, r_id)
SELECT d1.d_id, r1.r_id
FROM i
LEFT JOIN d1 USING (dm_id)
LEFT JOIN r1 USING (rm_id)
WHERE d1.dm_id IS NOT NULL OR
r1.rm_id IS NOT NULL
RETURNING z_id
)
INSERT INTO port (z_id)
SELECT z_id FROM z1;
备注
如果两个版本都不存在,也可以使用。
如果 SELECT
没有 returns 行,INSERT
不插入任何内容。
如果您必须处理可能与此操作冲突的并发写入访问,快速解决方法是在同一事务中 运行 此语句之前锁定涉及的 tables。
我的问题是对 Erwin Brandstetter 在 this thread 中关于正确使用 WITH
.
我的旧查询如下所示:
WITH x AS (
INSERT INTO d (dm_id)
SELECT dm_id
FROM dm, import i
WHERE dm.dm_name = i.dm_name
RETURNING d_id
), y AS (
INSERT INTO z (d_id)
SELECT d_id
FROM x
RETURNING z_id
)
INSERT INTO port (z_id)
SELECT z_id
FROM y;
这很有魅力。但是现在,添加了另一个 table (r
)(与 table d
相同的结构)并且可能 d_id
或 r_id
必须添加到 table z
。这个,取决于dm_name
或rm_name
在tableimport
中是否为空。所以我的理论方法是这样的:
SELECT dm_name, rm_name
,CASE WHEN dm_name != '' THEN
WITH x AS (
INSERT INTO d (dm_id)
SELECT dm_id
FROM dm, import i
WHERE dm.dm_name = i.dm_name
RETURNING d_id
), y AS (
INSERT INTO z (d_id)
SELECT d_id
FROM x
RETURNING z_id
)
INSERT INTO port (z_id)
SELECT z_id
FROM y
END
,CASE WHEN rm_name != '' THEN
WITH x AS (
INSERT INTO r (rm_id)
SELECT rm_id
FROM rm, import i
WHERE rm.rm_name = i.rm_name
RETURNING r_id
), y AS (
INSERT INTO z (r_id)
SELECT r_id
FROM x
RETURNING z_id
)
INSERT INTO port (z_id)
SELECT z_id
FROM y
END
FROM import;
但是 PostgreSQL 告诉我:
syntax error at or near "INSERT INTO port (z_id)"
虽然查询的那部分应该是正确的,因为它已经有效了。
我希望你能帮我解决这个问题。 :)
为了更好地理解 - 这是 table 结构:
CREATE TABLE import (
dm_name character varying,
rm_name character varying
-- many other columns which are not relevant
);
CREATE TABLE dm (
dm_id integer NOT NULL, -- serial
dm_name character varying
-- plus more columns
);
CREATE TABLE d (
d_id integer NOT NULL, -- serial
dm_id integer -- references dm.dm_id
-- plus more columns
);
CREATE TABLE rm (
rm_id integer NOT NULL, -- serial
rm_name character varying
-- plus more columns
);
CREATE TABLE r (
r_id integer NOT NULL, -- serial
rm_id integer -- references rm.rm_id
-- plus more columns
);
CREATE TABLE z (
z_id integer NOT NULL, -- serial
r_id integer, -- references r.r_id
d_id integer -- references d.d_id
-- plus more columns
);
CREATE TABLE port (
p_id integer NOT NULL, -- serial
z_id integer, -- references z.z_id
-- plus more columns
);
导入 table 不知道 id,因为它们是在原子化过程中生成的。 dm 和 rm tables 用于已从导入 table 中提取的设备模型。 d 和 r tables 用于实际设备。由于端口只能有一个 r-device 或 d-device 或 none,z-table 被引入以在 port-table 中只有一个字段代表所有可能性。 d/r 和 dm/rm table 不能合并,因为它们根据设备类型有不同的特殊列。
您不能在 CASE
表达式中嵌套 INSERT
语句。从我所看到的情况来看,这种完全不同的方法应该可以做到:
假设
你实际上并不需要外层
SELECT
.dm_name
/rm_name
在dm
/rm
中定义唯一且不为空 (<> ''
)。你应该有一个CHECK
约束来确保。z
中d_id
和r_id
的列默认值为 NULL(默认)。
dm_name
和rm_name
互斥
如果两者从未同时存在。
WITH d1 AS (
INSERT INTO d (dm_id)
SELECT dm.dm_id
FROM import
JOIN dm USING (dm_name)
RETURNING d_id
)
, r1 AS (
INSERT INTO r (rm_id)
SELECT rm.rm_id
FROM import
JOIN rm USING (rm_name)
RETURNING r_id
)
, z1 AS (
INSERT INTO z (d_id, r_id)
SELECT d_id, r_id
FROM d1 FULL JOIN r1 ON FALSE
RETURNING z_id
)
INSERT INTO port (z_id)
SELECT z_id
FROM z1;
FULL JOIN .. ON FALSE
生成一个派生的 table,其中所有来自 d1
和 r1
的行都为相应的其他列附加了 NULL(两者之间没有重叠)。所以我们只需要一个 INSERT
而不是两个。小优化。
dm_name
和rm_name
可以共存
WITH i AS (
SELECT dm.dm_id, rm.rm_id
FROM import
LEFT JOIN dm USING (dm_name)
LEFT JOIN rm USING (rm_name)
)
, d1 AS (
INSERT INTO d (dm_id)
SELECT dm_id FROM i WHERE dm_id IS NOT NULL
RETURNING dm_id, d_id
)
, r1 AS (
INSERT INTO r (rm_id)
SELECT rm_id FROM i WHERE rm_id IS NOT NULL
RETURNING rm_id, r_id
)
, z1 AS (
INSERT INTO z (d_id, r_id)
SELECT d1.d_id, r1.r_id
FROM i
LEFT JOIN d1 USING (dm_id)
LEFT JOIN r1 USING (rm_id)
WHERE d1.dm_id IS NOT NULL OR
r1.rm_id IS NOT NULL
RETURNING z_id
)
INSERT INTO port (z_id)
SELECT z_id FROM z1;
备注
如果两个版本都不存在,也可以使用。
如果SELECT
没有 returns 行,INSERT
不插入任何内容。
如果您必须处理可能与此操作冲突的并发写入访问,快速解决方法是在同一事务中 运行 此语句之前锁定涉及的 tables。