如何将列表存储在数据库表的列中

时间:2010-06-18 14:20:10

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

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

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

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

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

全部谢谢!

约翰

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

另外,为了让您更好地了解我的目标:在我的数据库中,我有一个Function表,它将包含(x,y)对的列表。 (该表还有其他信息对我们的讨论没有影响。)我永远不需要看到(x,y)对列表的一部分。相反,我将采取所有这些并在屏幕上绘制它们。我将允许用户拖动节点以偶尔更改值或向绘图添加更多值。

13 个答案:

答案 0 :(得分:148)

不,没有“更好”的方法将一系列项目存储在一个列中。关系数据库设计为具体,以存储每行/列组合的一个值。为了存储多个值,您必须将列表序列化为单个值以进行存储,然后在检索时对其进行反序列化。没有其他方法可以做你正在谈论的事情(因为你所谈论的是一个坏主意,通常应该永远不会完成)。

我知道您认为创建另一个表来存储该列表很愚蠢,但这正是关系数据库所做的事情。你正在进行一场艰苦的战斗并且没有任何理由违反关系数据库设计的最基本原则之一。既然你声明你刚刚学习SQL,我会强烈建议你避免这个想法,并坚持使用经验丰富的SQL开发人员推荐给你的做法。

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

存在过度简化事物的风险,数据库规范化是根据数据 定义数据库的过程,因此您可以针对它编写合理,一致的查询并能够对其进行维护容易。规范化旨在限制数据中的逻辑不一致和损坏,并且有很多级别。关于database normalization的维基百科文章实际上非常好。

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

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

最后一点显然是这里的重点。 SQL旨在为您存储您的集合,而不是为您提供一个“桶”供您自己存储集。是的,这是可能的。不,世界不会结束。但是,您已经通过立即使用ORM来了解SQL以及与之相关的最佳实践。 LINQ to SQL非常棒,就像图形计算器一样。然而,同样地,他们应该用来代替他们所采用的流程如何实际工作。

您的列表现在可能完全是“原子”,并且此项目可能不会更改。但是,你会养成在其他项目中做类似事情的习惯,并且你最终(很可能很快)遇到一个场景,你现在正在拟合你的快捷n列表中的列表接近完全不合适的方法。在为您要存储的内容创建正确的表时,没有太多额外的工作,并且当他们看到您的数据库设计时,您不会被其他SQL开发人员嘲笑。此外,LINQ to SQL将会看到您的关系并为您的列表自动提供正确的面向对象的接口。为什么你会放弃ORM为你提供的便利,以便你可以执行非标准和不明智的数据库hackery?

答案 1 :(得分:11)

你可以一起忘记SQL,并采用“NoSQL”方法。 RavenDBMongoDBCouchDB会想到可能的解决方案。使用NoSQL方法,您不使用关系模型。您甚至不受约束模式。

答案 2 :(得分:8)

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

下面给出了我在示例中使用的表格(该表格包含您给予特定女友的昵称。每位女友都有一个唯一的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;

答案 3 :(得分:3)

除了其他人所说的内容之外,我建议你用更长远的方式分析你的方法。 目前项目是唯一的。 目前是诉诸项目需要新列表的情况。几乎要求列表当前简短。即使我没有域名细节,但认为这些要求可能会发生变化并不是一件容易的事。如果您对列表进行序列化,那么您将陷入一种在更规范化的设计中不必要的不​​灵活性。顺便说一句,这并不一定意味着完整的许多:许多关系。您可以只有一个子表,其中包含父项的外键和项目的字符列。

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

答案 4 :(得分:2)

如果您需要查询列表,请将其存储在表格中。

如果您一直想要列表,可以将其存储为列中的分隔列表。即使在这种情况下,除非您有非特定原因,否则请将其存储在查找表中。

答案 5 :(得分:2)

简单回答:如果且仅当您确定该列表将始终用作列表时,请将该列表一起加入到您的角色(例如' \ 0&# 39;)将不会在文本中使用,并存储。然后,当您检索它时,您可以按' \ 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 want,使用JSON的MySqlMS SQL也有类似的技巧,而IBM's DB2也提供了数组类型(在他们自己的有用的中)文档)。如果不需要这种情况,这种情况就不那么常见了。

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

答案 6 :(得分:1)

我只是将它存储为CSV,如果它是简单的值,那么它应该是您所需要的全部(XML非常详细,并且序列化到/从它可能是过度的,但这也是一个选项)。

这是关于如何使用LINQ拉出CSV的good answer

答案 7 :(得分:1)

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

这是'传统'数据库设计:

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

这里是非规范化表:

Lists(ListID, ListContent)

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

答案 8 :(得分:0)

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

答案 9 :(得分:0)

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

答案 10 :(得分:0)

由于很多答案,我非常不愿选择我最终决定走的路。尽管他们对SQL及其原理有了更多的了解,但我还是决定取缔非法。我也很犹豫地发表自己的发现,因为有些发现更重要的是让挫败感破坏那些违反规则的人,而不是理解很少有普遍真理。

我已经对其进行了广泛的测试,在我的特定情况下,它比使用数组类型(通常由PostgreSQL提供)或查询另一个表的效率更高。

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

ALTER product ADD color varchar(80)

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

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

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

尽管我尊重人们遵循规则,但许多解释都是非常理论化的,并且常常无法承认,在某些特定情况下,尤其是在针对使用低延迟解决方案实现成本最优的目标时,有些细微的调整绝对值得欢迎。

“上帝禁止它违反了SQL的神圣神圣原则”:在引用规则之前采取更加开放和务实的方法始终是正确的方法。否则,您最终可能像一个坦率的狂热者,在被天网掩盖之前背诵Three Laws of Robotics

我不认为此解决方案是突破性的,也不是从可读性和数据库灵活性的角度来看是理想的,但是在延迟方面,它无疑可以给您带来优势。

答案 11 :(得分:0)

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

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

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

如果您允许单元格包含列表,表或任何其他集合,那么现在您不能在不完全修改键的概念的情况下提供对子项的键访问。

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

答案 12 :(得分:-1)

您可以将其存储为类似于列表的文本,并创建一个可以将其数据作为实际列表返回的函数。例如:

数据库:

 _____________________
|  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而不是字符串,请使mode等于'int'。同样使用“字符串”,“布尔”或“浮动”。

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