用于删除/插入行的SQL CTE语法

用于删除/插入行的SQL CTE语法,sql,postgresql,common-table-expression,postgresql-9.2,Sql,Postgresql,Common Table Expression,Postgresql 9.2,从表中删除,然后插入到同一个表并返回插入值的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_fie

从表中删除,然后插入到同一个表并返回插入值的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,并返回那些插入的记录而不是删除的记录。

您的问题更新清楚地表明,您不能在一条语句中执行此操作

打包到同一语句的CTE中,INSERT和DELETE操作将看到表的同一快照,并且几乎同时执行。也就是说,INSERT仍然会看到您认为已经删除的所有行

所有语句都使用相同的快照执行。请参见第章 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;
现在,插入可以看到删除的结果


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

详细介绍了skif1979的DelSert CTE方法,即记录的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 ; 

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

适用于

儿童档案 或不带FK的模式 或带级联删除的FK 不适用于

不带FK的父记录&无级联删除
与记录的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
    ;

另见:

Insert和delete似乎没有连接,但您想返回这两个操作的所有行吗?@ErwinBrandstetter可能不太关心删除,但不确定这是否是好的语法,在我能够按顺序执行操作后,将处理这一问题。好的,最后一位可能是个问题。请更新您的问题,定义您希望两者按顺序执行的确切方式;你是correct@vol7ron:好的,UNION从左到右求值参见此处的最后一章:。您还可以在下一个CTE中引用从RETURNING子句中获得的内容,以强制执行求值顺序。但是CTE总是看到底层表的相同快照。因此,可能存在另一个查询的竞争条件,没有表级别或约束保留,但在查询中,它将按预期顺序运行,对吗?只是想确保即使它是从左到右的,它也不会并行运行,或者在一个操作完成之前一直向前跳。@vol7ron:不确定你的意思是什么。如果DELETE和INSERT之间存在重叠,并且存在唯一约束,则不能为此使用单个语句的两个数据修改CTE。根据我的回答,你需要先在一个单独的声明中删除。我怀疑工会是解决问题的办法,并且确实促成了这种行为;它们可能是不同的快照,或者至少由两者处理相同的快照,从左到右依次说明在editI添加文档链接之前查询工作的原因。你不必相信我的话。从源头上阅读。
 RESULT: 
           _b4_id | _b4_fld2 | _aft_id | _aft_fld2 
          --------+----------+---------+-----------
                2 |      209 |       2 |       210
    -- 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