0

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

限制:会员每月只能借两本书。

@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;
4

2 回答 2

1

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

通常,表 X 上的行级触发器无法查询表 X。因此,在您的情况下,RENT通常不允许行级触发器查询表 -RENT这样做会引发 mutating trigger 异常。如果您想保证您的应用程序一次只使用一条INSERT ... VALUES语句插入 1 行,您将不会遇到变异触发器错误,但这通常不是适当的限制。在多用户环境中也不合适——如果有两个事务几乎同时运行,都向同一个用户签出一本书,这个触发器将潜在地允许两个事务成功。

添加此类检查的适当位置几乎可以肯定是在创建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. 创建一个与上述类似的 after 语句触发器,但它有一个附加条件,即member在您正在维护的集合中。

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

于 2012-11-13T19:31:59.013 回答
0

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

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

于 2012-11-13T19:40:56.803 回答