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


115

因此,按照Mehrdad对一个相关问题的回答,我得到一个“适当的”数据库表列不存储列表的信息。相反,您应该创建另一个表,该表可以有效地保存所述列表的元素,然后直接或通过联结表链接到该列表。但是,我要创建的列表类型将由唯一项组成(与链接问题的成果不同)例)。此外,列表中的项目已明确排序-这意味着如果将元素存储在另一个表中,则每次访问它们时都必须对它们进行排序。最后,该列表基本上是原子性的,因为无论何时我想访问该列表,我都想访问整个列表,而不只是其中的一部分-因此,必须发出数据库查询以将各个部分汇总在一起似乎很愚蠢。名单。

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

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

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

谢谢大家!

约翰

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

另外,为了让您更好地了解自己的工作情况:在数据库中,我有一个功能表,该表将具有(x,y)对的列表。(该表还将包含其他信息,这些信息对我们的讨论没有影响。)我将永远不需要看到(x,y)对列表的一部分。而是,我将所有这些都放置在屏幕上。我将允许用户拖动节点以偶尔更改值或向图中添加更多值。

Answers:


183

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

我了解您认为创建另一个表来存储该列表是很愚蠢的,但这正是关系数据库所做的。您没有充分的理由就在艰苦的战斗中违反了关系数据库设计的最基本原则之一。由于您声明自己只是在学习SQL,因此我强烈建议您避免这种想法,并坚持由经验丰富的SQL开发人员向您推荐的做法。

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

冒着过于简化的风险,数据库规范化是根据数据定义数据库的过程,以便您可以针对它编写明智,一致的查询并能够轻松地对其进行维护。规范化旨在限制数据中的逻辑不一致和损坏,并且有很多层次。维基百科上有关数据库规范化的文章实际上相当不错。

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

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

最后一点显然是这里的重点。SQL旨在为您存储集合,而不是为您提供用于存储集合的“存储桶”。是的,有可能做。不,世界不会终结。但是,您已经不了解如何通过立即跳入ORM来理解SQL及其相关的最佳实践。LINQ to SQL很棒,就像图形计算器一样。本着同样的精神,但是,他们应该被用作替代知道如何处理他们实际使用的工作。

您的列表现在可能完全是“原子的”,并且对于此项目可能不会更改。但是,您会养成在其他项目中做类似事情的习惯,最终(可能很快)就会遇到一种情况,您现在正在准备一份快速,容易的清单完全不合适的方法。为要存储的内容创建正确的表没有太多其他工作,并且当其他SQL开发人员看到您的数据库设计时,也不会嘲笑您。此外,LINQ to SQL将查看您的关系并自动为您的列表提供适当的面向对象的接口。您为什么要放弃ORM所提供的便利,以便您可以执行非标准且不明智的数据库黑客活动?


17
因此,您坚信将列表存储在列中不是一个好主意,但是您没有提及原因。由于我刚开始使用SQL,因此“为什么”的一小部分确实非常有帮助。例如,您说我在“无缘无故地打一场艰苦的战斗,违反关系数据库设计的最基本原则之一”……那么原则是什么?为什么我引用“不好”的原因?(特别是我列表的排序性质和原子性质)
JnBrymn 2010年

6
基本上,它归结为多年的经验,这些经验都被总结为最佳实践。所讨论的基本原理称为第一范式
Toby 2010年

1
谢谢亚当。非常丰富。关于您的最后一个问题的要点。
JnBrymn 2010年

8
“ […]并且,当其他SQL开发人员看到您的数据库设计时,您将不会嘲笑您。” 有很好的理由要尊重第一范式(和你的回答中提到它们),但同龄人的压力/“这就是事情是如何在这里做”是不是其中之一。
林恩

5
我们已经每天在数据库列中存储一堆列表。它们分别称为“ char”和“ varchar”。当然在Postgres中,它们也称为文本。1NF真正在说的是,您永远都不想将任何字段中的信息拆分为较小的字段,如果这样做,您就傻傻了。因此,您无需存储名称,而是存储个人名称,中间名和姓氏(取决于本地化),然后将它们缝合在一起。否则,我们根本不会存储字符串。另一方面,他想要的只是一串字符串。并且有很多方法可以做到这一点。
HaakonLøtveit17年

15

您可以完全忘记SQL,并采用“ NoSQL”方法。 RavenDBMongoDBCouchDB是可能的解决方案。使用NoSQL方法,您就不会使用关系模型。您甚至不会受限于模式。


11

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

下面是我在示例中使用的表格(该表格包括您为特定女友提供的昵称。每个女友都有一个唯一的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;

5

简单的答案:当且仅当您确定该列表将始终用作列表时,然后在列表的末尾使用一个字符(例如“ \ 0”)将其不使用,发短信,并将其存储。然后,当您检索它时,可以除以'\ 0'。当然,还有其他处理此问题的方法,但是这些方法取决于您的特定数据库供应商。

例如,您可以将JSON存储在Postgres数据库中。如果您的列表是文本,而您只希望该列表没有更多麻烦,那是一个合理的折衷方案。

其他人则提出了有关序列化的建议,但我并不认为序列化是个好主意:关于数据库的部分好处是,可以用几种语言编写的多个程序可以相互通信。如果Lisp程序想要加载,使用Java格式序列化的程序将无法很好地完成工作。

如果您想通过一种好方法来执行此类操作,通常可以使用数组或相似类型。例如Postgres,提供数组作为一种类型,如果需要的话,让您存储文本数组,并且使用JSON的MySqlMS SQL也有类似的技巧,而IBM的DB2也提供了数组类型(在它们的自己有帮助文档)。如果不需要的话,这不会那么普遍。

您走那条路所要做的就是将列表的概念按顺序排列为一堆东西。至少名义上,数据库将字段视为单个值。但是,如果仅此而已,那么就应该这样做。您必须自己做出价值判断。


3

除了其他人都说过的话,我建议您比现在更长时间地分析您的方法。这是目前的项目是独一无二的情况。这是目前是诉诸项目将需要一个新的列表的情况。这几乎是必需的,该清单是目前短。即使我没有域的详细信息,也可以认为这些要求可能会改变。如果序列化您的列表,那么您将以僵化的方式进行烘焙,这在更规范的设计中是没有必要的。顺便说一句,这并不一定意味着完整的Many:Many关系。您可能只有一个子表,该表具有指向父级的外键和该项目的字符列。

如果您仍然想走序列化列表的道路,可以考虑将列表存储为XML。某些数据库(例如SQL Server)甚至具有XML数据类型。我建议使用XML的唯一原因是,按照定义,此列表必须简短。如果列表很长,那么通常对其进行序列化是一种糟糕的方法。如果您使用CSV路由,则需要考虑包含定界符的值,这意味着您不得不使用带引号的标识符。假设列表很短,则无论使用CSV还是XML都不会有太大的不同。


+1以预测未来的变化-始终将数据模型设计为可扩展的。
coolgeek 2010年

2

我只是将其存储为CSV,如果它是简单的值,那么它就应该是您所需要的(XML非常冗长,并且向/从它进行序列化可能会过大,但这也是一种选择)。

对于如何使用LINQ提取CSV,这是一个很好的答案


我虽然如此。这仍然意味着我必须序列化和反序列化...但是我怀疑这是可行的。我希望有某种宽容的方式来做我想做的事,但我怀疑没有。
JnBrymn 2010年

capnproto.org是一种无需序列化和反序列化的方法,以类似的方式(与csv或xml相比)可以快速进行,以防您选择的语言不支持capnproto msgpack.org/index.html
VoronoiPotato

2

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

如果您始终需要列表,则可以将其作为定界列表存储在列中。即使在这种情况下,除非有非常特殊的原因,否则将其存储在查找表中。


1

答案中只提到一个选项。您可以对数据库设计进行非规范化。因此,您需要两个表。一个表包含适当的列表,每行一个项目,另一个表在一个列中包含整个列表(例如,用逗号分隔)。

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

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

这是非规范化表:

Lists(ListID, ListContent)

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


0

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


0

我认为在某些情况下,您可以在数据库中创建商品的FAKE“列表”,例如,商品中有一些图片可以显示其详细信息,您可以将所有图片的ID(以逗号分隔)连接起来并将字符串存储到DB,那么您只需要在需要时解析该字符串即可。我现在正在网站上,并且打算使用这种方式。


0

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

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

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

ALTER product ADD color varchar(80)

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

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

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

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

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

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


但这是一个非常特殊的情况:固定数量的固定长度项目。即使这样,它也比标准SQL更加难以进行简单的搜索,例如“所有产品至少具有颜色x”。
Gert Arnold

正如我多次指出的那样,我不将其用于颜色,不应将其用于颜色的字段进行索引或用作条件,但它是至关重要的
Antonin GAVREL,

我知道,我试图表明这是非常具体的。如果有任何其他小的要求,它很快就会比标准解决方案更加尴尬。绝大多数倾向于将列表存储在一个db字段中的人最好还是不要这样做。
Gert Arnold

0

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

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

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

如果您允许一个单元格包含一个列表,一个表或任何其他集合,那么您将无法提供对子项的带键访问,而无需完全重新设计键的概念。

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


-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
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.