具有来自不同表的多列的外键

时间:2012-09-24 21:20:38

标签: sql database database-design foreign-keys multiple-tables

让我们举一个愚蠢的例子:我有很多家养动物,每个动物都有一个NAME作为id和一个类型(CAT或DOG),让我们这样写(伪代码):

TABLE ANIMALS (
  NAME char,
  ANIMAL_TYPE char {'DOG', 'CAT'}
  PRIMARY KEY(NAME)
)

(例如,我有一个叫做Felix的猫,一只叫做Pluto的狗)

在另一张桌子上,我想为每只动物储存喜欢的食物:

TABLE PREFERED_FOOD (
  ANIMAL_NAME char,
  PREF_FOOD char
  FOREIGN KEY (ANIMAL_NAME) REFERENCES ANIMALS(NAME)
)

(例如,菲利克斯喜欢牛奶,冥王星喜欢骨头)

由于我想定义一组可能的首选食物,我会在第三张表中列出每种动物的食物类型:

TABLE FOOD (
  ANIMAL_TYPE char {'DOG', 'CAT'},
  FOOD_TYPE char
)

(例如,DOG吃骨头和肉,CAT吃鱼和牛奶)

我的问题出现了:我想在PREFERED_FOOD中添加一个外部约束,因为PREF_FOOD是来自FOOD的FOOD_TYPE,其中FOOD.ANIMAL_TYPE = ANIMALS.TYPE。如何在不复制PREFERED_FOOD上的ANIMAL_TYPE的情况下定义此外键?

我不是SQL的专家,所以如果真的很容易你就可以叫我傻了! - )

5 个答案:

答案 0 :(得分:3)

你不能在SQL中。如果SQL支持断言,我认为可以。 (SQL-92标准定义了断言。据我所知,还没有人支持它们。)

要解决该问题,请使用重叠约束。

-- Nothing special here.
create table animal_types (
  animal_type varchar(15) primary key
);

create table animals (
  name varchar(15) primary key,
  animal_type varchar(15) not null references animal_types (animal_type),
  -- This constraint lets us work around SQL's lack of assertions in this case.
  unique (name, animal_type)
);

-- Nothing special here.
create table animal_food_types (
  animal_type varchar(15) not null references animal_types (animal_type),
  food_type varchar(15) not null,
  primary key (animal_type, food_type)
);

-- Overlapping foreign key constraints.
create table animals_preferred_food (
  animal_name varchar(15) not null,
  -- This column is necessary to implement your requirement. 
  animal_type varchar(15) not null,
  pref_food varchar(10) not null,
  primary key (animal_name, pref_food),
  -- This foreign key constraint requires a unique constraint on these
  -- two columns in "animals".
  foreign key (animal_name, animal_type) 
    references animals (animal_name, animal_type),
  -- Since the animal_type column is now in this table, this constraint
  -- is simple.
  foreign key (animal_type, pref_food) 
    references animal_food_types (animal_type, food_type)
);

答案 1 :(得分:0)

FOREIGN KEY (PREF_FOOD) REFERENCES FOOD (FOOD_TYPE)
在PREFERRED_FOOD表中,这将确保PREFERRED_FOOD表中的每个PREFFOOD都已存在于FOOD表的FOOD_TYPE中。

并且在FOOD表中使用,现在它非常明显。

FOREIGN KEY (ANIMAL_TYPE) REFERENCES ANIMALS (ANIMAL_TYPE)

答案 2 :(得分:0)

根据您使用的DBMS(请编辑您的问题以包含此内容),您可能希望在ANIMAL_TYPEPREFERED_FOOD列上创建唯一约束。

这样的事情:

ALTER TABLE PREFERED_FOOD
ADD CONSTRAINT uc_FoodAnimal UNIQUE (ANIMAL_TYPE,PREFERED_FOOD)

答案 3 :(得分:0)

坦率地说,我在遵守您的要求时遇到了一些麻烦,但是代表动物及其食物的简单模型可能看起来像这样:

enter image description here

SPECIES_FOOD列出了给定物种可以吃的所有食物,然后INDIVIDUAL只通过PREFERRED_FOOD_NAME字段选择其中一种食物。

由于INDIVIDUAL.SPECIES_NAME是两者物种和物种_FFO的FK,个人永远不会喜欢其物种无法食用的食物。

这当然假设个体动物不能有一种以上的首选食物。 1 它也假设它没有 - 如果不是这样的话,只需制作INDIVIDUAL.PREFERRED_FOOD_NAME不是。

INDIVIDUAL_NAME故意做了一把钥匙,所以你可以说两只名为" Felix"的猫。如果这不合适,您可以轻松添加相应的密钥。

如果您需要了解食物的名称,并且您不需要独立于任何物种代表食物,则可以完全省略食物表。


1 如果每只动物可以有多种喜欢的食物,那么你需要再多一个餐桌"在"之间。个人和SPECIES_FOOD,并谨慎使用识别关系,因此SPECIES_NAME一直向下迁移(以防止更喜欢该物种不能食用的食物)。

答案 4 :(得分:0)

如果您采用ANIMALS和PREFERRED_FOOD的(自然)JOIN,那么您将得到一张表格,其中列出了每只动物的类型和首选食物。

您希望该组合对每只动物都“有效”,其中“有效”的意思是“出现在FOOD中列出的有效动物类型/食物类型组合的列举中。

所以你有一个类似于FK的约束,但这次“外键”不出现在基表中,而是出现在两个表的连接中。对于这种类型的约束,SQL语言具有CHECK约束和ASSERTIONS。

ASSERTION版本是最简单的。这是一个约束(我对属性名称有些自由,以避免仅仅混淆了点的属性重命名)

CREATE ASSERTION <name for your constraint here>
 CHECK NOT EXISTS (SELECT ANIMAL_TYPE, FOOD_TYPE
                     FROM ANIMALS NATURAL JOIN PREF_FOOD
                    WHERE (ANIMAL_TYPE, FOOD_TYPE) NOT IN
                          SELECT ANIMAL_TYPE, FOOD_TYPE FROM FOOD_TYPE);

但是你的普通SQL引擎不支持ASSERTION。所以你必须使用CHECK约束。例如,对于PREF_FOOD表,您需要的CHECK约束可能类似于

CHECK EXISTS (SELECT 1
                FROM FOOD NATURAL JOIN ANIMAL
               WHERE ANIMAL_TYPE = <animal type of inserted row> AND
                     FOOD_TYPE = <food type of inserted row>);

理论上,这应该足以强制执行您的约束,但是再一次,您的普通SQL引擎将再次不支持这种CHECK约束,因为除了定义约束之外的表的引用。

所以你有的选择是采用相当复杂的(*)设置,如catcall,或使用触发器强制执行约束(你必须写很多这些(至少三个或六个,没有)通过详细考虑这一点,你的下一个最佳选择是在应用程序代码中强制执行此操作,并且将再次有三个或六个(或多或少)不同的地方需要执行相同数量的不同检查。

在所有这三个场景中,您最好想要在其他地方记录约束的存在,以及它的确切含义。对于阅读此设计的第三方来说,三者中的任何一个都不会让这一点变得非常明显。

(*)“复杂”可能不是正确的词,但请注意,此类解决方案依赖于故意冗余,因此故意低于3NF的设计。这意味着您的设计暴露于更新异常,这意味着用户更难以更新数据库并使其保持一致(正是因为故意的冗余)。