我正在研究Oracle中的一组数据库表,并尝试找出一种通过略微多态数据来强制引用完整性的方法。
具体来说,我有一堆不同的桌子 - 假设我们说我有苹果,香蕉,橘子,橘子,葡萄和一百多种水果。现在我正在尝试制作一张描述涉及水果的步骤的表格。所以我想插入一行说“吃Apple ID 100”,然后另一行说“剥香蕉ID 250”,然后另一行说“冷藏Tangerine ID 500”,依此类推。
从历史上看,我们已经通过两种方式完成了这项工作:
1 - 为每种可能的水果类型添加一列。使用检查约束确保除一列之外的所有列都为NULL。使用外键确保我们的水果的参考完整性。因此,在我的假设示例中,我们有一个包含ACTION, APPLEID, BANANAID, ORANGEID, TANGERINEID
列和GRAPEID
列的表格。对于第一个操作,我们有一行'Eat', 100, NULL, NULL, NULL, NULL, NULL
。对于第二个动作,我们有'Peel', NULL, 250, NULL, NULL, NULL
。等等。
这种方法非常适合自动获取Oracle的所有RI优势,但它不能扩展到一百种类型的水果。你最终会得到太多的专栏来实用。只是弄清楚你正在处理哪种类型的水果成为一种挑战。
2 - 包含一个包含水果名称的列和一个包含水果ID的列。这也有效,但没有任何方法(AFAIK)让Oracle以任何方式强制执行数据的有效性。因此,我们的列将为ACTION, FRUITTYPE
和FRUITID
。行数据将是'Eat', 'Apple', 100
,然后是'Peel', 'Banana', 250
等。但是,即使我们没有Apple,也没有任何内容可以阻止某人删除Apple ID 100或插入'Eat', 'Apple', 90000000
的步骤有了这个ID。
有没有办法避免为每个单独的水果类型维护一个单独的列,但仍然保留了外键的大多数好处? (或者从技术上讲,如果我可以用一种巧妙的技巧隐藏复杂性,我可以说服使用一百列。它只需要在日常使用中看起来很清醒。)
澄清:在我们的实际逻辑中,“果实”是完全不同的表,几乎没有共性。考虑客户,员工,会议,房间,建筑物,资产标签等。步骤列表应该是自由格式的,并允许用户指定对这些事物的任何操作。如果我们有一个包含这些无关的东西的表,我就不会有问题,但这也是一个非常奇怪的设计。
答案 0 :(得分:6)
我不清楚为什么需要在TASKS表上识别FRUIT_TYPE。从表面上看,这只是一个糟糕的(非规范化的)数据模型。
根据我的经验,对这类数据进行建模的最佳方法是使用超类型的通用事物(在您的示例中为FRUIT)和子类型的特定(APPLE,GRAPE,BANANA)。这允许我们在一个地方存储公共属性,同时记录每个实例的特定属性。
这是超类型表:
create table fruits
(fruit_id number not null
, fruit_type varchar2(10) not null
, constraint fruit_pk primary key (fruit_id)
, constraint fruit_uk unique (fruit_id, fruit_type)
, constraint fruit_ck check (fruit_type in ('GRAPE', 'APPLE', 'BANANA'))
)
/
FRUITS有一个主键和一个复合唯一键。我们需要主键用于外键约束,因为复合键是一个痛苦的问题。除非它们不是,这是这些子类型表的情况。这里我们使用唯一键作为参考,因为通过约束子类型中FRUIT_TYPE的值,我们可以保证GRAPES表中的记录映射到'GRAPE'等类型的FRUITS记录。
create table grapes
(fruit_id number not null
, fruit_type varchar2(10) not null default 'GRAPE'
, seedless_yn not null char(1) default 'Y'
, colour varchar2(5) not null
, constraint grape_pk primary key (fruit_id)
, constraint grape_ck check (fruit_type = 'GRAPE')
, constraint grape_fruit_fk foreign key (fruit_id, fruit_type)
references fruit (fruit_id, fruit_type)
, constraint grape_flg_ck check (seedless_yn in ('Y', 'N'))
)
/
create table apples
(fruit_id number not null
, fruit_type varchar2(10) not null
, apple_type varchar2(10) not null default 'APPLE'
, constraint apple_pk primary key (fruit_id)
, constraint apple_ck check (fruit_type = 'APPLE')
, constraint apple_fruit_fk foreign key (fruit_id, fruit_type)
references fruit (fruit_id, fruit_type)
, constraint apple_type_ck check (apple_type in ('EATING', 'COOKING', 'CIDER'))
)
/
create table bananas
(fruit_id number not null
, fruit_type varchar2(10) not null default 'BANANA'
, constraint banana_pk primary key (fruit_id)
, constraint banana_ck check (fruit_type = 'BANANA')
, constraint banana_fruit_fk foreign key (fruit_id, fruit_type)
references fruit (fruit_id, fruit_type)
)
/
在11g中,我们可以使FRUIT_TYPE成为子类型的虚拟列,并取消检查约束。
所以,现在我们需要一个用于任务类型的表('Peel','Refrigerate','Eat'等)。
create table task_types
(task_code varchar2(4) not null
, task_descr varchar2(40) not null
, constraint task_type_pk primary key (task_code)
)
/
实际的TASKS表是FRUITS和TASK_TYPES之间的简单交集。
create table tasks
(task_code varchar2(4) not null
, fruit_id number not null
, constraint task_pk primary key (task_code, fruit_id)
, constraint task_task_fk ask foreign key (task_code)
references task_types (task_code)
, constraint task_fruit_fk foreign key (fruit_id)
references fruit (fruit_id)
/
如果这不能满足您的需求,请编辑您的问题以包含更多信息。
“......如果你想为不同的水果做不同的任务......”
是的,我想知道这是否是OP发布设计背后的动机。但通常工作流程比这更困难:有些任务适用于所有水果,有些只适用于(比如)成串的水果,其他只与香蕉有关。
“在我们的实际逻辑中,'水果'是完全不同的表格 很少的共性。想想客户,员工,会议,房间, 建筑物,资产标签等。应该是步骤列表 自由格式,并允许用户指定任何这些事情的行动。“
所以你有一堆现有的表。您希望能够将这些表中的记录分配给任意风格的任务,但能够保证识别拥有该任务的特定记录。
我认为你仍然需要一个通用表来为任务中的actor保存一个ID,但你需要以某种方式将它链接到其他表。我将如何处理它:
Soem示例现有表格:
create table customers
(cust_id number not null
, cname varchar2(100) not null
, constraint cust_pk primary key (fruit_id)
)
/
create table employees
(emp_no number not null
, ename varchar2(30) not null
, constraint emp_pk primary key (fruit_id)
)
/
用于保存演员的通用表:
create table actors
(actor_id number not null
, constraint actor_pk primary key (actor_id)
)
/
现在,您需要交叉表将现有表与新表关联起来:
create table cust_actors
(cust_id number not null
, actor_id number not null
, constraint cust_actor_pk primary key (cust_id, actor_id)
, constraint cust_actor_cust_fk foreign key (cust_id)
references customers (cust_id)
, constraint cust_actor_actor_fk foreign key (actor_id)
references actors (actor_id)
)
/
create table emp_actors
(emp_no number not null
, actor_id number not null
, constraint emp_actor_pk primary key (emp_no, actor_id)
, constraint emp_actor_emp_fk foreign key (emp_no)
references eployees (emp_no)
, constraint cust_actor_actor_fk foreign key (actor_id)
references actors (actor_id)
)
/
考虑到以前的情况,TASKS表格并不令人惊讶:
create table tasks
(task_code varchar2(4) not null
, actor_id number not null
, constraint task_pk primary key (task_code, actor_id)
, constraint task_task_fk ask foreign key (task_code)
references task_types (task_code)
, constraint task_actor_fk foreign key (actor_id)
references actors (actor_id)
/
我同意所有这些交集表看起来像很多开销,但没有任何其他方法来强制执行外键约束。每次在CUSTOMERS中创建记录时,附加障碍都会创建ACTORS和CUSTOMER_ACTORS记录。删除同上。唯一的好消息是,您可以生成所需的所有代码。
这个解决方案比带有一百个可选外键的表更好吗?也许不是:这是一个品味问题。但我比没有外键更喜欢它。如果在数据库实践中存在普遍存在的事实,那就是:依赖于应用程序代码来强制执行关系完整性的数据库是充斥着引用错误父级或根本不引用父级的子级的数据库。