我的一位同事创建了一个类似于以下的模式。这是一个简化的模式,仅包含解决此问题所需的部分。
系统规则如下:
架构是:
Department
----------
DepartmentID (PK) int NOT NULL
DepartmentName varchar(50) NOT NULL
Division
--------
DivisionID (PK) int NOT NULL
DepartmentID (FK) int NOT NULL
DivisonName varchar(50) NOT NULL
Article
-------
ArticleID (PK) int NOT NULL
UniqueID int NOT NULL
ArticleName varchar(50) NOT NULL
他使用虚构规则(缺少更好的术语)定义了模式,所有DepartmentID都在1到100之间,所有DivisionID都在101到200之间。他说当查询Article表时,你将根据它所属的范围知道UniqueID是来自Department表还是Division表。
我认为这是一个糟糕的设计,并提出了以下替代架构:
Department
----------
DepartmentID (PK) int NOT NULL
ParentDepartmentID (FK) int NULL /* Self-referencing foreign key. Divisions have parent departments. */
DepartmentName varchar(50) NOT NULL
Article
-------
ArticleID (PK) int NOT NULL
DepartmentID (FK) int NOT NULL
ArticleName varchar(50) NOT NULL
我相信这是一个正确规范化的架构,可以正确地执行关系和数据完整性,同时遵守上述业务规则。
我的具体问题是:
我知道使用一列来包含来自两个域的值是糟糕的设计,我可以在Article表中论证外键的好处。但是,有人可以提供我可用于备份我的职位的特定数据库设计文章/论文的参考。如果我可以指出一些具体的东西,它将使它变得更容易。
答案 0 :(得分:4)
您的同事实施了一项名为 Polymorphic Associations 的设计。也就是说,“外键”是指两个不同父表之一。大多数人添加另一列parent_type
或类似的东西,以便您可以告诉给定行引用哪个父表。在你的同事的情况下,他反而细分了id的范围。这是一个脆弱的设计,因为您无法在数据库级别强制执行它。如果您要插入部门编号> 100,您无法知道您的文章是否适用于部门或部门。
虽然您已经开发了一个类似于Single Table Inheritance的设计,您可以在一个表中存储多个相关类型,因此确保主键保持唯一,并且文章可以引用任何任何实例相关类型。
这是另一种选择:
考虑面向对象的设计。如果您想允许两个不同的类有文章,您可以为这两个类创建一个公共超类或公共接口。您可以在SQL中执行相同的操作:
ArticleProducer
---------------
ProducerID (PK) int NOT NULL
Department
----------
DepartmentID (PK) int NOT NULL, (FK)->ArticleProducer
DepartmentName varchar(50) NOT NULL
Division
--------
DivisionID (PK) int NOT NULL, (FK)->ArticleProducer
DepartmentID (FK) int NOT NULL
DivisonName varchar(50) NOT NULL
Article
-------
ArticleID (PK) int NOT NULL, (FK)->ArticleProducer
UniqueID int NOT NULL
ArticleName varchar(50) NOT NULL
所以一篇文章必须由一个ArticleProducer
制作。每个部门或部门都是ArticleProducer。
另见Why can you not have a foreign key in a polymorphic association?
有关多态关联的详情,请参阅我的演示文稿Practical Object-Oriented Models in SQL或我的书SQL Antipatterns: Avoiding the Pitfalls of Database Programming。
来自Erwin Smout的评论:
你是对的,试图强制执行所有子类型表中的一行行有点棘手。遗憾的是,MySQL不支持任何存储引擎中的CHECK约束。您可以使用查找表实现类似的功能:
CREATE TABLE ArticleProducerTypes (ProducerType TINYINT UNSIGNED PRIMARY KEY);
INSERT INTO ArticleProducerTypes VALUES (1), (2);
CREATE TABLE ArticleProducer (
ProducerID INT UNSIGNED NOT NULL PRIMARY KEY,
ProducerType TINYINT UNSIGNED NOT NULL,
UNIQUE KEY (ProducerID,ProducerType),
FOREIGN KEY (ProducerType)
REFERENCES ArticleProducerTypes(ProducerType)
) ENGINE=InnoDB;
CREATE TABLE DepartmentProducerType (ProducerType TINYINT UNSIGNED PRIMARY KEY);
INSERT INTO DepartmentProducerType VALUES (1);
CREATE TABLE Department (
DepartmentID INT UNSIGNED NOT NULL PRIMARY KEY,
DepartmentName VARCHAR(50) NOT NULL,
ProducerType TINYINT UNSIGNED NOT NULL,
FOREIGN KEY (DepartmentID, ProducerType)
REFERENCES ArticleProducer(ProducerID, ProducerType),
FOREIGN KEY (ProducerType)
REFERENCES DepartmentProducerType(ProducerType) -- restricted to '1'
) ENGINE=InnODB;
CREATE TABLE DivisionProducerType (ProducerType TINYINT UNSIGNED PRIMARY KEY);
INSERT INTO DivisionProducerType VALUES (2);
CREATE TABLE Division (
DivisionID INT UNSIGNED NOT NULL PRIMARY KEY,
ProducerType TINYINT UNSIGNED NOT NULL,
DepartmentID INT UNSIGNED NOT NULL,
FOREIGN KEY (DivisionID, ProducerType)
REFERENCES ArticleProducer(ProducerID, ProducerType),
FOREIGN KEY (ProducerType)
REFERENCES DivisionProducerType(ProducerType), -- restricted to '2'
FOREIGN KEY (DepartmentID)
REFERENCES Department(DepartmentID)
) ENGINE=InnODB;
CREATE TABLE Article (
ArticleID INT UNSIGNED NOT NULL PRIMARY KEY,
ArticleName VARCHAR(50) NOT NULL,
FOREIGN KEY (ArticleID)
REFERENCES ArticleProducer(ProducerID)
);
现在,ArticleProducer中的每个给定行都可以被Department或Division引用,但不能同时引用它们。
如果我们想要添加一个新的生成器类型,我们在ArticleProducerTypes查找表中添加一行,并为新类型创建一对新表。例如:
INSERT INTO ArticleProducerTypes VALUES (3);
CREATE TABLE PartnerProducerType (ProducerType TINYINT UNSIGNED PRIMARY KEY);
INSERT INTO PartnerProducerType VALUES (3);
CREATE TABLE Partner (
PartnerID INT UNSIGNED NOT NULL PRIMARY KEY,
ProducerType TINYINT UNSIGNED NOT NULL,
FOREIGN KEY (PartnerID, ProducerType)
REFERENCES ArticleProducer(ProducerID, ProducerType),
FOREIGN KEY (ProducerType)
REFERENCES PartnerProducerType(ProducerType) -- restricted to '3'
) ENGINE=InnODB;
但我们仍有可能 都不包含对ArticleProducer中给定行的引用;即,我们不能制定强制在其中一个从属表中创建行的约束。我没有解决方案。
答案 1 :(得分:2)
<强> 1NF 强>
每个行 - 列交集都只包含一个值 适用的域名(没有别的)。
http://en.wikipedia.org/wiki/First_normal_form#1NF_tables_as_representations_of_relations
解决问题的最简单方法是为每个部门引入“默认”部门,这意味着“整个部门”。之后,只需将所有文章链接到各个部门。
也许这样的事情(DepartmentDivisionNo = 0
意味着整个部门):
答案 2 :(得分:1)
我实际上喜欢Damir的回答 - 它重新思考了这个问题,并为这个新问题提供了正确的答案。但是,部门和部门之间存在差异 - 可能每个部门都可以访问属于其部门的文章。拥有属于默认或整个部门部门的文章意味着有两种不同类型的部门。从现在开始,您将进行诸如
之类的查询select * from xxx x inner join division d where d.joinkey = x.joinkey and d.division != 0.
相反,我称我的解决方案是“不要吝啬关系”:
Department
----------
DepartmentID (PK) int NOT NULL
DepartmentName varchar(50) NOT NULL
Division
--------
DivisionID (PK) int NOT NULL
DepartmentID (FK) int NOT NULL
DivisonName varchar(50) NOT NULL
Article
-------
ArticleID (PK) int NOT NULL
ArticleName varchar(50) NOT NULL
ArticleBelongsToDepartment
--------------------------
ArticleID (PK) (FK) int NOT NULL
DepartmentID (FK) int NOT NULL
ArticleBelongsToDivision
--------------------------
ArticleID (PK) (FK) int NOT NULL
DivisionID (FK) int NOT NULL
现在,如何强制执行一些已经提出的约束?为了解决这个问题,您可以创建一个“文章箱”,其中每篇文章都必须属于一个箱子,而且部门和部门都有箱子。
但是这已经进入了杂草,你将无法解决所有案例 - 要么一篇文章依赖于一个部门,一个部门,或一个垃圾箱,或者它不是。部门依赖于垃圾箱或垃圾箱依赖于部门。其中一些问题最好通过事务和存储过程来解决,也许可以通过夜间完整性检查来解决。
答案 3 :(得分:0)
部门和部门应存储在同一个表中。像这样:
DepDiv
----------
ID (PK) int NOT NULL
Name varchar(50) NOT NULL
Type int -- ex.: 1 for department, 2 for division, etc., incase you need to differentiate later
它们是如此相似的元素 - 你应该对它们进行相同的处理。
之后,没有棘手的逻辑:id号码的范围是必要的。无论如何,这种方法太不可扩展了。
祝你好运。答案 4 :(得分:0)
re:他使用虚构规则(缺少更好的术语)定义了模式,所有DepartmentID都在1到100之间,所有DivisionID都在101到200之间。 < / p>
如果那是他想要做的,他应该使用另一个领域,比如isDepartment yes / no。然后他会为ID,name和isDepartment的部门和部门设置一个表,ID字段将是Article表中的FK。
这将解决重叠的部门和部门ID,但不能解决部门和部门之间的1对多关系。要强制执行该关系,您需要两个表。
您还可以在与Article有FK关系的department和division表中引入AuthorID字段。这可能是一个自动生成的字段。这是一种规范分区表中复合键的方法。