2012-11-13 88 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'));

甲骨文觸發器,按月檢查約束](的