用空列创build唯一约束

我有这个布局的表格:

CREATE TABLE Favorites ( FavoriteId uuid NOT NULL PRIMARY KEY, UserId uuid NOT NULL, RecipeId uuid NOT NULL, MenuId uuid ) 

我想创build一个类似如下的唯一约束:

 ALTER TABLE Favorites ADD CONSTRAINT Favorites_UniqueFavorite UNIQUE(UserId, MenuId, RecipeId); 

但是,如果MenuId IS NULL ,这将允许具有相同(UserId, RecipeId)多行。 我想在MenuId允许NULL存储没有关联菜单的collections夹,但是我只需要每个用户/配方对的这些行中的最多一个。

我到目前为止的想法是:

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

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

  3. 只要忘记它,并检查中间件或插入函数中的空条目的存在,并没有这个约束。

我正在使用Postgres 9.0。

有什么方法可以忽略吗?

创build两个部分索引

 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; 

这样,只能有一个menu_id NULL的(user_id, recipe_id)组合,有效地实现所需的约束。

可能的缺点:不能使用外键引用(user_id, menu_id, recipe_id) ,不能在部分索引上使用CLUSTER ,而没有匹配WHERE条件的查询不能使用部分索引。

看来不太可能你想要一个FK引用三列宽(改用PK列)。 如果您需要一个完整的索引,您可以从favo_3col_uni_idx删除WHERE条件,并且您的需求仍然被执行。
现在由整个表格组成的索引与另一个索引重叠并变大。 根据典型的查询和NULL值的百分比,这可能有用或可能没有用。 在极端情况下,甚至可以帮助维护两个版本的favo_3col_uni_idx

除此之外:我build议不要在PostgreSQL中使用混合大小写标识符 。

您可以通过MenuId上的合并创build唯一索引:

 CREATE UNIQUE INDEX Favorites_UniqueFavorite ON Favorites (UserId, COALESCE(MenuId, '00000000-0000-0000-0000-000000000000'), RecipeId); 

你只需要select一个在现实生活中永远不会发生的COALESCE的UUID。 你可能永远不会在现实生活中看到一个零UUID,但是如果你是偏执的,你可以添加一个CHECK约束(并且因为他们真的出来让你…):

 alter table Favorites add constraint check (MenuId <> '00000000-0000-0000-0000-000000000000') 

您可以将不含关联菜单的collections夹存储在单独的表格中:

 CREATE TABLE FavoriteWithoutMenu ( FavoriteWithoutMenuId uuid NOT NULL, --Primary key UserId uuid NOT NULL, RecipeId uuid NOT NULL, UNIQUE KEY (UserId, RecipeId) ) 

我认为这里有一个语义问题。 在我看来,用户可以有一个(但只有一个 )最喜欢的食谱准备一个特定的菜单。 (OP的菜单和配方混淆了;如果我错了,请在下面交换MenuId和RecipeId)这意味着{user,menu}应该是这个表中唯一的键。 它应该指向一个配方。 如果用户对此特定菜单没有最喜欢的配方,则该{用户,菜单}密钥对不应该存在行 。 另外:代理键(FaVouRiteId)是多余的:复合主键完全有效的关系映射表。

这将导致表格定义的减less:

 CREATE TABLE Favorites ( UserId uuid NOT NULL REFERENCES users(id) , MenuId uuid NOT NULL REFERENCES menus(id) , RecipeId uuid NOT NULL REFERENCES recipes(id) , PRIMARY KEY (UserId, MenuId) ); 
Interesting Posts