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

Mad*_*ist 346 database database-design database-normalization

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

现在,我知道正确的解决方案是创建第二个表并正确地规范化数据库.实现简单的解决方案更快,我想快速获得该应用程序的概念验证,而无需花费太多时间.

我认为节省的时间和更简单的代码在我的情况下是值得的,这是一个可辩护的设计选择,还是我应该从一开始就将其标准化?

更多上下文,这是一个小型内部应用程序,实际上取代了存储在共享文件夹中的Excel文件.我也在问,因为我正在考虑清理程序并使其更易于维护.在那里有一些我并不完全满意的事情,其中​​一个是这个问题的主题.

Bil*_*win 541

除了因为存储在单个列中的重复值组而违反First Normal Form之外,逗号分隔列表还有许多其他更实际的问题:

  • 无法确保每个值都是正确的数据类型:无法阻止1,2,3,banana,5
  • 无法使用外键约束将值链接到查找表; 无法强制执行参照完整性.
  • 无法强制执行唯一性:无法阻止1,2,3,3,3,5
  • 如果不提取整个列表,则无法从列表中删除值.
  • 无法存储的列表长度超过字符串列中的列表.
  • 难以搜索列表中具有给定值的所有实体; 你必须使用低效的表扫描.可能不得不求助于正则表达式,例如MySQL:
    idlist REGEXP '[[:<:]]2[[:>:]]'*
  • 难以计算列表中的元素,或执行其他聚合查询.
  • 很难将值加入到他们引用的查找表中.
  • 难以按排序顺序获取列表.
  • 将整数存储为字符串所需的空间大约是存储二进制整数的两倍.更不用说逗号字符占用的空间.

要解决这些问题,您必须编写大量的应用程序代码,重新发明RDBMS 已经提供的功能.

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

有时您需要使用非规范化,但正如@OMG小马提到的那样,这些都是异常情况.任何非关系型"优化"都会以牺牲其他数据使用为代价,从而使一种查询受益,因此请确保您知道哪些查询需要特别对待,以至于它们需要进行非规范化.


* MySQL 8.0不再支持这种字边界表达式语法.

  • ARRAY(任何数据类型)都可以修复异常,只需检查PostgreSQL:http://www.postgresql.org/docs/current/static/arrays.html(@Bill:好书,任何开发人员必读或dba ) (8认同)
  • 另一种看待这个问题的方式是:挑战不在于设计一个允许行为良好的客户端插入正确形成的数据的数据库。挑战在于确保任何客户端插入的所有数据始终格式正确。 (6认同)
  • +1票据Karwin很棒的回答!可爱简洁的要点.这看起来也很棒.爱盖子也是+1 NullUserException.我正在设计MySQL数据库的模式以替换基于平面文件文本的系统.到目前为止,我遇到了几个困境.所以这本书值得购买. (4认同)
  • pragprog.com网站看起来也不错:漂亮的风格,布局,用户友好的清洁.这一定是相当新的,我过去无法购买他们的电子书.PS.我不为他们工作与作者有任何联系.当我看到它时,我喜欢庆祝好的产品,服务和帮助. (2认同)
  • @CraigRinger,是的,这是一种非规范化。如果仔细使用,非规范化对于您尝试优化的某个查询来说可能是正确的做法,但必须在充分理解它会损害其他查询的情况下完成。如果那些其他查询对您的应用程序并不重要,那么痛苦就会减轻一些。 (2认同)
  • 我知道它不推荐,但玩恶魔提倡:如果有一个ui处理唯一性和数据类型(否则会出错或行为异常),大部分都可以取消,ui掉线并创建它无论如何,有一个驱动程序表,其中值来自于使它们唯一,可以使用像'%P%'这样的字段,值为P,R,S,T,计数无关紧要,排序无关紧要.根据ui,可以拆分值[]例如,在最不常见的情况下检查驱动程序表中列表中的复选框,而不必转到另一个表来获取它们. (2认同)
  • @shmosel,是的,这很简单,但无法优化。搜索子字符串不能使用索引。 (2认同)
  • @PrabhuNandanKumar,我会将174 *行*存储在引用您的第一个表的第二个表中。不要存储具有相似数据的174列。 (2认同)
  • 我同意“不能”和“难以”之间存在重要区别。例如,在这种情况下,您_不能_强制数据保持正确的格式,因为任何客户端都可以将它们更新为格式错误的数据。 (2认同)

Ham*_*ite 41

"一个原因是懒惰".

这响起警钟.你应该做这样的事情的唯一原因是你知道如何以"正确的方式"做到这一点但你已经得出结论,有一个切实的理由不这样做.

话虽如此:如果您选择以这种方式存储的数据是您永远不需要查询的数据,那么可能存在以您选择的方式存储它的情况.

(有些用户会对我上一段中的陈述提出异议,说"你永远不知道将来会添加什么要求".这些用户要么被误导,要么说出宗教信仰.有时候按照你的要求工作是有利的.在你之前.)

  • 当我面对一些人关于不设置外键约束或在单个字段中存储列表之类的问题时,我总是听到一些人说“我的设计比你的更灵活”。对我来说,灵活性(在这种情况下)==没有纪律==懒惰。 (3认同)

OMG*_*ies 40

关于SO的问题有很多问题:

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

用逗号分隔的列表的另一个问题是确保值是一致的 - 存储文本意味着错别字的可能性...

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


bob*_*mcr 19

一般来说,如果符合项目要求,任何事物都可以防御.这并不意味着人们会同意或想要捍卫你的决定......

通常,以这种方式存储数据是不理想的(例如,难以进行有效的查询),如果修改表单中的项目,可能会导致维护问题.也许你可以找到一个中间地带并使用代表一组位标志的整数代替?


duf*_*ymo 13

是的,我会说这真的很糟糕.这是一种可辩护的选择,但这并不能使其正确或良好.

它破坏了第一个正常形式.

第二个批评是将原始输入结果直接放入数据库,而没有任何验证或绑定,这使您对SQL注入攻击持开放态度.

什么你打电话懒惰和缺乏SQL的知识是新手制成的东西.我建议花点时间做好,把它看作是学习的机会.

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

  • 我在这个问题中没有看到任何暗示他容易受到SQL注入的问题.SQL注入和数据库规范化是正交主题,您对注入的偏离与问题无关. (17认同)
  • @Paul:也许同样的态度会导致他在过马路时没有看到双向的公共汽车,但你没有告诫他.编辑:我以为你是这个答案的海报,我的错误. (4认同)
  • 是的,它的意图是荒谬的.它的荒谬说明了我正在制造的这一点,即警告他反对你没有理由认为他需要被警告的事情是没有意义的. (3认同)
  • @Hammerite - 你对公共汽车的推断是荒谬的。 (2认同)
  • 好的我知道了。我想我有更多的理由比你对公共汽车的警告。 (2认同)

Raj*_*Raj 7

嗯,我一直在使用SQL Server中的NTEXT列分隔列表超过4年的键/值对选项卡现在和它的作品.你确实失去了进行查询的灵活性,但另一方面,如果你有一个库,它可以持久存储/篡改键值对,那么这不是一个坏主意.

  • +1只是为了反击downvotes.告诉那些维护应用程序4年有关维护问题的人有点放肆.在sw开发中很少有"可怕"的想法 - 大多数只是那些适用性非常有限的想法.警告人们有关这些限制是合情合理的,但是惩罚那些已经完成并经历过这种限制的人会让我感到震惊,因为我可以做到比你更圣洁的态度. (28认同)
  • 不,这是一个可怕的想法.你已经设法逃脱了它,但是你几分钟的开发时间花费了你糟糕的查询性能,灵活性和代码的可维护性. (11认同)
  • 保罗,我同意.但正如我所说的那样,我是出于特定目的而使用的,那就是数据输入操作,你有多种形式.我正在修改设计,因为我已经学习了NHibernate但是当时我需要灵活性来在ASP.NET中设计表单并使用文本框ID作为键/值对中的键. (5认同)

Jam*_*ler 7

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

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

使用Xquery在sql server中查询XML列表.

通过作为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可以解决分隔列表中的一些问题,并且可以根据需要转换为分隔列表


Rob*_*bin 6

是的,那糟糕.我的观点是,如果你不喜欢使用关系数据库,那么寻找一个更适合你的替代方案,有很多有趣的"NOSQL"项目,其中有一些非常先进的功能.