给定一个包含三列的表格:
CREATE TABLE tbl
column_scope text
, col1 int
, col2 int
);
我想强制执行,按
column_scope
分区,col1
、col2
中的值在两列中都是唯一的 - 除了同一行中的重复项。
也就是说,在该范围内,col1
和col2
中的两个值不能相同,除非NULL
或它们位于同一行。
我尝试添加唯一索引(column_scope、col1、col2),但是这会失败,因为它不会考虑无效情况,例如:
row_1 -> (scope_1, x, NULL);
row_2 -> (scope_1, NULL, x);
或
row_1 -> (scope_1, x, y);
row_2 -> (scope_1, y, z);
我还尝试向表添加约束,但是我似乎无法获得正确的条件来确保
col1
和 col2
对于给定的 column_scope
没有共享值。
这与其他问题不同,因为我不关心值的特定组合,我只关心值在不同列之间不重复,全部在第三列的范围内。
系好安全带。这是一个先进的解决方案。
首先安装两个附加模块(每个数据库一次):intarray 和 btree_gist。然后我们可以使用单个 排除约束:
CREATE EXTENSION intarray; -- required!
CREATE EXTENSION btree_gist; -- required!
CREATE TABLE tbl (
column_scope text NOT NULL
, col1 int -- can be null
, col2 int -- can be null
);
-- Add THIS exclusion constraint !!!
ALTER TABLE tbl ADD CONSTRAINT tbl_cross_col_unique
EXCLUDE USING gist (column_scope WITH =
, array_remove(ARRAY[col1, col2], null) gist__int_ops WITH &&);
请注意我如何假设类型
integer
代表 col1
和 col2
。保持简单。对于其他类型,您需要做更多...
您希望在
col1
和 col2
中允许空值。因此,从排除约束中排除那些。幸运的是,生成的数组中无论如何都不允许出现空值。array_remove()
的手册:
比较是使用
语义完成的,因此可以删除IS NOT DISTINCT FROM
s。NULL
一切都恰到好处。
这有很多复杂的细节,涉及数据类型、索引方法、运算符类、空值、索引优化…… 但这超出了一个简单问题的范围,而是进入了付费咨询领域。
密切相关的案例,提供更多详细信息:
此表达式索引实现了
UNIQUE
索引,其中 (1,2)
和 (2,1)
的 (col1, col2)
被视为相等:
CREATE UNIQUE INDEX tbl_uni_idx ON tbl
(column_scope, GREATEST(col1, col2), LEAST(col1, col2));
参见:
空值不被视为相等,除非您添加
NULLS NOT DISTINCT
子句 - 这需要 Postgres 15 或更高版本。参见: