用空列创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夹,但是我只需要每个用户/配方对的这些行中的最多一个。
我到目前为止的想法是:
-
使用一些硬编码的UUID(如全零)而不是null。
但是,MenuId
对每个用户的菜单都有一个FK约束,所以我必须为每个用户创build一个特殊的“空”菜单,这很麻烦。 -
请使用触发器检查是否存在空条目。
我认为这是一个麻烦,我喜欢尽可能避免触发器。 另外,我不相信他们保证我的数据永远不会处于不良状态。 -
只要忘记它,并检查中间件或插入函数中的空条目的存在,并没有这个约束。
我正在使用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) );