表格设计和类层次结构

3

希望有人能通过示例或建议的阅读来解决这个问题。我想知道在建模表格时,如何最好地设计它们的类层次等效性。以下是一个示例:

abstract class Card{
    private $_name = '';
    private $_text = '';
}

class MtgCard extends Card{
    private $_manaCost = '';
    private $_power = 0;
    private $_toughness = 0;
    private $_loyalty = 0;
}

class PokemonCard extends Card{
    private $_energyType = '';
    private $_hp = 0;
    private $_retreatCost = 0;
}

现在,当建立与这个类层次结构同步的表格模型时,我选择了非常相似的方法:
TABLE Card
  id            INT, AUTO_INCREMENT, PK
  name          VARCHAR(255)
  text          TEXT

TABLE MtgCard
  id            INT, AUTO_INCREMENT, PK
  card_id       INT, FK(card.id)
  manacost      VARCHAR(32)
  power         INT
  toughness     INT
  loyalty       INT

TABLE PokemonCard
  id            INT, AUTO_INCREMENT, PK
  card_id       INT, FK(card.id)
  hp            INT
  energytype    ENUM(...)
  retreatcost   INT

我遇到的问题是如何将每个Card记录与包含其详细信息的相应表中的记录关联起来。具体来说,我应该查找哪个表格。
我应该向Card添加一个VARCHAR列以保存相关表的名称吗?这是我和同事们想到的唯一解决方案,但它似乎太“肮脏”了。保持设计可扩展性很重要,允许轻松添加新的子类。
如果有人能提供一个示例或资源,展示一种干净的方式来反映类/表层次结构,那就太好了。

你看过(N)Hibernate是做什么的吗?这可能会给你一些不同可能方法的想法。 - Philipp
@Philipp:不,但我在这个话题的其他帖子中看到过提到。我一定会调查。 - Dan Lugg
2个回答

7
请Google“泛化特化关系建模”。您会找到几篇关于如何使用关系表来建模gen-spec模式的优秀文章。这个问题在SO上已经被问了很多次,只是细节稍有不同。
最好的文章将确认您决定为通用数据使用一个表,而为专用数据使用单独的表。最大的区别在于他们推荐如何使用主键和外键。基本上,他们建议专业化表具有执行双重任务的单个列。它作为专业化表的主键,但也是复制广义表的PK的外键。
这有点复杂,但在加入时间时非常有用。
还要记住,在层次结构中添加新类时需要DDL。

您能否提供一个示例或参考,以说明您所描述的专用表中的主键? - orangepips
@orange,好评。我得看一下这些文章,看看它们是否有一个很好的插图来阐明这个观点。 - Walter Mitty
谢谢Walter Mitty;有一些很好的资源可以帮助克服对象关系不匹配问题,包括另一个SO线程(https://dev59.com/sXI_5IYBdhLWcg3wAeHl) - Dan Lugg
感谢提供Fowler的网站链接。这个真是好东西。 - Walter Mitty

3
基本上不要这样做。
忘掉类层次结构、存储模型以及与您的应用程序和特定应用程序语言相关的任何内容。除非您想将RDb仅用作文件的存储位置,一个从属的从属。
如果您想要关系数据库的强大和灵活性(特别是可扩展性),那么您需要独立于任何应用程序对其进行建模,并使用RDb原则,而不是应用程序语言要求。暂时放下您的应用程序上下文,将数据库设计为数据库。了解它们。规范化(消除所有重复)。学习结构和规则,并实施它们。当您这样做时,您的查询和“映射”将变得轻松自如。不会有“阻抗”。使用正确的数据类型,就不会出现不匹配。
您需要的结构是普通子类型-超类型。这些是关系数据库术语,在RM中已经存在了30多年,在关系数据库产品中已经存在了23年以上。没有必要给它们起奇怪的新名称。维基百科不是学术参考。
鉴于您的表格,这是一个很好的起点(您已经自动规范化),您需要:
  • 将Card.Id重命名为Card.CardId。

  • 删除子类型的ids,它们是100%多余的;CardId既是主键又是外键。

  • 添加一个鉴别器Card.CardType CHAR(1)或TINYINT。当不知道CardType时,这将确定要加入哪个子类型。

  • 看起来您并没有完全理解外键的概念,所以最好先了解一下。它在这里以简单、普通的形式实现:

    ALTER TABLE MtgCard
        ADD CONSTRAINT Card_MtgCard_fk
        FOREIGN KEY (CardId)
        REFERENCES Card(CardId)


  • Card和MtgCard或PokemonCard之间的关系始终是1:1。只有当存在具有相同CardId的Card加{MtgCard | PokemonCard}时,超类型才是完整的。在您的情况下,只能有一个子类型,可以通过简单的CHECK约束轻松执行。

    • 其他情况中,允许存在多个子类型。

    • 那里的子类型是“人是老师”或“人是学生”

  • 在关系型数据库中,不存在“从”或“到”(或上/下或左/右)连接的概念,这些概念仅用于帮助我们人类;您可以从任何表/键开始,并转到所需的任何表。只有在缺乏关系标识符(即使用附加代理,ID列作为PK而不是有意义的自然键的情况下),才需要中间表。

    • 在示例中,使用您的术语,可以直接从Enrollment转到Person(例如,获取LastName)或Course(获取Name),而无需访问中间表;关系线是实线。
      .
  • 现在,类层次结构(“Is”或“Is a”)和其他任何内容都很简单和轻松。

快速参考 标准关系数据库图。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接