UPSERT 基于具有 NULL 值的 UNIQUE 约束
UPSERT based on UNIQUE constraint with NULL values
我有一个 Postgres table 对多列有唯一约束,其中一列可以为 NULL。对于每个组合,我只想在该列中允许一条记录为 NULL。
create table my_table (
col1 int generated by default as identity primary key,
col2 int not null,
col3 real,
col4 int,
constraint ux_my_table_unique unique (col2, col3)
);
我有一个更新插入查询,当它遇到在 col2、col3 中具有相同值的记录时,我想更新 col4:
insert into my_table (col2, col3, col4) values (p_col2, p_col3, p_col4)
on conflict (col2, col3) do update set col4=excluded.col4;
但当 col3 为 NULL 时,冲突并未触发。我已经阅读了有关使用触发器的信息。请问解决冲突的最佳解决方案是什么?
如果你能找到一个永远不能合法存在于 col3
中的值(确保有检查约束),你可以使用唯一索引:
CREATE UNIQUE INDEX ON my_table (
col2,
coalesce(col3, -1.0)
);
并在您的 INSERT
:
中使用它
INSERT INTO my_table (col2, col3, col4)
VALUES (p_col2, p_col3, p_col4)
ON CONFLICT (col2, coalesce(col3, -1.0))
DO UPDATE SET col4 = excluded.col4;
Postgres 15
... 添加子句 NULLS NOT DISTINCT
。您的案例现在开箱即用:
ALTER TABLE my_table
DROP CONSTRAINT IF EXISTS ux_my_table_unique
, ADD CONSTRAINT ux_my_table_unique UNIQUE NULLS NOT DISTINCT (col2, col3);
INSERT INTO my_table (col2, col3, col4)
VALUES (p_col2, p_col3, p_col4)
ON CONFLICT (col2, col3) DO UPDATE
SET col4 = EXCLUDED.col4;
参见:
- Create unique constraint with null columns
Postgres 14 或更早版本
NULL
值被认为彼此不相等,因此永远不会触发 UNIQUE
违规。这意味着,您当前的 table 定义没有按照您所说的去做。 (col2, col3) = (1, NULL)
可以已经有多个行。 ON CONFLICT
在您当前的设置中 col3 IS NULL
永远不会触发。
UNIQUE
约束的 UNIQUE NULLS [NOT] DISTINCT
选项是 in development。但这充其量是在未来的 Postgres 15 中。
您可以使用两个部分 UNIQUE
索引强制执行 UNIQUE
约束,如下所述:
- Create unique constraint with null columns
适用于您的情况:
CREATE UNIQUE INDEX my_table_col2_uni_idx ON my_table (col2)
WHERE col3 IS NULL;
CREATE UNIQUE INDEX my_table_col2_col3_uni_idx ON my_table (col2, col3)
WHERE col3 IS NOT NULL;
但是ON CONFLICT ... DO UPDATE
只能基于单个UNIQUE
索引或约束。只有 ON CONFLICT DO NOTHING
变体可以作为“包罗万象”。参见:
看起来你想要的目前是不可能的...
完美解决
虽然有一个完美的解决方案。有了两个部分 UNIQUE
索引,您可以根据 col3
:
的输入值使用正确的语句
WITH input(col2, col3, col4) AS (
VALUES
(3, NULL::real, 5) -- ①
, (3, 4, 5)
)
, upsert1 AS (
INSERT INTO my_table AS t(col2, col3, col4)
SELECT * FROM input WHERE col3 IS NOT NULL
ON CONFLICT (col2, col3) WHERE col3 IS NOT NULL -- matching index_predicate!
DO UPDATE
SET col4 = EXCLUDED.col4
WHERE t.col4 IS DISTINCT FROM EXCLUDED.col4 -- ②
)
INSERT INTO my_table AS t(col2, col3, col4)
SELECT * FROM input WHERE col3 IS NULL
ON CONFLICT (col2) WHERE col3 IS NULL -- matching index_predicate!
DO UPDATE SET col4 = EXCLUDED.col4
WHERE t.col4 IS DISTINCT FROM EXCLUDED.col4; -- ②
db<>fiddle here
适用于所有情况。
甚至适用于 col3
.
的 NULL
和 NOT NULL
值的任意组合的多个输入行
而且甚至不会比普通语句花费更多,因为每一行只进入两个 UPSERT 之一。
这是其中一个“Eurika!”查询一切都只是点击,排除万难。 :)
① 注意在 CTE input
中显式转换为 ::real
。这个相关的答案解释了原因:
- Casting NULL type when updating multiple rows
② 最后的 WHERE
子句是可选的,但强烈推荐。如果 UPDATE
实际上没有改变任何东西,那将是一种浪费。参见:
- How do I (or can I) SELECT DISTINCT on multiple columns?
我有一个 Postgres table 对多列有唯一约束,其中一列可以为 NULL。对于每个组合,我只想在该列中允许一条记录为 NULL。
create table my_table (
col1 int generated by default as identity primary key,
col2 int not null,
col3 real,
col4 int,
constraint ux_my_table_unique unique (col2, col3)
);
我有一个更新插入查询,当它遇到在 col2、col3 中具有相同值的记录时,我想更新 col4:
insert into my_table (col2, col3, col4) values (p_col2, p_col3, p_col4)
on conflict (col2, col3) do update set col4=excluded.col4;
但当 col3 为 NULL 时,冲突并未触发。我已经阅读了有关使用触发器的信息。请问解决冲突的最佳解决方案是什么?
如果你能找到一个永远不能合法存在于 col3
中的值(确保有检查约束),你可以使用唯一索引:
CREATE UNIQUE INDEX ON my_table (
col2,
coalesce(col3, -1.0)
);
并在您的 INSERT
:
INSERT INTO my_table (col2, col3, col4)
VALUES (p_col2, p_col3, p_col4)
ON CONFLICT (col2, coalesce(col3, -1.0))
DO UPDATE SET col4 = excluded.col4;
Postgres 15
... 添加子句 NULLS NOT DISTINCT
。您的案例现在开箱即用:
ALTER TABLE my_table
DROP CONSTRAINT IF EXISTS ux_my_table_unique
, ADD CONSTRAINT ux_my_table_unique UNIQUE NULLS NOT DISTINCT (col2, col3);
INSERT INTO my_table (col2, col3, col4)
VALUES (p_col2, p_col3, p_col4)
ON CONFLICT (col2, col3) DO UPDATE
SET col4 = EXCLUDED.col4;
参见:
- Create unique constraint with null columns
Postgres 14 或更早版本
NULL
值被认为彼此不相等,因此永远不会触发 UNIQUE
违规。这意味着,您当前的 table 定义没有按照您所说的去做。 (col2, col3) = (1, NULL)
可以已经有多个行。 ON CONFLICT
在您当前的设置中 col3 IS NULL
永远不会触发。
UNIQUE
约束的 UNIQUE NULLS [NOT] DISTINCT
选项是 in development。但这充其量是在未来的 Postgres 15 中。
您可以使用两个部分 UNIQUE
索引强制执行 UNIQUE
约束,如下所述:
- Create unique constraint with null columns
适用于您的情况:
CREATE UNIQUE INDEX my_table_col2_uni_idx ON my_table (col2)
WHERE col3 IS NULL;
CREATE UNIQUE INDEX my_table_col2_col3_uni_idx ON my_table (col2, col3)
WHERE col3 IS NOT NULL;
但是ON CONFLICT ... DO UPDATE
只能基于单个UNIQUE
索引或约束。只有 ON CONFLICT DO NOTHING
变体可以作为“包罗万象”。参见:
看起来你想要的目前是不可能的...
完美解决
虽然有一个完美的解决方案。有了两个部分 UNIQUE
索引,您可以根据 col3
:
WITH input(col2, col3, col4) AS (
VALUES
(3, NULL::real, 5) -- ①
, (3, 4, 5)
)
, upsert1 AS (
INSERT INTO my_table AS t(col2, col3, col4)
SELECT * FROM input WHERE col3 IS NOT NULL
ON CONFLICT (col2, col3) WHERE col3 IS NOT NULL -- matching index_predicate!
DO UPDATE
SET col4 = EXCLUDED.col4
WHERE t.col4 IS DISTINCT FROM EXCLUDED.col4 -- ②
)
INSERT INTO my_table AS t(col2, col3, col4)
SELECT * FROM input WHERE col3 IS NULL
ON CONFLICT (col2) WHERE col3 IS NULL -- matching index_predicate!
DO UPDATE SET col4 = EXCLUDED.col4
WHERE t.col4 IS DISTINCT FROM EXCLUDED.col4; -- ②
db<>fiddle here
适用于所有情况。
甚至适用于 col3
.
的 NULL
和 NOT NULL
值的任意组合的多个输入行
而且甚至不会比普通语句花费更多,因为每一行只进入两个 UPSERT 之一。
这是其中一个“Eurika!”查询一切都只是点击,排除万难。 :)
① 注意在 CTE input
中显式转换为 ::real
。这个相关的答案解释了原因:
- Casting NULL type when updating multiple rows
② 最后的 WHERE
子句是可选的,但强烈推荐。如果 UPDATE
实际上没有改变任何东西,那将是一种浪费。参见:
- How do I (or can I) SELECT DISTINCT on multiple columns?