为什么带有DELETE的PostgreSQL CTE无法正常工作?

时间:2018-10-21 20:45:32

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

如果同一表中的更新使用两个CTE导致数量为0,我试图从库存表中删除一条记录。 ups工作,但删除未生成我期望的结果。库存表中的数量将更改为零,但未删除该记录。 表结构:

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中删除记录。

有道理吗?

2 个答案:

答案 0 :(得分:2)

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

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

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

如何解决??您需要将UPDATE&DELETE分为两个独立的语句。

如果您需要传递有关删除内容的信息,可以例如(1)创建一个临时表并插入已更新的数据主键,以便您可以加入该表在您后面的查询中(根据已更新的数据删除)。 (2)您可以通过简单地在已更新表中添加一列并更改其值以标记已更新的行或(3)来获得相同的结果,但是您希望获得它工作完成。您应该会感觉到上面的示例需要做什么。

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

  

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

     

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

答案 1 :(得分:0)

添加上面有用的解释...只要有可能,最好将修改过程分解为它们自己的语句。

但是,当 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 内的同时强制执行命令的方法(我不会认为它太老套)。

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

相关问题