如何重写查询以将数据修改CTE置于顶层

时间:2019-06-05 03:04:54

标签: sql postgresql

我正在尝试为Postgres写一个查询,以便在一个表中插入一行,而副作用是,该表可能在第二个表中插入一行(由于第一个表在第二个表上有外键约束)。

我想我可以作为两个单独的查询来执行此操作,可能在一个事务中,但是现在我已经开始了单个查询的路径,如果可能的话,我想知道如何完成所有。

我一直在尝试使用How to use RETURNING with ON CONFLICT in PostgreSQL?Insert if names do not exist and return ids if exist中列出的一般结构,这似乎是一个合理的起点,但是我遇到了错误WITH clause containing a data-modifying statement must be at the top level,并且我不确定如何遵循该建议。

a如下所示:

CREATE TABLE a (
    a_uuid uuid PRIMARY KEY,
    b_uuid uuid REFERENCES b(b_uuid) 
)

b是:

CREATE TABLE b (
    b_uuid uuid PRIMARY KEY,
    b_name text UNIQUE
)

我想在a中插入一行,其中b_uuid是根据匹配b_name的输入来计算的。如果b已经有该行,则使用相应的b_uuid;否则,在b中用该文本和一个新生成的UUID创建新行,并返回后者以用作a中插入的一部分。这是我在意识到自己不知所措之前写的:

WITH new_b AS (
        WITH input (b_uuid, b_name) AS (
                VALUES (
                        gen_random_uuid(), $1
                )
        ), ins AS (
                INSERT INTO b (
                        b_uuid, b_name
                )
                TABLE input
                ON CONFLICT DO NOTHING
                RETURNING b_uuid
        )
        TABLE ins
        UNION ALL
        SELECT b_uuid FROM input
)
INSERT INTO a (a_uuid, b_uuid)
VALUES (
        gen_random_uuid(), (SELECT b_uuid FROM new_b)
)

我在附近吗?最好的方法是什么?

1 个答案:

答案 0 :(得分:1)

demo:db<>fiddle(由于随机事物,如果随机uuid等于1,您可能会重新加载几次;而不是我使用uuid的类型int,因为小提琴引擎当前不支持pgcrypto扩展。我用自己的功能模拟了该功能。)

WITH input (b_uuid, b_name) AS (
    VALUES (
        gen_random_uuid(), $1
    )
), ins_b AS (
    INSERT INTO b (
        b_uuid, b_name
    )
    TABLE input
    ON CONFLICT DO NOTHING
    RETURNING b_uuid
), new_b AS (
    TABLE ins_b
    UNION ALL
    SELECT b.b_uuid FROM input
    JOIN b USING (b_uuid)
)
INSERT INTO a (a_uuid, b_uuid)
VALUES (
    gen_random_uuid(), (SELECT b_uuid FROM new_b)
);

您的解决方案并不遥远:

  1. 操纵语句(例如INSERT)不能放在嵌套的WITH子句中(此时:谢谢,我什至不知道嵌套的CTE功能:D)
  2. @ErwinBrandstetter令人难以置信的出色解决方案(How to use RETURNING with ON CONFLICT in PostgreSQL?)的要点是JOIN您的解决方案缺失: new_b部分的工作方式如下:如果存在冲突,ins_b不返回任何内容。因此,TABLE ins_b为空。在这种情况下,需要直接从b_uuid调用已经存在的TABLE b。拿出生成的UUID,将其与b结合会给出现有的b_uuid(并且,如果需要,此记录的所有其他列)。但是,如果没有冲突-b_uuid还不存在-那么ins_b返回新数据集,TABLE ins_b不为空,但是原始表上的联接失败,因为仍然没有可用于加入的记录。 当然,这可以插入多个记录。