【问题标题】:Many-to-many relationship: use associative table or delimited values in a column?多对多关系:在列中使用关联表或分隔值?
【发布时间】:2010-10-21 16:14:50
【问题描述】:

2009.04.24 更新

我的问题的重点不是开发人员的困惑以及如何解决它。

关键是要了解分隔值何时是正确的解决方案。

我见过商业产品数据库中使用的分隔数据(Ektron 哈哈)。

SQL Server 甚至具有 XML 数据类型,因此可以用于与分隔字段相同的目的。

/结束更新

我正在设计的应用程序有一些多对多的关系。过去,我经常使用关联表在数据库中表示这些。这给开发者带来了一些困惑。

这是一个示例数据库结构:

Document
---------------
ID (PK)
Title
CategoryIDs (varchar(4000))


Category
------------
ID (PK)
Title

Document 和 Category 之间存在多对多的关系。

在此实现中,Document.CategoryIDs 是一个大的以竖线分隔的 CategoryID 列表。

对我来说,这很糟糕,因为它需要在查询中使用子字符串匹配——这不能使用索引。我认为这会很慢并且不会扩展。

使用该模型,要获取某个类别的所有文档,您需要以下内容:

select * from documents where categoryids like '%|' + @targetCategoryId + '|%'

我的解决方案是创建一个关联表,如下所示:

Document_Category
-------------------------------
DocumentID (PK)
CategoryID (PK)

这让开发人员感到困惑。我是否缺少一些优雅的替代解决方案?

我假设 Document 中会有数千行。类别可能像 40 行左右。主要关注的是查询性能。我是否过度设计了这个?

是否存在优先将 ID 列表存储在数据库列中而不是将数据推送到关联表的情况?

还请考虑我们可能需要在文档之间创建多对多关系。这将建议一个关联表 Document_Document。这是首选设计还是将关联的文档 ID 存储在单个列中更好?

谢谢。

【问题讨论】:

  • 我的公司广泛使用 CSV 列。这绝对是一场噩梦。太糟糕了,他们甚至已经意识到并停止这样做,但我们仍然必须支持遗留:(
  • 我同意您的方法,但如果您不使用“ID”作为列名,它可能会有所帮助。 “DocumentID”和“CategoryID”更好,然后FK可以有相同的列名。
  • 如果您从不需要在任何 SQL 语句中剖析列表,并且从不需要查询它们,那么分隔值可能是可以接受的。即便如此,使用关联表设计通常会更好。或者,就像我的回答一样,使用本地处理列表的 DBMS。

标签: sql database database-design architecture many-to-many


【解决方案1】:

这让开发人员感到困惑。

获得更好的开发人员。这是正确的方法。

【讨论】:

  • 同意 100%。一个连接器表是要走的路。
  • 真的,摆脱他们和他们臭名昭著的 CSV 数据
  • 如果您的开发人员正在处理数据库系统并鼓励您(直接或间接)违反该死的 FIRST 范式,那么是的,他们应该在路边。
  • 让您的开发人员了解关联的工作原理。画图。编写示例代码。进行殴打,直到他们不再表现得糊涂为止。
【解决方案2】:

使用逗号分隔的 ID 几乎总是一个大错误。
RDBMS 旨在存储关系。

【讨论】:

  • 确实如此。使用 SET 类型代替多个、小的值集。
  • @DanMan 看起来 SET 类型不是一个广泛支持的约定
【解决方案3】:

您正在执行的多对多映射很好且已标准化。如果需要,它还允许稍后添加其他数据。例如,假设您想添加类别被添加到文档中的时间。

我建议在 document_category 表上也有一个代理主键。如果这样做有意义的话,还有一个 Unique(documentid, categoryid) 约束。

为什么开发人员会感到困惑?

【讨论】:

    【解决方案4】:

    我的解决方案是创建一个 关联表如下: 这是 让开发人员感到困惑

    真的吗?这是数据库 101,如果这让他们感到困惑,那么他们可能需要远离他们的向导生成的代码并学习一些基本的数据库规范化。

    你提出的是正确的解决方案!!

    【讨论】:

      【解决方案5】:

      您设计中的 Document_Category 表无疑是解决问题的正确方法。如果可能的话,我建议您对开发人员进行教育,而不是提出次优的解决方案(并且会影响性​​能,并且没有参照完整性)。

      您的其他选项可能取决于您使用的数据库。例如,在 SQL Server 中,您可以拥有一个 XML 列,该列允许您将数组存储在预定义的架构中,然后根据该字段的内容进行连接。其他数据库系统可能有类似的东西。

      【讨论】:

      • +1 强烈建议标准化,同时提供比 OP 开发人员建议的更好的替代方案。
      • 虽然规范化是我的偏好,但我肯定会研究此 XML 列的使用。针对此列的查询看起来如何?
      • eggheadcafe.com/community/aspnet/13/80066/… 有关于这样做的讨论但是,social.msdn.microsoft.com/Forums/en-US/sqlxml/thread/… 的讨论可能会提供更好的选择。简而言之,如果您的 XML 列是模式类型的(而不是无类型的),您可以使用 SELECT t1.* FROM t1 JOIN t2 on t2.x.exist ('/foo/bar[.=sql:column( "t1.i")]') = 1 这应该在 SQL Server 中使用本机 XML 索引。
      • 建议 XML 是一个可怕的想法。它仍然违反了封面下的第一范式。仅仅因为数据库引擎有更健壮的语法来处理 XML 并不能改变它固有的缺陷。 XML 列是有用的工具,但不是为了这个目的。
      • 在这种情况下,XML 是一个非常 遥远的第二选择,仅比分隔字符串稍微好一点。每次违反 1NF 都会咬你一口。我无法告诉你我在继承大量使用 multi 分隔字符串(基本上是对象的本土纯文本序列化)的数据库后所承受的痛苦。我的大部分大脑周期都在支付这笔技术债务的利息,而不是真正完成工作。
      【解决方案6】:

      您的建议是优雅、强大、最佳实践的解决方案。

      由于我认为其他答案不够有力地表达了以下内容,因此我将这样做。

      如果您的开发人员 1) 无法理解如何在关系数据库中建模多对多关系,并且 2) 强烈坚持将您的 CategoryID 存储为分隔字符数据,

      那么他们应该立即失去所有数据库设计权限。至少,他们需要一个真正有经验的专业人士加入他们的团队,有权阻止他们做这种不明智的事情,并且可以为他们提供他们完全缺乏的数据库设计培训。

      最后,您不应该再次将他们称为“数据库开发人员”,直到他们适当地跟上速度,因为这对我们这些真正有能力的开发人员和设计师来说是轻描淡写。

      希望这个回答对你很有帮助。

      更新

      我的问题的重点不是开发人员的困惑以及如何解决它。

      关键是要了解分隔值何时是正确的解决方案。

      除极少数情况外,分隔值是错误的解决方案。当单个值将被查询/插入/删除/更新时,这证明这是错误的决定,因为您必须解析并触摸所有其他值才能使用所需的值。这样做你违反了first (!!!) 正常形式(这句话在你听来应该像是一个令人难以置信的卑鄙脏话)。使用 XML 做同样的事情也是错误的。在列中存储分隔值或多值 XML 可能 当它被视为一个不可分割且不透明的“属性包”时,它不会被数据库查询,但总是被整个发送给另一个消费者(可能是 Web 服务器或 EDI 接收者)。

      这让我回到我最初的评论。认为违反第一范式是个好主意的开发人员在我的书中是非常缺乏经验的开发人员。

      我承认有一些非常复杂的非关系数据存储实现使用文本属性包(例如 Facebook(?) 和其他在数千台服务器上运行的数百万用户站点)。好吧,当你的数据库、用户群和每秒事务量大到足以需要它时,你就有钱来开发它。同时,坚持最佳做法。

      【讨论】:

      • –1 暗示您的“2.)”是由您的“1.)”引起的,然后进一步延伸说他们应该失去数据库设计权限。
      • @ChrisMarisic 这很奇怪。 2怎么不是由1引起的?无论如何,这意味着失去数据库设计特权是暂时的,直到他们接受了足够的培训,不再做这些愚蠢的事情。您是否坚持认为违反第一范式并不愚蠢?
      • 盲从任何教科书都是幼稚的。一切总是取决于,每一个设计选择都是在某个地方的权衡。关联表附带要求事务跨越多个表。多路复用的数据具有不同的查询和修改成本。如果数据没有在数据库内部修改并且没有过滤,那么最好不使用关联表。但是从不查询数据库中的字段是不常见的。
      【解决方案7】:

      “这让开发人员感到困惑”的设计意味着您的开发人员受教育程度较低。这是更好的关系数据库设计 - 如果可能,您应该使用它。

      如果您真的想使用列表结构,请使用能够理解它们的 DBMS。此类数据库的示例是U2 (Unidata, Universe) DBMS,它是(或者很久以前曾经是)基于 Pick DBMS。可能还有其他类似的 DBMS 提供商。

      【讨论】:

      • 自从我写下这个答案后,U2 DBMS 已被 IBM 出售给 Rocket Software (rocketsoftware.com)。
      【解决方案8】:

      这是经典的对象关系映射问题。开发人员可能并不愚蠢,只是缺乏经验或不习惯以正确的方式做事。大喊“3NF!”一遍又一遍不会说服他们正确的方法。

      我建议您请您的开发人员向您解释他们如何使用竖线分隔的方法按类别获取文档计数。这将是一场噩梦,而链接表让它变得非常简单。

      【讨论】:

      • 是的 - 文档计数用例是一个很好的例子来展示管道分隔方法的局限性。
      • 很高兴看到有人没有通过喊叫来解决问题。
      【解决方案9】:

      我的开发人员尝试这种“数据库列中的逗号分隔值”方法的第一个原因是,他们认为添加一个新表来满足对多个值的需求需要太长时间才能添加到数据中模型和数据库。

      他们中的大多数人都知道,由于各种原因,他们的解决方法很糟糕,但他们选择了这种次优方法,因为他们可以。他们可以做到这一点,而且可能永远不会被抓住,或者他们会在项目的后期被抓住,因为修复它的成本太高且风险太大。他们为什么这样做呢?因为它们的性能仅根据速度而不是质量或合规性来衡量。

      也可能是,就像在我的一个项目中一样,开发人员 有一个表格来放置多个值,但他们的印象是在父表格中复制该数据会加快速度表现。他们错了,他们被点名了。

      因此,虽然您确实需要了解如何处理这些代价高昂、风险大且 损害商业信心的技巧,但您也应该尝试找出开发人员认为采取这门课程的原因对项目和公司而言,短期和长期的行动都更好。然后修复感知和数据结构。

      是的,这可能只是懒惰、恶意或无知,但我敢打赌,大多数时候开发人员都会做这些事情,因为他们经常被告知“完成它”。我们在数据模型和数据库设计方面需要确保我们不会发送错误信息,说明我们对满足新实体/表/信息的业务需求的请求的响应速度。

      我们还应该看到,数据人员需要不断监控我们数据架构的“竣工”部分。

      就个人而言,我从不授权在关系数据库中使用逗号分隔值,因为实际上构建新表比构建解析例程来创建、更新和管理列中的多个值要快 处理引入的所有异常,因为有时这些数据也嵌入了逗号。

      最重要的是,不要使用逗号分隔值,而是找出开发人员想要这样做的原因并解决该问题。

      【讨论】:

      • “由于有时数据嵌入了逗号而引入了所有异常”,CSV 格式是一种非常强大的格式。如果您的 CSV 是字符串,则包含逗号的字符串没有任何异常。关于修改数据库中列表的另一点,如果数据是 CSV,那么对于一个充满存储过程的数据库驱动应用程序来说,使用真的不应该。查询是一个更交叉的问题
      猜你喜欢
      • 2011-09-02
      • 2019-04-29
      • 1970-01-01
      • 1970-01-01
      • 2014-05-11
      • 2017-05-25
      • 2014-12-24
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多