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

Posted

技术标签:

【中文标题】一对多关系的最大约束 - Oracle SQL【英文标题】:Maximum Constraint on One to Many Relationship - Oracle SQL 【发布时间】:2020-03-19 22:06:51 【问题描述】:

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

上面我有一个以经理 ID 作为外键的员工表。这与雇员表是一对多的关系。

是否可以将这种关系限制为最多 3 个?

谢谢。

【问题讨论】:

可能需要通过BEFORE UPDATE 和BEFORE INSERT 触发器强制执行。 【参考方案1】:

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

另一种解决方案是:

创建一个新表来跟踪每个经理的出现次数,例如employee_manager_cnt

employee表上设置一个触发器来保持表employee_manager_cnt是最新的(不需要扫描整个表,只反映基于manager_id的新旧值的变化)

employee_manager_cnt 添加一个检查约束,禁止超过目标计数的值

这是一个step by step demo,灵感来自the answer by nop77svk on this SO question

原表:

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

创建新表:

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

【讨论】:

【参考方案2】:

为了在表中找到每个经理的员工人数,您必须计算他们,对吗?但是,如果您这样做,您将遇到 mutating 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>

【讨论】:

【参考方案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;

【讨论】:

但是当没有选择行时,您必须在触发器中捕获异常 我认为new.manager_id 会抛出错误。您必须使用:new.manager_id: 在您的示例中丢失,也不需要处理 no rows found 异常,因为在这种情况下 count 将返回 0。

以上是关于一对多关系的最大约束 - Oracle SQL的主要内容,如果未能解决你的问题,请参考以下文章

oracle基础(CRUD)

MyBatis高级篇 - 关联查询(一对多)

EF Core / Sqlite 一对多关系在唯一索引约束上失败

oracle 一对多数据分页查询筛选

Hibernate JPA双向一对多结果与约束冲突异常

Mysql 一对多关系建立(在navicat中)