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

因此,根据 梅赫达德对一个相关问题的回答,我 去拿认为“适当的”数据库表列不存储列表。相反,您应该创建另一个表,有效地保存所述列表的元素,然后直接或通过连接表链接到它。但是,我想创建的列表类型将由唯一的项组成(不同于链接问题的 水果示例)。此外,我的列表中的项是显式排序的——这意味着如果我将元素存储在另一个表中,那么每次访问它们时都必须对它们进行排序。最后,这个列表基本上是原子的,因为无论何时我想访问这个列表,我都想访问整个列表,而不仅仅是其中的一部分——所以发出一个数据库查询来收集列表的各个部分看起来很愚蠢。

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

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

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

谢谢大家!

约翰

更新: 所以在我得到的第一批答案中,我看到“你可以走 CSV/XML 路线... ... 但是不要!”.所以现在我想知道为什么。给我一些好的推荐信。

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

272914 次浏览

我只是将它存储为 CSV,如果它是简单的值,那么它应该就是您所需要的全部(XML 非常冗长,序列化到/从它进行序列化可能有些夸张,但这也是一种选择)。

这里有一个 答得好关于如何使用 LINQ 拉出 CSV。

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

我理解您认为创建另一个表来存储该列表是愚蠢的,但这正是关系数据库所做的。你正在进行一场艰苦的斗争,并且毫无理由地违反了关系数据库设计的最基本原则之一。既然你说你只是在学习 SQL,我建议你避免这种想法,坚持经验丰富的 SQL 开发人员推荐给你的实践。

你违反的原则叫做 第一正规形式这是数据库规范化的第一步。

冒着把事情过于简单化的风险,数据库规范化是根据数据 来定义数据库的过程,这样你就可以针对它编写合理、一致的查询,并且能够轻松地维护它。规范化的设计目的是限制数据中的逻辑不一致性和损坏,它有很多级别。维基百科上关于 数据库规范化的文章实际上相当不错。

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

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

最后一点显然是这里的重点。SQL 的设计目的是为您存储集,而不是为您提供一个“桶”,让您自己存储集。是的,这是可能的。不,世界不会毁灭。然而,通过立即使用 ORM,您已经在理解 SQL 以及与之相关的最佳实践方面受到了损害。LINQtoSQL 非常棒,就像图形计算器一样。然而,在同样的情况下,它们应该使用 没有来替代了解它们所使用的流程实际上是如何工作的。

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

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

如果您总是需要该列表,则可以将其作为带分隔符的列表存储在列中。即使在这种情况下,除非有非常特殊的原因,否则也要将其存储在查找表中。

您可以完全忘记 SQL,而采用“ NoSQL”方法。RavenDBMongoDBCouchDB作为可能的解决方案跃入脑海。使用 NoSQL 方法时,你不会使用关系模型。.您甚至不受模式的约束。

如果您确实希望将它存储在一个列中并使其可查询,那么现在许多数据库都支持 XML。如果不进行查询,可以将它们存储为逗号分隔的值,并在需要将它们分隔开时使用函数将它们解析出来。我同意其他所有人的观点,但是如果你想使用一个关系数据库,那么标准化的很大一部分就是像这样分离数据。但我并不是说所有数据都符合关系数据库。如果您的许多数据不符合模型,您总是可以查看其他类型的数据库。

除了其他人所说的,我建议你比现在更长期地分析你的方法。这是 目前的情况下,项目是唯一的。这是 目前的情况下,重新采用的项目将需要一个新的清单。几乎要求列表 目前短。尽管我没有领域的具体信息,但是认为这些需求可能会发生变化并不困难。如果您序列化您的列表,那么您就会产生一种在更规范化的设计中不必要的僵硬性。顺便说一句,这并不一定意味着一个完整的许多: 许多关系。您可以只有一个子表,该子表具有父表的外键和该项的字符列。

如果您仍然希望继续这种序列化列表的方式,那么可以考虑将列表存储为 XML。有些数据库(如 SQLServer)甚至具有 XML 数据类型。我建议使用 XML 的唯一原因是,几乎从定义上讲,这个列表需要简短。如果列表很长,那么通常序列化它是一种糟糕的方法。如果使用 CSV 路由,则需要考虑包含分隔符的值,这意味着必须使用带引号的标识符。假设列表很短,那么使用 CSV 还是 XML 可能没有太大区别。

只有一个答案没有提到。你可以反常规化你的数据库设计。所以你需要两张桌子。一个表包含正确的列表,每行一个项目,另一个表包含一列中的整个列表(例如,昏迷分隔)。

这是“传统”的 DB 设计:

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

这里是非标准化表:

Lists(ListID, ListContent)

这里的想法是——使用触发器或应用程序代码维护 List 表。每次修改 List _ Item 内容时,List 中的适当行都会自动更新。如果你大部分时间阅读列表,它可以工作得很好。优点——你可以在一个语句中读取列表。缺点-更新需要更多的时间和精力。

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

我在这个例子中使用的表格如下所示(这个表格包括你给你特定的女朋友起的昵称)。每个女朋友都有一个独特的 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;

我认为在某些情况下,你可以在数据库中创建一个假的物品“列表”,例如,商品有一些图片来显示它的细节,你可以连接所有的 ID 图片分割逗号和存储到数据库中的字符串,然后你只需要解析字符串时,你需要它。我现在在一个网站上工作,我计划使用这种方式。

简单的回答: 如果,并且只有当,你确定列表将始终作为一个列表使用,然后将列表与一个字符(如’0’)连接在一起,这个字符将永远不会在文本中使用,并存储它。然后当你检索它,你可以分为’0’。当然还有其他方法来处理这些东西,但是这些方法取决于您的特定数据库供应商。

例如,可以将 JSON 存储在 Postgres 数据库中。如果你的列表是文本的,并且你只是想要这个列表而没有进一步的麻烦,这是一个合理的妥协。

其他人冒险提出了序列化的建议,但我并不真的认为序列化是一个好主意: 数据库的一个好处是,几个用不同语言编写的程序可以相互交谈。如果一个 Lisp 程序想要加载它,那么使用 Java 格式序列化的程序不会做得很好。

如果您想要一个很好的方法来做这类事情,通常有数组或类似的类型可用。MySql微软 SQL使用 JSON 也有类似的技巧,IBM 的 DB2也提供了数组类型(在它们自己的 很有帮助文档中)。如果没有这个必要,这种情况就不会如此普遍。

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

我非常不愿意选择我最终决定走的路,因为有很多答案。虽然他们增加了对什么是 SQL 及其原则的更多理解,我还是决定成为一个不法之徒。我也在犹豫要不要把我的发现公布出来,因为对于一些人来说,更重要的是向违反规则的人发泄沮丧,而不是理解世界上几乎没有普遍的真理。

我对它进行了广泛的测试,在我的特定案例中,它比使用数组类型(PostgreSQL 慷慨提供)或查询另一个表都要高效得多。

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

ALTER product ADD color varchar(80)

如果列表项的长度不同,您总是可以用0填充空白

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

原因: 1/非常方便: 在子字符串 i * n,(i + 1) * n 处检索条目 i。 2/没有跨表查询的开销。 3/在服务器端更加高效和节约成本。这个列表就像一个迷你斑点,客户端将不得不对其进行分割。

虽然我尊重遵守规则的人,但许多解释都非常理论化,而且往往没有认识到,在某些特定情况下,尤其是在以低延迟解决方案的成本最优为目标时,一些小的调整是非常受欢迎的。

“上帝不允许它违反 SQL 的某些神圣原则”: 在背诵规则之前采用更加开放和务实的方法总是可行的。否则,你可能会像一个坦率的狂热者背诵 机器人三定律之前,被天网消灭

我不认为这个解决方案是一个突破,也不认为它在可读性和数据库灵活性方面是理想的,但是它肯定会在延迟方面给您带来优势。

许多 SQL 数据库允许表包含子表作为组件。通常的方法是允许其中一列的域为表。除此之外,还要使用一些类似 CSV 的约定,以 DBMS 所不知道的方式对子结构进行编码。

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

为什么要这么麻烦?第一种格式的数据库允许密钥访问所有数据。如果提供表名、表中的键值和列名,则数据库最多只包含一个单元格,其中包含一个数据项。

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

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

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

资料库:

 _____________________
|  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 表加载的文本。从包含 list 的字符串返回字符串列表。如果希望返回 int 而不是 string,那么 make mode 等于‘ 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

这里还有一个 list-to-string 函数,以备不时之需。

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

我所做的是,如果需要存储的 List 很小,那么我只需要将它转换成一个字符串,然后在需要时将其分割。 在 python 中的例子-

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

当我需要的时候,我就从数据库调用它

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

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

想象一下你祖母的一盒食谱,都写在索引卡片上。这些食谱中的每一个都是一个配料列表,这些配料本身就是按顺序排列的成对的物品和数量。如果创建一个食谱数据库,则不需要为食谱名称创建一个表,并创建另一个表,其中每个成分都是一个单独的记录。这就是我们要说的。如果我误解了什么,我道歉。

来自微软的 T-SQL 基础:

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

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