如何在多对多关系中禁止多个类型的记录?

时间:2013-01-24 13:50:25

标签: sql oracle oracle11g

我有四张桌子。

PERSON     DELIVERY_MAPPING       GENERATION_SYSTEM       DELIVERY_METHOD
------     ----------------       -----------------       ---------------
ID         PERSON_ID              ID                      ID
NAME       GENERATION_SYSTEM_ID   NAME                    NAME
                                  DELIVERY_METHOD_ID      IS_SPECIAL

示例数据:

PERSON     DELIVERY_MAPPING       GENERATION_SYSTEM       DELIVERY_METHOD
------     ----------------       -----------------       ---------------
1. TOM       1    1               1. COLOR PRINTER 1      1. EMAIL    N
2. DICK      1    2               2. BW PRINTER    1      2. POST     N
3. HARRY     2    3               3. HANDWRITTEN   3      3. PIGEONS  Y

DELIVERY_METHOD包含投放新信件的方式 - EMAILPOSTPIGEONIS_SPECIAL列标记记录作为特殊交付的方式。它由YN的简单值表示。只有PIGEON是一种特殊的投放方式,即Y,其他方式不是N

GENERATION_SYSTEM包含最终打印信件的信息。示例值为COLOR PRINTERDOT MATRIX PRINTER。每个GENERATION_SYSTEM将始终使用DELIVERY_METHOD之一传递。 GENERATION_SYSTEMDELIVERY_METHOD之间有一个外键。

现在,每个PERSON可以让他的字母由不同的GENERATION_SYSTEM生成,因为它是多对多关系,我们有DELIVERY_MAPPING表,这就是为什么我们两端都有外键。

到目前为止,非常好。

我需要确保如果一个人的信件是由使用特殊交付方法的系统生成的,那么就不能允许他在映射列表中拥有多个生成系统。例如,迪克不能使用彩色打印机生成他的字母,因为他已经获得了所有由鸽子发送的手写字母(这是一种特殊的交付方式)。

我如何实现这样的约束?我尝试在DELIVERY_MAPPING表上使用before-insert-or update触发器,但这会在更新时导致变异触发器问题。

可以进一步规范这个场景吗?也许只是我没有正确地规范我的桌子。

无论哪种方式,我都很想听听你对这个问题的看法。我希望自己足够冗长(......如果你能为这篇文章提出更好的标题,那就太好了)

3 个答案:

答案 0 :(得分:1)

对于像这样的复杂约束,我认为你需要使用触发器。我不认为变异表问题是一个问题,因为你要么做更新,要么什么都不做。

您需要担心的唯一表格是Delivery_Mapping。在允许更改此表之前,您需要在现有表上运行查询以获取特殊数量和gs的数量:

select SUM(case when dme.is_special = 'Y' then 1 else 0 end) as NumSpecial,
       count(distinct gs.id) as NumGS,
       MIN(gs.id) as GSID
from delivery_mapping dm join
     generation_system gs
     on dm.generation_system_id = gs.id join
     delivery_method dme
     on gs.delivery_method_id = dme.id
where dm.person_id = PERSONID

使用此信息,您可以检查插入/更新是否可以继续。我想你需要 检查条件:

  • 如果NumSpecial = 0且新的投放方式不特殊,请继续。
  • 如果NumSpecial = 0且NumGS = 0,则继续。
  • 否则失败。

逻辑对于更新来说有点复杂。

顺便说一句,我更喜欢在存储过程中包装更新/插入/删除,因此这样的逻辑不会隐藏在触发器中。我发现调试和维护过程比处理触发器容易得多,这可能是级联的。

答案 1 :(得分:1)

除非你能保证序列化,否则我会避免基表上的触发器。

你可以使用API​​(最佳方式),如Gordon所说(再次确保序列化)或者如果不合适,请使用物化视图(我们不需要在此处序列化,因为检查已完成在提交):

SQL> create materialized view log on person with rowid, primary key including new values;

Materialized view log created.

SQL> create materialized view log on delivery_mapping with rowid, primary key including new values;

Materialized view log created.

SQL> create materialized view log on generation_system with rowid, primary key (delivery_method_id) including new values;

Materialized view log created.

SQL> create materialized view log on delivery_method with rowid, primary key (is_special) including new values;

Materialized view log created.

我们创建一个物化视图,以显示每个用户的特殊+非特殊链接的计数:

SQL> create materialized view check_del_method
  2  refresh fast on commit
  3  with primary key
  4  as
  5  select pers.id, count(case del_meth.is_special when 'Y' then 1 end) special_count,
  6         count(case del_meth.is_special when 'N' then 1 end) non_special_count
  7    from person pers
  8         inner join delivery_mapping del_map
  9                 on pers.id = del_map.person_id
 10         inner join generation_system gen
 11                 on gen.id = del_map.generation_system_id
 12         inner join delivery_method del_meth
 13                 on del_meth.id = gen.delivery_method_id
 14   group by pers.id;

Materialized view created.

MView定义为提交时快速刷新,因此修改后的行将在提交时重建。现在的规则是,如果特殊+非特殊计数不为零,那就是错误条件。

SQL> create trigger check_del_method_aiu
  2  after insert or update on check_del_method
  3  for each row
  4  declare
  5  begin
  6    if (:new.special_count > 0 and :new.non_special_count > 0)
  7    then
  8      raise_application_error(-20000, 'Cannot have a mix of special and non special delivery methods for user ' || :new.id);
  9   end if;
 11  end;
 12  /

Trigger created.

SQL> set serverout on
SQL> insert into delivery_mapping values (1, 3);

1 row created.

SQL> commit;
commit
*
ERROR at line 1:
ORA-12008: error in materialized view refresh path
ORA-20000: Cannot have a mix of special and non special delivery methods for
user 1
ORA-06512: at "TEST.CHECK_DEL_METHOD_AIU", line 6
ORA-04088: error during execution of trigger 'TEST.CHECK_DEL_METHOD_AIU'

答案 2 :(得分:0)

 CREATE MATERIALIZED VIEW special_queues_mv
  NOLOGGING
  CACHE
  BUILD IMMEDIATE 
  REFRESH ON COMMIT 
  ENABLE QUERY REWRITE
     AS SELECT dmap.person_id
             , SUM(DECODE(dmet.is_special, 'Y', 1, 0)) AS special_queues
             , SUM(DECODE(dmet.is_special, 'N', 1, 0)) AS regular_queues
          FROM delivery_mapping dmap
             , generation_system gsys
             , delivery_method dmet
         WHERE dmap.generation_system_id = gsys.id
           AND gsys.delevery_method_id = dmet.id
         GROUP
            BY dmap.person_id
/

  ALTER MATERIALIZED VIEW special_queues_mv
    ADD ( CONSTRAINT special_queues_mv_chk1 CHECK ((special_queues = 1 AND regular_queues = 0) OR ( regular_queues > 0 AND special_queues = 0 ) ) ENABLE VALIDATE)
/

我是怎么做到的。 DazzaL的答案给了我一个如何做到这一点的暗示。