如果我有以下三个表,
create table users (
id char(36),
email varchar(256) unique not null,
primary key (id)
);
create table groups (
id char(36),
name varchar(256),
primary key (id)
);
create table group_members (
id_groups char(36) references groups(id),
id_users char(36) references users(id),
is_owner boolean not null default false,
primary key (id_groups, id_users)
);
如何强制执行每个组必须至少拥有一个所有者的不变量?这是针对这种特殊情况的正确架构吗?
答案 0 :(得分:0)
如果您希望完全一个所有者或一个主要所有者,那么您可以这样做:
create table groups (
id char(36),
name varchar(256),
id_owner char(36) not null,
primary key (id),
constraint fk_id_owner foreign key (id_owner) references users(id)
);
尝试在group_members
上强制执行此操作会引入逻辑问题。 group_members
需要有效group
才能插入新成员。但是,group
需要创建有效成员(所有者)。您可以使用触发器或事务来处理此问题,但最好找到一个没有这种循环关系的方法。
答案 1 :(得分:0)
Gordon Linoff的回答是正确的,但让我稍微扩展一下,因为评论中似乎存在一些误解。
在SQL中,没有DRI语法存在X,因此X在{Y} 中。通过将所有者添加到groups
,您可以确保至少有1位所有者。但要确保所有者也是您需要的成员
where exists ( select 1 from group_members
where g.id_owner = id_users
and g.id = id_groups )
在groups
的触发器中。但是,如果你使用触发器,如何创建一个组,它必须在添加成员之前存在?!
我们好像被卡住了。为什么?规则所有组需要所有者不适合触发器(或DRI),因为它不是参照完整性的规则。如果该规则被破坏,数据仍将是一致的。可能不正确,但不会产生异常结果进行查询。
因此,对于您提出的明确问题如何强制执行不变的答案是,某些不变量是在程序上强制执行的。它可能是存储过程的唯一最佳参数,也是允许用户修改数据的唯一方式。您的过程开始一个事务,创建一个组和成员,检查该组是否拥有所有者,并提交。
数据库应该满足要求
你很对。事实上,你似乎偶然发现了对SQL的长期批评。从较小的规则构建规则,将规则应用于整行,行集或语句组,这不是很好。遗憾的是,行业趋势处于相反的方向,包含 容量的系统,用于逻辑表达和约束实施。因此,批评可能会持续很长时间,至少只要有周围的人知道足够的批评就可以做到这一点。
答案 2 :(得分:0)
您可以使用约束触发器来获取类似内容,该约束触发器会在group_members
表被修改时进行检查。由于您在groups
和group_members
之间存在循环依赖关系,因此您需要将约束定义为可延迟,以便仅在事务的末尾检查,而不是每行检查被删除,插入或更新。
这看起来像这样:
create or replace function check_owner()
returns trigger
as
$$
declare
l_groups integer[];
begin
select array_agg(id)
into l_groups
from (
select g.id, count(gm.id_groups) num_owners
from groups g
left join group_members gm on gm.id_groups = g.id and gm.is_owner
group by g.id
) t
where num_owners = 0;
if coalesce(cardinality(l_groups),0) > 0 then
raise 'The group(s) % do not have an owner', array_to_string(l_groups, ',');
end if;
return new;
end;
$$
language plpgsql;
通过将条件where num_owners = 0
更改为where num_owners <> 1
,您还可以强制执行规则&#34;恰好是一个所有者&#34;。
每当groups
或group_members
被修改时,都需要进行此检查:
create constraint trigger group_member_trigger
after insert or update or delete on group_members
deferrable initially deferred
for each row
execute procedure check_owner();
create constraint trigger groups_trigger
after insert on groups
deferrable initially deferred
for each row
execute procedure check_owner();
需要为group_members
和 groups
设置触发器,这会使效率非常低,因为当创建具有新成员的新组时,检查将完成两次。但是,如果你想允许&#34;空&#34;组(没有在group_members
中分配任何用户的组)然后您可以摆脱groups
表上的触发器。
然而这很慢,如果它涵盖所有情况,我不是100%肯定。
以上内容更能证明您可以获得类似的工作而不是建议。但我必须承认,我无法想出一种不同的方式来定义&#34;至少一个所有者&#34;通过静态约束的要求。
要求&#34;最多一个所有者&#34;通过在group_members
上使用唯一的过滤索引,可以在没有任何触发器的情况下完成。