【问题标题】:How to find out if an upsert was an update with PostgreSQL 9.5+ UPSERT?如何确定 upsert 是否是 PostgreSQL 9.5+ UPSERT 的更新?
【发布时间】:2016-04-18 04:39:01
【问题描述】:

Insert, on duplicate update in PostgreSQL? 中所述,可写 CTE 被视为 9.5 之前的 UPSERT 解决方案

可以使用以下信息执行 UPSERT,无论它最终是 UPDATE 还是 INSERT,具有以下 Writable CTE 习语:

WITH
    update_cte AS (
        UPDATE t SET v = $1 WHERE id = $2 RETURNING 'updated'::text status
    ),
    insert_cte AS (
        INSERT INTO t(id, v) SELECT $2, $1 WHERE NOT EXISTS
            (SELECT 1 FROM update_cte) RETURNING 'inserted'::text status
    )
 (SELECT status FROM update_cte) UNION (SELECT status FROM insert_cte)

此查询将返回“更新”或“插入”,或者可能(很少)因违反约束而失败,如 https://dba.stackexchange.com/questions/78510/why-is-cte-open-to-lost-updates 中所述

是否可以使用 PostgreSQL 9.5+ 新的“UPSERT”语法来实现类似的效果,受益于其优化并避免可能的约束违规?

【问题讨论】:

    标签: postgresql upsert postgresql-9.5


    【解决方案1】:

    (xmax::text::bigint > 0)(NOT xmax = 0)。一旦事务计数达到整数溢出,类型转换为整数就会中断。

    【讨论】:

      【解决方案2】:

      我相信xmax::text::int > 0 会是最简单的技巧:

      so=# DROP TABLE IF EXISTS tab;
      NOTICE:  table "tab" does not exist, skipping
      DROP TABLE
      so=# CREATE TABLE tab(id INT PRIMARY KEY, col text);
      CREATE TABLE
      so=# INSERT INTO tab(id, col) VALUES (1,'a'), (2, 'b');
      INSERT 0 2
      so=# INSERT INTO tab(id, col)
      VALUES (3, 'c'), (4, 'd'), (1,'aaaa')
      ON CONFLICT (id) DO UPDATE SET col = EXCLUDED.col
      returning *,case when xmax::text::int > 0 then 'updated' else 'inserted' end,ctid;
       id | col  |   case   | ctid
      ----+------+----------+-------
        3 | c    | inserted | (0,3)
        4 | d    | inserted | (0,4)
        1 | aaaa | updated  | (0,5)
      (3 rows)
      
      INSERT 0 3
      so=# INSERT INTO tab(id, col)
      VALUES (3, 'c'), (4, 'd'), (1,'aaaa')
      ON CONFLICT (id) DO UPDATE SET col = EXCLUDED.col
      returning *,case when xmax::text::int > 0 then 'updated' else 'inserted' end,ctid;
       id | col  |  case   | ctid
      ----+------+---------+-------
        3 | c    | updated | (0,6)
        4 | d    | updated | (0,7)
        1 | aaaa | updated | (0,8)
      (3 rows)
      
      INSERT 0 3
      

      【讨论】:

      • 您可以只使用:``` RETURNING (xmax = 0) AS 已插入 ``
      • 有什么方法可以找出哪些行没有因为DO NOTHING 子句而受到影响?
      【解决方案3】:

      SQL Server MERGE 语句中有$action 返回字符串'INSERT', 'UPDATE', or 'DELETE'

      对于Postgresql,我找不到与RETURNING 类似的函数/变量。

      一种解决方法是将is_updated 列添加到您的表中:

      DROP TABLE IF EXISTS tab;
      
      CREATE TABLE tab(id INT PRIMARY KEY, col VARCHAR(100),
                       is_updated BOOLEAN DEFAULT false);
      INSERT INTO tab(id, col) VALUES (1,'a'), (2, 'b');
      
      
      -- main query
      INSERT INTO tab(id, col)
      VALUES (3, 'c'), (4, 'd'), (1,'aaaa')
      ON CONFLICT (id) DO UPDATE SET col = EXCLUDED.col, is_updated = true
      RETURNING id,col,
                CASE WHEN is_updated THEN 'UPDATED' ELSE 'INSERTED' END AS action;
      

      Rextester Demo

      输出:

      ╔════╦══════╦══════════╗
      ║ id ║ col  ║  action  ║
      ╠════╬══════╬══════════╣
      ║  3 ║ c    ║ INSERTED ║
      ║  4 ║ d    ║ INSERTED ║
      ║  1 ║ aaaa ║ UPDATED  ║
      ╚════╩══════╩══════════╝
      

      【讨论】:

      • 输出中的 VALUES (2, 'b') 在哪里?
      • @kometen 不需要它。它以前存在。为什么要返回整个表?仅插入/更新的记录
      • 但是 (2, 'b') 也被插入了。并且 is_updated 默认为 false。所以我认为它会显示为 INSERTED。
      • @kometen 重点是:假设您有一个包含 200 万条记录的表。现在您执行主查询。返回将只返回 3 条记录(插入 2 条,更新 1 条)。第一个插入是预填充数据(用于演示)。
      • 好的,所以它与那个特定的 INSERT 相关。很有用,谢谢。
      【解决方案4】:

      借鉴@lad2025's answer,可以通过在WHERE子句中滥用settingscustomized optionsrelated functions来获得所需的副作用。

      CREATE TABLE t(id INT PRIMARY KEY, v TEXT);
      
      INSERT INTO t (id, v)
          SELECT $1, $2
          WHERE 'inserted' = set_config('upsert.action', 'inserted', true)
          ON CONFLICT (id) DO UPDATE
              SET v = EXCLUDED.v
              WHERE 'updated' = set_config('upsert.action', 'updated', true)
      RETURNING current_setting('upsert.action') AS "upsert.action";
      

      set_config 的第三个参数是is_localtrue 表示设置将在事务结束时消失。更准确地说,current_setting('upsert.action') 将返回 NULL(并且不会引发错误),直到会话结束。

      【讨论】:

      • 天哪,太丑了!它让我的视线变得模糊。这个我绝对不想记住,更别提应用了!~
      • 你可以用 xmax::text::int > 0 来做 - 没有事务级别设置(这确实是原始黑客)
      • @VaoTsun 是对的。有关如何为此目的使用 xmax 的示例,请参阅 stackoverflow.com/a/38858662/454126。不漂亮,但绝对比 set_config / current_setting 漂亮。
      猜你喜欢
      • 2017-11-22
      • 2016-10-01
      • 1970-01-01
      • 2014-12-29
      • 1970-01-01
      • 2018-09-26
      • 1970-01-01
      • 1970-01-01
      • 2012-12-06
      相关资源
      最近更新 更多