sql - 我可以使用模式约束来强制执行唯一性吗?
问题描述
假设我有两个相关的实体,例如。一个项目和一个过滤器。过滤器可以是:
- 共享,即。它适用于所有项目
- 私人的,即 它仅限于一个项目
每个过滤器都可以命名,我想确保过滤器具有唯一的名称。然而,唯一性取决于过滤器是共享的还是私有的。如果过滤器是共享的,则其名称在所有共享过滤器中必须是唯一的,并且任何私有过滤器都不能重复使用该名称。如果过滤器是私有的,那么它在其项目中必须是唯一的,即。两个不同的项目可以有两个同名的私有过滤器。
我要将过滤器数据存储在数据库表中,并且我想添加一个约束来强制执行这些命名规则。是否可以构造一个复合键来做到这一点?
我想象过滤表上有两列:
- 过滤器的名称(字符串)
- 项目 ID(数字,理想情况下是项目表的外键)
共享过滤器将有一个名称但没有项目 ID,而私有过滤器将有一个名称和它所属项目的 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 ;
推荐阅读
- java - 如何解密pbkdf的散列密码
- c++ - 如何为 std::wifstream 编写随机访问迭代器?
- javascript - 我的 js 平台游戏中的碰撞检测。无法停止物体,但检测有效
- docker - 为什么 docker 在 docker 容器中找不到 JSON 文件的 Env 变量?
- project - 如何使用快速绘图数据集来识别包含许多类别的绘图
- matlab - 为什么这个函数没有被调用?
- javascript - 网格布局的 CSS 调整大小问题
- c - 错误 gcc - 未定义对符号 '__getauxval@@GLIBC_2.17' 的引用
- leaflet - 在传单中使用条形图和散点图的问题
- c++ - 什么时候可以通过捕获异常来真正解决问题?