当两个表包含相同记录时抛出异常

Throwing an exception when two tables contain the same record

我正在尝试实现的 ER 模式调用两个 tables:

create table enrolled_in (
    student_id number(6) not null,
    course_crn number(6) not null,
    constraint registers_for_pk primary key (student_id, course_crn),
    constraint registers_for_s_fk foreign key (student_id) references student(id),
    constraint registers_for_c_fk foreign key (course_crn) references course(crn)
);

create table registered_for (
    student_id number(6) not null,
    course_crn number(6) not null,
    constraint preregisters_for_pk primary key (student_id, course_crn),
    constraint preregisters_for_s_fk foreign key (student_id) references student(id),
    constraint preregisters_for_c_fk foreign key (course_crn) references course(crn)
);

我必须创建某种完整性约束,以防止同一记录同时存在于两个 table 中。也就是说,学生可能无法注册他们注册的课程。

我的假设是我需要使用触发器,因为简单的约束不能调用 UDF(根据 CHECK 约束 documentation)。我还假设如果发现重复记录我应该抛出异常。这是我目前所拥有的:

create or replace trigger reg_duplicate_check
    BEFORE insert on registers_for or enrolled_in
declare
    duplicate_registration exception;
begin
    select * into reg from registers_for;
    FOR r in reg
    LOOP
        -- check for a duplicate record in enrolled_in?
    END LOOP;
    exception
    when duplicate_registration then
        raise_application_error(-20004,'Duplicate record (enrolled_in and registers_for)');
end;

我问两个问题:

  1. 我在右边吗track/is这是最好的方法吗?

  2. 如果是这样,那我该如何检查重复记录呢?如果不是,实现这种约束的最佳方法是什么?

我无法将它们合二为一table。

我对你问题的回答:

  1. 我认为你的方向不对。
  2. 您应该只使用一个 table,并附加一列,说明该行是否代表已注册或注册的课程学生。我将其命名为 what_is_it(第 4 行)并检查约束(第 11 行),其中 E 代表“注册”,而 R 代表“注册”。如果需要,它也可以是外键。

像这样:

SQL> create table student_in_for
  2    (student_id    number(6),
  3     course_crn    number(6),
  4     what_is_it    varchar2(1),
  5     --
  6     constraint pk_stuinfor primary key (student_id, course_crn),
  7     constraint fk_stuinfor_stu foreign key (student_id)
  8       references student (id),
  9     constraint fk_stuinfor_cou foreign key (course_crn)
 10       references course (crn),
 11     constraint ch_stuinfor_what check (what_is_it in ('E', 'R'))
 12    );

Table created.

SQL>

这样做,它的主键会阻止具有相同 [student_id、course_crn] 组合的两行。


出于学习目的(因为你必须使用两个table),然后使用两个触发器 - 一个每个 table。他们很像:

create or replace trigger trg_biu_enr
  before insert or update on enrolled_in
  for each row
declare
  l_cnt number;
begin
  select count(*)
    into l_cnt
    from registered_for
    where student_id = :new.student_id
      and course_crn = :new.course_crn;
  if l_cnt >= 1 then
     raise_application_error(-20001, 'Student has already REGISTERED that course');
  end if;
end;
/

create or replace trigger trg_biu_reg
  before insert or update on registered_for
  for each row
declare
  l_cnt number;
begin
  select count(*)
    into l_cnt
    from enrolled_in
    where student_id = :new.student_id
      and course_crn = :new.course_crn;
  if l_cnt >= 1 then
     raise_application_error(-20002, 'Student has already ENROLLED IN that course');
  end if;
end;
/

I am not able to combine these into one table.

创建第三个 table 仅用于强制执行约束:

CREATE TABLE enrolment_registration (
    student_id,
    course_crn,
    type CHAR(1),
    CONSTRAINT enrolment_registration__sct__pk PRIMARY KEY (student_id, course_crn, type),
    CONSTRAINT enrolment_registration__sc__u UNIQUE (student_id, course_crn),
    CONSTRAINT enrolment_registration__t__chk CHECK (type IN ('E', 'R')),
    CONSTRAINT enrolment_registration__s__fk
      FOREIGN KEY (student_id) REFERENCES student(id),
    CONSTRAINT enrolment_registration__c__fk
      FOREIGN KEY (course_crn) REFERENCES course(crn)
);

CREATE TABLE enrolled_in  (
    student_id,
    course_crn,
    type        CHAR(1) INVISIBLE GENERATED ALWAYS AS ('E'),
    CONSTRAINT enrolled_in_pk    PRIMARY KEY (student_id, course_crn),
    CONSTRAINT enrolled_in_sc_fk FOREIGN KEY (student_id, course_crn, type)
      REFERENCES enrolment_registration(student_id, course_crn, type)
);

CREATE TABLE registered_for  (
    student_id,
    course_crn,
    type        CHAR(1) INVISIBLE GENERATED ALWAYS AS ('R'),
    CONSTRAINT preregisters_for_pk    PRIMARY KEY (student_id, course_crn),
    CONSTRAINT preregisters_for_sc_fk FOREIGN KEY (student_id, course_crn, type)
      REFERENCES enrolment_registration(student_id, course_crn, type)
);

然后从子 table 中创建一个触发器 insert/delete/update 行与父 table 中的匹配行并让 PRIMARY/UNIQUE父项 table 上的键强制执行约束。


或者,只需创建子 table,然后使用 MATERIALIZED VIEW 将它们 UNION 在一起。类似于:

CREATE TABLE enrolled_in  (
    student_id,
    course_crn,
    CONSTRAINT enrolled_in_pk    PRIMARY KEY (student_id, course_crn),
    CONSTRAINT enrolled_in_s_fk FOREIGN KEY (student_id) REFERENCES students(id),
    CONSTRAINT enrolled_in_c_fk FOREIGN KEY (course_crn) REFERENCES courses(crn)
);

CREATE TABLE registered_for  (
    student_id,
    course_crn,
    CONSTRAINT preregisters_for_pk    PRIMARY KEY (student_id, course_crn),
    CONSTRAINT preregisters_for_sc_fk FOREIGN KEY (student_id) REFERENCES students(id),
    CONSTRAINT preregisters_for_sc_fk FOREIGN KEY (course_crn) REFERENCES courses(crn)
);

CREATE MATERIALIZED VIEW enrolment_registration_mv
   BUILD IMMEDIATE
   REFRESH FAST ON COMMIT
AS
SELECT student_id, course_crn, 'E' AS type FROM enrolled_in
UNION ALL
SELECT student_id, course_crn, 'R' AS type FROM registered_for;

ALTER VIEW enrolment_registration_mv
  ADD CONSTRAINT enrolment_registration_mv__sc__pk
  PRIMARY KEY (student_id, course_crn);