如何编写表约束以确保存在具有特定列值的行?

时间:2016-05-08 18:02:51

标签: sql postgresql

如果我有以下三个表,

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)
);

如何强制执行每个组必须至少拥有一个所有者的不变量?这是针对这种特殊情况的正确架构吗?

3 个答案:

答案 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表被修改时进行检查。由于您在groupsgroup_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;。

每当groupsgroup_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上使用唯一的过滤索引,可以在没有任何触发器的情况下完成。