upsert 冲突目标中的 Where 子句

Where clause in upsert conflict target

假设我有一个这样创建的table:

CREATE TABLE actions (name text primary key, value text);
INSERT INTO actions (name) VALUES ('bla');

尝试插入新行时,有三种可能:

  1. 不存在 'name' 的行。应插入新行。
  2. 'name' 的一行存在值为 NULL。现有行应该是 更新为新值。
  3. 'name ' 的一行存在非 NULL 值。插入应该会失败。

我认为我应该能够像这样使用 UPSERT 语法:

INSERT INTO actions (name, value) values ('bla', 'val1')
ON CONFLICT(name) WHERE value IS NULL
DO UPDATE SET value = excluded.value;

INSERT INTO actions (name, value) values ('bla', 'val2')
ON CONFLICT(name) WHERE value IS NULL
DO UPDATE SET value = excluded.value;

第一个命令按预期更新现有行。但是第二个命令又更新了这一行,这是我没想到的。

冲突目标中的 where 子句应该做什么?在我的测试中,它的计算结果是真还是假似乎并不重要。

基于,我尝试使用唯一的部分索引而不是主键,但最终我得到了多个同名行。

我觉得这像是一个错误。但您可以轻松解决它:

INSERT INTO actions (name, value) values ('bla', 'val1')
ON CONFLICT(name) 
DO UPDATE SET value = COALESCE(value, excluded.value);

Here 是一个 db<>fiddle.

使用 second WHERE 将执行您想要的操作:

INSERT INTO actions(name) VALUES ('bla');
INSERT INTO actions(name, value) VALUES ('bla', 'val1')
 ON CONFLICT(name) DO UPDATE SET value = excluded.value WHERE value IS NULL;
SELECT * FROM actions;
name        value
----------  ----------
bla         val1
INSERT INTO actions(name, value) VALUES ('bla', 'val2')
 ON CONFLICT(name) DO UPDATE SET value = excluded.value WHERE value IS NULL;
SELECT * FROM actions;
name        value
----------  ----------
bla         val1

冲突列之后的第一个 WHERE 用于 select 用于检测冲突的特定部分索引。举个例子给大家感受一下:

sqlite> CREATE TABLE ex(name, value1, value2);
sqlite> CREATE UNIQUE INDEX ex_idx_name ON ex(name) WHERE value1 IS NULL;
sqlite> INSERT INTO ex(name) VALUES ('bla');
sqlite> INSERT INTO ex(name, value2) VALUES ('bla', 'val1');
Error: UNIQUE constraint failed: ex.name
sqlite> INSERT INTO ex(name, value1, value2) VALUES ('bla', 1, 'val2');
sqlite> SELECT * from ex;
name        value1      value2
----------  ----------  ----------
bla         (null)      (null)
bla         1           val2
sqlite> INSERT INTO ex(name, value2) VALUES ('bla', 'val3')
   ...> ON CONFLICT(name) WHERE value1 IS NULL DO UPDATE SET value2 = excluded.value2;
sqlite> SELECT * from ex;
name        value1      value2
----------  ----------  ----------
bla         (null)      val3
bla         1           val2
sqlite> INSERT INTO ex(name, value2) VALUES ('bla', 'val4')
   ...> ON CONFLICT(name) DO UPDATE SET value2 = excluded.value2;
Error: ON CONFLICT clause does not match any PRIMARY KEY or UNIQUE constraint

请特别注意最后两个 INSERT:第一个使用空值更新行,第二个生成错误,因为它没有完全指定唯一索引。