在MongoDB中执行连接操作的简单技术
简介
数据库人员非常熟悉连接。当我们想从多个表中获取数据时,我们经常根据主键和外键连接表。本文将介绍在MongoDB中执行连接操作的简单技巧。
上图是任何组织的关系数据库模式的图示表示。这些方块是存储特定类型数据(学生/教授/员工)的表,而线条和箭头表示使用公共键之间的关系的表之间的关系。我们可以根据它们之间的关系在表之间执行连接操作。
例如:在一个组织中,有单独的表用于存储员工、部门、项目等数据,其中数据以规范化的方式存储。为了获取员工的详细信息以及他们所在的部门和项目,我们需要跨表执行连接操作并获取所需数据。
同样,在一个大学中,可能有单独的表来存储学生和教授的数据。为了找出哪些教授教授某个学生,我们需要跨表执行连接操作。
学习目标
在本教程中,我们将看到如何在MongoDB中执行不同的连接操作(内连接、外连接、右连接和左连接)。
本文作为数据科学博文马拉松的一部分发布。
理解不同类型的常见连接操作
A. SQL和不同类型的连接
我们大多数人都了解SQL数据库。在那里,我们经常执行四种主要的连接操作,下面我们将讨论它们。
1. 内连接:只有两个表中具有公共键的行才会出现在结果表中。
如我们所见,在执行内连接后,我们只返回了那些Roll No键在两个表中都存在的行。
2. 左外连接:左表的所有行(匹配+非匹配键)都将出现在结果表中。因此,在结果表中,只有右表中匹配的键的行;不能消除未匹配键的行。
执行左连接后,我们有左表的所有列。由于右表中Deepak K.的Class Rank不存在,因此用null填充。正如讨论的那样,结果中只有那些与左表的Roll No匹配的右表记录。因此,右表中的元组(3D5RE,16)不在结果中。
3. 右外连接:左外连接的相反。结果表中将有右表的所有行,只有与左表匹配的行。
如预期,右表中的所有记录/元组都出现在结果中,但左表中的记录(2A3AS, Deepak K., 87)缺失。
4. 全外连接:结果表中将包含两个表的所有行(匹配+非匹配键)。
如预期,我们在结果中有来自两个表的所有元组。值不存在的地方用null填充。
B. MongoDB简介
MongoDB是一种基于文档的NoSQL数据库。NoSQL数据库更适合存储大规模、非关系、非结构化和频繁变化的数据。下面两篇博客对MongoDB进行了比较和操作。
- MongoDB简介
- MongoDB的CRUD操作
MongoDB数据库由一个或多个集合组成。集合可以被视为SQL数据库中的表的等效物。每个集合由一个或多个文档组成。因此,文档可以被视为SQL数据库表中的行或元组。数据以BSON(二进制JSON格式)存储在MongoDB中。
MongoDB中的连接操作
现在让我们看看不同的连接操作在MongoDB集合上的执行方式。
将两个表Marks和Rank转换为各自集合中的每个元组作为相应集合的文档。将这些集合存储在名为School的数据库中的MongoDB中。
左外连接
代码:
db.Marks.aggregate([{$lookup:{from:"Rank",localField:"Roll No",
foreignField:"Roll No",as:"Ranks"}}])
[扩大输出]
如我们所见,相应学生的排名详细信息附加到他们的文档中。对于Deepak,在Rank表中没有排名详细信息,所以显然,他的Ranks字段是一个空列表。
现在让我们了解使用的参数:
- 这里Marks是我们的左表。
- $lookup是执行两个集合之间连接的聚合函数/操作符。
- 在lookup内部,from表示我们要执行连接的集合,即我们的右表(集合)。在我们的案例中,Rank是我们的右集合。
- localField表示左集合中将与右集合的键进行匹配的键。如果在右集合字段中找到匹配的键,则结果字段(这里是Ranks)不为空,否则为空(在我们的示例中是Deepak的情况)。
- foreignField是右集合的键。
- as表示由于连接而在结果表/集合中形成的新字段的名称,其中将存储来自右表(集合)的详细信息。
- 在我们的案例中,向结果表(集合)中添加新字段Ranks,其中包含相应学生的排名详细信息。
现在有一件事要记住,在MongoDB中,$lookup只能执行左连接,并且没有其他类型连接的特定语法可用。因此,我们需要通过使用不同的技巧和操作来获得其他连接。
右外连接
现在,右连接与左连接相反,除了匹配的记录外,右侧集合/表的非匹配记录也应该在结果集合/表中。
现在,一种简单的方法是只需改变两个集合的位置;然后我们的右集合变为左集合,反之亦然。这样,连接将包含我们右表的所有行(匹配+非匹配)。
代码:
db.Rank.aggregate([{$lookup:{from:"Marks", localField:"Roll No",
foreignField:"Roll No", as:"Marks_Students"}}])
[放大输出]
内连接
我们可以通过一个简单的技巧高效地执行内连接!我们将执行一个左连接,然后删除所有那些as字段为空的记录。因此,我们将只剩下那些两个表(集合)中都存在键的记录。
代码:
db.Rank.aggregate([{$lookup:{from:"Marks", localField:"Roll No", foreignField:"Roll No",
as: "Marks_Students"}}, {$match:{"Marks_Students":{$ne:[]}}}])
[放大输出]
正如我们在上面的结果中可以看到的,我们只有那些两个集合的键匹配的记录。这里的{$match:{“Marks_Students”:{$ne:[]}}}表示只匹配那些Marks_Students字段不为空(非空列表)的记录。
全外连接
全外连接有点复杂,我通过三个操作的组合来设计它。所以如果一开始看起来令人困惑,我请求你多读几次以便更好地理解。
步骤1:我们将执行Marks(左集合)和Rank(右集合)的左连接,并添加一个名为Marks的空字段到所有结果记录中,并将结果发送/输出到一个名为J2的新集合。
代码:
db.Marks.aggregate([{$lookup:{from:"Rank",localField:"Roll No", foreignField:"Roll No", as:"Rank"}},
{$addFields:{Marks:[]}},{$out:"J2"}])
[放大输出]
所以我们的新集合看起来像上面的截图。
- {$addFields:{Marks:[]}} -> 为所有记录添加额外的字段Marks。
- {$out:”J2″} -> 输出/发送结果到一个名为J2的新集合。
所以现在显而易见,我们的数据库School包含3个集合-
Marks,Rank,J2
步骤2:我们将执行右连接(如前所述,将Rank作为左侧集合),将Marks和Rank进行连接,并将结果追加到J2集合中。
代码:
db.Rank.aggregate([{$lookup:{from:"Marks",localField:"Roll No",foreignField:"Roll No",
as:"Marks"}},{$merge:"J2"}])
[放大输出]
[放大输出]
请注意系统如何将新的输出追加到步骤1的旧输出底部。
步骤3:
我们将只保留聚合结果中Match字段为[](空)的记录,并丢弃其他记录。通过这种方式,去除重复项,并且将我们的结果中只保留来自两个集合/表的所有不同字段。(您可能已经注意到,在Step 2的输出中有重复项,例如:有两条Nikita的记录)
最后,我们将从聚合结果中删除空的Marks字段,因为它是一个空字段,并且没有显示它的用途。它的目的是去除重复项。
代码:
db.J2.aggregate([{$redact:{$cond:[{$eq:["$Marks",[]]},"$KEEP","$PRUNE"]}},
{$unset:"Marks"}])
[放大输出]
因此,我们最终得到了我们想要的输出。我们拥有在两个表(集合)中匹配的所有记录,以及只存在于其中一个表(集合)中的其他记录(Marks中的Deepak和Rank中的3D5RE)。
结论
因此,我们成功地从MongoDB中的左连接推导出了不同类型的连接。因此,简而言之,在MongoDB中只有左连接有直接的语法可用。其他类型的连接应通过在左连接上应用不同类型的操作和技术来推导出来。例如:在内连接的情况下,删除带有空as字段的集合等。
在推导这些连接时,我们意识到:
- 需要对聚合查询有良好的了解。
- 需要仔细观察中间结果以决定下一步的操作。
- 可能存在其他(甚至更好的)推导连接的方法。
如果您知道更好的方法,欢迎在评论中分享。
要点
- 只有左连接操作在MongoDB中有直接的语法。
- 通过改变语法中集合的位置,可以推导出右连接。
- 通过先执行左连接,然后删除空的as字段,可以推导出内连接。
- 外连接可以通过一系列简单而巧妙的操作来执行。
常见问题
参考资料
- MongoDB Lookup文档
- MongoDB官方$redact文档
- MongoDB内连接101:语法和示例(由Samuel Salimon简化)
本文中显示的媒体不归Analytics Vidhya所有,并由作者自行决定使用。