2014-10-17 163 views
0

我試圖創建一個觸發器,限制讀者在給定的月份內可以閱讀的數量。由日期限制的觸發器

CREATE OR REPLACE trigger Readings_Limit 
Before update or insert on reading 
for each row declare 
readingcount integer; 
max_read integer := 5; 
Begin 
Select count(*) into readingcount 
from (select * 
from Reading 
where to_char(DateRead, 'YYYY-MM') = to_char(DateRead, 'YYYY-MM')) 
where employeeid = :new.employeeid; 
if :old.employeeid = :new.employeeid then 
return; 
else 
if readingcount >= max_read then 
raise_application_error (-20000, 'An Employee can only read 5 per month'); 
end if; 
end if; 
end; 

這限制了讀者最多5個總不管月份,我似乎無法得到它是5個最大的每個月。任何想法非常感謝!

+0

我不知道相當於你在這裏瞄準的東西:'to_char(DateRead,'YYYY-MM')= to_char(DateRead,'YYYY-MM')'但是再次閱讀它並思考它是否可以做任何有用的事情。 – 2014-10-17 06:07:48

回答

-1

你需要你正在尋找在每月設定,所以如果你正在考慮的當月,使內部查詢這樣寫的:

(select * from Reading 
where to_char(DateRead,'YYYY-MM') = to_char(DateRead,'YYYY-MM') 
and to_char(sysdate,'YYYY-MM') = to_char(DateRead,'YYYY-MM')) 

這樣,它總是會比較當前的月份和應該隨着日期的變化而移動。

+1

爲什麼要將日期轉換爲字符串?另外'WHERE to_char(DateRead,'YYYY-MM')= to_char(DateRead,'YYYY-MM')'很沒用! – 2014-10-17 05:43:25

+0

你是對的!我認爲這是限制日期,但它應該讀取,因爲我認爲別人說,DateRead的月份等於今天的月份:to_char(dateread,'YYYY-MM')= to_char(sysdate,'YYYY- MM') - 所以第一個條件可以完全消除! – JohnFL 2014-10-18 12:06:30

0

嘗試重寫你的觸發器是這樣的:

CREATE OR REPLACE trigger Readings_Limit 
    Before update or insert on reading 
    for each row 
declare 
    readingcount integer; 
    max_read integer := 5; 
Begin 
    Select count(*) into readingcount 
    from Reading 
    where DateRead between trunc(sysdate,'MM') and last_day(sysdate) 
     and employeeid = :new.employeeid; 

    if :old.employeeid = :new.employeeid then 
    return; 
    else 
    if readingcount >= max_read then 
     raise_application_error (-20000, 'An Employee can only read 5 per month'); 
    end if; 
    end if; 
end; 

您添加實際月份到YOUT選擇和你避免不必要的日期轉換。

我不明白

if :old.employeeid = :new.employeeid then 

這是否意味着條件下,觸發器應不火的更新?在這種情況下,最好只爲插入或使用從句創建觸發器if inserting then...

+0

當您在基於表'READING'的行級觸發器中選擇表'READING'時,您將得到一個'ORA-04091:表USER.READING正在變異,觸發器/函數可能看不到它'錯誤。 'TRUNC(SYSDATE)'給出當前日期,而不是當前的第一天,它必須是'TRUNC(SYSDATE,'MM')' – 2014-10-17 05:40:38

+0

好吧,trunc(sysdate,'MM')是正確的。但觸發器正在處理我的10g數據庫。我在閱讀時發現了一個主鍵約束(employeeid,dateread),它起作用。我在5次插入後得到一個ORA-20000錯誤... – 2014-10-17 06:01:14

+0

您將在表的UPDATE上得到'ORA-04091'。 – 2014-10-17 06:21:59

0

爲了使用觸發器正確地創建此驗證,應創建一個過程以獲取用戶指定的鎖,以便驗證可以正確地序列化爲多用戶環境。

PROCEDURE request_lock 
    (p_lockname      IN  VARCHAR2 
    ,p_lockmode      IN  INTEGER DEFAULT dbms_lock.x_mode 
    ,p_timeout      IN  INTEGER DEFAULT 60 
    ,p_release_on_commit   IN  BOOLEAN DEFAULT TRUE 
    ,p_expiration_secs    IN  INTEGER DEFAULT 600) 
IS 
    -- dbms_lock.allocate_unique issues implicit commit, so place in its own 
    -- transaction so it does not affect the caller 
    PRAGMA AUTONOMOUS_TRANSACTION; 
    l_lockhandle     VARCHAR2(128); 
    l_return      NUMBER; 
BEGIN 
    dbms_lock.allocate_unique 
    (lockname      => p_lockname 
    ,lockhandle      => p_lockhandle 
    ,expiration_secs    => p_expiration_secs); 
    l_return := dbms_lock.request 
    (lockhandle      => l_lockhandle 
    ,lockmode      => p_lockmode 
    ,timeout      => p_timeout 
    ,release_on_commit    => p_release_on_commit); 
    IF (l_return not in (0,4)) THEN 
    raise_application_error(-20001, 'dbms_lock.request Return Value ' || l_return); 
    END IF; 
    -- Must COMMIT an autonomous transaction 
    COMMIT; 
END request_lock; 

要具有可擴展性的系列化應該在最好的水平,這對於這個約束是每個僱員和月份完成的影響最小。可以使用類型來創建變量,以便在語句完成後檢查約束之前爲每行存儲此信息。這些類型可以在數據庫中定義,或者在包規範中定義(來自Oracle 12c)。

CREATE OR REPLACE TYPE reading_rec 
AS OBJECT 
    (employeeid NUMBER(10) -- Note should match the datatype of reading.employeeid 
    ,dateread DATE); 

CREATE OR REPLACE TYPE readings_tbl 
AS TABLE OF reading_rec; 

的過程和類型隨後可以在複合觸發器使用(至少假設甲骨文11,這將需要在早期版本中被劃分成各個觸發器)

CREATE OR REPLACE TRIGGER too_many_readings 
    FOR INSERT OR UPDATE ON reading 
    COMPOUND TRIGGER 

    -- Table to hold identifiers of inserted/updated readings 
    g_readings readings_tbl; 

BEFORE STATEMENT 
IS 
BEGIN 
    -- Reset the internal readings table 
    g_readings := readings_tbl(); 
END BEFORE STATEMENT; 

AFTER EACH ROW 
IS 
BEGIN 
    -- Store the inserted/updated readings 
    IF ( INSERTING 
    OR :new.employeeid <> :old.employeeid 
    OR :new.dateread <> :old.dateread) 
    THEN   
    g_readings.EXTEND; 
    g_readings(g_readings.LAST) := reading_rec(:new.employeeid, :new.dateread); 
    END IF; 
END AFTER EACH ROW; 

AFTER STATEMENT 
IS 
    CURSOR csr_readings 
    IS 
    SELECT DISTINCT 
      employeeid 
      ,trunc(dateread,'MM') monthread 
    FROM TABLE(g_readings) 
    ORDER BY employeeid 
      ,trunc(dateread,'MM'); 
    CURSOR csr_constraint_violations 
    (p_employeeid reading.employeeid%TYPE 
    ,p_monthread reading.dateread%TYPE) 
    IS 
    SELECT count(*) readings 
    FROM reading rdg 
    WHERE rdg.employeeid = p_employeeid 
    AND trunc(rdg.dateread, 'MM') = p_monthread 
    HAVING count(*) > 5; 
    r_constraint_violation csr_constraint_violations%ROWTYPE; 
BEGIN 
    -- Check if for any inserted/updated readings there exists more than 
    -- 5 readings for the same employee in the same month. Serialise the 
    -- constraint for each employeeid so concurrent transactions do not 
    -- affect each other 
    FOR r_reading IN csr_readings LOOP 
    request_lock('TOO_MANY_READINGS_' 
     || r_reading.employeeid 
     || '_' || to_char(r_reading.monthread, 'YYYYMM')); 
    OPEN csr_constraint_violations(r_reading.employeeid, r_reading.monthread); 
    FETCH csr_constraint_violations INTO r_constraint_violation; 
    IF csr_constraint_violations%FOUND THEN 
     CLOSE csr_constraint_violations; 
     raise_application_error(-20001, 'Employee ' || r_reading.employeeid 
     || ' now has ' || r_constraint_violation.readings 
     || ' in ' || to_char(r_reading.monthread, 'FMMonth YYYY')); 
    ELSE 
     CLOSE csr_constraint_violations; 
    END IF; 
    END LOOP; 
END AFTER STATEMENT; 

END; 
+0

當您定義一個對象表時,不能設置'g_readings'的單個組件 – 2014-10-17 12:07:27