【问题标题】:Combining INSERT statements in a data-modifying CTE with a CASE expression将数据修改 CTE 中的 INSERT 语句与 CASE 表达式相结合
【发布时间】:2015-03-13 22:46:10
【问题描述】:

我的问题是对 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_idr_id 添加到表 z。这取决于dm_namerm_name 在表import 中是否为空。所以我的理论方法是这样的:

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 告诉我:

“INSERT INTO 端口 (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-device 或 d-device 或没有,因此引入 z-table 以在 port-table 中只有一个字段表示所有可能性。 d/r 和 dm/rm 表不能组合,因为它们有不同的特殊列,具体取决于设备类型。

【问题讨论】:

  • @Lamak,它在 postgresql 9.2+ 中有效,但我希望大多数人不要这样使用它。对我来说,CTE 能够在事务中分离、简化和重用复杂的逻辑。将插入物放在那里只会否定受益
  • @HaleemurAli:这实际上是一个非常好的功能。在单个语句中删除、插入和显示已删除的行:sqlfiddle.com/#!15/7b0ea/2
  • 您的查询中缺少表别名:FROM dm, import i。还有一些 ; 不应该在那里。但还有更多。我会添加一个答案。
  • 感谢 Erwin,但这些错误仅在此编辑版本中出现。真正的查询已经修复了它们(只是忘记在这里更正)。但我仍然收到语法错误(phppgadmin 指向 INSERT):-(

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


【解决方案1】:

您不能将INSERT 语句嵌套在CASE 表达式中。从我所看到的情况来看,这种完全不同的方法应该可以做到:

假设

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

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

  • zd_idr_id 的列默认值为 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 生成一个派生表,其中来自d1r1 的所有行都为各自的另一列附加了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;

注意事项

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

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

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

【讨论】:

  • 嗨 Erwin,请参阅我的编辑了解更多详细信息。我正在尝试改编你的独家版本
  • 好的,独家版本工作(没有错误),但没有达到预期的结果。导入表有大约 800 条记录。该查询导致 d 有 629 个条目,r 有 45 个条目,z 和 port 有 28305 (!) 个条目。不知道这里出了什么问题。我能想到的与您的版本的唯一区别 - 我不能使用“USING”,因为实际表中的列名不匹配。所以我不得不依靠“ON”。
  • @Picl:哦,对不起。 FULL JOIN 必须是 ON FALSE。现在修好了。也添加了一些解释
  • @Picl:也应该尽可能短且快。 :)
猜你喜欢
  • 1970-01-01
  • 2016-05-27
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-03-28
  • 1970-01-01
相关资源
最近更新 更多