Oracle - 每月检查约束的触发器

时间:2012-11-13 19:05:54

标签: oracle plsql triggers

我一直在使用各种约束在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;

2 个答案:

答案 0 :(得分:1)

通常,该触发器不起作用。

通常,表X上的行级触发器无法查询表X.因此,在您的情况下,RENT上的行级触发器通常不允许查询RENT表 - - 这样做会抛出一个变异触发器异常。如果您想保证您的应用程序只使用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. 的集合
  3. 创建一个初始化此集合的before语句触发器。
  4. 创建一个行级触发器,将:new.member添加到此集合
  5. 创建一个类似于上述触发器的after语句触发器,但该条件具有member在您正在维护的集合中的附加条件。
  6. 这种“三触发解决方案”为系统增加了大量复杂性,特别是在适当的解决方案不首先使用触发器的情况下。

答案 1 :(得分:0)

我同意贾斯汀的说法,你的触发器因多种原因无效。物化视图或存储过程解决方案可以帮助您实现目标。我建议这个问题的最佳解决方案是一个简单的唯一索引:

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