[英]Adding a between date constraint in Oracle SQL with parent/child tables
我有兩個表Study和Case。 研究是父表,案例是子表。 我必須添加一個約束,以使Case表中的CASE_DATE在其父表Study的START_DATE和END_DATE之內。
Study
-------
ID
START_DATE
END_DATE
Case
-----
ID
STUDY_ID
CASE_DATE
解決方案要簡單得多:
CREATE OR REPLACE TRIGGER check_date
BEFORE UPDATE OR INSERT ON CASE
FOR EACH ROW
DECLARE
StartDate STUDY.START_DATE%TYPE;
EndDate STUDY.END_DATE%TYPE;
BEGIN
SELECT START_DATE, END_DATE
INTO StartDate, EndDate
FROM STUDY
WHERE ID = :NEW.STUDY_ID; -- Assuming ID is the primary key, i.e. unique
IF NEW.STUDY_ID NOT BETWEEN StartDate AND EndDate THEN
raise_application_error(-20001, 'Study date not in valid range');
END IF;
END;
/
但是,有一些先決條件:
只要存在這些前提條件,觸發器就應該起作用。
如果必須使用約束來執行此操作,也可以執行此操作。 創建一個檢查日期的函數,例如
create or replace function IsDateValid(StudyId in number, CaseDate in date)
return boolean is
declare
StartDate STUDY.START_DATE%TYPE;
EndDate STUDY.END_DATE%TYPE;
BEGIN
SELECT START_DATE, END_DATE
INTO StartDate, EndDate
FROM STUDY
WHERE ID = StudyId;
return CaseDate BETWEEN StartDate AND EndDate;
END;
/
然后創建約束:
ALTER TABLE case ADD CONSTRAINT check_date CHECK (IsDateValid(STUDY_ID, CASE_DATE));
假定通過標准外鍵約束強制實施了基本引用完整性,並且不允許將任何列設置為NULL。
為了使用觸發器正確地創建此驗證,應該創建一個過程來獲取用戶指定的鎖,以便可以在多用戶環境中正確地序列化驗證。
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 11,將需要在早期版本中將其拆分為單獨的觸發器)
CREATE OR REPLACE TRIGGER cases_exist
FOR UPDATE ON study
COMPOUND TRIGGER
-- Table to hold identifiers of updated studies (assuming numeric)
g_ids sys.odcinumberlist;
BEFORE STATEMENT
IS
BEGIN
-- Reset the internal study table
g_ids := sys.odcinumberlist();
END BEFORE STATEMENT;
AFTER EACH ROW
IS
BEGIN
-- Store the updated studies
IF ( :new.start_date <> :old.start_date
OR :new.end_date <> :old.end_date)
THEN
g_ids.EXTEND;
g_ids(g_ids.LAST) := :new.id;
END IF;
END AFTER EACH ROW;
AFTER STATEMENT
IS
CURSOR csr_studies
IS
SELECT DISTINCT
sty.column_value id
FROM TABLE(g_ids) sty
ORDER BY sty.column_value;
CURSOR csr_constraint_violations
(p_id study.id%TYPE)
IS
SELECT NULL
FROM study sty
INNER JOIN case cse
ON ( cse.study_id = sty.id
AND cse.case_date NOT BETWEEN sty.start_date AND sty.end_date)
WHERE sty.id = p_id;
r_constraint_violation csr_constraint_violations%ROWTYPE;
BEGIN
-- Check if for any updated study there exists a case outside the start and
-- end dates. Serialise the constraint for each study id so concurrent
-- transactions do not affect each other
FOR r_study IN csr_studies LOOP
request_lock('STUDY_CASES_' || r_study.id);
OPEN csr_constraint_violations(r_study.id);
FETCH csr_constraint_violations INTO r_constraint_violation;
IF csr_constraint_violations%FOUND THEN
CLOSE csr_constraint_violations;
raise_application_error(-20001, 'Study ' || r_study.id || ' has cases outside its dates');
ELSE
CLOSE csr_constraint_violations;
END IF;
END LOOP;
END AFTER STATEMENT;
END;
/
CREATE OR REPLACE TRIGGER study_dates
FOR INSERT OR UPDATE ON case
COMPOUND TRIGGER
-- Table to hold identifiers of studies (assuming numeric)
g_study_ids sys.odcinumberlist;
BEFORE STATEMENT
IS
BEGIN
-- Reset the internal study table
g_study_ids := sys.odcinumberlist();
END BEFORE STATEMENT;
AFTER EACH ROW
IS
BEGIN
-- Store the updated studies
IF ( INSERTING
OR :new.study_id <> :old.study_id
OR :new.case_date <> :old.case_date)
THEN
g_study_ids.EXTEND;
g_study_ids(g_study_ids.LAST) := :new.study_id;
END IF;
END AFTER EACH ROW;
AFTER STATEMENT
IS
CURSOR csr_studies
IS
SELECT DISTINCT
sty.column_value id
FROM TABLE(g_study_ids) sty
ORDER BY sty.column_value;
CURSOR csr_constraint_violations
(p_id study.id%TYPE)
IS
SELECT NULL
FROM study sty
INNER JOIN case cse
ON ( cse.study_id = sty.id
AND cse.case_date NOT BETWEEN sty.start_date AND sty.end_date)
WHERE sty.id = p_id;
r_constraint_violation csr_constraint_violations%ROWTYPE;
BEGIN
-- Check if for any updated case it is now outside the start and end dates of
-- the study. Serialise the constraint for each study id so concurrent
-- transactions do not affect each other
FOR r_study IN csr_studies LOOP
request_lock('STUDY_CASES_' || r_study.id);
OPEN csr_constraint_violations(r_study.id);
FETCH csr_constraint_violations INTO r_constraint_violation;
IF csr_constraint_violations%FOUND THEN
CLOSE csr_constraint_violations;
raise_application_error(-20001, 'Study ' || r_study.id || ' has cases outside its dates');
ELSE
CLOSE csr_constraint_violations;
END IF;
END LOOP;
END AFTER STATEMENT;
END;
/
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.