设想一个带有一组复选框的Web窗体(可以选择其中的任何一个或全部)。我选择将它们保存在数据库表的一列中以逗号分隔的值列表中。
现在,我知道正确的解决方案是创建第二个表并正确规范化数据库。实现这个简单的解决方案更快,我希望能够快速地证明该应用程序的概念,而不必在上面花费太多时间。
我认为节省的时间和更简单的代码在我的情况下是值得的,这是一个合理的设计选择,还是应该从一开始就规范化它?
还有一些上下文,这是一个小型的内部应用程序,基本上替换了存储在共享文件夹中的Excel文件。我也在问,因为我正在考虑清理程序,使其更易于维护。有一些事情我并不完全满意,其中之一就是这个问题的主题。
- 在这种情况下,为什么要干扰数据库?,保存到文件中即可。
- 同意@thavan。为什么还要保存数据作为概念验证?完成证明后,请正确添加数据库。你做的很好,轻量级的概念证明,只是不要使事情,你必须取消以后。
除了由于存储在单列中的重复值组而违反第一个正常形式之外,逗号分隔列表还有许多其他更实际的问题:
- 无法确保每个值都是正确的数据类型:无法阻止1、2、3、Banana、5
- 无法使用外键约束将值链接到查阅表;无法强制引用完整性。
- 无法强制唯一性:无法阻止1、2、3、3、3、5
- 如果不提取整个列表,则无法从列表中删除值。
- 存储列表的时间不能超过字符串列中的长度。
- 很难在列表中搜索具有给定值的所有实体;必须使用效率低下的表扫描。可能必须使用正则表达式,例如在MySQL中:idlist REGEXP '[[:<:]]2[[:>:]]'*
- 很难计算列表中的元素,或者执行其他聚合查询。
- 很难将这些值联接到它们引用的查阅表格中。
- 很难按排序顺序提取列表。
为了解决这些问题,您必须编写大量的应用程序代码,重新设计RDBMS已经提供了更有效的功能。
逗号分隔的列表是错误的,所以我把它作为书中的第一章:SQL反模式:避免数据库编程的陷阱。
有时需要使用非规范化,但正如@omg ponies所提到的,这些都是例外情况。任何非关系"优化"都有利于一种类型的查询,而不会牺牲数据的其他使用,因此请确保您知道哪些查询需要特别处理,以便它们值得非规范化。
*MySQL8.0不再支持这个单词边界表达式语法。
- 感谢您的详细列表,我考虑了其中一些问题,但肯定不是所有问题。
- 这本书看起来很有趣,所以我买了它。我早就认为这是需要一本书的主题。
- 数组(任何数据类型)都可以修复异常,只需检查postgresql:postgresql.org/docs/current/static/arrays.html(@bill:great book,a must read for any developer或dba)
- +1比尔·卡尔文回答得很好!可爱简洁的要点。这本书看起来也不错。也喜欢封面+1 NullUserException。我正在为MySQL数据库设计模式,以取代基于文本的平面文件系统。到目前为止,我遇到了几个难题。所以这本书值得买。
- pragprog.com网站看起来也不错:风格不错,布局合理,用户友好,整洁。这一定是相当新的,我过去没能买他们的电子书。我不为他们工作,与作者没有任何联系。当我看到好的产品、服务和帮助时,我喜欢庆祝它。
- 有关PostgreSQL的具体讨论,请参阅dba.stackexchange.com/q/55871/7788。逗号分隔也很糟糕,但如果仔细应用并考虑到后果,数组字段在某些情况下是可以接受的性能优化。
- @Craigringer,是的,这是一种非规范化。仔细使用时,对于您试图优化的某个查询,非规范化可能是正确的做法,但必须充分理解它会损害其他查询。如果这些其他查询对您的应用程序不重要,那么麻烦就小了。
- 香蕉不应该在3点之前来而不是3点之后来吗?
- @杰伊,谢谢,这只是指出了另一个弱点:逗号分隔的列表可能排序不正确。
- 从严肃的方面来说,我会加入你的名单:很难搜索。假设你想要包括"2"的所有记录。当然,你不能只搜索foobar='2',因为如果有其他值的话,这会丢失它。您不能像搜索"%2%"那样搜索foobar,因为这样会得到12和28等错误的点击率。不能搜索foobar,如"%、2、%",因为2可能是列表的第一个或最后一个元素,因此只有一个逗号。
- @杰伊,这是第六颗子弹的意图,"很难找到……"对不起,如果不清楚。
- 我只是想帮助澄清。我以为你可能已经想到了,但不想再长时间讨论猜测你的意思。
- @杰伊,没问题,谢谢你的贡献。我用一个例子编辑了上面的内容(它很难看)。
- 我知道这是不推荐的,但玩魔鬼的鼓吹者:如果有一个处理唯一性和数据类型的UI(否则会出错或行为不正常),UI会删除并创建它,有一个驱动程序表,其中的值来自于使它们唯一,可以使用像"%p%"这样的字段,值为p、r、s、t,计数不重要,排序也不重要。根据用户界面的不同,可以将值拆分为[],例如,在最不常见的情况下,从驱动程序表中选中列表中的复选框,而无需转到另一个表来获取这些值。
- mysql使得使用FIND_IN_SET()进行搜索变得非常容易。
- @Shmosel,是的,这很容易,但是它不能被优化。搜索子字符串不能使用索引。
- @比尔卡温你有没有想过表演。我有174个特性列表,如果您的方法必须将每个数据存储在数据库中,它将加载174列,但是如果我们使用逗号分隔值存储,那么可以快速解决并减少数据库引擎中的负载。我和魔鬼倡导者一样,也会问你别这么想。
- @prabhunandandumar,我将在引用第一个表的第二个表中存储174行。不要存储具有类似数据的174列。
- 那么,JSON数据类型呢…?
- @Sun,您可能会对我的演示感兴趣,如何在MySQL中使用JSON是错误的。
- 确切地说,杰伊。应该吗?我们怎么知道?
- 我已经多次提到这个问题/答案。但我不同意最后一点(存储大小)。在最坏的情况下,无符号整数作为字符串需要11个字节(通常更少)。规范化分隔字符串通常意味着创建一个新表,其中有两个int列,数据为8字节。但是许多存储引擎(如果不是大多数的话)将有每行开销。例如,mysql的innodb引擎有一个大约20字节的行头。因此,每个条目的结尾可以是28字节,而不是11字节。不是说我会很在意-其他的论点会让它超重。但这一点并不总是正确的。
- @保尔·斯皮格尔,我做了一些测试,你对存储空间的看法是对的。我将删除最后一点。其他点是选择规范化表的更好的原因。
关于这样的问题有很多:
- 如何从逗号分隔的列表中获取特定值的计数
- 如何从逗号分隔的列表中获取只有相同2/3/etc特定值的记录
逗号分隔列表的另一个问题是确保值一致-存储文本意味着可能出现拼写错误…
这些都是非规范化数据的症状,并强调了为什么应该始终为规范化数据建模。非规范化可以是一种查询优化,在实际需要出现时应用。
"一个原因是懒惰"。
这会敲响警钟。你应该这样做的唯一原因是你知道如何"正确地"去做,但是你得出的结论是,有一个切实的理由不这样做。
已经说过了:如果您选择以这种方式存储的数据是您永远不需要查询的数据,那么可能存在以您选择的方式存储它的情况。
(有些用户会对我上一段中的声明提出异议,说"你永远不知道将来会增加什么要求"。这些用户要么是被误导,要么是说宗教信仰。有时有利于满足您之前的要求。)
- 我经常听到一些人说,当我面对他们时,"我的设计比你的设计更灵活",比如不设置外键约束,或者在单个字段中存储列表。对我来说,灵活性(在这种情况下)==没有纪律性==懒惰。
一般来说,如果满足项目的要求,任何事情都是可以防御的。这并不意味着人们会同意或想捍卫你的决定…
一般来说,以这种方式存储数据是次优的(例如,更难进行有效的查询),如果修改表单中的项目,可能会导致维护问题。也许您可以找到一个中间位置,用一个表示一组位标志的整数来代替?
是的,我想说那真的很糟糕。这是一个合理的选择,但这并不能使它正确或良好。
它打破了第一个正常形态。
第二个批评是,将原始输入结果直接放入数据库中,而不进行任何验证或绑定,会使您面临SQL注入攻击。
你所说的懒惰和缺乏SQL知识是新手所用的东西。我建议你花点时间好好做,把它当作一个学习的机会。
或者保持原样,学习SQL注入攻击的痛苦教训。
- 在这个问题中,我没有看到任何迹象表明他易受SQL注入攻击。SQL注入和数据库规范化是正交的主题,您对注入的偏离与问题无关。
- 输入被转义,任何有权访问此应用程序的人都已经有了更简单的方法来造成破坏。我使用Drupal数据库查询访问数据库,单独提供参数。
- @Hammerite,即使这种特别的懒惰和不愿意学习不会导致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年多了,它可以工作了。您确实会失去查询的灵活性,但另一方面,如果您有一个持久化/去持久化键值对的库,那么这不是一个坏主意。
- 不,这是个可怕的主意。您已经设法摆脱了它,但是开发时间的几分钟成本已经使您的查询性能、灵活性和代码的可维护性变得很差。
- 保罗,我同意。但正如我所说,我将if用于特定目的,这是用于数据输入操作,其中您有多种表单。我现在正在修改设计,因为我已经学习了nhibernate,但在那之前,我需要灵活地在ASP.NET中设计表单,并使用文本框ID作为键/值对中的键。
- +1只是为了对抗落选。告诉已经维护应用程序4年的人有关维护的问题有点自以为是。在软件开发中,很少有"可怕"的想法——大多数都是适用性非常有限的想法。警告人们这些局限性是合理的,但是惩罚那些做过它并经历过它的人,让我觉得我是一个比你更神圣的态度,我可以不做。
如果有固定数量的布尔字段,则可以对每个字段使用INT(1) NOT NULL(如果存在,则使用BIT NOT NULL)或CHAR (0)(可以为空)。您还可以使用SET(我忘记了确切的语法)。
我可能会采取中间立场:将csv中的每个字段都变成数据库中的单独列,但不太担心规范化(至少目前如此)。在某种程度上,规范化可能会变得有趣,但是当所有的数据都被推到一个单独的列中时,使用数据库几乎没有任何好处。在有意义地操作数据之前,您需要将数据分为逻辑字段/列/您想调用它们的任何对象。
- 表单包含更多字段,这只是表单的一部分(我在问题中没有很好地解释)。