ChatGPT解决这个技术问题 Extra ChatGPT

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

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

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

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

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

在那种情况下,为什么要打扰数据库?保存在文件中就可以了。
同意@thavan。为什么还要保存数据以进行概念验证?完成证明后,请正确添加数据库。你做轻量级的概念证明很好,只是不要做你以后必须取消的东西。
在 Postgres 中,数组列应该优先于逗号分隔的列表。这至少确保了正确的数据类型,将分隔符与实际数据区分开来没有问题,并且可以有效地对其进行索引。

B
Bill Karwin

除了由于存储在单个列中的重复值组而违反 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,这些是例外情况。任何非关系“优化”都会以牺牲数据的其他用途为代价来使一种类型的查询受益,因此请确保您知道哪些查询需要特别处理,以使其值得去规范化。


一个数组(任何数据类型)可以修复异常,只需检查 PostgreSQL:postgresql.org/docs/current/static/arrays.html(@Bill:很棒的书,任何开发人员或 dba 都必须阅读)
有关 PostgreSQL 特定的讨论,请参阅 dba.stackexchange.com/q/55871/7788。逗号分隔同样糟糕,但如果仔细应用并考虑后果,数组字段在某些情况下可能是可接受的性能优化。
@CraigRinger,是的,这是一种非规范化。谨慎使用时,非规范化可能是您尝试优化的某个查询的正确做法,但必须在充分了解它会损害其他查询的情况下完成。如果这些其他查询对您的应用程序不重要,那么痛苦就会减少。
我知道它不推荐,但扮演魔鬼倡导者:如果有一个处理唯一性和数据类型的 ui(否则会出错或行为不端),大多数这些都可以取消,ui 无论如何都会删除并创建它,有一个驱动程序表,其中值来自使它们唯一,可以使用像'%P%'这样的字段,值是P,R,S,T,计数无关紧要,排序无关紧要。根据 ui,可以拆分值 [] 例如,在最不常见的情况下检查驱动程序表中的列表中的复选框,而无需转到另一个表来获取它们。
@PrabhuNandanKumar,我将在引用您的第一个表的第二个表中存储 174 行。不要存储具有相似数据的 174 列。
c
cHao

“一个原因是懒惰”。

这敲响了警钟。你应该做这样的事情的唯一原因是你知道如何“以正确的方式”去做,但你得出的结论是有一个切实的理由不这样做。

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

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


当我面对诸如不设置外键约束或将列表存储在单个字段中等问题时,我总是听到有人说“我的设计比你的更灵活”。对我来说,灵活性(在这种情况下)== 没有纪律== 懒惰。
O
OMG Ponies

关于 SO 的问题有很多:

如何从逗号分隔列表中获取特定值的计数

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

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

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


b
bobbymcr

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

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


d
duffymo

是的,我会说它真的那么糟糕。这是一个合理的选择,但这并不意味着它是正确的或好的。

它打破了第一范式。

第二个批评是,将原始输入结果直接放入数据库,根本没有任何验证或绑定,这会使您容易受到 SQL 注入攻击。

你所说的懒惰和缺乏 SQL 知识是新手们的东西。我建议花时间正确地做这件事,并将其视为学习的机会。

或者保持现状,吸取 SQL 注入攻击的惨痛教训。


我在这个问题中没有看到任何表明他容易受到 SQL 注入攻击的内容。 SQL 注入和数据库规范化是正交主题,您对注入的题外话与问题无关。
输入被转义,任何有权访问此应用程序的人都已经有了更容易造成破坏的方法。我使用 Drupal db_query 来访问数据库,单独提供参数。
@Hammerite,即使这种特殊的懒惰和不愿学习不会导致SQL注入,其他具有相同态度的示例也会。
@Hammerite,也没有什么可以排除这种可能性。我认为值得提出以防 OP 的无知也扩展到 SQL 注入。我同意规范化和 SQL 注入可以是正交的,但是在没有其他信息的情况下,我觉得应该提到它。这几乎无关紧要。
@Paul:也许同样的态度会导致他在过马路前没有向两边看时被公共汽车撞到,但你没有警告过他。编辑:我以为你是这个答案的发布者,我的错误。
C
Community

我需要一个多值列,它可以实现为 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 解决了分隔列表的一些问题,并且可以根据需要转换为分隔列表


R
Robin

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


R
Raj

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


不,这是一个可怕的想法。您已经设法摆脱了它,但是您几分钟的开发时间的成本已经使您的查询性能、灵活性和代码的可维护性变得糟糕。
保罗,我同意。但正如我所说,我将 if 用于特定目的,即用于具有多种表单的数据输入操作。我现在正在修改设计,因为我已经学习了 NHibernate,但那时我需要灵活性来设计 ASP.NET 中的表单,并使用文本框 ID 作为键/值对中的键。
告诉维护该应用程序 4 年的人有关维护问题有点冒昧。软件开发中很少有“可怕”的想法 - 大多数只是适用性非常有限的想法。警告人们注意限制是合理的,但惩罚那些做过并经历过它的人让我觉得这是一种比你更神圣的态度,我可以不用。
J
Jerry Coffin

我可能会采取中间立场:将 CSV 中的每个字段都放入数据库中的单独列中,但不必太担心规范化(至少目前如此)。在某些时候,规范化可能会变得有趣,但是由于所有数据都被塞到一个列中,因此您几乎没有从使用数据库中获得任何好处。您需要将数据分成逻辑字段/列/您想要调用它们的任何内容,然后才能有意义地对其进行操作。


S
Solomon Ucko

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


INT(1) 占用 4 个字节; (1) 没有意义。
INT(1) 占用多少字节取决于产品,以及 INT(1) 的含义。可以是一个数字、一个字节、一个单词,还是别的什么?