我有一个这样布局的表格:
CREATE TABLE Favorites (
FavoriteId uuid NOT NULL PRIMARY KEY,
UserId uuid NOT NULL,
RecipeId uuid NOT NULL,
MenuId uuid
);
我想创建一个类似这样的唯一约束:
ALTER TABLE Favorites
ADD CONSTRAINT Favorites_UniqueFavorite UNIQUE(UserId, MenuId, RecipeId);
但是,如果 MenuId IS NULL
,这将允许具有相同 (UserId, RecipeId)
的多行。我想允许 MenuId
中的 NULL
存储没有关联菜单的收藏夹,但每个用户/食谱对我最多只需要其中一行。
目前我的想法是:
使用一些硬编码的 UUID(例如全零)而不是 null。
但是,MenuId
对每个用户的菜单都有 FK 约束,因此我必须为每个用户创建一个特殊的“空”菜单,这很麻烦。改用触发器检查是否存在空条目。
我认为这很麻烦,我喜欢尽可能避免触发。另外,我不相信他们能保证我的数据永远不会处于不良状态。忘掉它并检查之前在中间件或插入函数中是否存在空条目,并且没有此约束。
我正在使用 Postgres 9.0。有没有我忽略的方法?
最佳答案
Postgres 15 或更新版本
Postgres 15 添加了子句 NULLS NOT DISTINCT
。 The release notes:
Allow unique constraints and indexes to treat NULL values as not distinct (Peter Eisentraut)
Previously NULL values were always indexed as distinct values, but this can now be changed by creating constraints and indexes using
UNIQUE NULLS NOT DISTINCT
.
使用此子句,null
被视为另一个值,UNIQUE
constraint不允许超过一行具有相同的 null
值。现在的任务很简单:
ALTER TABLE favorites
ADD CONSTRAINT favo_uni UNIQUE NULLS NOT DISTINCT (user_id, menu_id, recipe_id);
手册章节有例子"Unique Constraints" .
该子句切换相同索引的所有 键的行为。您不能将一个键的 null
视为相等,而另一个键则不相等。
NULLS DISTINCT
保持默认(与标准 SQL 一致)并且不必拼写出来。
相同的子句适用于 UNIQUE
index ,也是:
CREATE UNIQUE INDEX favo_uni_idx
ON favorites (user_id, menu_id, recipe_id) NULLS NOT DISTINCT;
注意关键字段之后新子句的位置。
Postgres 14 或以上
创建 two partial indexes :
CREATE UNIQUE INDEX favo_3col_uni_idx ON favorites (user_id, menu_id, recipe_id)
WHERE menu_id IS NOT NULL;
CREATE UNIQUE INDEX favo_2col_uni_idx ON favorites (user_id, recipe_id)
WHERE menu_id IS NULL;
这样,只有一个 (user_id, recipe_id)
的组合,其中 menu_id IS NULL
,有效地实现了所需的约束。
可能的缺点:
- 您不能有引用
(user_id, menu_id, recipe_id)
的外键。 (您似乎不太可能希望 FK 引用三列宽 - 请改用 PK 列!) - 您不能将
CLUSTER
建立在部分索引上。 - 没有匹配的
WHERE
条件的查询不能使用部分索引。
如果您需要一个完整索引,您也可以从 favo_3col_uni_idx
中删除 WHERE
条件,并且您的要求仍然得到执行。
现在包含整个表的索引与另一个重叠并变得更大。根据典型的查询和 null
值的百分比,这可能有用也可能没用。在极端情况下,它甚至可能有助于维护所有三个索引(两个部分的和顶部的总计)。
对于单个可为空的列,这可能是两个很好的解决方案。但它很快就会失去控制,因为您需要为每个可为空列的组合创建一个单独的部分索引,因此数字会按二项式增长。对于多个可为空的列,请参阅:
另外:我建议不要使用 mixed case identifiers in PostgreSQL .
关于sql - 使用空列创建唯一约束,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/8289100/