如何确定 upsert 是否是 PostgreSQL 9.5+ UPSERT 的更新?
How to find out if an upsert was an update with PostgreSQL 9.5+ UPSERT?
如 Insert, on duplicate update in PostgreSQL?
中所述,可写 CTE 被认为是 9.5 之前的 UPSERT 的解决方案
可以使用以下可写 CTE 惯用语来执行 UPSERT,其中包含它是作为 UPDATE 还是 INSERT 结束的信息:
WITH
update_cte AS (
UPDATE t SET v = WHERE id = RETURNING 'updated'::text status
),
insert_cte AS (
INSERT INTO t(id, v) SELECT , WHERE NOT EXISTS
(SELECT 1 FROM update_cte) RETURNING 'inserted'::text status
)
(SELECT status FROM update_cte) UNION (SELECT status FROM insert_cte)
此查询将 return "updated" 或 "inserted",或者可能(很少)因违反约束而失败,如 https://dba.stackexchange.com/questions/78510/why-is-cte-open-to-lost-updates
中所述
是否可以使用 PostgreSQL 9.5+ 新的 "UPSERT" 语法实现类似的东西,从其优化中获益并避免可能的约束冲突?
在SQL Server
MERGE
语句中有$action
那个returns字符串'INSERT', 'UPDATE', or 'DELETE'
.
对于 Postgresql
我找不到 function/variable 为 RETURNING
做类似的事情。
一种解决方法是将列 is_updated
添加到您的 table:
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;
输出:
╔════╦══════╦══════════╗
║ id ║ col ║ action ║
╠════╬══════╬══════════╣
║ 3 ║ c ║ INSERTED ║
║ 4 ║ d ║ INSERTED ║
║ 1 ║ aaaa ║ UPDATED ║
╚════╩══════╩══════════╝
从 WHERE 子句中的 , the result can be achieved by abusing settings and customized options with related functions 获取所需的 side-effect。
CREATE TABLE t(id INT PRIMARY KEY, v TEXT);
INSERT INTO t (id, v)
SELECT ,
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_local
:true
表示交易结束后设置消失。更准确地说,current_setting('upsert.action')
将 return NULL(并且不会抛出错误)直到会话结束。
我相信 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
(xmax::text::bigint > 0)
或 (NOT xmax = 0)
。一旦交易计数达到整数溢出,类型转换为整数就会中断。
如 Insert, on duplicate update in PostgreSQL?
中所述,可写 CTE 被认为是 9.5 之前的 UPSERT 的解决方案可以使用以下可写 CTE 惯用语来执行 UPSERT,其中包含它是作为 UPDATE 还是 INSERT 结束的信息:
WITH
update_cte AS (
UPDATE t SET v = WHERE id = RETURNING 'updated'::text status
),
insert_cte AS (
INSERT INTO t(id, v) SELECT , WHERE NOT EXISTS
(SELECT 1 FROM update_cte) RETURNING 'inserted'::text status
)
(SELECT status FROM update_cte) UNION (SELECT status FROM insert_cte)
此查询将 return "updated" 或 "inserted",或者可能(很少)因违反约束而失败,如 https://dba.stackexchange.com/questions/78510/why-is-cte-open-to-lost-updates
中所述是否可以使用 PostgreSQL 9.5+ 新的 "UPSERT" 语法实现类似的东西,从其优化中获益并避免可能的约束冲突?
在SQL Server
MERGE
语句中有$action
那个returns字符串'INSERT', 'UPDATE', or 'DELETE'
.
对于 Postgresql
我找不到 function/variable 为 RETURNING
做类似的事情。
一种解决方法是将列 is_updated
添加到您的 table:
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;
输出:
╔════╦══════╦══════════╗
║ id ║ col ║ action ║
╠════╬══════╬══════════╣
║ 3 ║ c ║ INSERTED ║
║ 4 ║ d ║ INSERTED ║
║ 1 ║ aaaa ║ UPDATED ║
╚════╩══════╩══════════╝
从 WHERE 子句中的
CREATE TABLE t(id INT PRIMARY KEY, v TEXT);
INSERT INTO t (id, v)
SELECT ,
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_local
:true
表示交易结束后设置消失。更准确地说,current_setting('upsert.action')
将 return NULL(并且不会抛出错误)直到会话结束。
我相信 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
(xmax::text::bigint > 0)
或 (NOT xmax = 0)
。一旦交易计数达到整数溢出,类型转换为整数就会中断。