一对多关系的最大约束 - Oracle SQL

Maximum Constraint on One to Many Relationship - Oracle SQL

使用 Orcale SQL 开发人员我想绘制出员工和经理之间的关系。但是,一个经理最多只能监管3名员工。

上面我有一个员工 table,经理 ID 作为外键。这与员工 table.

具有一对多关系

是否可以将此关系限制为最大值 3?

谢谢。

也许有触发器。

create or replace trigger constraint_trigger
before insert on employee 
DECLARE
    x number;
begin
    select count(*) into x from employee where manager_id=:new.manager_id;
    if (x=3) then
        raise your_exeption;
    end if;
end;

为了找到 table 中每位经理的员工人数,您必须数一数,对吗?但是,如果你这样做,你会遇到 mutating table 错误,因为你不能 select from a table which are currently being改变了。

如今,我们使用复合触发器 解决了这个问题。这是一个例子:

样本table:

SQL> create table temp
  2    (empid number primary key,
  3     name  varchar2(20),
  4     mgrid number references temp (empid)
  5    );

Table created.

复合触发器:

SQL> create or replace trigger trg_3emp
  2    for update or insert on temp
  3    compound trigger
  4
  5    type emprec is record (mgrid temp.mgrid%type);
  6    type row_t  is table of emprec index by pls_integer;
  7    g_row_t     row_t;
  8
  9  after each row is
 10  begin
 11    g_row_t (g_row_t.count + 1).mgrid := :new.mgrid;
 12  end after each row;
 13
 14  after statement is
 15    l_cnt number;
 16  begin
 17    for i in 1 .. g_row_t.count loop
 18      select count(*)
 19      into l_cnt
 20      from temp
 21      where mgrid = g_row_t(i).mgrid;
 22
 23      if l_cnt = 4 then
 24         raise_application_error(-20000, 'No more than 3 employees per manager');
 25      end if;
 26    end loop;
 27  end after statement;
 28  end;
 29  /

Trigger created.

SQL>

测试:

SQL> -- This will be the manager
SQL> insert into temp values (1, 'Little', null);

1 row created.

SQL> -- Next 3 rows will be OK
SQL> insert into temp values (2, 'Foot'  , 1);

1 row created.

SQL> insert into temp values (3, 'Scott' , 1);

1 row created.

SQL> insert into temp values (4, 'Tiger' , 1);

1 row created.

SQL> -- The 4th employee for the same manager should fail
SQL> insert into temp values (5, 'Mike'  , 1);
insert into temp values (5, 'Mike'  , 1)
            *
ERROR at line 1:
ORA-20000: No more than 3 employees per manager
ORA-06512: at "SCOTT.TRG_3EMP", line 22
ORA-04088: error during execution of trigger 'SCOTT.TRG_3EMP'


SQL> -- Someone else can be Mike's manager
SQL> insert into temp values (5, 'Mike', 2);

1 row created.

SQL>

这不能用检查约束来完成。应该可以创建一个物化视图来计算每个管理器的出现次数,对计数进行检查约束,并在提交时刷新原始 table。正如 Littlefoot 所演示的那样,可以使用复合触发器实现相同的功能。但这不是很可扩展,因为整个 table 需要在每次提交后扫描以刷新物化视图。

另一种解决方案是:

  • 创建一个新的 table 来跟踪每个经理的出现次数,比如 employee_manager_cnt

  • employee table 上设置触发器以保持 table employee_manager_cnt 最新(无需扫描整个 table,只是反映根据manager_id)

  • 的新旧值的变化
  • employee_manager_cnt 添加检查约束,禁止值超过目标计数

这是一个step by step demo, which is inspired by the answer by nop77svk on this SO question

原版table:

create table employees (
    employee_id number primary key,
    manager_id number
);

插入几条记录:

begin
    insert into employees values(1, null);
    insert into employees values(2, 1);
    insert into employees values(3, 1);
    insert into employees values(4, 1);    -- manager 1 has 3 employees
    insert into employees values(5, null);
    insert into employees values(6, 5);    -- manager 5 has just 1 employee
end;
/

新建 table:

create table employee_manager_cnt (
    manager_id          number not null primary key,
    cnt                 number(1, 0) not null check (cnt <= 3)
);

填充它:

insert into employee_manager_cnt(manager_id, cnt)
select manager_id, count(*) 
from employees 
where manager_id is not null
group by manager_id

查看结果:

MANAGER_ID  CNT
1           3
5           1

现在,创建触发器:

create or replace trigger trg_employee_manager_cnt
    after insert or delete or update of manager_id
    on employees
    for each row
begin

    -- decrease the counter when an employee changes manager or is removed
    if updating or deleting then
        merge into employee_manager_cnt t
        using dual
        on ( t.manager_id = :old.manager_id )
        when matched then
            update set t.cnt = t.cnt - 1
            delete where t.cnt = 0
        ;
    end if;

    -- increase the counter when a employee changes manager or is added
    if inserting or updating then
        merge into employee_manager_cnt T
        using dual
        on ( t.manager_id = :new.manager_id )
        when matched then
            update set t.cnt = t.cnt + 1
        when not matched then
            insert (manager_id, cnt) values (:new.manager_id, 1)
        ;
    end if;
end;
/

现在尝试添加一条引用经理 1(已有 3 名员工)的新记录

insert into employees values(4, 1);
-- error: ORA-00001: unique constraint (FIDDLE_QOWWVSAIOXRDGYREFVKM.SYS_C00276396) violated

同时仍然有可能将新员工影响到经理 5(他只有一名员工):

insert into employees values(10, 5);
-- 1 rows affected