将分隔列表存储在数据库列中真的那么糟糕吗?

Is storing a delimited list in a database column really that bad?

设想一个带有一组复选框的Web窗体(可以选择其中的任何一个或全部)。我选择将它们保存在数据库表的一列中以逗号分隔的值列表中。

现在,我知道正确的解决方案是创建第二个表并正确规范化数据库。实现这个简单的解决方案更快,我希望能够快速地证明该应用程序的概念,而不必在上面花费太多时间。

我认为节省的时间和更简单的代码在我的情况下是值得的,这是一个合理的设计选择,还是应该从一开始就规范化它?

还有一些上下文,这是一个小型的内部应用程序,基本上替换了存储在共享文件夹中的Excel文件。我也在问,因为我正在考虑清理程序,使其更易于维护。有一些事情我并不完全满意,其中之一就是这个问题的主题。


除了由于存储在单列中的重复值组而违反第一个正常形式之外,逗号分隔列表还有许多其他更实际的问题:

  • 无法确保每个值都是正确的数据类型:无法阻止1、2、3、Banana、5
  • 无法使用外键约束将值链接到查阅表;无法强制引用完整性。
  • 无法强制唯一性:无法阻止1、2、3、3、3、5
  • 如果不提取整个列表,则无法从列表中删除值。
  • 存储列表的时间不能超过字符串列中的长度。
  • 很难在列表中搜索具有给定值的所有实体;必须使用效率低下的表扫描。可能必须使用正则表达式,例如在MySQL中:idlist REGEXP '[[:<:]]2[[:>:]]'*
  • 很难计算列表中的元素,或者执行其他聚合查询。
  • 很难将这些值联接到它们引用的查阅表格中。
  • 很难按排序顺序提取列表。

为了解决这些问题,您必须编写大量的应用程序代码,重新设计RDBMS已经提供了更有效的功能。

逗号分隔的列表是错误的,所以我把它作为书中的第一章:SQL反模式:避免数据库编程的陷阱。

有时需要使用非规范化,但正如@omg ponies所提到的,这些都是例外情况。任何非关系"优化"都有利于一种类型的查询,而不会牺牲数据的其他使用,因此请确保您知道哪些查询需要特别处理,以便它们值得非规范化。

*MySQL8.0不再支持这个单词边界表达式语法。


关于这样的问题有很多:

  • 如何从逗号分隔的列表中获取特定值的计数
  • 如何从逗号分隔的列表中获取只有相同2/3/etc特定值的记录

逗号分隔列表的另一个问题是确保值一致-存储文本意味着可能出现拼写错误…

这些都是非规范化数据的症状,并强调了为什么应该始终为规范化数据建模。非规范化可以是一种查询优化,在实际需要出现时应用。


"一个原因是懒惰"。

这会敲响警钟。你应该这样做的唯一原因是你知道如何"正确地"去做,但是你得出的结论是,有一个切实的理由不这样做。

已经说过了:如果您选择以这种方式存储的数据是您永远不需要查询的数据,那么可能存在以您选择的方式存储它的情况。

(有些用户会对我上一段中的声明提出异议,说"你永远不知道将来会增加什么要求"。这些用户要么是被误导,要么是说宗教信仰。有时有利于满足您之前的要求。)


一般来说,如果满足项目的要求,任何事情都是可以防御的。这并不意味着人们会同意或想捍卫你的决定…

一般来说,以这种方式存储数据是次优的(例如,更难进行有效的查询),如果修改表单中的项目,可能会导致维护问题。也许您可以找到一个中间位置,用一个表示一组位标志的整数来代替?


是的,我想说那真的很糟糕。这是一个合理的选择,但这并不能使它正确或良好。

它打破了第一个正常形态。

第二个批评是,将原始输入结果直接放入数据库中,而不进行任何验证或绑定,会使您面临SQL注入攻击。

你所说的懒惰和缺乏SQL知识是新手所用的东西。我建议你花点时间好好做,把它当作一个学习的机会。

或者保持原样,学习SQL注入攻击的痛苦教训。


我需要一个多值列,它可以实现为XML字段

它可以根据需要转换为逗号分隔

使用XQuery查询SQL Server中的XML列表。

作为一个XML字段,可以解决一些问题。

使用csv:无法确保每个值都是正确的数据类型:无法阻止1、2、3、banana、5

WITH XML:标记中的值可以强制为正确的类型

使用csv:无法使用外键约束将值链接到查找表;无法强制引用完整性。

使用XML:仍然是一个问题

使用csv:无法强制唯一性:无法阻止1、2、3、3、3、5

使用XML:仍然是一个问题

使用csv:如果不提取整个列表,则无法从列表中删除值。

使用XML:可以删除单个项

使用csv:很难搜索列表中具有给定值的所有实体;必须使用效率低下的表扫描。

使用xml:xml字段可以被索引

使用csv:很难计算列表中的元素,或者执行其他聚合查询。**

使用XML:不是特别困难

使用csv:很难将值连接到它们引用的查找表中。**

使用XML:不是特别困难

使用csv:很难按排序顺序获取列表。

使用XML:不是特别困难

使用csv:将整数存储为字符串所占用的空间大约是存储二进制整数的两倍。

使用XML:存储比CSV更糟糕

与csv:加上许多逗号字符。

WITH XML:使用标记而不是逗号

简而言之,使用XML可以解决带分隔列表的一些问题,并且可以根据需要转换为带分隔列表。


是的,真是太糟了。我的观点是,如果您不喜欢使用关系数据库,那么请寻找一个更适合您的替代方案,那里有许多有趣的"nosql"项目,其中包含一些真正高级的特性。


我在SQL Server的ntext列中使用键/值对选项卡分隔列表已经有4年多了,它可以工作了。您确实会失去查询的灵活性,但另一方面,如果您有一个持久化/去持久化键值对的库,那么这不是一个坏主意。


如果有固定数量的布尔字段,则可以对每个字段使用INT(1) NOT NULL(如果存在,则使用BIT NOT NULL)或CHAR (0)(可以为空)。您还可以使用SET(我忘记了确切的语法)。


我可能会采取中间立场:将csv中的每个字段都变成数据库中的单独列,但不太担心规范化(至少目前如此)。在某种程度上,规范化可能会变得有趣,但是当所有的数据都被推到一个单独的列中时,使用数据库几乎没有任何好处。在有意义地操作数据之前,您需要将数据分为逻辑字段/列/您想调用它们的任何对象。