使用触发器或脚本来维护Oracle不是为了强制实施的数据完整性是不是很糟糕,或者这是一个我正在以糟糕的方式对数据进行建模的标志?
从对上一篇文章(Implementing User Defined Fields)的回复中,我决定要继续使用Class和Concrete继承进行设计。我希望所有SAMPLE都有一个基类,然后是每个唯一属性集的具体表。
虽然我可以通过使SAMPLE
主键具有外键约束来强制执行每个具体表在SAMPLE.sample_id
中具有父条目。但是,我不知道如何强制SAMPLE
条目只有一个子项,因为子条目可以在任意数量的表中。
我该如何强制执行此操作?如果解决方案是INSERT,UPDATE和DELETE触发器,这被认为是不好的做法吗?
答案 0 :(得分:3)
我认为您可以通过使用物理化视图解决此问题,该视图是主表id上的TABLEA,TABLEB和TABLEC + groub的并集。您必须创建物化视图日志,以使其成为快速可刷新的物化视图。并且您添加了一个检查约束,当每个主表id在实例化视图中有多个行时,该约束会引发错误。
Rob van Wijk在这里http://rwijk.blogspot.com/2009/07/fast-refreshable-materialized-view.html解释了很多关于快速可刷新的mv的问题。 Rob van Wijk也经常出现在stackoverflow上。
在这里,您可以阅读在物化视图上使用检查约束:http://technology.amis.nl/blog/475/introducing-materialized-views-as-mechanism-for-business-rule-implementation-complex-declarative-constraints
使用快速可刷新的mv意味着在提交期间完成完整性检查,而不是在插入或更新数据期间。
我很累,我不能自己测试,我不能提供一个真实的例子。
edit1:以下是示例:
当您使用检查约束和基于唯一函数的索引创建快速刷新mv时,它可以正常工作。
首先我们创建表格:
SQL> create table mastertable (id number(10) not null primary key);
SQL> create table tablea
(id number(10) not null primary key
, master_id number(10) not null references mastertable (id));
SQL> create table tableb
(id number(10) not null primary key
, master_id number(10) not null references mastertable (id));
SQL> create table tablec
(id number(10) not null primary key
, master_id number(10) not null references mastertable (id));
然后我们创建mv日志:
SQL> create materialized view log on tablea with rowid (master_id)
including new values;
SQL> create materialized view log on tableb with rowid (master_id)
including new values;
SQL> create materialized view log on tablec with rowid (master_id)
including new values;
mv(真正需要umarker专栏!):
SQL> create materialized view table_abc
refresh fast with rowid on commit
as
select master_id,count(*) master_count, 'A' umarker
from tablea
group by master_id
union all
select master_id,count(*) master_count, 'B' umarker
from tableb
group by master_id
union all
select master_id,count(*) master_count, 'C' umarker
from tablec
group by master_id
/
现在我们为这个mv添加一个检查约束,以确保你不能在每个master_id的同一个详细信息表中插入两次:
SQL> alter table table_abc add check (master_count in (0,1) );
我们为此mv添加了一个基于函数的唯一索引,以确保您不能在具有相同master_id的表a和表b中插入:
SQL> create unique index table_abc_ufbi1 on table_abc
(case when master_count = 1 then master_id else null end);
测试1(快乐路径):
SQL>插入mastertable值(1);
1 rij是aangemaakt。
SQL>插入表格值(1,1);
1 rij是aangemaakt。
SQL>提交;
提交是voltooid。
测试2(表a中的一个插入和表b中的一个插入具有相同的master_id)
SQL>插入mastertable值(2);
1 rij是aangemaakt。
SQL>插入表格值(2,2);
1 rij是aangemaakt。
SQL>插入tableb值(3,2);
1 rij是aangemaakt。
SQL>承诺; 承诺 * 在regel 1中的FOUT: .ORA-12008:Fout in pad voor vernieuwen van snapshot。 ORA-00001:Schending van UNIQUE-beperking(TESTT.TABLE_ABC_UFBI1)。
测试3(在表格中插入两次,使用相同的master_id)
SQL>插入mastertable值(3);
1 rij是aangemaakt。
SQL>插入表格值(4,3);
1 rij是aangemaakt。
SQL>插入表格值(5,3);
1 rij是aangemaakt。
SQL>承诺; 承诺 * 在regel 1中的FOUT: .ORA-12008:Fout in pad voor vernieuwen van snapshot。 ORA-02290:CHECK-beperking(TESTT.SYS_C0015406)是geschonden。
答案 1 :(得分:2)
假设您的“主”表名为TableA,它的主键称为“ID”。创建第二个表,比如TableB,也使用名为“ID”的主键。现在将TableB(ID)定义为TableA(A)的外键。
将TableB(ID)作为外键意味着它只能具有TableA(ID)中存在的值,并且将其作为主键意味着它不能具有多于一次的值。
答案 2 :(得分:1)
如果数据只是由您自己的存储过程修改过,那么我就不会费心去检查这个约束了。
事实上,现在我想起来了,没有必要检查INSERT案例。您在同一事务中插入了SAMPLE和CONCRETE_1
行。不能有CONCRETE_2
行具有相同的PK,因为SAMPLE行以前不存在。
答案 3 :(得分:1)
不能依赖触发器来强制执行完整性。
Tom Kyte解释原因:http://asktom.oracle.com/pls/asktom/f?p=100:11:0::::P11_QUESTION_ID:599808600346047256
问题是交易无法看到其他未经通信的交易正在做什么。
举一个简单的例子,事务A和B都可以打算在表中插入相同的值“X”,并且都可以检查这样的值是否已经存在。事务A在事务B检查现有值“X”后提交。事务B找不到X,因此它也插入自己的X和提交。现在唯一性要求被打败了。
避免这种情况的唯一方法是检查现有值,插入并提交插入序列化的整个过程。
除此之外,触发器无法看到它触发的表内容的问题,因为它正在变异......
答案 4 :(得分:1)
我认为在这种特殊情况下,改变你的数据库模型,而不是创建脚本或触发器,就是答案。
你在之前的评论中提到过:
好吧,我有TABLE然后TABLE_A,TABLE_B和TABLE_C。 TABLE中的每个条目必须在EITHER TABLE_A,TABLE_B或TABLE_C中具有完全一个条目。 A,B,C上的FK约束只有一半。然后,表A和表B都可以具有相同的父(我不想要)。
我会建议:
此设计在 TABLE_A , TABLE_B 和 TABLE_C 中创建了一个额外的 isABC 列,作为强制执行此操作的价格约束,但您可以使用脚本,触发器或过程摆脱毛茸茸的实现。
答案 5 :(得分:0)
嗯,这取决于您正在寻找的完整性。
数据库专为参照完整性而设计。因此,如果您正在寻找,请使用数据库的结构。不要自己动手。
如果您尝试维护其他类型的完整性(例如行的MAC),则触发器完全可以接受。
答案 6 :(得分:0)
在对这里的一些答案进行评论之后,我觉得有必要展示一个示例,说明如何使用触发器来执行超出RDBMS提供的基本RI的规则。
从我们自己的系统中,我们有几个表都有指向同一个表的外键(“MasterTable”)。随着时间的推移,这些表中的行将被删除;删除最后一个子行后,我们要删除父行并执行一些操作。我们通过在父表上创建一个“ChildCount”列来强制执行此规则,该列指示引用此表的行数(换句话说,引用计数)。
在所有各种子表的insert触发器中,我们的代码如下所示:
SELECT ChildCount
INTO numrows
FROM MasterTable mt
WHERE :new.MasterTableId = MasterTable.MasterTableId
FOR UPDATE;
UPDATE MasterTable
SET ChildCount = ChildCount + 1
WHERE :new.MasterTableId = MasterTable.MasterTableId;
在子表的删除触发器中,我们有:
SELECT ChildCount
INTO numrows
FROM MasterTable
WHERE :old.MasterTableId = MasterTable.MasterTableId
FOR UPDATE;
DELETE MasterTable
WHERE ChildCount = 1
AND :old.MasterTableId = MasterTable.MasterTableId;
IF Sql%RowCount = 0 THEN
UPDATE MasterTable
SET ChildCount = ChildCount - 1
WHERE :old.MasterTableId = MasterTable.MasterTableId;
END IF;
更新触发器包含两位代码。
此逻辑中的关键位是使用带有FOR UPDATE子句的单独select语句,而不是仅使用单个语句更新列。它确保同步事务将被正确序列化。
由于MasterTable已经向所有子表声明了删除级联规则,因此上面的代码在删除具有现有子项的MasterTable行的上下文中执行时会导致ORA-04091(变异表)错误,因此这些语句在EXCEPTION块的上下文中完成,该块捕获并忽略此错误。
最后,上面的代码是从我们用于数据建模的ERE工具(ERWin)自动生成的。 ERWin允许您创建“用户定义属性”(UDP),它有一种宏语言,可用于根据您的模式生成几乎所需的任何代码,因此我们需要做的就是添加ChildCount列到相应的父表,并将“Ref-Counted Relationship”的UDP设置为true。
正如我在上面的评论中指出的那样,触发器不能用于完全替换声明的RI,因为你不能使用FOR UPDATE来使删除级联规则正常工作。但这对于像这样的补充规则来说非常棒。
注意:此代码已经生产了11年 - 它是在我们仍在使用Oracle 7时设计的。如果有人使用内置的Oracle功能有更现代的方法,我会感兴趣在听到它。
答案 7 :(得分:0)
确保在没有子项的情况下无法插入父记录的最佳方法是使用INSERT ALL语法。这允许我们在同一个语句中将记录插入到多个表中。
INSERT ALL INTO parent (pk_col, val1, val2) INTO child1 (pk_col, val3, val4) SELECT some_seq.nextval as pk_col , val1 , val2 , val3 , val4 FROM where_ever
表WHERE_EVER可以是临时表(可能是外部表)。在你的情况下,它将是DUAL,VAL列是存储过程签名的参数。
如果没有CHILD n 记录,您将无法阻止流氓开发人员编写插入PARENT记录的代码。同样,您无法使用已拥有CHILD1记录的PARENT的主键停止将记录插入CHILD2。为此,我担心你需要代码审查。