想象一个带有一组复选框的 Web 表单(可以选择其中任何一个或全部)。我选择将它们保存在一个逗号分隔的值列表中,该列表存储在数据库表的一列中。
现在,我知道正确的解决方案是创建第二个表并正确规范化数据库。实施简单的解决方案更快,我希望快速获得该应用程序的概念验证,而不必花费太多时间。
我认为在我的情况下节省的时间和更简单的代码是值得的,这是一个合理的设计选择,还是我应该从一开始就对其进行规范化?
更多上下文,这是一个小型内部应用程序,它基本上替换了存储在共享文件夹中的 Excel 文件。我也在问,因为我正在考虑清理程序并使其更易于维护。里面有些东西我并不完全满意,其中之一就是这个问题的主题。
除了由于存储在单个列中的重复值组而违反 First Normal Form 之外,逗号分隔的列表还有许多其他更实际的问题:
不能保证每个值都是正确的数据类型:没办法防止1,2,3,banana,5
不能使用外键约束将值链接到查找表;没有办法强制引用完整性。
不能强制唯一性:没有办法阻止 1,2,3,3,3,5
如果不获取整个列表,则无法从列表中删除值。
不能存储比字符串列中的长度更长的列表。
难以搜索列表中具有给定值的所有实体;您必须使用低效的表扫描。可能不得不求助于正则表达式,例如在 MySQL 中:idlist REGEXP '[[:<:]]2[[:>:]]' 或在 MySQL 8.0 中:idlist REGEXP '\\b2\\b'
难以计算列表中的元素,或进行其他聚合查询。
很难将这些值加入到它们引用的查找表中。
很难按排序顺序获取列表。
很难选择保证不会出现在值中的分隔符
为了解决这些问题,您必须编写大量的应用程序代码,重新发明 RDBMS 已经提供的功能,使其更有效。
逗号分隔的列表是错误的,以至于我把它作为我书中的第一章:SQL Antipatterns, Volume 1: Avoiding the Pitfalls of Database Programming。
有时您需要使用非规范化,但作为 @OMG Ponies mentions,这些是例外情况。任何非关系“优化”都会以牺牲数据的其他用途为代价来使一种类型的查询受益,因此请确保您知道哪些查询需要特别处理,以使其值得去规范化。
“一个原因是懒惰”。
这敲响了警钟。你应该做这样的事情的唯一原因是你知道如何“以正确的方式”去做,但你得出的结论是有一个切实的理由不这样做。
话虽如此:如果您选择以这种方式存储的数据是您永远不需要查询的数据,那么可能会有以您选择的方式存储它的情况。
(有些用户会对我上一段的说法提出异议,说“你永远不知道将来会增加什么要求”。这些用户要么被误导,要么表明了宗教信仰。有时按照你的要求工作是有利的。摆在你面前。)
关于 SO 的问题有很多:
如何从逗号分隔列表中获取特定值的计数
如何从该逗号分隔列表中获取仅具有相同 2/3/etc 特定值的记录
逗号分隔列表的另一个问题是确保值一致 - 存储文本意味着可能出现拼写错误......
这些都是非规范化数据的症状,并强调了为什么您应该始终为规范化数据建模。非规范化可以是一种查询优化,在实际需要时应用。
一般来说,只要满足项目的要求,任何东西都是可以防御的。这并不意味着人们会同意或想要捍卫你的决定......
一般来说,以这种方式存储数据不是最理想的(例如,更难进行有效的查询),如果您修改表单中的项目,可能会导致维护问题。也许您可以找到一个中间立场并使用一个表示一组位标志的整数来代替?
是的,我会说它真的那么糟糕。这是一个合理的选择,但这并不意味着它是正确的或好的。
它打破了第一范式。
第二个批评是,将原始输入结果直接放入数据库,根本没有任何验证或绑定,这会使您容易受到 SQL 注入攻击。
你所说的懒惰和缺乏 SQL 知识是新手们的东西。我建议花时间正确地做这件事,并将其视为学习的机会。
或者保持现状,吸取 SQL 注入攻击的惨痛教训。
我需要一个多值列,它可以实现为 xml 字段
它可以根据需要转换为逗号分隔
querying an XML list in sql server using Xquery。
通过成为 xml 字段,可以解决一些问题。
使用 CSV:无法确保每个值都是正确的数据类型:没有办法防止 1,2,3,banana,5
使用 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:加上很多逗号字符。
使用 XML:使用标签而不是逗号
简而言之,使用 XML 解决了分隔列表的一些问题,并且可以根据需要转换为分隔列表
是的,就是这么糟糕。我的观点是,如果您不喜欢使用关系数据库,那么请寻找更适合您的替代方案,那里有很多有趣的“NOSQL”项目,它们具有一些非常高级的功能。
好吧,我已经在 SQL Server 的 NTEXT 列中使用键/值对制表符分隔列表超过 4 年了,并且它可以工作。您确实失去了进行查询的灵活性,但另一方面,如果您有一个持久化/去持久化键值对的库,那么这不是一个坏主意。
我可能会采取中间立场:将 CSV 中的每个字段都放入数据库中的单独列中,但不必太担心规范化(至少目前如此)。在某些时候,规范化可能会变得有趣,但是由于所有数据都被塞到一个列中,因此您几乎没有从使用数据库中获得任何好处。您需要将数据分成逻辑字段/列/您想要调用它们的任何内容,然后才能有意义地对其进行操作。
如果您有固定数量的布尔字段,则可以为每个字段使用 INT(1) NOT NULL
(或 BIT NOT NULL
,如果存在)或 CHAR (0)
(可为空)。您也可以使用 SET
(我忘记了确切的语法)。
INT(1)
占用 4 个字节; (1)
没有意义。