Sql 当主表用于其他表时,如何防止在表中输入值?

Sql 当主表用于其他表时,如何防止在表中输入值?,sql,oracle,inheritance,subtype,supertype,Sql,Oracle,Inheritance,Subtype,Supertype,我必须构造和填充一个超类型-子类型关系,但我不能让它按预期的方式工作。基本上,人表是表学生和教师(子类型)的超类型。但是一个人可以是学生也可以是老师 属性: 人员(p_id、姓名、dob) 学生(学生证、学生证、年级) 教师(t\U id、p\U id、电话) 学生和教师都应该将姓名和DOB以及p_id作为外键,但如果它存在于一个表中,则不应该在另一个表中 CREATE TABLE PERSON ( -- SUPERTYPE p_id NUMBER(2) CONSTRAINT c1 P

我必须构造和填充一个超类型-子类型关系,但我不能让它按预期的方式工作。基本上,表是表学生教师(子类型)的超类型。但是一个人可以是学生也可以是老师

属性:

人员(p_id、姓名、dob)

学生(学生证、学生证、年级)

教师(t\U id、p\U id、电话)


学生和教师都应该将姓名和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);


如何防止Person 11(John)出现在两个表中?

一个选项是使用数据库触发器,每个表一个(
STUDENT
TEACHER
);它们看起来一样:

学生上的触发器:

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.
INSERT INTO TEACHER VALUES (1234, 11, 14809510);

COMMIT;
教师上的触发器:

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>
有四种方法可以将继承映射到关系数据库

您正在使用最奇特的第三个选项,这导致了问题的出现。考虑切换到其他选项之一作为解决方案。

前三个是很好的理解和记录,这提供了有用的链接

基本上你可以绘制地图

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);

使用具有适当约束的物化视图来检查这样的复杂需求

例如,让我们创建表和物化视图,向表中添加数据,并刷新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)));
此约束允许数据存在于以下位置:

  • 存在一个人行,但不存在相关的学生行或教师行
  • PERSON行与相关的STUDENT行或TEACHER行同时存在,但不能同时存在
因此,当我们从物化视图执行最终选择时,我们得到:

P_ID  S_ID  T_ID
11     98    - 
33     -     - 
22     -     - 
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
现在,让我们修改上面的脚本,在插入学生的
之后添加以下内容:

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.
INSERT INTO TEACHER VALUES (1234, 11, 14809510);

COMMIT;
如果我们重新运行整个脚本,我们会发现当调用DBMS_MVIEW.REFRESH来刷新我们得到的物化视图时:

P_ID  S_ID  T_ID
11     98    - 
33     -     - 
22     -     - 
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
这是甲骨文相当冗长的说法,说约束被违反了


根据@Bob的物化视图方法,在提交时捕获问题的版本可能如下所示:

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(并违反其主键)时出错。使用并发插入,要提交的第二个会话将看到错误

,尽管它似乎与MVs有一些问题(在别处报道)。它应该报告约束冲突,而不是抛出ORA-12008。但我目前无法访问其他地方的测试—SQL FIDLE或DBFIDLE都不允许创建MVs



当然,如果你还没有学过MVs,那么在作业中使用MVs可能会有点奇怪。更有可能的情况是,您已经学习过对象,而分配也在期待这些东西——尽管它们在现实世界中很少使用(在数据库中),但它们似乎还是被教授过,还有旧的连接语法和其他不好的做法……

这看起来像Oracle SQL。你真的在使用sql server吗(这个脚本可能会失败)?我忘记在上面的例子中输入值,但现在已经更正了。对我来说确实有效,但问题仍然存在。sql server中没有截止日期()
。您的屏幕截图看起来像Oracle的
sql*plus
命令行实用程序。我正在标记您的问题
oracle
,请随意回滚。打得好,我不知道sql server和sql*plus之间有什么区别。您使用的术语表明您可能会使用对象而不是普通表。(在问题中)确切地包括你被要求做什么,以及为什么你认为你所做的是错的,这可能会有帮助。哦,汤姆不会被我写的很多代码逗乐的。尽管你发布了一个提示(“并发插入”),请——还有一个提示——关于并发插入,@Marmite?很抱歉我删除了这个公式。我的观点是1)触发器是在数据库中强制执行约束的错误方法。2) 正确的方法是根据映射类表继承的标准方法更改DDL。@Littlefoot-re并发插入”:插入人并提交。然后一节课插入学生;另一个会话插入具有相同人员ID的教师。两个触发器都看不到另一个会话的未提交数据,因此都计数为零并继续,并且都可以提交,然后该人员出现在两个表中。A-ha!现在我明白了,谢谢@Alex和Marmite。问题仍然存在,我尝试使用了你的建议,但对我无效。在学生表和教师表中都加上“John”有什么意义?它不应该接受它。@Loizosvasilei你也可以是一名教师(在其他课程中)。如果您想禁用它,可以使用选项1)并添加一个定义“person_type”的判别列,或者使用从单个序列分配的键添加选项2),这将确保表没有间断键。这是一个很好的方法,+1。但问题是,当您刷新MV时,它只会在之后检测重复项。它不能防止重复,