32

可写 CTE 被认为是 9.5 之前的 UPSERT 解决方案,如插入中所述,关于 PostgreSQL 中的重复更新?

可以使用以下信息执行 UPSERT,无论它最终是 UPDATE 还是 INSERT,具有以下可写 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中所述-丢失更新

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

4

4 回答 4

30

我相信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
于 2017-10-29T15:17:18.353 回答
9

借鉴@lad2025的回答,可以通过在WHERE子句中滥用设置和具有相关功能的自定义选项来获得所需的副作用。

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_configis_localtrue表示设置将在交易结束时消失。更准确地说,current_setting('upsert.action')将返回 NULL(并且不会引发错误),直到会话结束。

于 2016-01-13T13:41:14.590 回答
6

InSQL 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 演示

输出:

╔════╦══════╦══════════╗
║ id ║ col  ║  action  ║
╠════╬══════╬══════════╣
║  3 ║ c    ║ INSERTED ║
║  4 ║ d    ║ INSERTED ║
║  1 ║ aaaa ║ UPDATED  ║
╚════╩══════╩══════════╝
于 2016-01-13T10:39:01.380 回答
2

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

于 2018-09-03T16:03:50.627 回答