Oracle—具有多种数据类型的引用完整性

Oracle—具有多种数据类型的引用完整性,oracle,foreign-keys,referential-integrity,Oracle,Foreign Keys,Referential Integrity,我正在研究Oracle中的一组数据库表,并试图找出一种方法,用略微多态的数据强制实现引用完整性 具体来说,我有一大堆不同的桌子——假设,我有苹果、香蕉、橘子、橘子、葡萄,还有上百种水果。现在我试着做一个表格,描述涉及水果的执行步骤。所以我想插入一行,上面写着“吃苹果ID 100”,然后是另一行,上面写着“剥香蕉ID 250”,然后是另一行,上面写着“冷藏橘子ID 500”,依此类推 历史上,我们通过两种方式实现了这一点: 1-为每种可能的水果类型包括一列。使用检查约束以确保除一列之外的所有列都为

我正在研究Oracle中的一组数据库表,并试图找出一种方法,用略微多态的数据强制实现引用完整性

具体来说,我有一大堆不同的桌子——假设,我有苹果、香蕉、橘子、橘子、葡萄,还有上百种水果。现在我试着做一个表格,描述涉及水果的执行步骤。所以我想插入一行,上面写着“吃苹果ID 100”,然后是另一行,上面写着“剥香蕉ID 250”,然后是另一行,上面写着“冷藏橘子ID 500”,依此类推

历史上,我们通过两种方式实现了这一点:

1-为每种可能的水果类型包括一列。使用检查约束以确保除一列之外的所有列都为空。使用外键确保我们的水果的引用完整性。因此,在我假设的示例中,我们有一个表,其中包含列
ACTION、APPLEID、BANANAID、ORANGEID、TANGERINEID
GRAPEID
。对于第一个操作,我们将有一行
'Eat',100,NULL,NULL,NULL,NULL,NULL
。对于第二个操作,我们将使用
'Peel',NULL,250,NULL,NULL,NULL
。等等等等

这种方法非常适合自动获得Oracle的所有RI好处,但它无法扩展到一百种水果。你最终会得到太多的专栏而变得不实用。仅仅弄清楚你要吃的是哪种水果就成了一个挑战

2-包括一个包含水果名称的列和一个包含水果ID的列。这也可以,但Oracle没有任何方式(AFAIK)以任何方式强制执行数据的有效性。因此,我们的专栏将是
ACTION、水果类型
水果ID
。行数据将是
'Eat',Apple',100
,然后是
'Peel',Banana',250
,等等。但是没有任何东西阻止某人删除苹果ID 100,或者插入一个步骤说
'Eat',Apple',90000000
,即使我们没有具有该ID的苹果

有没有一种方法可以避免为每种水果类型维护单独的列,但仍然保留外键的大部分优点?(或者从技术上讲,如果我能用巧妙的技巧隐藏复杂性,我可能会被说服使用100个专栏。它只是在日常使用中看起来很正常。)


澄清:在我们的实际逻辑中,“结果”是完全不同的表,几乎没有什么共同性。想想客户、员工、会议、房间、建筑物、资产标签等。步骤列表应该是自由形式的,允许用户指定对这些事情的操作。如果我们有一个表包含这些不相关的东西,我不会有问题,但这也是一个非常奇怪的设计。

我不清楚为什么需要识别任务表上的水果类型。从表面上看,这只是一个糟糕的(非标准化的)数据模型

根据我的经验,对这类数据建模的最佳方法是使用一个超级类型来表示泛型对象(在您的示例中是水果),并使用子类型来表示特定对象(苹果、葡萄、香蕉)。这允许我们在一个地方存储公共属性,同时记录每个实例的特定属性

下面是超级类型表:

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'))
    )
/
水果有一个主键和一个复合唯一键。我们需要在外键约束中使用主键,因为复合键是一个棘手的问题。除非它们不是,这是这些子类型表的情况。在这里,我们使用unique键作为参考,因为通过约束子类型中水果类型的值,我们可以保证GRAPES表中的记录映射到类型为“GRAPE”的水果记录,等等

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中,我们可以为子类型创建一个虚拟列,并取消检查约束

所以,现在我们需要一个任务类型表(“剥”、“冷藏”、“吃”等)

而实际任务表是水果和任务类型之间的简单交集

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发布设计的动机。但通常工作流要比这困难得多:有些任务将适用于所有水果,有些仅适用于(比如)成串的水果,其他任务仅适用于香蕉


“在我们的实际逻辑中,“水果”是完全不同的表,具有 几乎没有共同点。想想客户、员工、会议、房间, 建筑物、资产标签等。步骤列表应为 自由格式,并允许用户指定对其中任何内容的操作。”

所以你有一堆现有的表。您希望能够以自由的方式将这些表中的记录分配给任务,同时还希望能够确保识别拥有该任务的特定记录

我认为您仍然需要一个通用表来保存任务中参与者的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)
    )
/
鉴于之前的情况,任务表并不令人惊讶:

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记录。删除也一样。唯一的好消息是,您可以生成所需的所有代码

此解决方案是否比具有100个可选外键的表更好?也许不是:这是品味的问题。但我喜欢它胜过没有外键。如果数据库中存在普遍真理
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)
/