核心要点
$lookup
操作符,可以在两个或多个集合上执行类似LEFT-OUTER-JOIN的操作,从而实现类似关系型数据库的数据管理。然而,此操作符仅限于聚合操作中使用,聚合操作比简单的查找查询更复杂,通常也更慢。$lookup
操作符需要四个参数:localField
(输入文档中的查找字段)、from
(要连接的集合)、foreignField
(在from
集合中查找的字段)和as
(输出字段的名称)。此操作符可用于聚合查询中,以匹配帖子、按顺序排序、限制项目数量、连接用户数据、展平用户数组并仅返回必要的字段。$lookup
操作符很有用,可以帮助在NoSQL数据库中管理少量关系数据,但它不能替代SQL中更强大的JOIN子句。如果在MongoDB中删除用户文档,孤儿帖子文档将保留,表明缺乏约束。因此,如果频繁使用$lookup
操作符,可能表明使用了错误的数据存储,关系型(SQL)数据库可能更适合。感谢Julian Motz的同行评审帮助。
SQL和NoSQL数据库之间最大的区别之一是JOIN。在关系型数据库中,SQL JOIN子句允许您使用它们之间的一个公共字段来组合来自两个或多个表的行。例如,如果您有书籍和出版商的表,您可以编写如下SQL命令:
<code class="language-sql">SELECT book.title, publisher.name FROM book LEFT JOIN book.publisher_id ON publisher.id;</code>
换句话说,book表有一个publisher_id字段,它引用publisher表中的id字段。
这是实用的,因为单个出版商可以提供数千本书籍。如果我们将来需要更新出版商的详细信息,我们可以更改单个记录。数据冗余被最小化,因为我们不需要为每一本书重复出版商的信息。此技术称为规范化。
SQL数据库提供一系列规范化和约束功能,以确保维护关系。
并非总是如此…
面向文档的数据库(如MongoDB)旨在存储非规范化数据。理想情况下,集合之间不应该有任何关系。如果相同的数据需要在两个或多个文档中,则必须重复。
这可能会令人沮丧,因为几乎没有你永远不需要关系数据的情况。幸运的是,MongoDB 3.2引入了一个新的$lookup
操作符,它可以在两个或多个集合上执行类似LEFT-OUTER-JOIN的操作。但是有一个问题…
$lookup
仅允许在聚合操作中使用。可以将其视为一系列操作符的管道,这些操作符查询、过滤和分组结果。一个操作符的输出用作下一个操作符的输入。
聚合比简单的查找查询更难理解,并且通常运行速度较慢。但是,它们功能强大,对于复杂的搜索操作来说是一个宝贵的选项。
最好用一个例子来解释聚合。假设我们正在创建一个具有用户集合的社交媒体平台。它将每个用户的详细信息存储在单独的文档中。例如:
<code class="language-sql">SELECT book.title, publisher.name FROM book LEFT JOIN book.publisher_id ON publisher.id;</code>
我们可以根据需要添加任意数量的字段,但是所有MongoDB文档都需要一个具有唯一值的_id
字段。_id
类似于SQL主键,如果需要,将自动插入。
我们的社交网络现在需要一个帖子集合,该集合存储用户的大量有见地的更新。文档存储文本、日期、评级和在user_id
字段中引用撰写它的用户:
<code class="language-json">{ "_id": ObjectID("45b83bda421238c76f5c1969"), "name": "User One", "email": "userone@email.com", "country": "UK", "dob": ISODate("1999-09-13T00:00:00.000Z") }</code>
我们现在想显示所有用户按时间逆序排列的最后二十个评级为“important”的帖子。每个返回的文档都应包含文本、帖子的时间以及关联用户的姓名和国家/地区。
MongoDB聚合查询传递一个管道操作符数组,这些操作符按顺序定义每个操作。首先,我们需要使用$match
过滤器从帖子集合中提取所有具有正确评级的文档:
<code class="language-json">{ "_id": ObjectID("17c9812acff9ac0bba018cc1"), "user_id": ObjectID("45b83bda421238c76f5c1969"), "date": ISODate("2016-09-05T03:05:00.123Z"), "text": "My life story so far", "rating": "important" }</code>
我们现在必须使用$sort
操作符按时间逆序对匹配的项目进行排序:
<code class="language-javascript">{ "$match": { "rating": "important" } }</code>
由于我们只需要二十个帖子,因此我们可以应用$limit
阶段,以便MongoDB只需要处理我们想要的数据:
<code class="language-javascript">{ "$sort": { "date": -1 } }</code>
我们现在可以使用新的$lookup
操作符连接来自用户集合的数据。它需要一个具有四个参数的对象:
localField
:输入文档中的查找字段from
:要连接的集合foreignField
:在from
集合中查找的字段as
:输出字段的名称。因此,我们的操作符是:
<code class="language-javascript">{ "$limit": 20 }</code>
这将在我们的输出中创建一个名为userinfo
的新字段。它包含一个数组,其中每个值都与用户文档匹配:
<code class="language-javascript">{ "$lookup": { "localField": "user_id", "from": "user", "foreignField": "_id", "as": "userinfo" } }</code>
我们之间存在一对一的关系post.user_id
和user._id
,因为一个帖子只能有一个作者。因此,我们的userinfo
数组将永远只包含一个项目。我们可以使用$unwind
操作符将其分解成一个子文档:
<code class="language-json">"userinfo": [ { "name": "User One", ... } ]</code>
输出现在将转换为更实用的格式,可以应用其他操作符:
<code class="language-javascript">{ "$unwind": "$userinfo" }</code>
最后,我们可以使用管道中的$project
阶段返回文本、帖子的时间、用户的姓名和国家/地区:
<code class="language-sql">SELECT book.title, publisher.name FROM book LEFT JOIN book.publisher_id ON publisher.id;</code>
我们的最终聚合查询匹配帖子、按顺序排序、限制为最新的二十个项目、连接用户数据、展平用户数组并仅返回必要的字段。完整命令:
<code class="language-json">{ "_id": ObjectID("45b83bda421238c76f5c1969"), "name": "User One", "email": "userone@email.com", "country": "UK", "dob": ISODate("1999-09-13T00:00:00.000Z") }</code>
结果是一个最多包含二十个文档的集合。例如:
<code class="language-json">{ "_id": ObjectID("17c9812acff9ac0bba018cc1"), "user_id": ObjectID("45b83bda421238c76f5c1969"), "date": ISODate("2016-09-05T03:05:00.123Z"), "text": "My life story so far", "rating": "important" }</code>
MongoDB $lookup
很有用且功能强大,但即使这个基本示例也需要一个复杂的聚合查询。它不能替代SQL中更强大的JOIN子句。MongoDB也不提供约束;如果删除用户文档,孤儿帖子文档将保留。
理想情况下,$lookup
操作符应该很少需要。如果您经常需要它,您可能使用了错误的数据存储……
如果您有关系数据,请使用关系型(SQL)数据库!
也就是说,$lookup
是MongoDB 3.2的一个受欢迎的补充。它可以克服在NoSQL数据库中使用少量关系数据时的一些更令人沮丧的问题。
在SQL数据库中,连接操作根据它们之间相关的列组合来自两个或多个表的行。但是,MongoDB作为NoSQL数据库,不支持传统的SQL连接。相反,MongoDB提供两种执行类似操作的方法:聚合中的$lookup
阶段和$graphLookup
阶段。这些方法允许您将来自多个集合的数据组合到单个结果集中。
$lookup
阶段是如何工作的?MongoDB中的$lookup
阶段允许您连接来自另一个集合(“已连接”集合)的文档,并将已连接的文档添加到输入文档中。$lookup
阶段指定“from”集合、“localField”和“foreignField”用于匹配文档,以及“as”字段用于输出文档。它类似于SQL中的左外连接,返回来自输入集合的所有文档以及来自“from”集合的匹配文档。
是的,MongoDB为递归搜索提供了$graphLookup
阶段。$graphLookup
阶段对指定的集合执行递归搜索,并可以选择限制搜索的深度和广度。它对于查询分层数据或图很有用,其中级别数未知或可能变化。
为了在使用MongoDB连接时优化性能,请考虑以下策略:对“localField”和“foreignField”使用索引以加快匹配过程;限制“from”集合中的文档数量;以及在$lookup
阶段之前使用$match
和$project
阶段来过滤和转换文档。
是的,您可以通过在聚合管道中链接多个$lookup
阶段来连接多个MongoDB集合。每个$lookup
阶段都会将来自另一个集合的已连接文档添加到输入文档中。
在使用MongoDB连接时,如果输入集合中的文档与“from”集合中的任何文档都不匹配,则$lookup
阶段会向“as”字段添加一个空数组。您可以通过在$lookup
阶段之后添加$match
阶段来处理这些空值或缺失值,以过滤掉具有空“as”字段的文档。
从MongoDB 3.6开始,$lookup
和$graphLookup
阶段可以接受分片集合作为“from”集合。但是,由于额外的网络开销,性能可能不如非分片集合。
您可以通过在聚合管道中的$lookup
阶段之后添加$sort
阶段来对MongoDB中的已连接文档进行排序。$sort
阶段按升序或降序对指定字段中的文档进行排序。
find()
方法一起使用吗?不可以,MongoDB连接不能与find()
方法一起使用。$lookup
和$graphLookup
阶段是聚合框架的一部分,它提供比find()
方法更高级的数据处理功能。
要调试或排除MongoDB连接故障,您可以使用explain()
方法来分析聚合管道的执行计划。explain()
方法提供有关阶段的详细信息,包括处理的文档数量、花费的时间以及索引的使用情况。
以上是在MongoDB NOSQL数据库中使用加入的详细内容。更多信息请关注PHP中文网其他相关文章!