一对多关系的最大约束 - 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的主要内容,如果未能解决你的问题,请参考以下文章