如何向Oracle表添加约束以确保表中的每一行至少在另一表中被引用?

huangapple go评论54阅读模式
英文:

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.

huangapple
  • 本文由 发表于 2023年5月22日 22:09:21
  • 转载请务必保留本文链接:https://go.coder-hub.com/76307061.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定