1

我有四张桌子。

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

ADELIVERY_METHOD包含传递新信件的方式 - EMAIL, POST, PIGEON. 该IS_SPECIAL列将记录标记为特殊交付方式。它由Y或的简单值表示N。只有PIGEON一种特殊的交付方式ie Y,其他都不是ie N

GENERATION_SYSTEM具有最终将打印这封信的信息。示例值为COLOR PRINTERDOT MATRIX PRINTER。每个都GENERATION_SYSTEM将始终使用其中一个来交付DELIVERY_METHODGENERATION_SYSTEM和之间有一个外键DELIVERY_METHOD

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

到现在为止还挺好。

我需要确保如果一个人的信件是由使用特殊传递方法的系统生成的,那么他不能被允许在映射列表中拥有多个生成系统。例如,Dick 无法使用彩色打印机生成他的信件,因为他的所有手写信件都已经由一只鸽子投递(这是一种特殊的投递方式)。

我将如何完成这样的约束?我尝试在表上使用插入或更新前触发器来执行此操作,DELIVERY_MAPPING但这会在更新时导致变异触发器问题。

Can is normalise this scenario even more? Maybe it is just that i haven't normalised my table properly.

Either way, I'd love to hear your take on this issue. I hope I've been verbose enough (...and if you can propose a better title for this post, that would be great)

4

3 回答 3

1

For a complicated constraint like this, I think you need to use triggers. I don't think the mutating table problem is an issue, because you are either going to do an update or do nothing.

The only table you need to worry about is Delivery_Mapping. Before allowing a change to this table, you need to run a query on the existing table to get the number of specials and gs's:

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

With this information, you can check if the insert/update can proceed. I think you need to check the conditions:

  • If NumSpecial = 0 and the new delivery method is not special, then proceed.
  • If NumSpecial = 0 and NumGS = 0, then proceed.
  • Otherwise fail.

The logic is a bit more complicated for updates.

By the way, I prefer to wrap updates/inserts/deletes in stored procedures, so logic like this doesn't get hidden in triggers. I find that debugging and maintaining procedures is much easier than dealing with triggers, which may be possibly cascading.

于 2013-01-24T14:15:20.130 回答
1

I'd avoid triggers on the base tables for this unless you can guarantee serialization.

you could use an API (best way) as Gordon says (again, be sure to serialize) or if that isn't suitable, use a materialized view (we don't need to serialize here, as the check is done on commit):

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.

we create a materialized view to show the counts of special + non special links for each user:

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.

the MView is defined as fast refresh on commit, so the modified rows are rebuilt on commit. now the rule is that if special+non special counts are non-zero, that's an error condition.

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'
于 2013-01-24T17:25:00.560 回答
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)
/

That's how I did it. DazzaL's answer gave me a hint on how to do it.

于 2013-01-28T12:26:57.227 回答