【问题标题】:Why PostgreSQL CTE with DELETE is not working?为什么带有 DELETE 的 PostgreSQL CTE 不起作用?
【发布时间】:2018-10-21 20:45:31
【问题描述】:

如果同一表中的更新导致使用两个 CTE 的数量为 0,我试图从我的 stock 表中删除一条记录。 upserts 正在工作,但删除没有产生我期望的结果。库存表中的数量正在更改为零,但记录没有被删除。 表结构:

CREATE TABLE IF NOT EXISTS stock_location (
    stock_location_id SERIAL
    , site_code VARCHAR(10) NOT NULL
    , location_code VARCHAR(50) NOT NULL
    , status CHAR(1) NOT NULL DEFAULT 'A'
    , CONSTRAINT pk_stock_location PRIMARY KEY (stock_location_id)
    , CONSTRAINT ui_stock_location__keys UNIQUE (site_code, location_code)
);

CREATE TABLE IF NOT EXISTS stock (
    stock_id SERIAL
    , stock_location_id INT NOT NULL
    , item_code VARCHAR(50) NOT NULL
    , quantity FLOAT NOT NULL
    , CONSTRAINT pk_stock PRIMARY KEY (stock_id)
    , CONSTRAINT ui_stock__keys UNIQUE (stock_location_id, item_code)
    , CONSTRAINT fk_stock__stock_location FOREIGN KEY (stock_location_id)
        REFERENCES stock_location (stock_location_id)
        ON DELETE CASCADE ON UPDATE CASCADE
);

这就是语句的样子:

WITH stock_location_upsert AS (
    INSERT INTO stock_location (
        site_code
        , location_code
        , status
    ) VALUES (
        inSiteCode
        , inLocationCode
        , inStatus
    )
    ON CONFLICT ON CONSTRAINT ui_stock_location__keys
        DO UPDATE SET
            status = inStatus
    RETURNING stock_location_id
)
, stock_upsert AS (
    INSERT INTO stock (
        stock_location_id
        , item_code
        , quantity
    )
    SELECT
        slo.stock_location_id
        , inItemCode
        , inQuantity
    FROM stock_location_upsert slo
    ON CONFLICT ON CONSTRAINT ui_stock__keys
        DO UPDATE SET
            quantity = stock.quantity + inQuantity
        RETURNING stock_id, quantity
)
DELETE FROM stock stk
USING stock_upsert stk2
WHERE stk.stock_id = stk2.stock_id
    AND stk.quantity = 0;

有人知道怎么回事吗?

这是我正在尝试做的一个例子:

DROP TABLE IF EXISTS test1;

CREATE TABLE IF  NOT EXISTS test1 (
    id serial
    , code VARCHAR(10) NOT NULL
    , description VARCHAR(100) NOT NULL
    , quantity INT NOT NULL
    , CONSTRAINT pk_test1 PRIMARY KEY (id)
    , CONSTRAINT ui_test1 UNIQUE (code)
);

-- UPSERT
WITH test1_upsert AS (
    INSERT INTO test1 (
        code, description, quantity
    ) VALUES (
        '01', 'DESC 01', 1
    ) 
    ON CONFLICT ON CONSTRAINT ui_test1 
        DO UPDATE SET
            description = 'DESC 02'
            , quantity = 0
    RETURNING test1.id, test1.quantity
)
DELETE FROM test1 
USING test1_upsert
WHERE test1.id = test1_upsert.id
    AND test1_upsert.quantity = 0;

UPSERT 命令第二次运行时,一旦数量更新为零,它应该从 test1 中删除记录。

有道理吗?

【问题讨论】:

  • 什么不起作用?请添加一些数据和数据结构的示例。
  • @GrzegorzGrabek 我添加了表格结构。
  • 这不是回答问题所必需的,但这是无效的陈述(没有数据来源):INSERT INTO stock_location (site_code, location_code, status) VALUES (inSiteCode, inLocationCode, inStatus) 我想这是由过程的参数传递的,但这也应该包含在您的问题或更改为静态值。

标签: sql postgresql common-table-expression sql-delete


【解决方案1】:

在这里,DELETE 正在按照设计的方式工作。答案实际上非常简单明了并且有文档记录。几年前我也经历过同样的行为。

您的删除实际上并未删除数据的原因是,就删除语句所见而言,您的 where 条件与表中存储的内容不匹配。

CTE(公用表表达式)中的所有子语句都使用相同的快照数据执行,因此他们看不到目标上的其他语句效果桌子。在这种情况下,当您运行UPDATE,然后运行DELETE 时,DELETE 语句会看到与UPDATE 相同的数据,而不会看到UPDATE 语句修改的更新数据。

您如何解决这个问题?您需要将 UPDATE 和 DELETE 分成两个独立的语句。

如果您需要传递有关删除内容的信息,例如 (1) 创建一个临时表并插入已更新的数据主键,以便您可以加入该表在您的后一个查询中(根据已更新的数据删除)。 (2) 您可以通过简单地在更新的表中添加一列并更改其值以标记更新的行或 (3) 来实现相同的结果完成的工作。通过上面的例子你应该对需要做的事情有所了解。

引用手册来支持我的发现: 7.8.2. Data-Modifying Statements in WITH

WITH 中的子语句相互并发执行 并与主要查询。因此,当使用数据修改 WITH 中的语句,指定的实际更新顺序 发生是不可预测的。 所有语句都以相同的方式执行 快照(参见第 13 章),因此他们无法“看到”彼此的效果 在目标表上。

(...) 这也适用于删除已在同一语句中更新的行:仅执行更新

【讨论】:

  • 如果是这样的话,那么当我们在另一个cte中使用一个cte的输出时会发生什么,它是否等到前一个cte的执行,以便它可以使用前一个cte的输出?
  • @SABER-FICTIONALCHARACTER 在您使用 RETURNING 子句然后从另一个子句中调用一个 CTE 的情况下,它必须在该子句之前执行,因为如果您正在构建结果-set 稍后将用于让我们说一个更新语句,它必须知道在什么上运行它。不过,从本质上讲,CTE 是要同时执行的。
  • 非常感谢@KamilGosciminski。实际上我看到了这个文档,但我不太了解。现在对我来说更清楚了。我现在更改了我的功能以在 CTE 之后的另一个命令中验证并删除它。
【解决方案2】:

添加到上述有用的解释中...尽可能将修改过程分解为自己的语句是绝对最好的。

但是,当 CTE 有多个引用相同子查询的修改过程并且临时表不理想(例如在存储过程中)时,您只需要一个好的解决方案。

在这种情况下,如果您想要一个关于如何确保秩序的简单技巧,请考虑以下示例:

WITH
    to_insert AS
(
SELECT
    *
FROM new_values
)
,   first AS
(
DELETE FROM some_table
WHERE
    id in (SELECT id FROM to_insert)
RETURNING *
)
INSERT INTO some_other_table
SELECT * FROM new_values
WHERE
    exists (SELECT count(*) FROM first)
;

这里的窍门是exists (SELECT count(*) FROM first) 部分,它必须在插入发生之前首先执行。这是一种在执行命令的同时将所有内容都保留在一个 CTE 内的方式(我不会认为它太老套)。

但这只是概念 - 对于给定的上下文,还有更优化的方法可以做同样的事情。

【讨论】:

    猜你喜欢
    • 2012-11-28
    • 1970-01-01
    • 1970-01-01
    • 2017-06-20
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多