sql - 使用空列创建唯一约束

标签 sql postgresql database-design null referential-integrity

我有一个这样布局的表格:

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 存储没有关联菜单的收藏夹,但每个用户/食谱对我最多只需要其中一行。

目前我的想法是:

  1. 使用一些硬编码的 UUID(例如全零)而不是 null。
    但是,MenuId 对每个用户的菜单都有 FK 约束,因此我必须为每个用户创建一个特殊的“空”菜单,这很麻烦。

  2. 改用触发器检查是否存在空条目。
    我认为这很麻烦,我喜欢尽可能避免触发。另外,我不相信他们能保证我的数据永远不会处于不良状态。

  3. 忘掉它并检查之前在中间件或插入函数中是否存在空条目,并且没有此约束。

我正在使用 Postgres 9.0。有没有我忽略的方法?

最佳答案

Postgres 15 或更新版本

Postgres 15 添加了子句 NULLS NOT DISTINCTThe 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/

相关文章:

sql - 如何在Sql中为临时表设置order by

MYSQL:左连接并使用 group_concat 将外观限制为一个

java - 级联持久化会创建重复行吗?

java - Hibernate 5.2 在我的表 postgres 上级联删除

mysql - 表结构 - 将一个学生与多个类(class) ID 联系起来

mysql - 在 MySql 中循环,还是替代?

sql - 在 PostgreSQL 中选择具有特定列名的列

c# - 存储过程或函数需要未提供的参数

r - 操纵数据框,使每个人的所有相关值都在一行中

ruby-on-rails - 搜索特定表是否比在特定表中搜索特定条目更快?