引言
MongoDB是一个非关系型数据库,大受欢迎的原因之一是很方便在多个集合(就是"表"的概念)中进行联合查询。
如果你是初学者,那么你可能会问什么时候需要联合查询。举个例子,如果你有一个学生集合和一个成绩集合,你可能会想要查询一个学生所有课程的成绩。
本文将介绍如何使用MongoDB进行多表联合查询,包括内嵌文档和$lookup操作符两种方法。
内嵌文档
内嵌文档是MongoDB中最基本且最常见的多表联合查询方法。
小标题1:内嵌文档基础
首先,需要知道如何在一个文档中嵌入其他文档。这个操作可以使用Mongo Shell或任何MongoDB操作客户端。这里使用Mongo Shell作为演示。
> db.students.insert({ "name": "小明", "age": 20, "score": { "math": 80, "chinese": 90, "english": 85 } })
上面这个例子将一个名为"小明"的学生文档插入了学生集合中。此文档有一个嵌套的分数文档。
接下来,我们将展示如何查询学生的成绩记录:
> db.students.find({"name": "小明"}) { "_id" : ObjectId("60efa25968ca5ef8843f0c0c"), "name" : "小明", "age" : 20, "score" : { "math" : 80, "chinese" : 90, "english" : 85 } }
很简单,只需要将查询条件指定为"name"为"小明"即可。
小标题2:多个文档的联合查询
接下来,我们将学生文档和成绩文档嵌入一个班级文档,以此演示如何使用内嵌文档查询多个文档:
> db.classes.insert({ "name": "一年级", "students": [ { "name": "小明", "age": 20, "score": { "math": 80, "chinese": 90, "english": 85 } }, { "name": "小红", "age": 19, "score": { "math": 78, "chinese": 88, "english": 86 } } ] })
上面这个例子将一个名为"一年级"的班级文档插入了班级集合中。此文档有一个嵌套的学生文档数组,数组里包含两个学生文档,每个学生文档有一个嵌套的成绩文档。
接下来,我们将展示如何查询这个班级里所有学生的姓名和成绩记录:
> db.classes.find({}, {"students.name": 1, "students.score": 1}) { "_id" : ObjectId("60efa5c068ca5ef8843f0c0d"), "students" : [ { "name" : "小明", "score" : { "math" : 80, "chinese" : 90, "english" : 85 } }, { "name" : "小红", "score" : { "math" : 78, "chinese" : 88, "english" : 86 } } ] }
只需将查询条件指定为一个空文档,然后指定需要返回的字段即可。在这个例子中,我们指定了"name"和"score"两个字段。
$lookup操作符
$lookup操作符是MongoDB中用于执行多表联合查询的最强大且最灵活的方法。
小标题1:单个查询
首先,我们需要知道如何使用$lookup操作符查询单个文档中包含的多个子文档。这个操作将会使用到餐厅和菜品两个文档。
> db.restaurants.insert({ "name": "餐厅1", "dishes": [ { "name": "红烧肉", "price": 28 }, { "name": "鱼香肉丝", "price": 18 } ] })
上面这个例子将一个名为"餐厅1"的餐厅文档插入了餐厅集合中。此文档有一个嵌套的菜品文档数组,数组里包含两个菜品文档。
接下来,我们将展示如何查询一个餐厅,并将其菜品信息一并返回:
> db.restaurants.aggregate([ { $match: {"name": "餐厅1"} }, { $lookup: { from: "dishes", localField: "dishes.name", foreignField: "name", as: "dishes" } }, { $project: {"dishes.name": 1, "dishes.price": 1} } ])
这是一个比较复杂的查询,需要使用聚合管道(aggregate pipeline)来实现。首先使用$match操作符找到名称为"餐厅1"的餐厅文档,并将其作为管道的第一个操作。
然后,使用$lookup操作符查找"dishes"集合,并将其结果嵌入"restaurants"文档中。$lookup操作符需要指定四个参数:
- from:需要联结的文档集合名
- localField:连接到当前文档的本地键字段
- foreignField:连接到目标文档的外键字段
- as:联结到当前文档的新键名
最后使用$project操作符将$dishes数组中的"name"和"price"字段投影出来。
小标题2:多个查询
现在,我们将举一个更复杂的例子,使用$lookup操作符查询多个文档的联合结果。这个例子将使用到学生、成绩和课程三个文档。
首先,我们需要在Mongo Shell中插入所有的测试文档:
> db.students.insertMany([ {"name": "小明", "age": 20}, {"name": "小红", "age": 19} ]) > db.courses.insertMany([ {"name": "math", "credit": 3}, {"name": "chinese", "credit": 3}, {"name": "english", "credit": 2} ]) > db.scores.insertMany([ {"student": "小明", "course": "math", "score": 80}, {"student": "小明", "course": "chinese", "score": 90}, {"student": "小明", "course": "english", "score": 85}, {"student": "小红", "course": "math", "score": 78}, {"student": "小红", "course": "chinese", "score": 88}, {"student": "小红", "course": "english", "score": 86} ])
接下来,我们将展示如何使用$lookup操作符查询所有学生的所有成绩和对应的课程信息:
> db.students.aggregate([ { $lookup: { from: "scores", localField: "name", foreignField: "student", as: "scores" } }, { $unwind: "$scores" }, { $lookup: { from: "courses", localField: "scores.course", foreignField: "name", as: "course_info" } }, { $unwind: "$course_info" }, { $project: {"name": 1, "course": "$course_info.name", "credit": "$course_info.credit", "score": "$scores.score"} } ])
这是一个相对复杂的查询,以便演示如何在多个MongoDB文档中执行联合查询。
首先,使用$lookup操作符将"students"文档和"scores"文档联结起来。将"students.name"字段和"scores.student"字段匹配。联结结果将插入到"students.scores"数组中。
然后,使用$unwind操作符展开"students.scores"数组,以便进行下一步联结。这里使用了两次$unwind,因为后续还会有一个联结操作。
接下来,使用$lookup操作符将课程信息加入联结结果中。将"students.scores.course"字段和"courses.name"字段匹配。联结结果将插入到"students.scores.course_info"数组中。
最后,使用$project操作符来投影出我们想要的结果。在这个例子中,我们将"name"、"course"、"credit"和"score"字段都投影出来。
结论
本文介绍了MongoDB中的两种多表联合查询方法:内嵌文档和$lookup操作符。内嵌文档可以很容易地在一个文档中嵌入其他文档,但是只适用于简单的联合查询场景。$lookup操作符则可以在多个MongoDB文档之间执行更为灵活和复杂的联合查询。这些方法都可以用于多个领域,例如学生成绩、餐厅菜品和体育比赛信息等。