【问题标题】:Database design for Tagging multiple types of entities用于标记多种类型实体的数据库设计
【发布时间】:2010-09-23 01:28:21
【问题描述】:

我目前正在设计一个用于存储食谱的数据库架构。在这个数据库中,我希望能够标记不同类型的实体(配料、配方发布者、配方等)。所以一个标签有多个 n:m 关系。如果我使用“三表设计”,这将导致我拥有的每种实体类型(配方、成分、发行人)的表(交叉表)。换句话说,每次我引入一个实体时,我都必须为其添加一个交叉表。

我正在考虑创建一个具有唯一 id 的表,所有实体都引用该表,以及标签表和“唯一 id”表之间的 n:m 关系。这样,“唯一标识”表和标签表之间就只有一个交叉表。

以防有些人认为这个问题已经被问过了。我已经读过Database Design for Tagging。并提到了三表设计。

【问题讨论】:

    标签: database-design tagging


    【解决方案1】:

    我认为你在正确的轨道上。你描述得很好,你有几个不同的实体。您可以创建一个名为实体的表,其中包含所有常见属性(如果有的话)。比如

    实体

    • 实体 ID
    • 姓名

    成分

    • 实体 ID
    • 金额

    配方发布者

    • 实体 ID
    • 一些其他信息

    现在您可以使用表格来标记实体。

    【讨论】:

    • 这正是我现在的想法。我唯一担心的是实体表可能会导致潜在的死锁。但我认为我可以通过不在实体表中存储关键数据来避免这种情况。
    【解决方案2】:

    这个怎么样?

    类型(PK:Type,set_id[,TypeDesc])

    属性(PK:(set_id,FK:Type),Value)

    PS:粗体/斜体真的很烂

    【讨论】:

    • 如果我理解这一点,这将是实现类型及其属性的通用方式。这意味着我需要在此之上添加一个额外的层,以方便在代码中使用。
    • 这是一种叫做Entity-Attribute-Value的设计。它有很多问题,对于萨博只有少数不同实体的情况来说,这完全是矫枉过正。
    【解决方案3】:

    如常为食谱、配料等制作表格。

    那么你的标签表应该是这样的:Id, Type, Tag

    我建议在代码中使用枚举来区分不同的“类型”(实体)。

    【讨论】:

    • 我不喜欢这一点,因为有些东西在数据库中,有些在代码中。我想将数据库用于它擅长的事情:存储关系数据。
    • @Kyle:这种设计被称为“多态关联”。这是一个损坏的设计,因为它混淆了数据和元数据。例如。如何确保 Type 存储有效的表名?
    • 即使您的代码有一个枚举,也没有 database 约束来确保该列中命名的表确实存在。
    • 此外,多态关联会阻止您声明实际的外键。也就是说,tag.id 引用了多个实体表中的任何一个,但外键必须指定它引用的 one 父表。因此,您必须在应用程序代码中强制执行完整性。
    【解决方案4】:

    我认为为所有标记分配使用一个表没有任何问题(与多个表相反 - 每个可标记实体一个表)。

    然而,你设计中的一个重要细节对我来说仍然是模棱两可的:如果你打算在这些方面有所作为

    - - - - - - - - - -
    Tag
        ID           // PK
        Name
        ...
    
    - - - - - - - - - -
    Taggable
        ID           // PK
        ...
    
    - - - - - - - - - -
    TagAssignment
        Tag_ID       // FK -> Tag.ID
        Taggable_ID  // FK -> Taggable.ID
        ...
    
    - - - - - - - - - -
    EntityOne
        Taggable_ID  // FK -> Taggable.ID
        ...
    
    - - - - - - - - - -
    EntityTwo
        Taggable_ID  // FK -> Taggable.ID
        ...
    

    那么您的实体类是否将拥有自己的主键,或者您打算使用EntityOne.TaggableIDEntityTwo.TaggableID 作为EntityOneEntityTwo 的事实上的主键?

    在大多数情况下,我会谨慎并让实体拥有自己的 ID:

    - - - - - - - - - -
    EntityOne
        ID           // PK
        Taggable_ID  // FK -> Taggable.ID (Nullable)
        ...
    
    - - - - - - - - - -
    EntityTwo
        ID           // PK
        Taggable_ID  // FK -> Taggable.ID (Nullable)
        ...
    

    这不会要求每个实体都有一个对应的Taggable 实例,因此这不会要求与实体相关的每一段代码也都知道标签。但是,如果标记将在系统中真正无处不在,并且如果您确定不需要任何其他实体的“共同祖先”(即,除了Taggable),那么您可能无需实体的“内在”ID。

    注意:我从来没有尝试过实现这样的东西,所以我所有的建议都是纯理论的。所以如果我没有看到一些明显的缺陷,请不要拍我。 :-)


    回应比尔·卡尔文的评论:

    你是对的:上面描述的设计并没有阻止多个实体引用同一个Taggable。但是:

    1. 就像我说的,一切都取决于需求。如果我们确定Taggable 将成为实体的唯一“共同祖先”,那么可以使用Taggable_ID FKs 作为实体的PK。但是,例如,如果某些碰巧是“可标记”的实体也必须是“可观察的”(想想通知、通知计划等)或“随便什么” :-) 怎么办?我们可以通过将任何实体硬绑定到Taggable 来切断所有这些“能力”吗?

    2. 如果您真的想在 DB 级别强制执行一个可标记一个实体的约束... AFAIK,至少有一种常见的方法可以做到这一点,而无需将 FK 用作 PK:通过引入“类型”的可标记(无论如何这可能对其他一些功能有用)。

    类似这样的东西可以让我们吃蛋糕:

    - - - - - - - - - -
    Taggable
        ID           // PK
        Type        
        ... 
        - - - - - - - -
        Constraint: (ID, Type) is unique
    
    
    - - - - - - - - - -
    EntityOne
        ID
        Taggable_ID   
        Taggable_Type // Constraint: always = 'EntityOne'
        ...
        - - - - - - - -
        FK: (Taggable_ID, Taggable_Type) -> (Taggable.ID, Taggable.Type)
    

    当然,所有这些都比将实体绑定到可标记对象要复杂得多。但我只是想讨论一下,在我的拙见中,除了原始问题提供的狭隘图片之外,还应该考虑什么。

    【讨论】:

    • 让每个实体的 Taggable_ID 列既是 FOREIGN KEY 又是 NOT NULL PRIMARY KEY 要简单得多。这样,您可以确保所有内容都映射到 Taggables 表中的一行。您的设计允许多个食谱映射到同一个可标记行。
    • 你是对的。我已经详细说明了我的回复以解决您的评论。 :-)
    • @Bill:看起来我对你的关注的回应也是你所谓的“多态关联”的变体,对吧?
    • 不,多态关联是两个不同父表的外键。给定行使用哪个父级取决于子表中的另一个属性。当然,你不能对多态关联使用传统的外键约束。
    • 关于您的解决方案,我明白了,但您仍然没有说明为什么不能简单地放弃 ID 列,而是使用 Taggable_ID 作为 PK。并非每个表都需要名为“ID”的列。
    【解决方案5】:

    我会说这取决于您要如何使用标签。

    我想你可以为你想要标记的每个实体类型创建一个额外的交叉表,如果你一次只搜索一种类型的实体。换句话说,说“给我看带有'yummy'标签的成分”是很正常的,但不清楚这意味着“给我看带有'yummy'标签的成分和食谱发布者。”在这个在这种情况下,每个实体有一个单独的交集表就可以了。

    但是,如果您确实需要使用给定标签搜索所有类型的所有实体,那么使用单个“ID”表会更容易。使用您定义为主键和外键的列使所有实体表指向它:

    CREATE TABLE Recipes (
      recipe_id INT NOT NULL PRIMARY KEY, -- not auto-generated
      FOREIGN KEY (recipe_id) REFERENCES Taggables(id)
    );
    

    这个计划的唯一弱点是你不能阻止RecipesIngredients 中的一行指向Taggables 中的同一行。

    INSERT INTO Taggables (id) VALUES (327);
    INSERT INTO Recipes (recipe_id, name) VALUES (327, 'Hollandaise sauce');
    INSERT INTO Ingredients (ingr_id, name) VALUES (327, 'eggs');
    

    您是否希望与鸡蛋相关的每个标签也适用于荷兰酱?

    我只是指出单表设计的这一方面。考虑到其他要求,它可能仍然是为您的标记建模的最佳方式。但是您应该注意从属表中 id 冲突的可能性。

    【讨论】:

    • 嗯...我认为两个对象(例如两个食谱;或一个食谱和一个成分)可以共享一个标签,但不能是“可标记”的同一个实例。你认为让实体共享“可标记”(不是标签)有什么实际意义吗?
    • 对,这就是我的意思;我希望多个实体在可标记中引用同一行是罕见的或根本没有意义。理想情况下,DB 约束可以防止无效条目,但在这种设计中它们不能。
    【解决方案6】:

    我手上也有类似的“问题”。我正在开发一个小型产品数据库,其中涉及标签并为标签赋予值(例如标签名称:颜色,值:绿色)。

    两个主要表格是项目 (I) 和文章 (A)。项目是实际的物理项目,并且文章是从项目派生的。文章是可以在网站上展示的东西,物品是要存放在仓库中的东西。 这种关系的一个小例子可能是汽车零件。具有已知尺寸和其他数据的散热器实际上可以适合许多不同的型号和制造,这就是为什么用于表示散热器的项目涉及到多个表明散热器可以适合的物品的原因。 另一方面,我们可能为一种型号提供两种不同的散热器,一种是全新的版本,另一种是刚刚再制造的。在这种情况下,有两个项目与同一篇文章有​​关。

    所以,我和 A 有 N:M 关系。

    物品和物品具有某些属性。例如,散热器项目可能具有条件、材料、重量、高度、宽度和厚度等数据。 这篇文章还包含一些基本信息,如品牌、型号、年份、发动机等,但可能还需要一些特殊数据,如底盘型号、变速箱类型,或其他一些信息,如同一型号上使用的两种不同的配件类型。 因为两个项目可以链接到一篇文章,这意味着我不能只标记文章。用两个条件值标记一篇文章是愚蠢的,另一方面,用一个模型的多个实例、品牌或一些特殊要求标记一个项目也不是一个好主意。 _有两种类型的属性,第一种表示某物是什么样的,第二种表示它适合什么。

    标签不必有值,它们可以简单地充当分配给实体的常规标签。

    散热器只是简单产品的一个例子。我们不妨将一些计算机零件或服装放入我们的数据库中。这意味着我需要能够在两个不同的实体 I 和 A 上放置不同的“标签”。

    我需要能够在网上商店中搜索文章。假设我正在使用基于树的导航,其中我有一个名为“二手日产散热器”的类别。 搜索将涉及搜索文章和项目,文章具有标签 Model:Nissan,并且项目具有标签 Condition:Used。 当然,当用户查看文章时,他确实会看到与文章相关的所有项目。

    我正在考虑的解决方案之一是三角形数据库设计,其中有一个通用表,称为所有属性和标签的标签。

    我们有表格项目 (I)、文章 (A) 和标签 (T) 它们加入了 N:M 关系: I2A 将项目连接到文章。 T2I 将标签连接到项目,并且也可能存储标签或属性的值。 T2A 将标签连接到文章中,并且还可能为标签存储一个值。

    在纸面上,这个解决这个问题的 6 表设计看起来相当不错,但我在形成一个体面的查询时感到头疼,我可以在其中选择与一组不同标签及其值匹配的文章,例如: 条件=再制造,制造=日产

    我希望能够做到的,类似于 www.summitracing.com。从“商店”下方的左侧选择部门,选择任何类别,您将看到他们如何设法为项目提供一些属性。它们具有适用于大多数应用的引擎尺寸,但在寻找轮辋时,它们还具有宽度属性。

    任何对此的反馈将不胜感激,我开始尝试设计这个。

    【讨论】:

      猜你喜欢
      • 2015-12-28
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-05-02
      • 2011-09-15
      • 1970-01-01
      • 2010-11-18
      • 1970-01-01
      相关资源
      最近更新 更多