【问题标题】:SQL CTE Syntax to DELETE / INSERT rows删除/插入行的 SQL CTE 语法
【发布时间】:2015-06-17 21:10:57
【问题描述】:

从表中删除,然后插入到同一个表并返回插入的值的CTE语法是什么?

在 2 小时的睡眠中运行,但看起来有些不对劲(除了这不会执行):

WITH delete_rows AS (
   DELETE FROM <some_table> WHERE id = <id_value>
   RETURNING *
)
SELECT * FROM delete_rows
UNION
(
   INSERT INTO <some_table> ( id, text_field )
      VALUES ( <id_value>, '<text_field_value>' )
      RETURNING *
)

预期的行为是首先清除一个 ID 的所有记录,然后插入相同 ID 的记录(故意不是 upsert)并返回那些插入的记录(不是删除)。

【问题讨论】:

  • 插入和删除似乎没有关联,但是您想从 both 操作中返回所有行吗?
  • @ErwinBrandstetter 可能真的不太关心 delete,但不确定这是一个好的语法,在我可以执行操作之后处理它订购
  • 嗯,最后一点可能是个问题。请更新您的问题,定义您希望两者“按顺序”执行的方式确切
  • 感谢欧文,已更新;你是对的

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


【解决方案1】:

您的问题更新清楚地表明您不能在单个语句中做到这一点。

打包到同一语句的 CTE 中,两个操作(INSERTDELETE)将看到表的相同快照并几乎同时执行。即,INSERT 仍会看到您认为已被删除的所有行。 The manual:

所有语句都使用相同的快照执行(参见Chapter 13),因此它们无法“看到”彼此对目标表的影响。

您可以将它们作为两个独立的语句包装到同一个事务中——这似乎也不是绝对必要的,但它可以让整个操作自动成功/失败:

BEGIN;

DELETE FROM <some_table> WHERE id = <id_value>;

INSERT INTO <some_table> (id, text_field)
VALUES ( <id_value>, '<text_field_value>')
RETURNING *;

COMMIT;

现在,INSERT 可以看到DELETE 的结果。

【讨论】:

  • @vol7ron:嗯,UNION 从左到右计算(参见此处的最后一章:stackoverflow.com/a/30813090/939860)。您还可以在下一个 CTE 中引用从 RETURNING 子句中获得的内容来强制执行评估顺序。但 CTE总是看到基础表的相同快照。
  • 因此可能存在与另一个查询的竞争条件,没有表级或约束保留,但在查询中,它将按预期顺序执行,对吗?只是想确保即使它是从左到右的,它也不会并行操作,或者向前跳直到一个操作完成。
  • @vol7ron:不知道你在说什么。如果DELETEINSERT 之间有任何重叠并且存在唯一约束,则您不能为此使用单个语句的两个数据修改CTE。根据我的回答,您需要先在单独的语句中删除。
  • 我怀疑union 是解决方法并且确实启用了该行为;它们可能是不同的快照,或者至少两者都处理相同的快照,从左到右的顺序(您的查询在编辑之前起作用的原因)
  • 我添加了一个文档链接。你不必相信我的话。阅读源代码。
【解决方案2】:
CREATE TABLE test_table (value TEXT UNIQUE);
INSERT INTO test_table SELECT 'value 1';
INSERT INTO test_table SELECT 'value 2';

WITH delete_row AS (DELETE FROM test_table WHERE value='value 2' RETURNING 0)
  INSERT INTO test_table
    SELECT DISTINCT 'value 2' 
    FROM (SELECT 'dummy') dummy
    LEFT OUTER JOIN delete_row ON TRUE
    RETURNING *;

上面的查询处理DELETE删除0/1/一些行的情况。

【讨论】:

    【解决方案3】:

    详述skif1979的“DelSert”CTE方法,“Logged DelSert:”

    -- setups
    DROP TABLE IF EXISTS _zx_t1 ;
    
    CREATE TEMP TABLE 
      IF NOT EXISTS 
         _zx_t1 
         ( id bigint
         , fld2 bigint
         , UNIQUE (id)
         );
    -- unique records
    INSERT INTO _zx_t1 SELECT 1, 99;
    INSERT INTO _zx_t1 SELECT 2, 98;
    
    
    WITH 
      _cte_del_row AS 
       (   DELETE 
           FROM _zx_t1 
           WHERE id = 2 
         RETURNING id as _b4_id, fld2 as _b4_fld2 -- returns complete deleted row
       )
     , _cte_delsert AS
         (  INSERT 
           INTO _zx_t1 
           SELECT DISTINCT 
              _cte_del_row._b4_id
            , _cte_del_row._b4_fld2 + 1 
            from (SELECT null::integer AS _zunk) _zunk  -- skif1979's trick here
                 LEFT OUTER JOIN _cte_del_row         -- clever LOJ magic  
                 ON TRUE                              -- LOJ cartesian product
            RETURNING id as _aft_id , fld2 as _aft_fld2 -- return newly "delserted" rows
           )
      SELECT * -- returns before & after snapshots from CTE's
      FROM 
       _cte_del_row
       , _cte_delsert ; 
    

     RESULT: 
               _b4_id | _b4_fld2 | _aft_id | _aft_fld2 
              --------+----------+---------+-----------
                    2 |      209 |       2 |       210
    

    AFAICT 这些都在一个工作单元中线性发生,类似于日志或日志更新。

    • 适用于

      • 子记录
      • OR Schema w/no FK
      • 或带有级联删除的 FK
    • 不适合

      • 带 FK 且无级联删除的父记录

    【讨论】:

      【解决方案4】:

      一个相关的(和 IMO 更好的)答案,类似于“Logged DelSert”,这是一个记录的“SelUp”:

          -- setups
          DROP TABLE IF EXISTS _zx_t1 ;
      
          CREATE TEMP TABLE 
            IF NOT EXISTS 
               _zx_t1 
               ( id bigint
               , fld2 bigint
               , UNIQUE (id)
               );
          -- unique records
          INSERT INTO _zx_t1 SELECT 1, 99;
          INSERT INTO _zx_t1 SELECT 2, 98;
      
      
          WITH 
            _cte_sel_row AS 
             (   SELECT                 -- start unit of work with read
                    id as _b4_id        -- fields need to be aliased 
                   ,fld2 as _b4_fld2    -- to prevent ambiguous column errors
                 FROM _zx_t1 
                 WHERE id = 2
                 FOR UPDATE 
             )
           , _cte_sel_up_ret AS           -- we're in the same UOW
             (  UPDATE _zx_t1             -- actual table
                 SET fld2 = _b4_fld2 + 1  -- some actual work
                FROM  _cte_sel_row    
                  WHERE id = _b4_id
                     AND fld2 < _b4_fld2 + 1  -- gratuitous but illustrates the point 
                RETURNING id as _aft_id, fld2 as _aft_fld2
               ) 
          SELECT  
                _cte_sel_row._b4_id
               ,_cte_sel_row._b4_fld2         -- before
               ,_cte_sel_up_ret._aft_id  
               ,_cte_sel_up_ret._aft_fld2     -- after
             FROM _cte_sel_up_ret  
                INNER JOIN _cte_sel_row  
                 ON TRUE AND _cte_sel_row._b4_id = _cte_sel_up_ret._aft_id
          ;
      

       RESULT: 
                 _b4_id | _b4_fld2 | _aft_id | _aft_fld2 
                --------+----------+---------+-----------
                      2 |      209 |       2 |       210
      

      另请参阅: https://rob.conery.io/2018/08/13/transactional-data-operations-in-postgresql-using-common-table-expressions/

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2021-08-16
        • 2013-08-04
        • 2018-03-20
        • 1970-01-01
        • 2023-03-26
        • 1970-01-01
        • 1970-01-01
        • 2022-06-11
        相关资源
        最近更新 更多