I have two tables Study and Case. The Study is the parent table and Case is the child table. I have to add a constraint such that the CASE_DATE in the Case table is within it's parent table Study's START_DATE and END_DATE.
Study
-------
ID
START_DATE
END_DATE
Case
-----
ID
STUDY_ID
CASE_DATE
The solution is much simpler:
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;
/
However, there are some pre-requisites:
As long as these pre-requisites are present the trigger should work.
In case you have to do this with a constraint you can do it also. Create a function that checks the date, eg
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;
/
Then create the constraint:
ALTER TABLE case ADD CONSTRAINT check_date CHECK (IsDateValid(STUDY_ID, CASE_DATE));
Assuming that the basic referential integrity is being enforced through a standard foreign key constraint, and that no columns are allowed to be NULL.
In order to properly create this validation using a trigger a procedure should be created to obtain user-specified locks so the validation can be correctly serialized in a multi-user environment.
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;
This procedure can then be used in two compound triggers (assuming at least Oracle 11, this will need to be split into individual triggers in earlier versions)
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;
/
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.