您的位置:

掌握MongoDB多表关联查询的技巧

引言

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文档之间执行更为灵活和复杂的联合查询。这些方法都可以用于多个领域,例如学生成绩、餐厅菜品和体育比赛信息等。