簡體   English   中英

在具有父/子表的Oracle SQL中添加日期之間的約束

[英]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;
/

但是,有一些先決條件:

  • 表Study中的ID是唯一鍵
  • 插入表CASE后,不能再更改表Study中的START_DATE和END_DATE,否則您還必須為表Study編寫另一個觸發器
  • 您從Study.ID到CASE.STUDY_ID有一個外鍵約束

只要存在這些前提條件,觸發器就應該起作用。

如果必須使用約束來執行此操作,也可以執行此操作。 創建一個檢查日期的函數,例如

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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM