【问题标题】:How to store a list in a column of a database table如何将列表存储在数据库表的列中
【发布时间】:2011-03-05 11:09:26
【问题描述】:

因此,根据Mehrdad's answer to a related question,我明白“正确的”数据库表列不存储列表。相反,您应该创建另一个有效保存所述列表元素的表,然后直接或通过联结表链接到它。但是,我要创建的列表类型将由独特的项目组成(与链接问题的 fruit 示例不同)。此外,我的列表中的项目是明确排序的——这意味着如果我将元素存储在另一个表中,我每次访问它们时都必须对它们进行排序。最后,列表基本上是原子的,因为任何时候我想访问列表,我都想访问整个列表,而不仅仅是其中的一部分——所以不得不发出数据库查询来收集碎片似乎很愚蠢列表。

AKX 的解决方案(上面链接)是将列表序列化并将其存储在二进制列中。但这似乎也很不方便,因为这意味着我必须担心序列化和反序列化。

有没有更好的解决方案?如果没有没有更好的解决方案,那为什么呢?看来这个问题应该时不时出现。

...只是更多的信息让你知道我来自哪里。当我刚开始了解 SQL 和一般数据库时,我就开始使用 LINQ to SQL,所以现在我有点被宠坏了,因为我希望处理我的编程对象模型而不必考虑对象是如何处理的被查询或存储在数据库中。

谢谢大家!

约翰

更新:所以在我得到的第一批答案中,我看到“你可以走 CSV/XML 路线……但不要!”。所以现在我正在寻找原因的解释。给我一些好的参考。

另外,为了让您更好地了解我在做什么:在我的数据库中,我有一个函数表,其中包含 (x,y) 对的列表。 (该表还将包含其他对我们的讨论无关紧要的信息。)我永远不需要查看 (x,y) 对列表的一部分。相反,我会将它们全部绘制在屏幕上。我将允许用户拖动节点以偶尔更改值或向绘图添加更多值。

【问题讨论】:

  • 原子访问/修改的值实际上只是一个值。例如,没有人会反对将 URL(实际上只是一个字符列表)存储为 db 表中的字段。我不同意下面的答案“如果你现在这样做,你以后会再做一次”。听起来您已经考虑过这种情况是否对您的用例有意义。当您发现自己需要在列表上添加约束或对列表项进行单独访问时,您应该规范化您的数据库并创建一个 JOIN 表。

标签: sql linq linq-to-sql database-design linq-to-entities


【解决方案1】:

不,没有“更好”的方法可以将一系列项目存储在单个列中。关系数据库被设计为专门为每个行/列组合存储一个值。为了存储多个值,您必须将列表序列化为单个值进行存储,然后在检索时对其进行反序列化。没有其他方法可以做你所说的(因为你所说的是一个坏主意,一般来说,永远不要这样做)。

我知道您认为创建另一个表来存储该列表很愚蠢,但这正是关系数据库所做的。您正在打一场艰苦的战斗,并且无缘无故地违反了关系数据库设计的最基本原则之一。既然您说您只是在学习 SQL,我会强烈建议您避免这种想法,并坚持使用更有经验的 SQL 开发人员向您推荐的做法。

您违反的原则称为第一范式,这是数据库规范化的第一步。

冒着过度简化事情的风险,数据库规范化是根据数据是什么来定义数据库的过程,以便您可以针对它编写合理、一致的查询并能够维护它容易地。规范化旨在限制数据中的逻辑不一致和损坏,并且它有很多级别。 database normalization 上的维基百科文章实际上相当不错。

基本上,规范化的第一条规则(或形式)规定您的表必须表示关系。这意味着:

  • 您必须能够将一行与任何其他行区分开来(换句话说,您的表必须有一些可以用作主键的内容。这也意味着不应重复任何行。
  • 数据的任何排序都必须由数据定义,而不是由行的物理排序(SQL 基于集合的概念,这意味着您应该依赖 only 排序是您在查询中明确定义的)
  • 每个行/列交叉点必须包含一个且只有一个

最后一点显然是这里的重点。 SQL 旨在为您存储集合,而不是为您提供一个“桶”让您自己存储集合。是的,有可能做到。不,世界不会终结。但是,您已经通过立即开始使用 ORM,从而削弱了自己对 SQL 和与之相伴的最佳实践的理解。 LINQ to SQL 非常棒,就像图形计算器一样。然而,同样地,它们不应被用来代替了解它们所采用的流程是如何实际工作的。

您的列表现在可能完全是“原子的”,并且对于这个项目可能不会改变。但是,您会养成在其他项目中做类似事情的习惯,并且您最终(可能很快)会遇到一个场景,您现在正在适应您的快速-n-easy list-in-a-column在完全不合适的情况下进行处理。为您尝试存储的内容创建正确的表并没有太多额外的工作,并且当其他 SQL 开发人员看到您的数据库设计时,他们不会嘲笑您。此外,LINQ to SQL 将查看您的关系并为您的列表提供适当的面向对象接口自动。您为什么要放弃 ORM 为您提供的便利,以便您可以执行非标准和不明智的数据库黑客攻击?

【讨论】:

  • 因此您坚信将列表存储在列中是一个坏主意,但您没有提及原因。因为我刚刚开始使用 SQL,所以一点点“为什么”确实会很有帮助。例如,您说我“打一场硬仗,无缘无故地违反了关系数据库设计的最基本原则之一”……那么原则是什么?为什么我引用的原因是“不好”? (特别是我的列表的排序和原子性质)
  • 基本上,这归结为将多年的经验浓缩为最佳实践。有问题的基本主体称为 1st Normal Form
  • 谢谢亚当。信息量很大。你的最后一个问题很好。
  • “[...] 并且当其他 SQL 开发人员看到您的数据库设计时,他们不会嘲笑您。” 尊重第一范式(和您的回答提到了他们),但同伴压力/“这就是这里的工作方式”在其中。
  • 我们已经每天在数据库列中存储大量列表。它们被称为“char”和“varchar”。当然在 Postgres 中,它们也被称为文本。 1NF 真正说的是,您永远不应该将任何字段中的信息拆分为较小的字段,如果您这样做,您就大错特错了。因此,您无需存储姓名,而是存储个人姓名、中间名和姓氏(取决于本地化),然后将它们拼接在一起。否则我们根本不会存储文本字符串。另一方面,他想要的只是一串串。并且有办法做到这一点。
【解决方案2】:

您可以完全忘记 SQL,而采用“NoSQL”方法。 RavenDBMongoDBCouchDB 跳到脑海中作为可能的解决方案。使用 NoSQL 方法,您不会使用关系模型。您甚至不会受限于模式。

【讨论】:

    【解决方案3】:

    我见过很多人这样做(这可能不是最好的方法,如果我错了,请纠正我):

    我在示例中使用的表格如下(表格包括您给特定女朋友的昵称。每个女朋友都有一个唯一的 id):

    nicknames(id,seq_no,names)
    

    假设,您想在一个 id 下存储许多昵称。这就是我们包含seq_no 字段的原因。

    现在,将这些值填充到您的表格中:

    (1,1,'sweetheart'), (1,2,'pumpkin'), (2,1,'cutie'), (2,2,'cherry pie')
    

    如果你想找到你给你女朋友 id 1 的所有名字,那么你可以使用:

    select names from nicknames where id = 1;
    

    【讨论】:

    • 我喜欢这个答案,但是,假设昵称是很常见的事情,我们会给出数百个昵称 - 我们将有一个毫无意义的大表,它有点混合 SQL 和 NOSQL,我认为 SQL 应该解决那个问题
    【解决方案4】:

    简单的答案:当且仅当您确定该列表将始终用作列表时,然后在您的末尾用一个字符(例如 '\0')将列表连接在一起,该字符不会曾经在文本中使用过,并将其存储起来。然后当你检索它时,你可以用'\0'分割。当然还有其他方法可以解决这些问题,但这些取决于您的特定数据库供应商。

    例如,您可以将 JSON 存储在 Postgres 数据库中。如果您的列表是文本,并且您只想要该列表而不需要进一步的麻烦,那么这是一个合理的折衷方案。

    其他人提出了关于序列化的大胆建议,但我并不认为序列化是一个好主意:关于数据库的巧妙之处在于,用不同语言编写的多个程序可以相互通信。如果 Lisp 程序想要加载它,那么使用 Java 格式序列化的程序就不会做得那么好。

    如果你想要一个好的方法来做这种事情,通常可以使用数组或类似的类型。 Postgres for instance, offers array as a type, and lets you store an array of text, if that's what you wantMySqlMS SQL 使用 JSON 也有类似的技巧,IBM's DB2 也提供数组类型(在他们自己的帮助文档中)。如果不需要,这不会那么常见。

    走这条路,你确实失去了将列表视为一堆按顺序排列的东西的概念。至少名义上,数据库将字段视为单个值。但如果这就是你想要的,那么你应该去做。这是你必须为自己做出的价值判断。

    【讨论】:

      【解决方案5】:

      除了其他人所说的之外,我建议您比现在更长时间地分析您的方法。 目前的情况是项目是唯一的。 目前的情况是,重新处理这些项目需要一个新列表。几乎要求列表当前短。尽管我没有具体的领域,但认为这些要求可能会改变并不是一件容易的事。如果你序列化你的列表,你就会陷入一种僵化的状态,而这在更规范化的设计中是不必要的。顺便说一句,这并不一定意味着完整的多:多关系。您可以只拥有一个子表,其中包含一个指向父项的外键和一个用于项的字符列。

      如果您仍想走这条序列化列表的道路,您可以考虑将列表存储在 XML 中。某些数据库(例如 SQL Server)甚至具有 XML 数据类型。我建议 XML 的唯一原因是几乎按照定义,这个列表需要很短。如果列表很长,那么通常将其序列化是一种糟糕的方法。如果您采用 CSV 路线,则需要考虑包含分隔符的值,这意味着您必须使用带引号的标识符。假设列表很短,使用 CSV 或 XML 可能不会有太大区别。

      【讨论】:

      • +1 用于预测未来的变化 - 始终将您的数据模型设计为可扩展的。
      【解决方案6】:

      如果需要对列表进行查询,则将其存储在表中。

      如果您总是需要该列表,您可以将其作为分隔列表存储在列中。即使在这种情况下,除非您有非常具体的理由不这样做,否则将其存储在查找表中。

      【讨论】:

        【解决方案7】:

        许多 SQL 数据库允许表包含子表作为组件。通常的方法是允许其中一列的域是一个表。这是除了使用诸如 CSV 之类的约定以 DBMS 未知的方式对子结构进行编码之外。

        当 Ed Codd 在 1969-1970 年开发关系模型时,他特别定义了一个标准形式,它不允许这种表格的嵌套。范式后来被称为第一范式。然后他继续证明,对于每个数据库,都有一个第一范式的数据库表达相同的信息。

        为什么要为此烦恼?嗯,第一范式的数据库允许对所有数据进行键控访问。如果您提供表名、该表的键值和列名,则数据库将最多包含一个包含一项数据的单元格。

        如果您允许单元格包含列表或表格或任何其他集合,现在您无法提供对子项的键控访问,除非完全重新设计键的概念。

        对所有数据的键控访问是关系模型的基础。没有这个概念,模型就不是关系型的。至于为什么关系模型是一个好主意,以及这个好主意的局限性是什么,你必须看看 50 年来积累的关系模型经验。

        【讨论】:

        • 这是“赤裸裸的”“具体细节”的原因。
        【解决方案8】:

        我只是将它存储为 CSV,如果它是简单的值,那么它应该是你所需要的(XML 非常冗长,序列化到/从它可能会过大,但这也是一个选项)。

        这里是good answer,了解如何使用 LINQ 提取 CSV。

        【讨论】:

        • 我想到了。这仍然意味着我必须序列化和反序列化......但我怀疑这是可行的。我希望有一些宽恕的方式来做我想做的事,但我怀疑没有。
        • capnproto.org 是一种不必序列化和反序列化的方法,同样快速(与 csv 或 xml 相比),以防您选择的语言不支持 capnproto msgpack.org/index.html
        【解决方案9】:

        答案中只有一个选项未提及。您可以反规范化您的数据库设计。所以你需要两张桌子。一个表包含正确的列表,每行一个项目,另一个表在一列中包含整个列表(例如,逗号分隔)。

        这是“传统”数据库设计:

        List(ListID, ListName) 
        Item(ItemID,ItemName) 
        List_Item(ListID, ItemID, SortOrder)
        

        这里是非规范化表:

        Lists(ListID, ListContent)
        

        这里的想法 - 您使用触发器或应用程序代码维护 Lists 表。每次修改 List_Item 内容时,列表中的相应行都会自动更新。如果您主要阅读列表,它可以工作得很好。优点 - 您可以在一份声明中阅读列表。缺点 - 更新需要更多时间和精力。

        【讨论】:

          【解决方案10】:

          因为很多答案,我非常不愿意选择我最终决定走的路。虽然他们增加了对什么是 SQL 及其原理的理解,但我决定成为一名亡命之徒。我也不愿发表我的发现,因为对于某些人来说,向违反规则的人发泄挫败感比理解普遍真理很少。

          我已经对其进行了广泛的测试,在我的具体情况下,它比使用数组类型(PostgreSQL 慷慨提供)或查询另一个表更有效。

          这是我的答案: 通过利用列表中每个项目的固定长度,我已经成功地将列表实现到 PostgreSQL 中的单个字段中。假设每个项目都是 ARGB 十六进制值的颜色,这意味着 8 个字符。因此,您可以通过乘以每个项目的长度来创建最多 10 个项目的数组:

          ALTER product ADD color varchar(80)
          

          如果您的列表项长度不同,您始终可以使用 \0 填充填充

          注意:显然这不一定是十六进制数的最佳方法,因为整数列表会消耗更少的存储空间,但这只是为了通过使用分配给每个项目的固定长度来说明数组的这种想法。

          原因: 1/ 非常方便:在子字符串 i*n, (i +1)*n 处检索项目 i。 2/ 没有跨表查询的开销。 3/ 在服务器端更高效、更节省成本。该列表就像客户端必须拆分的迷你 blob。

          虽然我尊重遵守规则的人,但许多解释都是非常理论化的,并且经常无法承认,在某些特定情况下,尤其是在以低延迟解决方案实现成本最优时,一些细微的调整是非常受欢迎的。

          “上帝保佑它违反了一些神圣的SQL原则”:在背诵规则之前采取更加开放和务实的态度总是要走的路。否则你可能会像一个坦率的狂热者一样在被天网抹去之前背诵Three Laws of Robotics

          我不假装这个解决方案是一个突破,也不假装它在可读性和数据库灵活性方面是理想的,但它肯定可以在延迟方面为您提供优势。

          【讨论】:

          • 但这是一个非常特殊的情况:固定数量的固定长度项目。即使这样,它也比标准 SQL 更难进行简单的搜索,例如“所有产品至少具有颜色 x”。
          • 正如我多次说过的,我不将它用于颜色,我使用它的字段不应该被索引或用作条件,但它是一个关键的
          • 我知道,我试图表明这是非常具体的。如果有任何小的附加要求潜入其中,它很快就会变得比标准解决方案更尴尬。绝大多数想在一个数据库字段中存储列表的人可能最好不要这样做。
          【解决方案11】:

          我所做的是,如果需要存储的列表很小,那么我只需将其转换为字符串,然后在需要时将其拆分。 python中的示例-

          for y in b:
              if text1 == "":
                  text1 = y
              else:
                  text1 = text1 + f"~{y}"
          

          然后当我需要它时,我只需从数据库中调用它 -

          out = query.split('~')
          print(out)
          

          这将返回一个列表,并将一个字符串存储在数据库中。但如果您要在列表中存储大量数据,那么创建一个表是最好的选择。

          【讨论】:

            【解决方案12】:

            如果您真的想将它存储在列中并使其可查询,那么现在很多数据库都支持 XML。如果不查询,您可以将它们存储为逗号分隔值,并在需要分隔它们时使用函数解析它们。我同意其他所有人的观点,但如果您希望使用关系数据库,那么规范化的很大一部分就是像这样分离数据。我并不是说所有数据都适合关系数据库。如果您的大量数据不适合模型,您可以随时查看其他类型的数据库。

            【讨论】:

              【解决方案13】:

              我认为在某些情况下,您可以在数据库中创建一个 FAKE 商品“列表”,例如商品有几张图片来显示其详细信息,您可以将所有图片的 ID 以逗号分隔并存储将字符串导入数据库,然后您只需要在需要时解析字符串。我现在正在做一个网站,我打算用这种方式。

              【讨论】:

                【解决方案14】:

                您可以将其存储为看起来像列表的文本,并创建一个可以将其数据作为实际列表返回的函数。示例:

                数据库:

                 _____________________
                |  word  | letters    |
                |   me   | '[m, e]'   |
                |  you   |'[y, o, u]' |  note that the letters column is of type 'TEXT'
                |  for   |'[f, o, r]' |
                |___in___|_'[i, n]'___|
                

                还有列表编译器函数(用python编写,但它应该很容易翻译成大多数其他编程语言)。 TEXT 表示从 sql 表加载的文本。从包含列表的字符串返回字符串列表。如果您希望它返回整数而不是字符串,请使模式等于'int'。 'string'、'bool' 或 'float' 也是如此。

                def string_to_list(string, mode):
                    items = []
                    item = ""
                    itemExpected = True
                    for char in string[1:]:
                        if itemExpected and char not in [']', ',', '[']:
                            item += char
                        elif char in [',', '[', ']']:
                            itemExpected = True
                            items.append(item)
                            item = ""
                    newItems = []
                    if mode == "int":
                        for i in items:
                            newItems.append(int(i))
                
                    elif mode == "float":
                        for i in items:
                            newItems.append(float(i))
                
                    elif mode == "boolean":
                        for i in items:
                            if i in ["true", "True"]:
                                newItems.append(True)
                            elif i in ["false", "False"]:
                                newItems.append(False)
                            else:
                                newItems.append(None)
                    elif mode == "string":
                        return items
                    else:
                        raise Exception("the 'mode'/second parameter of string_to_list() must be one of: 'int', 'string', 'bool', or 'float'")
                    return newItems
                

                这里还有一个列表到字符串的函数,以备不时之需。

                def list_to_string(lst):
                    string = "["
                    for i in lst:
                        string += str(i) + ","
                    if string[-1] == ',':
                        string = string[:-1] + "]"
                    else:
                        string += "]"
                    return string
                

                【讨论】:

                  【解决方案15】:

                  想象一下你祖母的一盒食谱,全都写在索引卡上。这些食谱中的每一个都是一份成分列表,这些成分本身就是成对的物品和数量。如果您创建一个配方数据库,您将不需要为配方名称创建一个表和另一个表,其中每种成分都是单独的记录。这听起来就像我们在这里所说的。如果我看错了什么,我深表歉意。

                  来自 Microsoft 的 T-SQL 基础知识:

                  属性的原子性是主观的,就像 集合的定义是主观的。例如,员工应该 雇员关系中的姓名用一个属性表示 (全名)、两个(名字和姓氏)或三个(名字、 中间名和姓氏)?答案取决于应用程序。如果 应用程序需要操作员工姓名的各个部分 分开(例如出于搜索目的),打破它们是有意义的 分开;否则,它不会。

                  因此,如果您需要通过 SQL 操作坐标列表,则需要将列表中的元素拆分为单独的记录。但是您是否只是想存储一个列表并检索它以供其他软件使用,然后将列表存储为单个值更有意义。

                  【讨论】:

                    猜你喜欢
                    • 1970-01-01
                    • 2012-07-17
                    • 1970-01-01
                    • 2016-11-27
                    • 1970-01-01
                    • 2021-07-31
                    • 2011-01-19
                    相关资源
                    最近更新 更多