假设我有两个相关实体,例如一个项目和一个过滤器。过滤器可以是:
每个过滤器都有一个名称,我想确保过滤器具有唯一的名称。但是,唯一性取决于过滤器是共享的还是私有的。如果过滤器是共享的,则其名称在所有共享过滤器中必须唯一[,任何私有过滤器都不能重复使用该名称。如果过滤器是私有的,则它必须是唯一的[[在其项目内,即。两个不同的项目可以有两个具有相同名称的专用过滤器。
我将过滤器数据存储在数据库表中,我想添加一个约束以强制执行这些命名规则。是否可以构造一个复合密钥来做到这一点?我正在想象过滤器表上的三列:过滤器的名称(字符串)
true
的名称,没有项目ID,而私有过滤器将具有共享共享为false
的名称及其所属项目的ID。对我的方法有任何想法吗?我是在正确的轨道上还是有更好的方法?
create table filters (
filter_id int auto_increment primary key,
filter_name varchar(20) not null,
project_id int,
is_shared bool as (case when project_id is null then true else null end),
unique key (filter_name, project_id),
unique key (filter_name, is_shared)
);
这将强制执行以下约束:共享过滤器必须是唯一的,并且私有过滤器在同一项目的其他过滤器中必须是唯一的。
即以下过滤器成功:
insert into filters set filter_name = 'shared1', project_id = null; -- OK insert into filters set filter_name = 'shared2', project_id = null; -- OK insert into filters set filter_name = 'private1', project_id = 1; -- OK insert into filters set filter_name = 'private2', project_id = 1; -- OK insert into filters set filter_name = 'private1', project_id = 2; -- OK insert into filters set filter_name = 'private2', project_id = 2; -- OK
但是这些失败,正确的是:
insert into filters set filter_name = 'shared1', project_id = null; -- DUPLICATE
insert into filters set filter_name = 'private1', project_id = 1; -- DUPLICATE
insert into filters set filter_name = 'private1', project_id = 2; -- DUPLICATE
但是,我无法解决的棘手部分是使私有过滤器名称与共享过滤器名称冲突,但与来自不同项目的私有过滤器名称不冲突。
insert into filters set filter_name = 'shared1', project_id = 1;
insert into filters set filter_name = 'private1', project_id = null;
我们不能仅仅使filter_name为唯一,因为这会使来自不同项目的私有过滤器相互冲突。
unique key (filter_name) -- WRONG
我也研究过创建VIEW WITH CHECK OPTION,但是这将需要使用自联接或NOT EXISTS子查询,并且这两种情况都意味着该视图不支持WITH CHECK OPTION。
我能够通过MySQL 5.7中的触发器来做到这一点:
delimiter // create trigger it1 before insert on filters for each row begin if exists (select * from filters as f2 where f2.filter_name = NEW.filter_name and f2.project_id is null) then signal sqlstate '45000' set message_text = 'filter conflicts with an existing shared filter'; end if; if exists (select * from filters as f2 where f2.filter_name = NEW.filter_name and NEW.project_id is null) then signal sqlstate '45000' set message_text = 'filter conflicts with an existing private filter'; end if; if exists (select * from filters as f2 where f2.filter_name = NEW.filter_name and f2.project_id = NEW.project_id) then signal sqlstate '45000' set message_text = 'filter conflicts with an existing private filter in the same project'; end if; end// delimiter ;