简体   繁体   English

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

[英]Adding a between date constraint in Oracle SQL with parent/child tables

I have two tables Study and Case. 我有两个表Study和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. 我必须添加一个约束,以使Case表中的CASE_DATE在其父表Study的START_DATE和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: 但是,有一些先决条件:

  • ID in table STUDY is a unique key 表Study中的ID是唯一键
  • START_DATE and END_DATE in table STUDY must not change after insert into table CASE, otherwise you have to write another trigger also for table STUDY 插入表CASE后,不能再更改表Study中的START_DATE和END_DATE,否则您还必须为表Study编写另一个触发器
  • You have a foreign key constraint from STUDY.ID to CASE.STUDY_ID 您从Study.ID到CASE.STUDY_ID有一个外键约束

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. 假定通过标准外键约束强制实施了基本引用完整性,并且不允许将任何列设置为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) 然后,可以在两个复合触发器中使用此过程(假设至少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