Как предотвратить ввод значения в таблицу, когда основной используется в другой таблице? - PullRequest
2 голосов
/ 07 марта 2020

Мне нужно построить и заполнить отношения супертип-подтип, но я не могу заставить их работать так, как это должно быть. В основном PERSON таблица является супертипом таблицы STUDENT и TEACHER (подтипы). Но человек может быть учеником или учителем.

Атрибуты:

ЧЕЛОВЕК (p_id, name, dob)

STUDENT (s_id, p_id, grade)

TEACHER (t_id, p_id, tel)


И ученик, и учитель должны иметь имена и DOB вместе с p_id в качестве внешнего ключа, но если он существует на одной таблицы не должно быть на другой

CREATE TABLE PERSON ( -- SUPERTYPE
    p_id NUMBER(2) CONSTRAINT c1 PRIMARY KEY,
    name CHAR(15),
    dob DATE
);

CREATE TABLE STUDENT ( -- SUBTYPE
    s_id NUMBER(2) CONSTRAINT c2 PRIMARY KEY,
    p_id_fk,
    grade CHAR(1),
    FOREIGN KEY (p_id_fk) REFERENCING PERSON (p_id)
);

CREATE TABLE TEACHER( -- SUBTYPE
    t_id NUMBER(4) CONSTRAINT c3 PRIMARY KEY,
    p_id_fk,
    tel CHAR(8),
    FOREIGN KEY (p_id_fk) REFERENCING PERSON (p_id)
);

INSERT INTO PERSON VALUES (11, 'John', to_date('12/12/12', 'dd/mm/yy'));
INSERT INTO PERSON VALUES (22, 'Maria', to_date('01/01/01', 'dd/mm/yy'));
INSERT INTO PERSON VALUES (33, 'Philip', to_date('02/02/02', 'dd/mm/yy'));

INSERT INTO STUDENT VALUES (98, 11, 'A');

INSERT INTO TEACHER VALUES (1234, 11, 14809510);

Как предотвратить присутствие Лица 11 (Джона) в обеих таблицах?

Ответы [ 4 ]

2 голосов
/ 08 марта 2020

Используйте материализованное представление с соответствующими ограничениями для проверки сложных требований, таких как это.

Например, давайте создадим таблицы и материализованное представление, добавим данные в таблицы и обновим sh MV :

CREATE TABLE PERSON ( -- SUPERTYPE
    p_id NUMBER(2) CONSTRAINT c1 PRIMARY KEY,
    name CHAR(15),
    dob DATE
);

CREATE TABLE STUDENT ( -- SUBTYPE
    s_id NUMBER(2) CONSTRAINT c2 PRIMARY KEY,
    p_id_fk,
    grade CHAR(1),
    FOREIGN KEY (p_id_fk) REFERENCING PERSON (p_id)
);

CREATE TABLE TEACHER( -- SUBTYPE
    t_id NUMBER(4) CONSTRAINT c3 PRIMARY KEY,
    p_id_fk,
    tel CHAR(8),
    FOREIGN KEY (p_id_fk) REFERENCING PERSON (p_id)
);

CREATE MATERIALIZED VIEW PERSON_MV
  REFRESH COMPLETE
  AS SELECT p.P_ID,
            s.S_ID,
            t.T_ID
       FROM PERSON p
       LEFT OUTER JOIN STUDENT s
         ON s.P_ID_FK = p.P_ID
       LEFT OUTER JOIN TEACHER t
         ON t.P_ID_FK = p.P_ID;

-- Add constraint to the table underlying the MV

ALTER MATERIALIZED VIEW PERSON_MV
  ADD CONSTRAINT PERSON_MV_CK1
    CHECK( (S_ID IS NULL AND       -- either both are NULL
            T_ID IS NULL) OR
           ( (S_ID IS NULL OR      -- or only one is NULL
              T_ID IS NULL) AND
             (S_ID IS NOT NULL OR
              T_ID IS NOT NULL)));

INSERT ALL
  INTO PERSON (P_ID, NAME, DOB) VALUES (11, 'John', to_date('12/12/2012', 'dd/mm/yyyy'))
  INTO PERSON (P_ID, NAME, DOB) VALUES (22, 'Maria', to_date('01/01/2001', 'dd/mm/yyyy'))
  INTO PERSON (P_ID, NAME, DOB) VALUES (33, 'Philip', to_date('02/02/2002', 'dd/mm/yyyy'))
SELECT * FROM DUAL;

COMMIT;

INSERT INTO STUDENT VALUES (98, 11, 'A');

COMMIT;

BEGIN
  DBMS_MVIEW.REFRESH('PERSON_MV', 'C', '', TRUE, FALSE, 0, 0, 0, FALSE, FALSE);
END;
/

SELECT *
  FROM PERSON_MV;

Обратите внимание на ограничение, добавленное к материализованному представлению:

ALTER MATERIALIZED VIEW PERSON_MV
  ADD CONSTRAINT PERSON_MV_CK1
    CHECK( (S_ID IS NULL AND       -- either both are NULL
            T_ID IS NULL) OR
           ( (S_ID IS NULL OR      -- or only one is NULL
              T_ID IS NULL) AND
             (S_ID IS NOT NULL OR
              T_ID IS NOT NULL)));

Это ограничение позволяет существовать данным там, где:

  • существует строка ЧЕЛОВЕКА, но не существует связанной строки STUDENT или TEACHER
  • существует строка PERSON вместе с либо связанной строкой STUDENT или TEACHER, но не обе

Так что, когда мы выполнить последний SELECT из материализованного представления, которое мы получим:

P_ID  S_ID  T_ID
11     98    - 
33     -     - 
22     -     - 

Теперь давайте изменим скрипт выше, добавив следующее сразу после INSERT INTO STUDENT:

INSERT INTO TEACHER VALUES (1234, 11, 14809510);

COMMIT;

Если мы повторно запустив весь сценарий, мы обнаружим, что когда DBMS_MVIEW.REFRE SH вызывается для рефракции sh материализованного представления, мы получаем:

ORA-12008: error in materialized view or zonemap refresh path ORA-06512: at "SYS.DBMS_SNAPSHOT_KKXRCA", line 3012
ORA-06512: at "SYS.DBMS_SNAPSHOT_KKXRCA", line 2424
ORA-06512: at "SYS.DBMS_SNAPSHOT_KKXRCA", line 88
ORA-06512: at "SYS.DBMS_SNAPSHOT_KKXRCA", line 253
ORA-06512: at "SYS.DBMS_SNAPSHOT_KKXRCA", line 2405
ORA-06512: at "SYS.DBMS_SNAPSHOT_KKXRCA", line 2968
ORA-06512: at "SYS.DBMS_SNAPSHOT_KKXRCA", line 3255
ORA-06512: at "SYS.DBMS_SNAPSHOT_KKXRCA", line 3287
ORA-06512: at "SYS.DBMS_SNAPSHOT", line 16
ORA-06512: at line 2
ORA-06512: at "SYS.DBMS_SQL", line 1721

Это довольно длинный путь Oracle к сказать, что ограничение было нарушено. * 102 9 *

Смотрите этот Live SQL Oracle сеанс

2 голосов
/ 07 марта 2020

Один из вариантов - использовать триггеры базы данных, по одному для каждой таблицы (STUDENT и TEACHER); они выглядят одинаково:

Триггер на STUDENT:

SQL> create or replace trigger trg_bi_stu
  2    before insert on student
  3    for each row
  4  declare
  5    l_cnt number;
  6  begin
  7    -- inserting into STUDENT: check whether that person exists in TEACHER table
  8    select count(*)
  9      into l_cnt
 10      from teacher
 11      where p_id_fk = :new.p_id_fk;
 12
 13    if l_cnt > 0 then
 14       raise_application_error(-20001, 'That person is a teacher; can not be a student');
 15    end if;
 16  end;
 17  /

Trigger created.

Триггер на TEACHER:

SQL> create or replace trigger trg_bi_tea
  2    before insert on teacher
  3    for each row
  4  declare
  5    l_cnt number;
  6  begin
  7    -- inserting into TEACHER: check whether that person exists in STUDENT table
  8    select count(*)
  9      into l_cnt
 10      from student
 11      where p_id_fk = :new.p_id_fk;
 12
 13    if l_cnt > 0 then
 14       raise_application_error(-20001, 'That person is a student; can not be a teacher');
 15    end if;
 16  end;
 17  /

Trigger created.

SQL>

Тестирование:

SQL> INSERT INTO STUDENT VALUES (98, 11, 'A');

1 row created.

SQL>
SQL> INSERT INTO TEACHER VALUES (1234, 11, 14809510);
INSERT INTO TEACHER VALUES (1234, 11, 14809510)
            *
ERROR at line 1:
ORA-20001: That person is a student; can not be a teacher
ORA-06512: at "SCOTT.TRG_BI_TEA", line 11
ORA-04088: error during execution of trigger 'SCOTT.TRG_BI_TEA'


SQL>
0 голосов
/ 08 марта 2020

Исходя из подхода материализованного представления @ Боба, версия, которая улавливает проблему при фиксации, может выглядеть следующим образом:

CREATE MATERIALIZED VIEW LOG ON STUDENT WITH PRIMARY KEY, ROWID;

CREATE MATERIALIZED VIEW LOG ON TEACHER WITH PRIMARY KEY, ROWID;

CREATE MATERIALIZED VIEW PERSON_HACK (p_id_fk, marker, rid) 
BUILD IMMEDIATE 
REFRESH ON COMMIT AS 
SELECT p_id_fk, 1, ROWID FROM STUDENT 
UNION ALL 
SELECT p_id_fk, 2, ROWID FROM TEACHER;

ALTER MATERIALIZED VIEW PERSON_HACK 
ADD CONSTRAINT PERSON_HACK_PK PRIMARY KEY (p_id_fk);

Это должно действовать аналогично отложенному ограничению с ошибкой при обновлении MV ( и его первичный ключ нарушен) при коммите. При одновременных вставках во второй сеанс фиксации будет отображаться сообщение об ошибке.

Live SQL, хотя, похоже, имеются некоторые проблемы с MV (о которых сообщалось в другом месте). Он должен сообщить о нарушении ограничения, а не выбрасывать ORA-12008. Но в настоящее время у меня нет доступа к тестированию в другом месте - ни SQL Fiddle, ни db <> fiddle не позволяют создавать MV.


Конечно, если вас еще не учили о MV тогда использование одного в назначении может выглядеть немного странно. Кажется немного более вероятным, что вас учили об объектах, и задание ожидает их - даже если они очень редко используются в реальном мире (в БД), их все равно учат вместе со старым синтаксисом соединения и другие плохие практики ...

0 голосов
/ 07 марта 2020

Существует четыре способа , как сопоставить наследование с реляционной базой данных.

Вы используете самый экзотичный c третий вариант, который вызывает проблему . Рассмотрите возможность переключения на один из других вариантов в качестве решения.

Первые три хорошо поняты и задокументированы, эта страница содержит полезные ссылки.

По сути, вы можете отобразить

1) все классы в одной таблице

2) использовать одну таблицу для всех конкретных классов

3) определить одну таблицу для класса aech

Все это Опции имеют некоторые проблемы либо с избыточными объединениями, либо с деактивированными ограничениями (например, вы не можете определить необнуляемые столбцы в опции 1), поэтому для полноты опции 4) равен , не используйте наследование в реляционной базе данных .

Вы пытались реализовать опцию 3), но проблема в том, что все таблицы должны наследовать один и тот же первичный ключ (для соблюдения соотношения 1: 1) и использовать этот первичный ключ в качестве внешний ключ .

Вот обзор всех опций для вашего примера

-- 1) single table
CREATE TABLE PERSON (  
    p_id NUMBER(2) CONSTRAINT pers_pk PRIMARY KEY,
    name CHAR(15),
    dob DATE,
    grade CHAR(1),
    tel CHAR(8),
    person_type VARCHAR2(10) CONSTRAINT pers_type CHECK  (person_type in ('STUDENT','TEACHER'))
);

-- 2) table per concrete class
CREATE TABLE STUDENT (  
    p_id NUMBER(2) CONSTRAINT stud_pk PRIMARY KEY,
    name CHAR(15),
    dob DATE,
    grade CHAR(1) 
);

CREATE TABLE TEACHER(  
    p_id NUMBER(2) CONSTRAINT tech_pk PRIMARY KEY,
    name CHAR(15),
    dob DATE,
    tel CHAR(8)
);

-- 3) table per class
CREATE TABLE PERSON (  
    p_id NUMBER(2) CONSTRAINT pers_pk PRIMARY KEY,
    name CHAR(15),
    dob DATE
);

CREATE TABLE STUDENT (  
    p_id NUMBER(2) CONSTRAINT stud_pk PRIMARY KEY,
    grade CHAR(1),
    FOREIGN KEY (p_id) REFERENCING PERSON (p_id)
);

CREATE TABLE TEACHER( 
    p_id NUMBER(2) CONSTRAINT tech_pk PRIMARY KEY,
    tel CHAR(8),
    FOREIGN KEY (p_id) REFERENCING PERSON (p_id)
);

INSERT INTO PERSON (P_ID, NAME, DOB) VALUES (11, 'John', to_date('12/12/2012', 'dd/mm/yyyy'));
INSERT INTO PERSON (P_ID, NAME, DOB) VALUES (22, 'Maria', to_date('01/01/2001', 'dd/mm/yyyy'));
INSERT INTO PERSON (P_ID, NAME, DOB) VALUES (33, 'Philip', to_date('02/02/2002', 'dd/mm/yyyy'));

INSERT INTO STUDENT (P_ID, GRADE) VALUES (11, 'A');
INSERT INTO TEACHER (P_ID, TEL) VALUES (11, 14809510);
...