根据Oracle

时间:2015-10-27 22:09:03

标签: sql database oracle

我正在开展一项任务,在这项任务中,我必须为某些业务逻辑实现数据库级约束(我理解这不应该是方法,但我必须遵循这种方法。)

有问题的数据库是Oracle 10g。

我有一个名为Entry的表和一个名为Entry_Department的连接表,它包含以下列,所有varchars:

项:

  • ID
  • 名称
  • 类型
  • User_ID(可空)

Entry_Department:

  • Entry_ID(Entry.ID的外键)
  • Department_ID(部门表格中某个字段的外键,我认为在此上下文中无需进行描述)

当前有效的类型是" UserEntry"和" DepartmentEntry"。以下是预期的验证规则:

  • 不属于UserEntry类型的条目不能具有相同的名称;换句话说,不是UserEntry的每个条目都必须具有唯一的名称。
  • 如果是UserEntry,只要User_ID也是唯一的,条目的名称就可以相同。
  • UserEntry不能与另一个不是UserEntry的条目共享同一名称。
  • Entry_Department.Entry_ID不应引用哪个类型不是DepartmentEntry的Entry.ID。

如何在数据库级别实现此验证?

2 个答案:

答案 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不等效的类型的行数。