将分隔列表存储在数据库列中真的不好吗?


363

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

现在,我知道正确的解决方案是创建第二个表并正确规范化数据库。实施简单的解决方案的速度更快,我想快速对该应用程序进行概念验证,而不必花费太多时间。

我认为节省时间和简化代码在我的情况下是值得的,这是一个合理的设计选择,还是我应该从一开始就对其进行标准化?

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


21
在这种情况下,为什么要打扰数据库?将保存在文件中。
thavan

6
同意@thavan。为什么还要保存数据以进行概念验证?证明完成后,请正确添加数据库。您可以做轻巧的产品来证明概念,只是不必做以后就不需要做的事情。
杰夫·戴维斯

1
在Postgres中,数组列应优先于逗号分隔的列表。这至少确保了正确的数据类型,将定界符与实际数据区分开来没有问题,并且可以有效地对其进行索引。
a_horse_with_no_name

Answers:


567

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

  • 无法确保每个值都是正确的数据类型:无法阻止1,2,3,banana,5
  • 不能使用外键约束将值链接到查找表;无法强制执行参照完整性。
  • 无法强制唯一性:无法阻止1,2,3,3,3,5
  • 如果不获取整个列表,则无法从列表中删除值。
  • 列表的存储时间不能超过字符串列的容纳时间。
  • 很难在列表中搜索具有给定值的所有实体;您必须使用效率低下的表格扫描。可能不得不求助于正则表达式,例如在MySQL中:
    idlist REGEXP '[[:<:]]2[[:>:]]'*
  • 难以计数列表中的元素,或执行其他汇总查询。
  • 很难将值连接到它们引用的查找表中。
  • 难以按排序顺序获取列表。

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

逗号分隔的列表是非常错误的,以至于我将此本书作为本书的第一章:SQL反模式:避免数据库编程的陷阱

有时候您需要使用非规范化,但是正如@OMG Ponies所提到的,这是例外情况。任何非关系式“优化”都会以一种查询类型为代价,而以其他用途使用数据为代价,因此,请确保您知道哪些查询需要特别处理,以使它们值得进行非规范化处理。


* MySQL 8.0不再支持此字边界表达式语法。


8
(任何数据类型的)ARRAY都可以解决该异常,只需检查PostgreSQL:postgresql.org/docs/current/static/arrays.html(@Bill:很棒的书,对于任何开发人员或dba来说都是必读的书)
Frank Heikens

4
+1帐单Karwin很好的答案!可爱简洁的要点。那看起来也像一本好书。也爱封面+1 NullUserException。我正在为MySQL数据库设计架构,以替换基于平面文件文本的系统。到目前为止,我遇到了几个难题。因此,这本书值得购买。
therobyouknow 2012年

2
pragprog.com网站也看起来不错:漂亮的样式,布局,友好的用户界面。这肯定是很新的,过去我没能买到他们的电子书。PS。我不工作,因为他们与作者没有任何关系。我喜欢赞美优质的产品,服务和帮助。
therobyouknow 2012年

2
认真的一面,我要添加到您的列表中:很难搜索。假设您要所有包含“ 2”的记录。当然,您不能只搜索foobar ='2',因为如果有其他值,它将丢失它。您无法搜索'%2%'之类的foobar,因为这样会导致12和28等错误命中,依此类推。您不能像'%,2,%'这样搜索foobar,因为2可能是列表的第一个或最后一个元素,因此只有一个逗号。
杰伊

2
我知道不推荐这样做,但是扮演魔鬼的拥护者:如果有处理唯一性和数据类型的ui(否则会出错或行为不当)的ui会被删除,无论如何ui都会删除并创建它,有一个驱动程序表这些值来自于使其具有唯一性,可以使用“%P%”之类的字段,这些值分别为P,R,S,T,计数无关紧要,排序无关紧要。根据ui,值可以被split []例如在最不常见的情况下从驱动程序表中检查列表中的复选框,而不必转到另一个表来获取它们。
jmcclure

44

“一个原因是懒惰”。

这会敲响警钟。您应该执行此类操作的唯一原因是您知道如何“正确地进行”操作,但是您得出的结论是,有明显的理由不这样做。

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

(有些用户会对我在上一段中的说法提出异议,说“您永远不会知道将来会增加什么要求”。这些用户要么被误导了,要么说是出于宗教信仰。有时,按照您的要求来做是有利的。在你之前。)


我经常听到有人说“我的设计比您的设计更灵活”,例如不设置外键约束或将列表存储在单个字段中。对我来说,灵活性(在这种情况下)==没有纪律==懒惰。
foresightyj 2015年

41

因此有很多问题要问:

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

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

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


19

通常,只要符合项目要求,任何东西都是可以辩护的。这并不意味着人们会同意或想捍卫您的决定...

通常,以这种方式存储数据不是最佳选择(例如,很难进行有效的查询),并且如果您修改表单中的项目,可能会导致维护问题。也许您可以找到一个中间立场,并使用代表一组位标志的整数代替?


10

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

它破坏了第一范式。

第二种批评是,将原始输入结果直接放入数据库中,而无需任何验证或绑定,这使您容易受到SQL注入攻击的影响。

您所说的懒惰和缺乏SQL知识就是新手制造的东西。我建议花些时间正确地做它,并将其视为学习的机会。

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


19
我在这个问题上没有发现任何暗示他容易受到SQL注入攻击的信息。SQL注入和数据库规范化是正交的主题,而您对注入的看法与问题无关。
Hammerite 2010年

5
@Paul:也许同样的态度会导致他在过马路之前不看两眼时被公共汽车撞到,但是您没有对此警告过。编辑:我以为你是这个答案的发源人,我的错。
Hammerite 2010年

1
@Hammerite-您对公交车的推断​​是荒谬的。
duffymo's

4
是的,这本来是荒谬的。它的荒谬可笑说明了我的观点,那就是警告他不要担心您没有理由认为需要警告他的事情。
Hammerite 2010年

1
是的,我明白了。我想我有更多理由让您对公共汽车提出警告。
duffymo's

7

好吧,我已经在SQL Server的NTEXT列中使用键/值对选项卡分隔的列表了4年以上了,并且可以正常工作。您的确失去了进行查询的灵活性,但是,另一方面,如果您拥有一个可以持久存储/持久化键值对的库,那么这并不是一个坏主意。


13
不,这是一个可怕的想法。您已经设法摆脱了它,但是几分钟的开发时间却浪费了糟糕的查询性能,灵活性和代码可维护性。
Paul Tomblin'9

5
保罗,我同意。但是正如我所说,我将if用于特定目的,这是用于数据输入操作,其中您使用多种形式。我已经学习了NHibernate,现在正在修改设计,但是那时候我需要灵活性才能在ASP.NET中设计表单,并使用文本框ID作为键/值对中的键。
拉吉2010年

28
+1只是为了抵消反对票。告诉维护了该应用程序4年的人有关维护方面的问题有点冒昧。软件开发中很少有“可怕”的想法-大多只是适用性非常有限的想法。在局限性上警告人们是合理的,但是对那些已经做到并经受住了考验的人进行严厉抨击,这让我印象深刻,这是我不能没有的一种比你更神圣的态度。
Mark Brackett

7

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

可以根据需要将其转换为以逗号分隔的逗号

使用Xquery在sql服务器中查询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可以解决分隔列表的一些问题,并且可以根据需要将其转换为分隔列表


6

是的,那糟糕。我的观点是,如果您不喜欢使用关系数据库,然后寻找更适合您的替代方法,那么这里有很多有趣的“ NOSQL”项目,它们具有一些真正的高级功能。


0

我可能会采取中间立场:将CSV中的每个字段放入数据库中的单独列中,但不必担心标准化(至少现在如此)。在某些时候,规范化可能会变得很有趣,但是将所有数据推到一个单独的列中,使用数据库几乎没有任何好处。您需要将数据分为逻辑字段/列/任何您想调用它们的地方,然后才能进行有意义的操作。


表单包含更多字段,这只是表单的一部分(我在问题中没有很好地解释)。
疯狂的科学家

0

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


1
INT(1)占用4个字节;这(1)是没有意义的。
瑞克·詹姆斯
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.