此方案的正确结构是SubClass / Inheritance模型,并且与我在此答案中提出的概念几乎相同:值的异类排序列表。
这个问题中提出的模型实际上非常接近,因为该Animal
实体包含类型(即race
)和所有类型共有的属性。但是,需要进行两个较小的更改:
从它们各自的实体中删除Cat_ID和Dog_ID字段:
这里的关键概念是,一切是Animal
,不管race
:Cat
,Dog
,Elephant
,等等。在此出发点的情况下,由于以下原因race
,Animal
并不需要任何特定的标识符:
- 这
Animal_ID
是独特的
- 的
Cat
,Dog
和任何其他race
在未来加入的实体不这样做,由自己,完全代表任何特定的Animal
; 仅当与父实体中包含的信息结合使用时,它们才有意义Animal
。
因此,在Animal_ID
产权的Cat
,Dog
等实体既是PK和FK回Animal
实体。
区分以下类型breed
:
即使两个属性共享相同的名称,也不一定意味着这些属性是相同的,即使名称相同也暗示了这种关系。在这种情况下,您真正拥有的实际上是CatBreed
和DogBreed
单独的“类型”
最初的笔记
- SQL特定于Microsoft SQL Server(即T-SQL)。意思是,请注意数据类型,因为所有RDBMS的数据类型都不相同。例如,我正在使用,
VARCHAR
但是如果您需要存储标准ASCII集之外的任何内容,则应该使用NVARCHAR
。
- “类型”表(
Race
,CatBreed
和DogBreed
)的ID字段不是自动递增的(即T-SQL的IDENTITY),因为它们是应用程序常量(即它们是应用程序的一部分),它们是数据库,并enum
以C#(或其他语言)表示为。如果添加值,则会在受控情况下添加它们。我保留通过应用程序输入的用户数据使用自动递增字段的功能。
- 我使用的命名约定是为每个子类表命名,从主类名开始,然后是子类名。这有助于组织表并清楚地指示(无需查看FK)子类表与主实体表之间的关系。
- 请参阅最后的“最终编辑”部分,以获取有关视图的注释。
“品种”作为“种族”特定方法
第一组表是查找/类型表:
CREATE TABLE Race
(
RaceID INT NOT NULL PRIMARY KEY
RaceName VARCHAR(50) NOT NULL
);
CREATE TABLE CatBreed
(
CatBreedID INT NOT NULL PRIMARY KEY,
BreedName VARCHAR(50),
CatBreedAttribute1 INT,
CatBreedAttribute2 VARCHAR(10)
-- other "CatBreed"-specific properties as needed
);
CREATE TABLE DogBreed
(
DogBreedID INT NOT NULL PRIMARY KEY,
BreedName VARCHAR(50),
DogBreedAttribute1 TINYINT
-- other "DogBreed"-specific properties as needed
);
第二个清单是主要的“动物”实体:
CREATE TABLE Animal
(
AnimalID INT NOT NULL IDENTITY(1, 1) PRIMARY KEY,
RaceID INT NOT NULL, -- FK to Race
Name VARCHAR(50)
-- other "Animal" properties that are shared across "Race" types
);
ALTER TABLE Animal
ADD CONSTRAINT [FK_Animal_Race]
FOREIGN KEY (RaceID)
REFERENCES Race (RaceID);
这第三组表是免费的子类实体完成每个定义Race
的Animal
:
CREATE TABLE AnimalCat
(
AnimalID INT NOT NULL PRIMARY KEY, -- FK to Animal
CatBreedID INT NOT NULL, -- FK to CatBreed
HairColor VARCHAR(50) NOT NULL
-- other "Cat"-specific properties as needed
);
ALTER TABLE AnimalCat
ADD CONSTRAINT [FK_AnimalCat_CatBreed]
FOREIGN KEY (CatBreedID)
REFERENCES CatBreed (CatBreedID);
ALTER TABLE AnimalCat
ADD CONSTRAINT [FK_AnimalCat_Animal]
FOREIGN KEY (AnimalID)
REFERENCES Animal (AnimalID);
CREATE TABLE AnimalDog
(
AnimalID INT NOT NULL PRIMARY KEY, -- FK to Animal
DogBreedID INT NOT NULL, -- FK to DogBreed
HairColor VARCHAR(50) NOT NULL
-- other "Dog"-specific properties as needed
);
ALTER TABLE AnimalDog
ADD CONSTRAINT [FK_AnimalDog_DogBreed]
FOREIGN KEY (DogBreedID)
REFERENCES DogBreed (DogBreedID);
ALTER TABLE AnimalDog
ADD CONSTRAINT [FK_AnimalDog_Animal]
FOREIGN KEY (AnimalID)
REFERENCES Animal (AnimalID);
breed
在“其他说明”部分之后显示使用共享类型的模型。
补充笔记
- 的概念
breed
似乎是混乱的焦点。jcolebrand提出(在问题中进行评论)建议,这breed
是一个跨不同race
s 共享的属性,而其他两个答案也将其整合到了它们的模型中。但是,这是一个错误,因为的值breed
未在的不同值之间共享race
。是的,我知道另外两个提议的模型都试图通过使成为race
的父代来解决此问题breed
。虽然从技术上解决了关系问题,但这无助于解决关于不常见属性该怎么办的整体建模问题,也无助于解决race
不具有的属性的整体建模问题breed
。但是,在保证此类属性存在于所有情况下的情况下Animal
s,我还将为此提供一个选项(如下)。
- vijayp和DavidN提出的模型(看起来相同)不起作用,因为:
- 他们要么
- 不允许存储非公共属性(至少不允许存储任何的单个实例
Animal
),或者
- 要求将所有的所有属性都
race
存储在Animal
实体中,这是一种非常扁平(几乎是非关系)的表示此数据的方式。是的,人们一直在这样做,但这意味着每行有很多NULL字段用于不适合该特定属性的属性,并且race
知道每行哪些字段与该race
记录的特定内容相关联。
- 他们不允许添加
race
的Animal
在没有未来breed
的财产。即使ALL Animal
都具有a breed
,也不会因先前提到的内容而改变结构breed
:breed
依赖于race
(即breed
for Cat
不同于breed
for Dog
)。
“繁殖”为共同/共享财产方式
请注意:
下面的SQL可以在与上述模型相同的数据库中运行:
- 该
Race
表是相同的
- 该
Breed
表是新
- 这三个
Animal
表都附加了一个2
- 即使
Breed
是现在的共同财产,似乎也没有Race
在主要/母公司中注明(即使从技术上讲是正确的)。因此,RaceID
和BreedID
均以表示Animal2
。为了防止RaceID
in Animal2
和a 之间的不匹配BreedID
,对于另一个RaceID
,我在两者上均添加了FK,该FK RaceID, BreedID
引用了Breed
表中这些字段的UNIQUE CONSTRAINT 。我通常不喜欢将FK指向UNIQUE CONSTRAINT,但这是这样做的几个有效理由之一。从逻辑上说,UNIQUE CONSTRAINT是“备用键”,因此对于此用途有效。另请注意,该Breed
表格的PK仍为BreedID
。
- 之所以没有在组合字段上仅使用PK且没有UNIQUE CONSTRAINT的原因是,它允许
BreedID
在的不同值上重复相同的内容RaceID
。
- 不切换PK和UNIQUE CONSTRAINT的原因是,这可能不是的唯一用法
BreedID
,因此在Breed
没有RaceID
可用值的情况下,仍然应该可以引用特定值。
- 尽管以下模型可以正常工作,但是它在共享的概念方面存在两个潜在的缺陷
Breed
(这就是为什么我偏爱Race
-specific Breed
表)。
- 有一个隐含的假设,即的ALL值
Breed
具有相同的属性。在此模型中,没有简单的方法可以在Dog
“品种”和Elephant
“品种” 之间具有完全不同的属性。但是,仍有一种方法可以做到这一点,请参见“最终编辑”部分。
- 无法共享一个
Breed
以上的种族。我不确定这样做是否可取(或者也许不是在动物概念上,但在其他可能使用这种模型的情况下),但是在这里是不可能的。
CREATE TABLE Race
(
RaceID INT NOT NULL PRIMARY KEY,
RaceName VARCHAR(50) NOT NULL
);
CREATE TABLE Breed
(
BreedID INT NOT NULL PRIMARY KEY,
RaceID INT NOT NULL, -- FK to Race
BreedName VARCHAR(50)
);
ALTER TABLE Breed
ADD CONSTRAINT [UQ_Breed]
UNIQUE (RaceID, BreedID);
ALTER TABLE Breed
ADD CONSTRAINT [FK_Breed_Race]
FOREIGN KEY (RaceID)
REFERENCES Race (RaceID);
CREATE TABLE Animal2
(
AnimalID INT NOT NULL IDENTITY(1, 1) PRIMARY KEY,
RaceID INT NOT NULL, -- FK to Race, FK to Breed
BreedID INT NOT NULL, -- FK to Breed
Name VARCHAR(50)
-- other properties common to all "Animal" types
);
ALTER TABLE Animal2
ADD CONSTRAINT [FK_Animal2_Race]
FOREIGN KEY (RaceID)
REFERENCES Race (RaceID);
-- This FK points to the UNIQUE CONSTRAINT on Breed, _not_ to the PK!
ALTER TABLE Animal2
ADD CONSTRAINT [FK_Animal2_Breed]
FOREIGN KEY (RaceID, BreedID)
REFERENCES Breed (RaceID, BreedID);
CREATE TABLE AnimalCat2
(
AnimalID INT NOT NULL PRIMARY KEY, -- FK to Animal
HairColor VARCHAR(50) NOT NULL
);
ALTER TABLE AnimalCat2
ADD CONSTRAINT [FK_AnimalCat2_Animal2]
FOREIGN KEY (AnimalID)
REFERENCES Animal2 (AnimalID);
CREATE TABLE AnimalDog2
(
AnimalID INT NOT NULL PRIMARY KEY,
HairColor VARCHAR(50) NOT NULL
);
ALTER TABLE AnimalDog2
ADD CONSTRAINT [FK_AnimalDog2_Animal2]
FOREIGN KEY (AnimalID)
REFERENCES Animal2 (AnimalID);
最终编辑(希望;-)
- 关于处理类型之间不同的性能的可能性(然后难度)
Breed
,它是能够使用相同的子类/继承的概念,但与Breed
作为主要实体。在此设置中,Breed
表格将具有所有类型的属性Breed
(就像Animal
表格一样),RaceID
并表示类型Breed
(与Animal
表格中的相同)。然后,你将有子表,如BreedCat
,BreedDog
等。对于较小的项目,这可能被认为是“过度工程”,但它被提及是可以从中受益的情况的一种选择。
对于这两种方法,有时都有助于将视图创建为完整实体的快捷方式。例如,考虑:
CREATE VIEW Cats AS
SELECT an.AnimalID,
an.RaceID,
an.Name,
-- other "Animal" properties that are shared across "Race" types
cat.CatBreedID,
cat.HairColor
-- other "Cat"-specific properties as needed
FROM Animal an
INNER JOIN AnimalCat cat
ON cat.AnimalID = an.AnimalID
-- maybe add in JOIN(s) and field(s) for "Race" and/or "Breed"
- 尽管不是逻辑实体的一部分,但是在表中具有审计字段以至少了解何时插入和更新记录是很普遍的。因此,实际上:
- 一个
CreatedDate
字段将被添加到Animal
表中。在任何子类表(例如AnimalCat
)中都不需要此字段,因为为两个表插入的行应在事务中同时完成。
- 一个
LastModifiedDate
字段将被添加到Animal
表和所有子类表中。如果某个表被更新此字段只获取更新:如果发生在更新AnimalCat
,但不是Animal
为特定的AnimalID
,那么只有LastModifiedDate
在现场AnimalCat
将设置。