2012-11-13 86 views
0

我一直在用各种约束在oracle中试验触发器函数,最近有人建议我在下面的条件下使用物化视图而不是触发器,我认为这是一个明智的选择所以。但为了学习的目的,我想知道触发函数是如何工作的。Oracle - 触发器每月检查约束

创建一个触发器来检查基于每月的指定约束。

表租金

|ID|Member|book| 
---------------------- 
1 | John |fairytale|2-jun-12| 
2 | Peter |friction|4-jun-12| 
3 | John |comic|12-jun-12| 
4 | Peter |magazine|20-jun-12| 
5 | Peter |magazine|20-jul-12| 
6 | Peter |magazine|20-jul-12| 

约束:部件只允许每月借2本书。

代码贡献的@HiltoN我不太明白:

create or replace trigger tr_rent 
    before insert on rent 
    for each row 
declare 
    v_count number; 
begin 
    select count(id) 
    into v_count 
    from rent 
    where member = :new.member; 

    if v_count > 2 then 
    raise_application_error (-20001, 'Limit reached'); 
    end if; 
end; 
+0

可能重复http://stackoverflow.com/questions/13349350/oracle-trigger-that-c​​heck-constraint-on-a-monthly) – shellter

回答

1

一般来说,触发不起作用。

一般情况下,在表X行级触发器不能查询表X.那么,在你的情况下,RENT行级触发器一般是不允许查询RENT table--这样做会抛出一个不同诱变触发异常。如果您希望保证您的应用程序只使用INSERT ... VALUES语句一次只插入一行,则不会触发突变触发器错误,但这通常不是合适的限制。在多用户环境中也是不合适的 - 如果有两个事务同时运行并同时向同一用户检出一本书,则此触发器可能会允许两个事务成功。

添加这种检查的适当位置几乎肯定存在于创建RENT记录的存储过程中。该存储过程应该检查该成员在当月的租金数量,如果超出限制,则会出错。类似于

CREATE OR REPLACE PROCEDURE rent_book(p_member IN rent.member%type, 
             p_book IN rent.book%type) 
AS 
    l_max_rentals_per_month constant number := 2; 

    type rental_nt is table of rent.rend_id%type; 
    l_rentals_this_month    rental_nt; 

BEGIN 
    SELECT rent_id 
    BULK COLLECT INTO l_rentals_this_month 
    FROM rent 
    WHERE member = p_member 
    AND trunc(rental_date,'MM') = trunc(sysdate, 'MM') 
    FOR UPDATE; 

    IF(l_rentals_this_month.count > l_max_rentals_per_month) 
    THEN 
    RAISE_APPLICATION_ERROR(-20001, 'Rental limit exceeded'); 
    ELSE 
    INSERT INTO rent(rent_id, member, book, rental_date) 
     VALUES(rent_id_seq.nextval, p_member, p_book, sysdate); 
    END IF; 
END; 

如果您确实想使用触发器强制执行此类操作,则解决方案将变得复杂得多。如果不关心效率,你可以创建一个语句级触发器

create or replace trigger tr_rent 
    after insert on rent 
declare 
    v_count number; 
begin 
    select count(id) 
    into v_count 
    from (select member, count(*) 
      from rent 
      where trunc(rental_date,'MM') = trunc(sysdate,'MM') 
      group by member 
      having count(*) > 2); 

    if v_count >= 1 then 
    raise_application_error (-20001, 'At least one person has exceeded their rental limit'); 
    end if; 
end; 

这工作,但它需要(至少),你的每一次每一个成员做了验证。当你有大量的成员时,这是非常低效的。您可以通过大幅增加复杂性来减少工作量。如果你

  1. 创建一个包声明一个包含全局变量的包,它是rent.member%type的集合。
  2. 创建一个用于初始化此集合的before语句触发器。
  3. 创建一个行级触发器,增加了:new.member到此集合
  4. 创建语句之后触发类似于上面的一个,但有一个附加条件,即member是你保持集中。

这种“三触发解决方案”为系统增加了大量的复杂性,尤其是在适当的解决方案首先不使用触发器的情况下。

+0

感谢您的快速回复和详细解释!对此,我真的非常感激! – user1816507

+0

对于过程方法(和触发器),在选择count()之前,您需要先锁定,否则您在多用户环境中遇到同样的问题。 (count()没有看到未提交的行) – DazzaL

+0

@DazzaL - 啊,是的,叹了口气。更新以反映这一点。 –

0

我同意贾斯汀,你的触发器不会出于多种原因。物化视图或存储过程解决方案可以帮助您。我认为这个问题最好的解决方案将是一个简单的唯一索引:

create unique index rent_user_limit on rent (member, trunc(rental_date, 'month'));

甲骨文触发器,按月检查约束](的