【问题标题】:Foreign key which accepts only selected values仅接受选定值的外键
【发布时间】:2019-02-10 08:24:34
【问题描述】:

假设我有以下表格:

EMPLOYEE : 
    ID(PrimaryKey) | NAME | DESIGNATION 

假设 DESIGNATION 可以有这些值:

  1. 经理
  2. 工程师
  3. 等等等等..
MANAGERS_DETAILS 

    ID | NAME | SALARY

MANAGERS_DETAILS 中的 ID 是 EMPLOYEE 表 ID 的外键。即,

CONSTRAINT managers_details$fk1 FOREIGN KEY (ID) 
    REFERENCES EMPLOYEE(ID) ON DELETE CASCADE

有没有限制插入不是经理的员工?即仅当指定员工为 MANAGER 时才插入表中?

【问题讨论】:

  • 嗨。这是一个常见问题解答。主题是sql/database sutyping/inheritance。请始终在谷歌上搜索您的问题/问题/目标的许多清晰、简洁和特定的版本/措辞,带和不带您的特定字符串/名称,并阅读许多答案。将您发现的相关关键字添加到搜索中。如果您没有找到答案,请发布,使用一个变体搜索您的标题和关键字作为您的标签。请参阅向下投票箭头鼠标悬停文本。

标签: sql oracle database-design foreign-keys


【解决方案1】:

如果此人是经理,您可以向包含idEMPLOYEE 表添加一个虚拟列,并向该虚拟列添加UNIQUE 约束。然后您可以将其引用为FOREIGN KEY:

CREATE TABLE employee (
  id          NUMBER(10,0) PRIMARY KEY,
  name        VARCHAR2(100),
  designation VARCHAR2(10),
  idIfManager NUMBER(10,0) GENERATED ALWAYS AS (
                  CASE DESIGNATION WHEN 'MANAGER' THEN id END
                ) VIRTUAL
                CONSTRAINT employee__idIfManager__u UNIQUE
)
/

CREATE TABLE manager_details (
  id     NUMBER(10,0)
         PRIMARY KEY
         REFERENCES employee ( idIfManager ),
  name   VARCHAR2(100),
  salary NUMBER(12,2)
)
/

【讨论】:

  • 如果您使用的是旧版本的 oracle,您也可以使用触发器来执行此任务。但这应该是另一种方式!
  • @MT0 我们需要UNIQUE VIRTUAL 列的约束吗?
  • @ihm017 FOREIGN KEY 约束必须引用受 PRIMARY KEYUNIQUE 约束约束的列。由于虚拟列包含 NULL 值,因此它不能是 PRIMARY KEY 约束,因此它必须是 UNIQUE 约束。
  • @MT0 你也已经有一个PRIMARY KEY 了,而且只能有一个。 @ihm017 另一方面,这个PRIMARY KEY 已经保证了虚拟列的唯一性,所以你不必担心UNIQUE KEY - 可能只是额外的磁盘空间......
  • @Radagast81 是的,唯一性由底层的PRIMARY KEY 虚拟列引用保证 - 但是要引用具有FOREIGN KEY 的列,您必须具有PRIMARY KEYUNIQUE 约束在该引用的列上,因此 UNIQUE 键不是可选的,并且是此操作所必需的。尝试将UNIQUE 约束注释掉,当您尝试创建manager_details 表时,您将得到ORA-02270: no matching unique or primary key for this column-list
【解决方案2】:

没有任何方法可以在 Oracle 中强制执行 SQL 断言。但是,你可以这样捏造:

 alter table manager_details add designation varchar2(7) default 'MANAGER' not null;
 alter table manager_details add constraint md_mgr_ck check (designation = 'MANAGER');

现在向 EMPLOYEE 添加另一个约束:

alter table employee add constraint emp_uk unique (id, designation);

现在您可以将外键更改为:

CONSTRAINT managers_details$fk1 FOREIGN KEY (ID, designation) 
     REFERENCES EMPLOYEE(ID, designation);

这很复杂,但它确实意味着 MANAGER_DETAILS 中的记录必须在 EMPLOYEE 中定义为 'MANAGER'

【讨论】:

    【解决方案3】:

    如果员工的指定不是经理,您可以使用触发器来避免将数据插入经理表。 您可能希望对 Managers 表中记录的插入和更新都执行此触发器。

    CREATE OR REPLACE TRIGGER manager_validation 
    BEFORE INSERT OR UPDATE 
    ON MANAGERS_DETAILS
    FOR EACH ROW 
    DECLARE
      desig VARCHAR2(10);
    BEGIN
    
      SELECT designation
        INTO desig
        FROM employee
       WHERE id = :new.id;
    
      IF (desig != 'MANAGER') THEN 
        raise_application_error( -20001, 'Only Employees who are Managers can be inserted into table.');
      END IF; 
    
    END;
    

    以下是我的工作示例:

    -- Create Employee Table
    CREATE TABLE employee_ihm
      (
        id          NUMBER PRIMARY KEY,
        name        VARCHAR2(1),
        designation VARCHAR2(10)
      );
    
    -- Create Managers table
    CREATE TABLE managers
      (
        id     NUMBER REFERENCES employee_ihm(id) ON DELETE CASCADE,
        name   VARCHAR2(1),
        salary NUMBER
      );
    
    
    -- Create trigger on insert and update on Managers table
    CREATE OR REPLACE TRIGGER manager_validation BEFORE
      INSERT OR UPDATE
      ON managers 
      FOR EACH ROW 
      DECLARE
        desig VARCHAR2(10);
      BEGIN
        SELECT designation INTO desig FROM employee_ihm WHERE id = :new.id;
        IF (desig != 'MANAGER') THEN
          raise_application_error( -20001, 'Only Employees who are Managers can be inserted into table.');
        END IF;
      END;
    
    INSERT INTO employee_ihm VALUES
      (1, 'A', 'MANAGER');
    INSERT INTO employee_ihm VALUES
      (2, 'B', 'EMP');
    INSERT INTO employee_ihm VALUES
      (3, 'C', 'MANAGER');
    INSERT INTO employee_ihm VALUES
      (4, 'D', 'TEAMLEAD');
    INSERT INTO employee_ihm VALUES
      (5, 'E', 'TEAMMEM');
    INSERT INTO employee_ihm VALUES
      (6, 'F', 'MANAGER');
    
    -- Employee id 1 will be inserted successfully
    INSERT INTO managers VALUES
      (1, 'A', 200);
    
    -- Employee id 2 will fail to get inserted into Managers
    INSERT INTO managers VALUES
      (2, 'B', 300);
    

    让我知道这个解决方案是否有效。

    【讨论】:

    • 我刚刚发布了相同的答案,但决定删除它,因为使用虚拟列的解决方案是迄今为止更好的方法......例如,如果表中的数据员工更改,它也可以工作,否则需要另一个触发器。
    • yes .. 似乎是一种简洁的方法,但我的解决方案适用于早期版本的 Oracle,也可以在其他数据库上实现 ..
    猜你喜欢
    • 2019-11-13
    • 1970-01-01
    • 2019-10-13
    • 2020-01-12
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多