我正在开展一项任务,在这项任务中,我必须为某些业务逻辑实现数据库级约束(我理解这不应该是方法,但我必须遵循这种方法。)
有问题的数据库是Oracle 10g。
我有一个名为Entry的表和一个名为Entry_Department的连接表,它包含以下列,所有varchars:
项:
Entry_Department:
当前有效的类型是" UserEntry"和" DepartmentEntry"。以下是预期的验证规则:
如何在数据库级别实现此验证?
答案 0 :(得分:0)
前两个要求是在同一个表中的列上。可以使用基于函数的唯一索引来强制执行它们。
示例架构
create table entry
(
id number primary key,
name varchar2(100) not null,
type varchar2(100) not null,
user_id number
);
create table entry_department
(
id number primary key,
entry_id number references entry(id),
department_id number not null
);
前两个规则的索引
create unique index entry_uk1 on entry(
case when type <> 'UserEntry' then name else name||'|'||user_id end);
--Not UserEntry, cannot have the same name.
--The second insert fails with:
--ORA-00001: unique constraint (JHELLER.ENTRY_UK1) violated
insert into entry values(1, 'A', 'type1', 1);
insert into entry values(2, 'A', 'type1', 2);
--UserEntry, the name can be same. These work.
insert into entry values(3, 'A', 'UserEntry', 3);
insert into entry values(4, 'A', 'UserEntry', 4);
--But if NAME and USER_ID are not unique, this fails with:
--ORA-00001: unique constraint (JHELLER.ENTRY_UK1) violated
insert into entry values(5, 'A', 'UserEntry', 4);
第四条规则
第三个要求因跨越多个表格而变得更加困难。有几种方法可以做到这一点 - 触发器,基于虚拟列的参照完整性和物化视图。
触发方法需要大量代码才能正确完成,而我的经验永远不会正确完成。虚拟列是11g功能,不可用。 (但是现在是时候和你的DBA谈谈了 - 现在10g已经很老了。即使是11g也几乎没有得到支持。)
我更喜欢使用物化视图来强制执行多表约束。创建一个返回应该永远不存在的行的物化视图,然后在物化视图上创建一个约束,如果生成任何行则该约束失败。
--Create materialized view logs, so that relevant columns are tracked.
create materialized view log on entry with rowid(id, name, type);
create materialized view log on entry_department with rowid(entry_id);
--Create a SELECT statement that should never be true.
--Fast refresh materialized views are picky.
--For example, they must use the old-fashioned join syntax, and in this case
--must return ROWIDs from both tables.
create materialized view entry_department_wrong_type_mv
build immediate
refresh fast on commit as
select 1 should_not_exist, entry.rowid e_rowid, entry_department.rowid ed_rowid
from entry, entry_department
where entry.id = entry_department.entry_id
and entry.type <> 'DepartmentEntry';
--Create constraint to make sure that the materialized view never has any rows.
alter materialized view entry_department_wrong_type_mv
add constraint entry_deparment_wrong_type_ck check (should_not_exist <> 1);
--Works fine:
insert into entry values(1, 'A', 'DepartmentEntry', 1);
insert into entry_department values (1, 1, 1);
commit;
--The COMMIT fails with this error:
--ORA-12008: error in materialized view refresh path
--ORA-02290: check constraint (JHELLER.ENTRY_DEPARMENT_WRONG_TYPE_CK) violated
insert into entry values(2, 'B', 'Wrong Type!', 2);
insert into entry_department values (2, 2, 2);
commit;
第三条规则
--A UserEntry cannot share the same name as another Entry that is not a UserEntry.
create materialized view entry_ue_not_ue_sharing_mv
build immediate
refresh fast on commit as
select 1 should_not_exist, e1.rowid e1_rowid, e2.rowid e2_rowid
from entry e1, entry e2
where e1.name = e2.name
and e1.type = 'UserEntry'
and e2.type <> 'UserEntry';
--Create constraint to make sure that the materialized view never has any rows.
alter materialized view entry_ue_not_ue_sharing_mv
add constraint entry_ue_not_ue_sharing_ck check (should_not_exist <> 1);
--COMMIT fails with:
--ORA-12048: error encountered while refreshing materialized view "JHELLER"."ENTRY_UE_NOT_UE_SHARING_MV"
--ORA-02290: check constraint (JHELLER.ENTRY_UE_NOT_UE_SHARING_CK) violated
insert into entry values(10, 'Same Name 1', 'Not UserEntry', 1);
insert into entry values(11, 'Same Name 1', 'UserEntry', 2);
commit;
答案 1 :(得分:0)
在插入之前设置要调用的基于行的触发器允许我根据需要验证数据。
不幸的是,由于NDA,我无法发布确切的代码,但一般的逻辑是获取具有相同条目名称的行数,如果它不是UserEntry,或者如果是UserEntry,则获取具有相同条目名称且具有相同User_ID或与UserEntry不等效的类型的行数。