为什么关系数据库不支持以嵌套格式返回信息?


46

假设我正在建立一个博客,希望发布和发表评论。因此,我创建了两个表,一个具有自动递增整数“ id”列的“ posts”表,以及一个具有外键“ post_id”的“ comments”表。

然后,我想运行可能是我最常见的查询,即检索帖子及其所有评论。对于关系数据库而言,它是相当新的东西,对我而言,最明显的方法是编写看起来像这样的查询:

SELECT id, content, (SELECT * FROM comments WHERE post_id = 7) AS comments
FROM posts
WHERE id = 7

这会给我我想要的帖子的ID和内容,以及所有相关的注释行,它们整齐地打包在一个数组中(嵌套表示形式,就像您在JSON中使用的一样)。当然,SQL和关系数据库不是这样工作的,它们可以得到的最接近的结果是在“帖子”和“注释”之间进行联接,这将返回很多不必要的数据重复(重复相同的帖子信息)在每一行中),这意味着要花费大量的时间在数据库上以将它们放在一起,也需要花费我的ORM来解析和撤消所有内容。

即使我指示我的ORM急切地加载帖子的评论,最好的办法是调度一个对该帖子的查询,然后进行第二个查询以检索所有评论,然后将它们放到客户端,效率也不高。

我知道关系数据库是经过验证的技术(地狱,它们比我还旧),并且在过去的几十年中,对它们进行了大量研究,我敢肯定,它们(以及SQL标准)旨在按其功能运行,但我不确定为什么上面概述的方法不可行。在我看来,这是实现记录之间最基本关系之一的最简单,最明显的方法。为什么关系数据库不提供这样的功能?

(免责声明:我主要使用Rails和NoSQL数据存储来编写Web应用程序,但是最近我一直在尝试Postgres,我实际上非常喜欢它。我并不是要攻击关系数据库,只是感到困惑。)

我不是在问如何优化Rails应用程序,或如何在特定数据库中解决此问题。我在问为什么SQL标准在我看来违反直觉和浪费时会以这种方式工作。SQL的原始设计者希望他们的结果看起来像这样有一定的历史原因。


1
并非所有规范都以这种方式工作。hibernate / nhibernate允许指定连接,并且可以渴望从单个查询中加载整个对象树。
森·冈萨雷斯

1
同样,尽管讨论的重点很有趣,但我不确定如果不与ansi sql专家见面,这是否真的可以解决?
Nathan Gonzalez

@nathan:是的,不是全部。我一直在使用Sequel,它可以让您选择针对给定查询(docs)的首选方法,但是它们仍然鼓励多查询方法(出于性能原因,我想)。

5
因为RDBMS旨在存储和检索集,所以它不打算返回数据进行显示。就像MVC一样思考-为什么它会尝试以降低模型使用速度或更困难为代价来实现视图?RDBMS具有NoSQL数据库无法提供的优点(反之亦然)-如果您正在使用它,因为它是解决问题的正确工具,则不会要求它返回准备显示的数据。

1
他们确实看到了xml
Ian

Answers:


42

CJ Date在SQL和关系理论的第7章和附录B中对此进行了详细介绍。没错,在关系理论中,没有什么能禁止属性的数据类型成为关系本身,只要它在每一行上都是相同的关系类型即可。您的示例将符合条件。

但是伊达表示,这样的结构“通常-但并非总是-受约束”(即一个坏主意),因为关系层次结构是不对称的。例如,从嵌套结构到熟悉的“平面”结构的转换不能总是反向以重新创建嵌套。

如果允许关系值属性(RVA),则查询,约束和更新更加复杂,更难编写,并且RDBMS难以支持。

它还使数据库设计原则变得混乱,因为最佳的关系层次结构尚不明确。我们是否应该为给定供应商提供的零件设计带有嵌套RVA的供应商关系?还是为供应给定零件的供应商提供带有嵌套RVA的零件关系?还是同时存储两者,以便轻松运行不同类型的查询?

这是分层数据库面向文档的数据库模型所导致的相同难题。最终,访问嵌套数据结构的复杂性和成本促使设计人员冗余地存储数据,以便更轻松地通过不同的查询进行查找。关系模型不鼓励冗余,因此RVA可以违背关系建模的目标。

据我了解(我没有使用过它们),RelDataphor是支持关系值属性的RDBMS项目。


来自@dportas的评论:

结构化类型是SQL-99的一部分,Oracle支持这些结构化类型。但是它们不在基表的每一行的嵌套表中存储多个元组。常见的示例是“地址”属性,该属性似乎是基表的单个列,但还有街道,城市,邮政编码等其他子列。

嵌套表也受Oracle支持,并且它们确实允许基表的每一行有多个元组。但是我不知道这是标准SQL的一部分。并记住一个博客的结论:“我永远不会在CREATE TABLE语句中使用嵌套表。您将所有时间都花在取消嵌套上,使它们再次有用!”


3
我不想将一个关系实际存储在另一个关系中-它们将放在单独的表中并照常进行非规范化。我只是问为什么查询中不允许这种类型的结果嵌入,这在我看来比连接模型更直观。
PreciousBodilyFluids

结果集和表是一种。Date分别称它们为Relationrelvars(以此类推,42是整数,而变量x可以具有整数42的值)。相同的操作适用于关系和relvar,因此它们的结构需要兼容。
Bill Karwin 2011年

2
标准SQL确实支持嵌套表。它们被称为“结构化类型”。Oracle是具有此功能的一种DBMS。
nvogel

2
为避免数据重复,您必须以一种扁平的,数据重复的方式编写查询,这有点荒谬吗?
Eamon Nerbonne

1
@EamonNerbonne,关系运算的对称性。例如投影。如果我从RVA中选择了一些子属性,如何对结果集应用反向操作以重现原始层次结构?我发现日期的书页页293是谷歌图书,所以你可以看到什么,他写道:books.google.com/...
比尔Karwin

15

一些最早的数据库系统是基于Hierarchical Database模型的。这代表带有父级和子级的树状结构中的数据,就像您在此处建议的那样。HDMS在很大程度上被基于关系模型的数据库所取代。这样做的主要原因是RDBMS可以对层次结构数据库很难建立的“多对多”关系进行建模,并且RDBMS可以轻松地执行原始设计中不包含的查询,而HDBMS会限制您通过设计时指定的路径进行查询。

仍然有一些野蛮的分层数据库系统的示例,尤其是Windows注册表和LDAP。

一篇文章提供了对该主题的广泛介绍


10

我想您的问题确实集中在以下事实上:虽然数据库基于可靠的逻辑并设置理论基础,并且它们在(确保二维)集合中存储,处理和检索数据方面做得很好,但同时又确保了引用完整性,并发性以及其他许多功能,它们没有提供以所谓的面向对象格式或分层格式发送(和接收)数据的(附加)功能。

然后您声称“即使我指示我的ORM急切地加载帖子的评论,最好的做法是调度一个对该帖子的查询,然后进行第二个查询以检索所有评论,然后将它们放在一起客户端,这也是低效率的

我发现发送2个查询和接收2批结果的过程中没有发现效率低下的问题:

--- Query-1-posts
SELECT id, content 
FROM posts
WHERE id = 7


--- Query-2-comments
SELECT * 
FROM comments 
WHERE post_id = 7

我认为这是(几乎)最有效的方法(几乎,因为您实际上并不需要posts.id,也不是的所有列comments.*

正如Todd在评论中指出的那样,您不应该要求数据库返回准备好显示的数据。这是应用程序的工作。您可以编写(一个或几个)查询以获得每个显示操作所需的结果,这样就不会从数据库(db)到应用程序通过有线(或内存总线)发送的数据中出现不必要的重复。

我不能真正谈论ORM,但也许其中一些可以为我们完成这项工作。

类似的技术可以用于Web服务器和客户端之间的数据传递。使用了其他技术(例如缓存),因此数据库(或Web或其他服务器)不会因重复的请求而过载。

我的猜测是,像SQL这样的标准,如果只专注于某一领域而不是试图覆盖一个领域的所有领域,则是最佳选择。

另一方面,设置SQL标准的受托人将来可能会另作考虑,并为这种附加功能提供标准。但这不是一夜之间可以设计的。


1
我的意思是效率低下,因为我的应用程序必须承担两个数据库调用而不是一个的开销和延迟。除此之外,执行联接是否还只是以准备好显示的格式返回数据?还是使用数据库视图?如果愿意,您也可以通过简单地运行更多小的查询并将它们拼接到应用程序中来消除它们,但它们仍然是有用的工具。除了易于使用和性能更高之外,我认为我的建议与联接没有显着区别。

2
@Precious:运行多个查询不必增加任何开销。大多数数据库允许您单批提交多个查询,并从单个查询接收多个结果集。
Daniel Pryden 2011年

@PreciousBodilyFluids-ypercube的答案中的SQL代码段是单个查询,它将在单个数据库调用中发送并在单个响应中返回两个结果集。
Carson63000 2011年

5

我无法以适当的,有争议的答案回答问题,因此,如果我错了,请随意拒绝我(但请纠正我,以便我们学习新的知识)。我认为原因是关系数据库集中在关系模型上,而关系模型又基于我一无所知的所谓“一阶逻辑”。您可能要问的内容在概念上可能不适合建立在数学/逻辑框架上的关系数据库。此外,您所要求的内容通常可以通过图形数据库轻松解决,这提供了更多暗示,即数据库的底层概念与您要实现的目标相冲突。


5

我知道至少当您使用FOR XML时,SQLServer确实支持嵌套查询。

SELECT id, content, (SELECT * FROM comments WHERE post_id = posts.id FOR XML PATH('comments'), TYPE) AS comments
FROM posts
WHERE id = 7
FOR XML PATH('posts')

这里的问题不是缺少RDBMS的支持,而是缺少表中嵌套表的支持。

另外,什么阻止了您使用内部联接?

SELECT id, content, comments.*
FROM posts inner join comments on comments.post_id = posts.id
WHERE id = 7

您可以将内部联接实际看做一个嵌套表,只重复前两个字段的内容。我不必担心联接的性能,像这样的查询中唯一慢的部分是从数据库到客户端的io。仅当内容包含大量数据时,这才是问题。在这种情况下,我会建议两个查询,一个查询带有select id, content一个,另一个查询带有内部连接和select posts.id, comments.*。即使您有多个帖子,这也可以扩展,因为您仍然只使用2个查询。


问题解决了这个问题。您必须进行两次往返(不是最佳),或者必须在前两列中返回冗余数据(也不是最佳)。他想要最佳解决方案(在我看来,这并非不切实际)。
Scott Whitlock,

我知道,但是没有烂东西可以作为最佳解决方案。我唯一能争辩的是开销在哪里最小,以及它所依赖的地方。如果您需要最佳解决方案,请进行基准测试并尝试其他方法。根据具体情况,甚至XML解决方案都可能会变慢,而且我对NoSQL数据存储区并不熟悉,因此我不能说它是否具有与相似的东西for xml
Dorus

5

实际上,Oracle支持您想要的内容,但是您需要使用“ cursor”关键字来包装子查询。通过打开的游标获取结果。例如,在Java中,注释将显示为结果集。有关更多信息,请参见Oracle的“ CURSOR表达式”文档。

SELECT id, content, cursor(SELECT * FROM comments WHERE post_id = 7) AS comments
FROM posts
WHERE id = 7

1

有些确实支持嵌套(分层)。

如果您想要一个查询,则可以有一个自我引用自己的表。一些RDMS支持此概念。例如,使用SQL Server可以使用通用表表达式(CTE)进行层次查询。

在您的情况下,帖子的级别为0,然后所有评论的级别为1。

其他选项是2个查询或一个Join,其中包含有关返回的每条记录的一些额外信息(其他人已经提到)。

分层示例:

https://stackoverflow.com/questions/14274942/sql-server-cte-and-recursion-example

在上面的链接中,EmpLevel显示嵌套级别(或层次结构)。


我在SQL Server中找不到有关子结果集的任何文档。即使使用CTE。通过结果集,我的意思是带有足够强类型列的数据行。您可以在答案中添加参考吗?
SandRock 2014年

@SandRock-数据库将从SQL查询发回单个结果集。通过标识查询本身中的级别,您可以创建将要处理的分层或嵌套结果集。我认为,目前最接近返回嵌套数据的方法。
乔恩·雷诺2014年

0

很抱歉,我不确定我是否完全了解您的问题。

在MSSQL中,您只能执行2条SQL语句。

SELECT id, content
FROM posts
WHERE id = 7

SELECT * FROM comments WHERE post_id = 7

它将同时返回您的2个结果集。


询问该问题的人说,这效率较低,因为它导致两次数据库往返,并且由于开销,我们通常尝试将往返次数降至最低。他想进行一次往返旅行,并把两张桌子都拿回来。
Scott Whitlock


0

RDBM是基于理论的,并且坚持理论。这样可以保持良好的一致性,并在数学上证明其可靠性。

因为该模型很简单,并且又是基于理论的,所以它使人们可以轻松进行优化并实现许多实现。这与NoSQL不同,NoSQL的每个人都略有不同。

过去曾尝试创建分层数据库,但是IIRC(似乎无法在Google上找到它)存在问题(想到循环和平等)。


0

您有特定需要。最好以所需的格式从数据库中提取数据,因此您可以根据需要进行处理。

有些事情数据库做得不好,但是无论如何也不是要建立它们来做。当前的建议是将格式留给其他应用程序使用,但这并不能说明为什么无法完成格式化的理由。

我唯一反对您建议的论点是能够以“ sql”方式处理此结果集。在数据库中创建结果而不能够使用它或在某种程度上进行操作将是一个坏主意。假设我以您的建议方式创建了一个视图,如何将其包含在另一个select语句中?数据库喜欢取得结果并用结果来做事。我如何将其加入另一个桌子?我如何将您的结果集与另一个进行比较?

那么RDMS的好处就是sql的灵活性。从表中选择数据的语法非常接近系统中用户或其他对象的列表(至少这是目标。)。不确定做完全不同的事情是否有意义。他们甚至没有使他们非常高效地处理过程代码/游标或BLOBS数据。


0

在我看来,这主要是由于SQL和执行聚合查询的方式-聚合函数和分组在大型二维行集中执行以返回结果。从一开始就采用这种方式,而且速度非常快(大多数NoSQL解决方案的聚合速度都很慢,并且依赖于非规范化模式而不是复杂的查询)

当然,PostgreSQL具有面向对象数据库的某些功能。根据此邮件(message),您可以通过创建自定义聚合来实现所需的功能。

我个人使用的是诸如Doctrine ORM(PHP)之类的框架,这些框架在聚合应用程序端进行操作并支持诸如延迟加载之类的功能以提高性能。


0

PostgreSQL确实支持多种结构化数据类型,包括ArraysJSON。使用SQL或一种嵌入式过程语言,您可以构建具有任意复杂结构的值,并将其返回给您的应用程序。您也可以使用任何结构化类型的列创建表,尽管您应仔细考虑是否不必要地对设计进行了非规范化。


1
在先前的13个答案中,这似乎并没有提供任何实质性的要点和解释
gna

这个问题专门提到了JSON,这个答案是唯一指出可以在至少一个RDBMS的查询中返回JSON的答案。我宁愿评论这个问题,说它是基于错误的前提,因此不能指望任何明确的答案。但是,StackExchange不允许我这样做。
乔纳森·罗杰斯
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.