5

假设我有以下表格:SQL:数据库的标准化,同时保持约束

 ____________________    ____________________ 
    |  Organisms  |   |  Species  | 
    |--------------------|   |--------------------| 
    |OrganismId (int, PK)|   |SpeciesId (int, PK) | 
    |SpeciesId (int, FK) |∞---------1|Name (varchar)  | 
    |Name (varchar)  |   |____________________| 
    |____________________|      1 
       1         | 
       |         | 
       |         | 
       ∞         ∞ 
    ______________________  ____________________   _______________ 
    | OrganismPropsValues |  | SpeciesProps  |  |  Props  | 
    |----------------------|  |--------------------|  |---------------| 
    |OrganismId (int, FK) |  |PropId (int,PK,FK) | ∞-----1|PropId (int,PK)| 
    |PropId (int, FK)  |  |SpeciesId(int,PK,FK)|  |Name (varchar) | 
    |Value (varchar)  |  |____________________|  |_______________| 
    |______________________|            1 
       ∞               | 
       |               | 
       ----------------------------------------------------------- 

的什么,我想在这里表示一个快速的解释:假设我们有品种,如猫的清单,狗,人类等等。我们也有一套属性(缩写为Props,所以我可以更容易地将它放在图中),它们适用于一些但不一定是所有的物种 - 例如,这可能是尾巴长度(对于尾巴物种),眼睛颜色(眼睛)等。

SpeciesProps是一个链接器表,它定义了哪些属性适用于哪些物种 - 所以在这里,我们将ld具有{人类,眼睛颜色},{狗,眼睛颜色},{猫,眼睛颜色},{狗,尾巴长度},{猫,尾巴长度}。我们没有{Human,Tail Length},因为尾巴长度显然不适用于人类。

生物体表拥有物种的实际“实施” - 所以在这里我们可能有{Human,Bob},{Dog,Rufus}和{Cat,Felix}。

这里是我的问题:在OrganismPropsValues表中,我想存储每个生物体属性的'值' - 例如,我想存储Bob {眼睛颜色,蓝色}。对于Rufus,我想存储{Rufus,Eye Color,Brown}和{Rufus,Tail Length,20}(与Felix类似)。然而,我的问题是,在我详细说明的模式中,尽管SpeciesProps中不存在{Human,Tail Length}元组,但存储{Bob,Tail Length,10}是完全可能的。我怎样才能修改这个模式,这样我就可以执行在OrganismPropsValues中的SpeciesProps中定义的约束,同时保持适当的规范化?

+0

取决于数据库(例如Oracle),我会为INSERT/UPDATE/DELETE创建一些存储过程并在其中实现任何复杂约束... – Yahia

+0

@Yahia感谢您的建议,但是如果有方法不用介绍程序,触发器等来做到这一点。我宁愿这样做。这是MS-SQL(2008)。 – Andrew

+0

这让我头痛。这对于查询来说是很可怕的(想想它会花费多少加入才能获得关于人类的所有数据!),这是一个糟糕的设计,我不知道从哪里开始。数据库不是对象,不应该像对象一样设计。 EAV表是一个极其糟糕的解决方案。雇用一名真正的数据库设 – HLGEM

回答

4

你实现Entity-Attribute-Value反模式。这不能是一个规范化的数据库设计,因为它不是关系型的。

我的建议反而是Class Table Inheritance设计模式:

  • 创建生物一个表,包含适用于所有种类的属性。
  • 为每个物种创建一个表,其中包含特定于该物种的特性。这些表中的每一个与生物体都有一对一的关系,但每个属性都属于它自己的列。

    ____________________    ____________________ 
    |  Organisms  |   |  Species  | 
    |--------------------|   |--------------------| 
    |OrganismId (int, PK)|   |SpeciesId (int, PK) | 
    |SpeciesId (int, FK) |∞---------1|Name (varchar)  | 
    |Name (varchar)  |   |____________________| 
    |____________________| 
          1 
          | 
          | 
          1 
    ______________________ 
    | HumanOrganism  | 
    |----------------------| 
    |OrganismId (int, FK) | 
    |Sex  (enum)  | 
    |Race  (int, FK) | 
    |EyeColor (int, FK) | 
    |....     | 
    |______________________| 
    

这是否意味着你会创造出很多的表,但认为这是一起在一个关系性正确的方式存储性能的诸多实际利益的权衡:

  • 您可以使用SQL数据类型适当,而不是将所有内容都视为自由格式的varchar。
  • 您可以使用约束或查找表通过预定义的一组值限制某些属性。
  • 您可以使属性成为必需的(即NOT NULL)或使用其他约束。
  • 更高效地存储数据和索引。
  • 查询对您来说更容易编写,并且更易于执行RDBMS。

有关这方面的设计,见Martin Fowler的书Patterns of Enterprise Application Architecture,或者我的介绍Practical Object-Oriented Models in SQL,或者我的书,SQL Antipatterns: Avoiding the Pitfalls of Database Programming

+0

感谢您的替代建议...我会研究这种模式。 – Andrew

2

嗯...
这里是做到这一点的一种方法:
添加SpeciesPropsId到SpeciesProps表。
用OrganismPropsValues表中的SpecPropsId替换PropId。
您需要稍微更改约束。
需要将SpeciesProps添加到OrganismPropsValues约束。
需要将OrganismPropsValues移除到道具约束。

从技术上讲,您不必从OrganismPropsValues中删除PropId,但如果您保留它,则会使数据冗余。

1

实现这些约束的另一种方法是通过删除OrganismId并添加No来更改Organism表的PK。然后使PK化合物(SpeciesId, No)。所以,"Bob"(Human, 1)"Rufus"(Dog, 1)

然后,添加在OrganismPropsValues表中,SpeciesIdNo(去除OrganismId)。

这将允许在FK从OrganismPropsValues改变到Props参考SpeciesProps代替:

 ____________________    ____________________ 
    |  Organisms  |   |  Species  | 
    |--------------------|   |--------------------| 
    |SpeciesId (int, FK) |   |SpeciesId (int, PK) | 
    |No (int)   |∞---------1|Name (varchar)  | 
    |Name (varchar)  |   |____________________| 
    |PK (SpeciedId,No) |      1 
    |____________________|      | 
       1         | 
       |         | 
       |         | 
       ∞         ∞ 
    ______________________  ____________________   _______________ 
    | OrganismPropsValues |  | SpeciesProps  |  |  Props  | 
    |----------------------|  |--------------------|  |---------------| 
    |SpeciesId (int, PK) |  |PropId (int,PK,FK) | ∞-----1|PropId (int,PK)| 
    |No (int, PK)   |  |SpeciesId(int,PK,FK)|  |Name (varchar) | 
    |PropId (int, PK)  |  |____________________|  |_______________| 
    |Value (varchar)  |     1 
    |FK (SpeciesId,No)  |     | 
    |FK (SpeciesId,PropId) |     | 
    |______________________|     | 
       ∞        | 
       |        | 
       ------------------------------- 
+0

@HLGEM引用的内容与“EAV”问题是“OrganismPropsValues.Value”字段。因为它可以存储不同类型的数据,所以没有简单的方法在该字段上进行完整性检查。例如,你避免用这个db结构存储'{Bob,Tail Length,10}',但是你不能避免使用'{Rufus,Tail Length,Blue}'或'{Bob,Eye Color,20}'。 –

+0

谢谢 - 我之前听说过EAV的基础知识,但从未在实践中使用过。这实际上是我正在研究的一个侧面项目 - 不像我将要投入生产软件 - 但我试图在这里建模的数据似乎要求它。当然,欢迎任何替代方案的建议 - 我不打算使用它。 – Andrew

2

每当你有这样一个菱形的依赖关系,考虑把更多的重点放在composite PRIMARY KEYS上。

具体来说,确定生物不仅仅是OrganismId,但通过SpeciesIdOrganismSubId组合(你仍然可以有OrganismId,但保持它作为一个备用键 - 在这里没有显示简洁)。

一旦你这样做,你的模型可以做出这样的:

ER Model

这里要注意的关键一点是,SpeciesId是“传播”这个菱形的下降两边形状图。这就是为什么不能为给定物种“未声明”的属性“赋值”所需的限制。

顺便说一句,命名您的表时使用单数。另外,考虑使用自然主键(例如SpeciesName而不是SpeciesId作为PK) - 如果做得对,它可以显着提高JOIN的速度(特别是与群集结合时)。