英文:
How can I add a constraint to an Oracle table to make sure, each row in the table is referenced at least once in another table?
问题
在我的Oracle数据库中,我有一个名为A的表,其中的ID列在表B中被引用。然而,目前,并非A的每一行都在B中被引用。我想要在A表中添加一种约束或虚拟列,确保A中的每一行至少在表B中有一行,换句话说,A表的每一行都至少被B表的一行引用。但是我不知道该如何做。我考虑过在A表中创建一个可延迟的虚拟列,就像这里的伪代码:
alter table A add (b_id as (
case select id into b_id from B中引用A的第一行
END) unique deferrable initially deferred);
但是这对我来说没有意义,而且我甚至不确定是否可能在表的虚拟列中引用另一个表。作为一个新手,我觉得可能有更好的选择。你会怎么做?有什么想法吗?
英文:
In my oracle DB i have table A that is referenced through its ID column in table B. Though, currently, not every row of A is referenced in B. I would like to add a contraint of sorts or a virtual column to A that ensures that for EVERY row in A there is at least one row in table B or, in other words, every table A row is referenced by at least one row in table B. But i don't know how to go about it. i thought about doing a deferrable virtual column in table A like in this pseudo code here:
alter table A add (b_id as (
case select id into b_id from the first row of B that references A
END) unique deferrable initially deferred);
but this doesn't make sense to me and, i'm not even sure if it is at all possible to reference another table in the virtual column of a table. i feel like, as a newbie, there might be some better options out there. how would you go about it, any ideas?
答案1
得分: 3
以下是翻译好的部分:
如果您想要建立一个父子关系,其中每个父项都至少有一个子项,那么您可以从父项创建一个可延迟的外键链接到子项:
create table employees (
employee_id int primary key,
first_name varchar2(100) not null,
last_name varchar2(100) not null,
department_id int not null
);
create table departments (
department_id int primary key,
department_name varchar2(30) not null,
manager_id int not null
);
alter table employees
add constraint empl_department_fk
foreign key ( department_id )
references departments;
alter table departments
add constraint dept_manager_fk
foreign key ( manager_id )
references employees ( employee_id )
deferrable;
/* 延迟可延迟的约束验证 */
alter session set constraints = deferred;
insert into departments ( department_id, department_name, manager_id )
values ( 42, 'Test dept', 42 );
/* 在此处验证约束 - 引发错误并回滚 */
commit;
--ORA-02291: integrity constraint (CHRIS.DEPT_MANAGER_FK) violated - parent key not found
insert into departments ( department_id, department_name, manager_id )
values ( 42, 'Test dept', 42 );
insert into employees ( employee_id, first_name, last_name, department_id )
values ( 42, 'Tess', 'Ting', 42 );
commit;
您可以在每个父行都有来自子行的默认/主要/所有者行时使用此方法。这比通常假设的要常见 - 例如,每个部门必须有一个经理,每个客户必须有一个默认的付款方法,每个人必须有一个主要联系方法。
如果没有默认/主要/所有者,您可以通过创建一个查询外连接子项到父项的“在提交时快速刷新”的物化视图来强制执行此操作。在MV中的子列上添加(可延迟的)非空约束:
create materialized view log on employees
with rowid, primary key ( department_id ),
sequence
including new values;
create materialized view log on departments
with rowid, primary key,
sequence
including new values;
/*
是否有没有员工的部门?
MV将子项与父项外连接
*/
create materialized view department_employees_mv
refresh fast on commit
as
select e.rowid empl_rid, d.rowid dept_rid,
e.employee_id, d.department_id
from employees e, departments d
where d.department_id = e.department_id (+);
/* 检查子列不为空 */
alter table department_employees_mv
modify employee_id
constraint deem_employee_nn
not null
deferrable;
/* 证明MV有效 - 删除部门 -> 员工FK */
alter table departments
drop constraint dept_manager_fk;
/* 添加没有员工的新部门 */
insert into departments ( department_id, department_name, manager_id )
values ( 99, 'test', 99 );
/* MV查询 - 请注意employee_id为空 */
select e.employee_id, d.department_id
from employees e, departments d
where d.department_id = e.department_id (+)
and d.department_id = 99;
commit;
--ORA-02091: transaction rolled back
--ORA-02290: check constraint (CHRIS.DEEM_EMPLOYEE_NN) violated
/* 插入被回滚 */
select e.employee_id, d.department_id
from employees e, departments d
where d.department_id = e.department_id (+)
and d.department_id = 99;
insert into departments ( department_id, department_name, manager_id )
values ( 99, 'test', 99 );
insert into employees ( employee_id, first_name, last_name, department_id )
values ( 99, 'Tess', 'Ting', 99 );
commit;
select * from department_employees_mv
where department_id = 99;
EMPL_RID DEPT_RID EMPLOYEE_ID DEPARTMENT_ID
------------------ ------------------ ----------- -------------
AAAToCAAPAAAANHAAB AAAToEAAPAAAAMPAAB 99 99
有关更多信息,请参阅我最近在高级数据库约束上的Ask TOM Office Hours。
英文:
If you want a parent-child relational where every parent has (at least) one child then you can create a deferrable foreign key from the parent back to the child:
create table employees (
employee_id int primary key,
first_name varchar2(100) not null,
last_name varchar2(100) not null,
department_id int not null
);
create table departments (
department_id int primary key,
department_name varchar2(30) not null,
manager_id int not null
);
alter table employees
add constraint empl_department_fk
foreign key ( department_id )
references departments;
alter table departments
add constraint dept_manager_fk
foreign key ( manager_id )
references employees ( employee_id )
deferrable;
/* Delay deferrable constraint validation */
alter session set constraints = deferred;
insert into departments ( department_id, department_name, manager_id )
values ( 42, 'Test dept', 42 );
/* Constraint is validated here - raise error & rollback */
commit;
--ORA-02291: integrity constraint (CHRIS.DEPT_MANAGER_FK) violated - parent key not found
insert into departments ( department_id, department_name, manager_id )
values ( 42, 'Test dept', 42 );
insert into employees ( employee_id, first_name, last_name, department_id )
values ( 42, 'Tess', 'Ting', 42 );
commit;
You can use this whenever each parent row has a default/primary/owner row from the child rows. This is more common than often assumed - e.g. Every department must have a manager, every customer must have a default payment method, every person must have a primary contact method.
If there is no default/primary/owner, you can enforce this by creating a fast refresh on commit
materialized view with a query outer joining the child to the parent. Add a (deferrable) not null constraint over child columns in the MV and voila:
create materialized view log on employees
with rowid, primary key ( department_id ),
sequence
including new values;
create materialized view log on departments
with rowid, primary key,
sequence
including new values;
/*
Is there a dept with no employees?
MV outer joining child to parent
*/
create materialized view department_employees_mv
refresh fast on commit
as
select e.rowid empl_rid, d.rowid dept_rid,
e.employee_id, d.department_id
from employees e, departments d
where d.department_id = e.department_id (+);
/* Check child columns are not null */
alter table department_employees_mv
modify employee_id
constraint deem_employee_nn
not null
deferrable;
/* Prove MV works - drop dept -> emp FK */
alter table departments
drop constraint dept_manager_fk;
/* Add new dept with no employees */
insert into departments ( department_id, department_name, manager_id )
values ( 99, 'test', 99 );
/* MV query - note employee_id is null */
select e.employee_id, d.department_id
from employees e, departments d
where d.department_id = e.department_id (+)
and d.department_id = 99;
EMPLOYEE_ID DEPARTMENT_ID
----------- -------------
<null> 99
commit;
--ORA-02091: transaction rolled back
--ORA-02290: check constraint (CHRIS.DEEM_EMPLOYEE_NN) violated
/* Insert rolled back */
select e.employee_id, d.department_id
from employees e, departments d
where d.department_id = e.department_id (+)
and d.department_id = 99;
insert into departments ( department_id, department_name, manager_id )
values ( 99, 'test', 99 );
insert into employees ( employee_id, first_name, last_name, department_id )
values ( 99, 'Tess', 'Ting', 99 );
commit;
select * from department_employees_mv
where department_id = 99;
EMPL_RID DEPT_RID EMPLOYEE_ID DEPARTMENT_ID
------------------ ------------------ ----------- -------------
AAAToCAAPAAAANHAAB AAAToEAAPAAAAMPAAB 99 99
For more on this see my recent Ask TOM Office Hours on Advanced Database Constraints.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论