sql - 将数据修改 CTE 中的 INSERT 语句与 CASE 表达式组合

标签 sql postgresql case common-table-expression sql-insert

我的问题是对 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;

这就像一个魅力。但现在,添加了另一个表 (r)(与表 d 结构相同),并且可能是 d_id >r_id 必须添加到表 z 中。这取决于表 import 中的 dm_namerm_name 是否为空。所以我的理论方法是这样的:

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)"

尽管查询的这一部分应该是正确的,因为它已经可以工作了。
我希望你能帮我解决这个问题。 :)

为了更好地理解 - 这是表结构:

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
);

导入表不知道 ID,因为它们是在原子化过程中生成的。 dm 和 rm 表适用于已从导入表中提取的设备模型。 d 和 r 表适用于实际设备。由于端口只能有 r 设备或 d 设备或没有,因此引入 z 表以在端口表中只有一个字段代表所有可能性。 d/r 和 dm/rm 表无法组合,因为它们根据设备类型具有不同的特殊列。

最佳答案

你不能嵌套INSERT CASE 中的语句表达。根据我的观察,这种完全不同的方法应该可以做到这一点:

假设

  • 你实际上并不需要外部 SELECT .

  • dm_name/rm_namedm 中定义为唯一/rm并且不为空( <> '' )。你应该有一个CHECK约束以确保。

  • d_id 的列默认值和r_idz为 NULL(默认)。

dm_namerm_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生成一个派生表,其中包含 d1 中的所有行和r1为相应的其他列附加 NULL(两者之间没有重叠)。所以我们只需要一个INSERT而不是两个。小幅优化。

dm_namerm_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;

注释

如果都不存在,两个版本也都可以工作。

INSERT如果 SELECT 则不插入任何内容不返回行。

如果您必须处理可能与此操作冲突的并发写入访问,快速修复方法是在同一事务中运行此语句之前锁定相关表。

关于sql - 将数据修改 CTE 中的 INSERT 语句与 CASE 表达式组合,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/28992241/

相关文章:

php - Symfony2 查找重复行并对另一列求和

sql - Firebird rownum *or* linq 样式跳过

sql - 如何使用SQL中的casewhen和partitionby将多个值合并为一个值?

sql - 根据值是否存在于另一个表中进行更新

SQL Case 语句在 where 子句中指定条件?

php - 使用 PHP 更新数据库中现有库存水平和价格的数组值

mysql - 如何为以下 SQL 查询编写 HQL 查询?

postgresql - 在 Rust 中使用 Tokio-postgres 向 Postgres 插入多个值

python - 无法从 scrapy 项目中将数据插入到 sql 表中

PostgreSQL WITH RECURSIVE 查询通过分区键获取有序的父子链