如何在一个语句中使用单个值 UPSERT 多行?

How to UPSERT multiple rows with individual values in one statement?

我创建了以下 table:

CREATE TABLE t1(
a   INT UNIQUE, 
b   varchar(100) NOT NULL,
c   INT, 
d   INT DEFAULT 0,  
PRIMARY KEY (a,b));

在一行中,这个 SQL 语句效果很好(SQL 在代码中生成):

INSERT INTO t1 (a, b, c, d)
VALUES($params.1, '${params.2}', $params.3, params.4) 
ON CONFLICT (a,b) DO 
UPDATE SET d=params.4

是否可以一次插入多行?对于每次更新,params.4 的值都不同。

var sqlStr = 'INSERT INTO t1 (a, b, c, d) VALUES '
for(let i =0 i < params.length; i++){
   sqlStr += `(${params[i].1}, '${params[i].2}', ${params[i].3}, ${params[i].4}),`
   
}
sqlStr = sqlStr.substring(0, sqlStr .length - 2) +')';
sqlStr += 'ON CONFLICT (a,b) DO UPDATE SET **d=???**' <-- this is the problem

params[i].4 每行都有不同的值,ON CONFLICT 语句只出现一次(不是每行),SET 不支持 WHERE

例如,如果我的 table 有以下行:

 a | b | c | d 
---+---+---+---
 1 | 1 | 1 | 1
 2 | 2 | 2 | 2

我的新输入是 [(1,'1',1,11),(2,'2',2,22),(3,'3',3,3)]
有两个冲突 - (1,1)(2,2)。结果应该是:

 a | b | c | d 
---+---+---+---
 1 | 1 | 1 | 11
 2 | 2 | 2 | 22
 3 | 3 | 3 | 3

UPSERT (INSERT ... ON CONFLICT ... DO UPDATE) 自动跟踪名为 EXCLUDED 的特殊 table 中排除的行。 The manual:

Note that the special excluded table is used to reference values originally proposed for insertion

所以真的很简单:

INSERT INTO t1 (a, b, c, d)
VALUES (...) 
ON     CONFLICT (a,b) DO UPDATE
SET    d = EXCLUDED.d;          -- that's all !!!

除了更简单和更快之外,与您提出的解决方案在极端情况下存在细微差别。 The manual:

Note that the effects of all per-row BEFORE INSERT triggers are reflected in excluded values, since those effects may have contributed to the row being excluded from insertion.

此外,第 DEFAULT 列的值已应用到 EXCLUDED 行,其中未提供任何输入。 (为您的专栏 d 点赞 DEFAULT 0。)

两者通常都是您想要的。

为将来处理相同问题的用户编写解决方案

第 1 步:创建临时 table(t1 的克隆)

CREATE TABLE t2(                 
  a   INT UNIQUE, 
  b   varchar(100) NOT NULL,
  c   INT,                                  
  d   INT DEFAULT 0,  
  PRIMARY KEY (a,b));

create table t2 as (select * from t1) with no data;

第 2 步:将新输入插入 t2

INSERT INTO t2 (a, b, c, d) 
values (1,'1',1,1),(2,'2',2,2),(3,'3',3,3)`

第 3 步:在发生冲突的情况下从 t2

更新到 t1 select d
INSERT INTO t1 (a, b, c, d)
VALUES (1,'1',1,1),(2,'2',2,2),(3,'3',3,3)
ON CONFLICT(a,b) DO UPDATE
SET d=(SELECT d FROM t2 WHERE t2.a=t1.a);

第 4 步:删除 t2

DROP TABLE t2;