如何在MongoDB中创建查询

来自菜鸟教程
跳转至:导航、​搜索

作为 Write for DOnations 计划的一部分,作者选择了 Open Internet/Free Speech Fund 来接受捐赠。

介绍

存储在 MongoDB 数据库中的文档可以有很大的不同。 有些可能相对较小并且仅包含几个条目,例如购物清单中的项目。 其他的可能非常复杂,包含几十个不同类型的字段、包含多个值的数组,甚至是嵌套在更大结构中的其他文档。

无论您的文档有多复杂或有多少,您通常都不需要一次查看所有文档中的数据。 相反,您可能只想检索满足一个或多个特定条件的文档。 类似于通过在预订网站上选择一系列过滤器来找到您的度假目的地,例如与海边的距离、宠物友好性、游泳池和附近的停车场,您可以精确地查询 MongoDB 以准确找到您需要的文件. MongoDB 提供了一种强大的查询机制,用于在检索文档时定义过滤条件。

在本教程中,您将学习如何使用不同范围的过滤器和条件来查询 MongoDB 集合。 您还将了解什么是游标以及如何在 MongoDB shell 中使用它们。

先决条件

要遵循本教程,您将需要:

  • 具有 sudo 权限的常规非 root 用户和配置了 UFW 的防火墙的服务器。 本教程使用运行 Ubuntu 20.04 的服务器进行了验证,您可以按照此 Ubuntu 20.04 初始服务器设置教程来准备您的服务器。
  • MongoDB 安装在您的服务器上。 要进行此设置,请按照我们关于 如何在 Ubuntu 20.04 上安装 MongoDB 的教程进行操作。
  • 通过启用身份验证和创建管理用户来保护服务器的 MongoDB 实例。 要像这样保护 MongoDB,请按照我们关于 如何在 Ubuntu 20.04 上保护 MongoDB 的教程进行操作。
  • 熟悉 MongoDB CRUD 操作,尤其是从集合中检索对象。 要了解如何使用 MongoDB shell 执行 CRUD 操作,请按照教程 如何在 MongoDB 中执行 CRUD 操作。

注意:有关如何配置服务器、安装和安全 MongoDB 安装的链接教程参考 Ubuntu 20.04。 本教程专注于 MongoDB 本身,而不是底层操作系统。 只要启用了身份验证,它通常适用于任何 MongoDB 安装,无论操作系统如何。


第 1 步 — 准备示例数据库

为了解释如何在 MongoDB 中创建查询——包括如何过滤具有多个字段、嵌套文档和数组的文档——本指南使用了一个示例数据库,其中包含描述世界上五座最高山峰的文档集合。

要创建此示例集合,请以您的管理用户身份连接到 MongoDB shell。 本教程遵循先决条件 MongoDB 安全教程 的约定,并假设此管理用户名为 AdminSammy,其身份验证数据库为 admin。 如果不同,请务必在以下命令中更改这些详细信息以反映您自己的设置:

mongo -u AdminSammy -p --authenticationDatabase admin

出现提示时,输入您在创建管理用户时设置的密码。 提供密码后,您的提示将变为大于 (>) 符号:


注意: 在新连接时,MongoDB shell 默认会自动连接到 test 数据库。 您可以安全地使用此数据库来试验 MongoDB 和 MongoDB shell。

或者,您也可以切换到另一个数据库来运行本教程中给出的所有示例命令。 要切换到另一个数据库,请运行 use 命令,后跟数据库名称:

use database_name

要了解 MongoDB 如何过滤具有多个字段、嵌套文档和数组的文档,您需要足够复杂的示例数据以允许探索不同类型的查询。 如前所述,本指南使用世界上五座最高山峰的样本集。

此集合中的文档将遵循此格式。 本示例文档描述了珠穆朗玛峰:

珠穆朗玛峰文件

{
    "name": "Everest",
    "height": 8848,
    "location": ["Nepal", "China"],
    "ascents": {
        "first": {
            "year": 1953,
        },
        "first_winter": {
            "year": 1980,
        },
        "total": 5656,
    }
}

本文档包含以下字段和值:

  • name:峰名
  • height:峰高,单位为米
  • location:山所在的国家。 此字段将值存储为数组,以允许位于多个国家/地区的山脉
  • ascents:该字段的值是另一个文档。 当一个文档像这样存储在另一个文档中时,它被称为 嵌入嵌套 文档。 每个 ascents 文档都描述了给定山峰的成功攀登。 具体来说,每个 ascents 文档都包含一个 total 字段,其中列出了每个给定峰的成功上升总数。 此外,这些嵌套文档中的每一个都包含两个字段,其值也是嵌套文档:


尽管现在只包含年份,但将首次登顶记录为嵌套文档的原因是为了便于将来扩展登顶详细信息,增加更多字段,例如登顶者姓名或探险详细信息。

在 MongoDB shell 中运行以下 insertMany() 方法,同时创建一个名为 peaks 的集合,并在其中插入五个示例文档。 这些文件描述了世界上五座最高的山峰:

db.peaks.insertMany([
    {
        "name": "Everest",
        "height": 8848,
        "location": ["Nepal", "China"],
        "ascents": {
            "first": {
                "year": 1953
            },
            "first_winter": {
                "year": 1980
            },
            "total": 5656
        }
    },
    {
        "name": "K2",
        "height": 8611,
        "location": ["Pakistan", "China"],
        "ascents": {
            "first": {
                "year": 1954
            },
            "first_winter": {
                "year": 1921
            },
            "total": 306
        }
    },
    {
        "name": "Kangchenjunga",
        "height": 8586,
        "location": ["Nepal", "India"],
        "ascents": {
            "first": {
                "year": 1955
            },
            "first_winter": {
                "year": 1986
            },
            "total": 283
        }
    },
    {
        "name": "Lhotse",
        "height": 8516,
        "location": ["Nepal", "China"],
        "ascents": {
            "first": {
                "year": 1956
            },
            "first_winter": {
                "year": 1988
            },
            "total": 461
        }
    },
    {
        "name": "Makalu",
        "height": 8485,
        "location": ["China", "Nepal"],
        "ascents": {
            "first": {
                "year": 1955
            },
            "first_winter": {
                "year": 2009
            },
            "total": 361
        }
    }
])

输出将包含分配给新插入对象的对象标识符列表。

Output{
        "acknowledged" : true,
        "insertedIds" : [
                ObjectId("610c23828a94efbbf0cf6004"),
                ObjectId("610c23828a94efbbf0cf6005"),
                ObjectId("610c23828a94efbbf0cf6006"),
                ObjectId("610c23828a94efbbf0cf6007"),
                ObjectId("610c23828a94efbbf0cf6008")
        ]
}

您可以通过运行不带参数的 find() 方法来验证文档是否已正确插入,该方法将检索您刚刚添加的所有文档:

db.peaks.find()
Output{ "_id" : ObjectId("610c23828a94efbbf0cf6004"), "name" : "Everest", "height" : 8848, "location" : [ "Nepal", "China" ], "ascents" : { "first" : { "year" : 1953 }, "first_winter" : { "year" : 1980 }, "total" : 5656 } }

. . .

至此,您已成功创建了山脉示例文档列表,这些文档将用作创建查询的测试数据。 接下来,您将学习如何使用引用单个字段的条件进行查询。

第 2 步 — 查询单个字段

在上一步结束时,您使用 MongoDB 的 find() 方法返回 peaks 集合中的每个文档。 但是,这样的查询在实践中并不是很有用,因为它不会过滤任何文档并且总是返回相同的结果集。

您可以通过定义文档必须遵守才能包含在结果集中的特定条件来过滤 MongoDB 中的查询结果。 如果你学过如何在MongoDB中执行CRUD操作教程,你已经使用了最基本的过滤条件:相等条件。

例如,运行以下查询,该查询返回 name 值等于 Everest 的任何文档:

db.peaks.find(
    { "name": "Everest" }
)

第二行 — { "name": "Everest" } — 是 查询过滤器文档 ,它是一个 JSON 对象,用于指定在搜索集合以查找满足条件的文档时要应用的过滤器。 此示例操作告诉 MongoDB 检索 peaks 集合中的 name 值与字符串 Everest 匹配的任何文档:

Output{ "_id" : ObjectId("610c23828a94efbbf0cf6004"), "name" : "Everest", "height" : 8848, "location" : [ "Nepal", "China" ], "ascents" : { "first" : { "year" : 1953 }, "first_winter" : { "year" : 1980 }, "total" : 5656 } }

MongoDB 返回一个文档,因为只有一个 Mt. peaks 系列中的珠穆朗玛峰。

相等条件指定 MongoDB 将尝试与集合中的文档匹配的单个值。 MongoDB 提供 比较查询运算符 允许您指定也引用单个字段的其他条件,但以比搜索精确匹配更复杂的方式过滤文档。

比较运算符由运算符本身、前面有一个美元符号 ($) 的单个键以及查询运算符将用于过滤文档的值组成。

为了说明,运行以下查询搜索 name 等于 Everest 的任何文档:

db.peaks.find(
    { "name": { $ne: "Everest" } }
)

这次查询过滤文档包含{ $ne: "Everest" }$ne 是本例中的比较运算符,代表“不等于”。 峰名称 Everest 再次作为该运算符的值出现。 由于此查询正在搜索 name 值为 不等于Everest 的文档,因此它返回四个文档:

Output{ "_id" : ObjectId("610c23828a94efbbf0cf6005"), "name" : "K2", "height" : 8611, "location" : [ "Pakistan", "China" ], "ascents" : { "first" : { "year" : 1954 }, "first_winter" : { "year" : 1921 }, "total" : 306 } }
{ "_id" : ObjectId("610c23828a94efbbf0cf6006"), "name" : "Kangchenjunga", "height" : 8586, "location" : [ "Nepal", "India" ], "ascents" : { "first" : { "year" : 1955 }, "first_winter" : { "year" : 1986 }, "total" : 283 } }
. . .

$in 运算符允许您编写查询,这些查询将返回值与数组中保存的多个值之一匹配的文档。

以下示例查询包含 $in 运算符,并将返回 name 值与 EverestK2 匹配的文档:

db.peaks.find(
    { "name": { $in: ["Everest", "K2"] } }
)

传递给 $in 运算符的值不是单个值,而是方括号中的两个峰名称的数组。 正如预期的那样,MongoDB 返回两个文档:

Output{ "_id" : ObjectId("610c23828a94efbbf0cf6004"), "name" : "Everest", "height" : 8848, "location" : [ "Nepal", "China" ], "ascents" : { "first" : { "year" : 1953 }, "first_winter" : { "year" : 1980 }, "total" : 5656 } }
{ "_id" : ObjectId("610c23828a94efbbf0cf6005"), "name" : "K2", "height" : 8611, "location" : [ "Pakistan", "China" ], "ascents" : { "first" : { "year" : 1954 }, "first_winter" : { "year" : 1921 }, "total" : 306 } }

到目前为止的示例已经使用文本值查询了 name 字段。 您还可以根据数值过滤文档。

以下示例查询搜索 height 值大于 8500 的文档:

db.peaks.find(
    { "height": { $gt: 8500 } }
)

此查询包括 $gt 运算符,它代表 大于 。 通过将值 8500 传递给它,MongoDB 将返回 height 值大于 8500 的文档:

Output{ "_id" : ObjectId("610c23828a94efbbf0cf6004"), "name" : "Everest", "height" : 8848, "location" : [ "Nepal", "China" ], "ascents" : { "first" : { "year" : 1953 }, "first_winter" : { "year" : 1980 }, "total" : 5656 } }
{ "_id" : ObjectId("610c23828a94efbbf0cf6005"), "name" : "K2", "height" : 8611, "location" : [ "Pakistan", "China" ], "ascents" : { "first" : { "year" : 1954 }, "first_winter" : { "year" : 1921 }, "total" : 306 } }
{ "_id" : ObjectId("610c23828a94efbbf0cf6006"), "name" : "Kangchenjunga", "height" : 8586, "location" : [ "Nepal", "India" ], "ascents" : { "first" : { "year" : 1955 }, "first_winter" : { "year" : 1986 }, "total" : 283 } }
{ "_id" : ObjectId("610c23828a94efbbf0cf6007"), "name" : "Lhotse", "height" : 8516, "location" : [ "Nepal", "China" ], "ascents" : { "first" : { "year" : 1956 }, "first_winter" : { "year" : 1988 }, "total" : 461 } }

除了本节中概述的操作符之外,MongoDB 还提供了许多比较查询操作符。 有关这些运算符的完整列表,请参阅有关该主题的官方文档

现在您知道如何在单个文档字段上使用相等条件和比较运算符,您可以继续学习如何在单个查询中将多个条件连接在一起。

第 3 步 — 使用多个条件

有时,基于单个文档字段的过滤不足以精确选择感兴趣的文档。 在这种情况下,您可能希望一次使用多个条件过滤文档。

MongoDB中有两种连接多个条件的方法。 第一种是使用逻辑 AND 连接来选择集合中匹配 all 条件的文档,或者使用逻辑 OR 从列表中选择匹配 至少一个 条件的文档。

在 MongoDB 中,当在查询过滤器文档中使用多个字段时,AND 连接是隐含的。 尝试选择与珠穆朗玛峰名称和确切高度 8848 米相匹配的山峰:

db.peaks.find(
    { "name": "Everest", "height": 8848 }
)

请注意,语法类似于上一步中的相等条件示例,但这次查询过滤器文档中出现了两个字段。 MongoDB 检查两个字段是否相等,并要求两者都匹配请求的值才能选择文档:

Output{ "_id" : ObjectId("610c23828a94efbbf0cf6004"), "name" : "Everest", "height" : 8848, "location" : [ "Nepal", "China" ], "ascents" : { "first" : { "year" : 1953 }, "first_winter" : { "year" : 1980 }, "total" : 5656 } }

在这种情况下,将返回单个文档,但如果您尝试将高度更改为任何其他数值,则结果集将为空,因为任何返回的文档必须同时满足这两个条件。 例如,以下示例不会向 shell 返回任何输出:

db.peaks.find(
    { "name": "Everest", "height": 9000 }
)

通过包含 $and 逻辑查询运算符,后跟返回文档必须满足的条件列表,可以使这种隐式 AND 显式化。 以下示例与上一个查询基本相同,但包含 $and 运算符而不是隐式 AND 连词:

db.peaks.find(
    { $and: [{"name": "Everest"}, {"height": 8848}] }
)

这次包含 $and 查询运算符的 JSON 对象是查询过滤器文档本身。 在这里,比较运算符采用列表中出现的两个单独的相等条件,一个用于 name 匹配,另一个用于 height 匹配。

为了选择匹配任何选定条件而不是所有条件的文档,您可以改用 $or 运算符:

db.peaks.find(
    { $or: [{"name": "Everest"}, {"name": "K2"}] }
)

使用 $or 运算符时,文档只需要满足两个相等过滤器之一:

Output{ "_id" : ObjectId("610c23828a94efbbf0cf6004"), "name" : "Everest", "height" : 8848, "location" : [ "Nepal", "China" ], "ascents" : { "first" : { "year" : 1953 }, "first_winter" : { "year" : 1980 }, "total" : 5656 } }
{ "_id" : ObjectId("610c23828a94efbbf0cf6005"), "name" : "K2", "height" : 8611, "location" : [ "Pakistan", "China" ], "ascents" : { "first" : { "year" : 1954 }, "first_winter" : { "year" : 1921 }, "total" : 306 } }

尽管此示例的每个条件都是单字段相等条件,但 $and$or 运算符都可以包含任何有效的查询过滤器文档。 它们甚至可以包含嵌套的 AND/OR 条件列表。

使用此步骤中概述的 $and$or 运算符将多个过滤器连接在一起对于检索细粒度的查询结果非常有帮助。 但是,到目前为止的示例都使用了基于单个值进行过滤的查询过滤器文档。 下一步概述了如何查询存储在数组字段中的值。

第 4 步 — 查询数组值

有时单个字段可能包含存储在数组中的多个值。 在我们的山峰示例中,location 就是这样一个字段。 因为山脉通常跨越一个以上的国家,例如尼泊尔和印度的干城章嘉峰,对于这个领域来说,一个值可能并不总是足够的。

在此步骤中,您将学习如何构建与数组字段中的项目匹配的查询过滤器。

让我们首先尝试选择代表尼泊尔山脉的文档。 但是,对于此示例,如果山上列出了多个位置,只要其中一个是尼泊尔,就可以了:

db.peaks.find(
    { "location": "Nepal" }
)

此查询使用相等条件,告诉 MongoDB 返回 location 值与给定字符串值 Nepal 完全匹配的文档,类似于前面使用 name 字段的示例。 MongoDB 将选择请求值出现在数组中任何位置的任何文档:

Output{ "_id" : ObjectId("610c23828a94efbbf0cf6004"), "name" : "Everest", "height" : 8848, "location" : [ "Nepal", "China" ], "ascents" : { "first" : { "year" : 1953 }, "first_winter" : { "year" : 1980 }, "total" : 5656 } }
{ "_id" : ObjectId("610c23828a94efbbf0cf6006"), "name" : "Kangchenjunga", "height" : 8586, "location" : [ "Nepal", "India" ], "ascents" : { "first" : { "year" : 1955 }, "first_winter" : { "year" : 1986 }, "total" : 283 } }
{ "_id" : ObjectId("610c23828a94efbbf0cf6007"), "name" : "Lhotse", "height" : 8516, "location" : [ "Nepal", "China" ], "ascents" : { "first" : { "year" : 1956 }, "first_winter" : { "year" : 1988 }, "total" : 461 } }
{ "_id" : ObjectId("610c23828a94efbbf0cf6008"), "name" : "Makalu", "height" : 8485, "location" : [ "China", "Nepal" ], "ascents" : { "first" : { "year" : 1955 }, "first_winter" : { "year" : 2009 }, "total" : 361 } }

对于这个查询,MongoDB 返回了尼泊尔出现在 location 字段中的四个文档。

但是,如果您想找到位于'中国和尼泊尔的山脉怎么办? 为此,您可以在过滤器文档中包含一个数组,而不是单个值:

db.peaks.find(
    { "location": ["China", "Nepal"] }
)

尽管数据库中有尼泊尔和中国的四座山,但只有一座按照此查询中给出的顺序列出国家/地区,因此此查询返回单个文档:

Output{ "_id" : ObjectId("610c23828a94efbbf0cf6008"), "name" : "Makalu", "height" : 8485, "location" : [ "China", "Nepal" ], "ascents" : { "first" : { "year" : 1955 }, "first_winter" : { "year" : 2009 }, "total" : 361 } }

请注意,Makalu 的 location 字段的值与查询的过滤器文档相同。 当您提供一个数组作为这样的相等条件的值时,MongoDB 将检索 location 字段与查询过滤器完全匹配的文档,包括数组内元素的顺序。 为了说明,再次运行查询,但将中国与尼泊尔交换:

db.peaks.find(
    { "location": ["Nepal", "China"] }
)
Output{ "_id" : ObjectId("610c23828a94efbbf0cf6004"), "name" : "Everest", "height" : 8848, "location" : [ "Nepal", "China" ], "ascents" : { "first" : { "year" : 1953 }, "first_winter" : { "year" : 1980 }, "total" : 5656 } }
{ "_id" : ObjectId("610c23828a94efbbf0cf6007"), "name" : "Lhotse", "height" : 8516, "location" : [ "Nepal", "China" ], "ascents" : { "first" : { "year" : 1956 }, "first_winter" : { "year" : 1988 }, "total" : 461 } }

现在,另外两座山被归还了,但马卡鲁没有。

在您只关心数组中的元素(不管它们的顺序如何)而不是精确匹配的情况下,使用这样的相等条件是没有帮助的。 幸运的是,MongoDB 允许您使用 $all 查询运算符在数组中的任意位置检索包含多个数组元素的文档。

为了说明,运行以下查询:

db.peaks.find(
    { "location": { $all: ["China", "Nepal"] } }
)

$all 运算符将确保检查文档的 location 数组是否以任意顺序包含中国和尼泊尔。 MongoDB 将在一个查询中返回所有三座山:

Output{ "_id" : ObjectId("610c23828a94efbbf0cf6004"), "name" : "Everest", "height" : 8848, "location" : [ "Nepal", "China" ], "ascents" : { "first" : { "year" : 1953 }, "first_winter" : { "year" : 1980 }, "total" : 5656 } }
{ "_id" : ObjectId("610c23828a94efbbf0cf6007"), "name" : "Lhotse", "height" : 8516, "location" : [ "Nepal", "China" ], "ascents" : { "first" : { "year" : 1956 }, "first_winter" : { "year" : 1988 }, "total" : 461 } }
{ "_id" : ObjectId("610c23828a94efbbf0cf6008"), "name" : "Makalu", "height" : 8485, "location" : [ "China", "Nepal" ], "ascents" : { "first" : { "year" : 1955 }, "first_winter" : { "year" : 2009 }, "total" : 361 } }

此步骤概述了如何在查询过滤文档中使用数组来检索在单个字段中具有多个值的文档。 如果要查询嵌套文档中保存的数据,则需要使用此类操作所需的特殊语法。 继续下一步以了解如何执行此操作。

第 5 步 — 查询嵌套文档中的字段

回想一下,示例数据库文档包含一个 ascent 字段,该字段包含有关每座山峰首次登顶的各种详细信息的数组。 这样,关于第一次攀登、冬季攀登和攀登总数的数据被清晰地分组在单个嵌套文档中。 此步骤说明在构建查询时如何访问嵌套文档中的字段。

再次查看示例 Everest 文档:

珠穆朗玛峰文件

{
    "name": "Everest",
    "height": 8848,
    "location": ["Nepal", "China"],
    "ascents": {
        "first": {
            "year": 1953,
        },
        "first_winter": {
            "year": 1980,
        },
        "total": 5656,
    }
}

访问 nameheight 字段很简单,因为单个值驻留在这些键下。 但是假设您想找到给定峰的上升总数。 ascents 字段包含的数据不仅仅是内部的上升总数。 有一个 total 字段,但它不是主文档的一部分,因此无法直接访问它。

为了解决这个问题,MongoDB 提供了一个 dot notation 来访问嵌套文档中的字段。

为了说明 MongoDB 的点表示法是如何工作的,请运行以下查询。 这将返回集合中所有上升超过 1000 次的山脉,使用之前突出显示的 $gt 运算符:

db.peaks.find(
    { "ascents.total": { $gt: 1000 } }
)

公吨。 珠穆朗玛峰是集合中唯一一座登顶次数超过 1000 次的山峰,因此只会返回它的文档:

Output{ "_id" : ObjectId("610c23828a94efbbf0cf6004"), "name" : "Everest", "height" : 8848, "location" : [ "Nepal", "China" ], "ascents" : { "first" : { "year" : 1953 }, "first_winter" : { "year" : 1980 }, "total" : 5656 } }

虽然使用 $gt 运算符的 { $gt: 1000 } 查询过滤器很熟悉,但请注意此查询如何访问存储在 ascents 字段中的文档中保存的 total 字段。 在嵌套文档中,任何给定字段的访问路径都由点构成,这些点表示进入嵌套对象的操作。

所以,ascents.total 表示 MongoDB 应该首先打开 ascents 字段指向的嵌套文档,然后在其中找到 total 字段。

该符号也适用于多个嵌套文档:

db.peaks.find(
    { "ascents.first_winter.year": { $gt: 2000 } }
)

此查询将返回任何描述仅在 2000 年之后在冬季首次登顶的山脉的文档:

Output{ "_id" : ObjectId("610c23828a94efbbf0cf6008"), "name" : "Makalu", "height" : 8485, "location" : [ "China", "Nepal" ], "ascents" : { "first" : { "year" : 1955 }, "first_winter" : { "year" : 2009 }, "total" : 361 } }

和以前一样,ascents.first_winter.year 表示法意味着 MongoDB 首先找到 ascents 字段并在那里找到嵌套文档。 然后它进入另一个嵌套文档 first_winter,最后从其中检索 year 字段。

点符号可用于访问 MongoDB 中任何深度的嵌套文档。

到目前为止,您将对如何访问嵌套文档中的数据以及如何过滤查询结果有了很好的理解。 您可以继续学习如何限制查询返回的字段列表。

第 6 步 — 返回字段的子集

在到目前为止的所有示例中,每当您查询 peaks 集合时,MongoDB 都会返回一个或多个完整文档。 通常,您只需要来自少数几个领域的信息。 例如,您可能只想在数据库中查找山脉的名称。

这不仅是易读性的问题,也是性能的问题。 如果只需要文档的一小部分,则检索整个文档对象将对数据库造成不必要的性能负担。 在处理像本教程示例这样的小型数据集时,这可能不是问题,但在处理许多大型复杂文档时,它成为一个重要的考虑因素。

例如,假设您只对存储在 peaks 集合中的山名感兴趣,但这次攀登细节或位置并不重要。 您可以通过使用 projection 跟踪查询过滤器文档来限制查询将返回的字段。

投影文档是一个 JSON 对象,其中键对应于查询文档的字段。 投影可以构造为 包含投影排除投影。 当投影文档包含以 1 作为其值的键时,它描述了将包含在结果中的字段列表。 另一方面,如果投影键设置为 0,则投影文档将描述将从结果中排除的字段列表。

运行以下查询,其中包括现在熟悉的 find() 方法。 此查询的 find() 方法包含两个参数,而不是一个。 第一个,{},是查询过滤器文档。 这里它是一个空的 JSON 对象,这意味着它不会应用任何过滤。 第二个参数 { "name": 1 } 描述了投影,意味着查询结果将只包含每个文档的 name 字段:

db.peaks.find(
    {},
    { "name": 1 }
)

运行此示例查询后,MongoDB 返回以下结果:

Output{ "_id" : ObjectId("610c23828a94efbbf0cf6004"), "name" : "Everest" }
{ "_id" : ObjectId("610c23828a94efbbf0cf6005"), "name" : "K2" }
{ "_id" : ObjectId("610c23828a94efbbf0cf6006"), "name" : "Kangchenjunga" }
{ "_id" : ObjectId("610c23828a94efbbf0cf6007"), "name" : "Lhotse" }
{ "_id" : ObjectId("610c23828a94efbbf0cf6008"), "name" : "Makalu" }

请注意,返回的文档是简化的,仅包含 name_id 字段。 MongoDB 总是包含 _id 键,即使它没有被明确请求。

为了说明如何指定要排除的字段,请运行以下查询。 它将从每个文档返回数据,但会排除 ascentslocation 字段:

db.peaks.find(
    {},
    { "ascents": 0, "location": 0 }
)

MongoDB 再次返回所有五座山,但这次只有 nameheight_id 字段存在:

Output{ "_id" : ObjectId("610c23828a94efbbf0cf6004"), "name" : "Everest", "height" : 8848 }
{ "_id" : ObjectId("610c23828a94efbbf0cf6005"), "name" : "K2", "height" : 8611 }
{ "_id" : ObjectId("610c23828a94efbbf0cf6006"), "name" : "Kangchenjunga", "height" : 8586 }
{ "_id" : ObjectId("610c23828a94efbbf0cf6007"), "name" : "Lhotse", "height" : 8516 }
{ "_id" : ObjectId("610c23828a94efbbf0cf6008"), "name" : "Makalu", "height" : 8485 }

注意: 指定投影时,不能混合包含和排除。 您要么必须指定要包含的字段列表,要么必须指定要排除的字段列表。

然而,这条规则有一个例外。 即使查询应用了包含投影,MongoDB 也允许您从结果集中排除 _id 字段。 要抑制 _id 字段,您可以将 "_id": 0 附加到投影文档。 以下示例与前面的示例查询类似,但将排除所有字段,包括 _id,但 name 字段除外:

db.peaks.find(
    {},
    { "_id": 0, "name": 1 }
)
Output{ "name" : "Everest" }
{ "name" : "K2" }
{ "name" : "Kangchenjunga" }
{ "name" : "Lhotse" }
{ "name" : "Makalu" }

投影也可用于在嵌套文档中包含或排除字段。 例如,假设您想知道每座山的第一次冬季攀登和攀登总数,这两者都嵌套在 ascents 字段中。 此外,您还想返回每座山的名称。 为此,您可以运行如下查询:

db.peaks.find(
    {},
    { "_id": 0, "name": 1, "ascents": { "first_winter": 1, "total": 1 } }
)

请注意如何为 ascents 字段指定投影,以及它如何遵循嵌套文档的结构,即嵌套投影本身。 通过使用 "first_winter": 1, "total": 1,该查询告诉数据库只包含嵌套文档中的这两个字段,不包含其他字段。

返回的文档将仅包含请求的字段:

Output{ "name" : "Everest", "ascents" : { "first_winter" : { "year" : 1980 }, "total" : 5656 } }
{ "name" : "K2", "ascents" : { "first_winter" : { "year" : 1921 }, "total" : 306 } }
{ "name" : "Kangchenjunga", "ascents" : { "first_winter" : { "year" : 1988 }, "total" : 461 } }
{ "name" : "Makalu", "ascents" : { "first_winter" : { "year" : 2009 }, "total" : 361 } }

将返回文档的大小限制为仅字段的子集有助于使结果集更具可读性,甚至可以提高性能。 下一步概述如何限制查询返回的文档数量,并详细说明如何对查询返回的数据进行排序。

第 7 步 - 使用游标对查询结果进行排序和限制

从大型集合中检索对象时,有时您可能想要限制结果的数量或可能按特定顺序对它们进行排序。 例如,购物网站的一种流行方法是按价格对产品进行分类。 MongoDB 使用 cursors 允许您限制查询结果集中返回的文档数量,并按升序或降序对结果进行排序。

回想一下步骤 1 中的示例查询:

db.peaks.find()

您可能还记得,此查询返回的结果集包括 peaks 集合中每个文档的所有数据。 虽然 MongoDB 似乎返回了 peaks 集合中的所有对象,但事实并非如此。 MongoDB 实际返回的是一个 cursor 对象。

游标是指向查询结果集的指针,但它不是结果集本身。 这是一个可以迭代的对象,这意味着您可以请求光标返回行中的下一个文档,然后才能从数据库中检索完整的文档。 在此之前,光标仅指向列表中的下一个文档。

使用游标,MongoDB 可以确保仅在需要时才进行实际的文档检索。 当所讨论的文档很大或一次请求其中的许多文档时,这可能会对性能产生重大影响。

为了说明游标是如何工作的,运行以下包含 find()count() 方法的操作:

db.peaks.find().count()

MongoDB 将响应 5

Output5

在底层,find() 方法找到并返回一个游标,然后在该游标上调用 count() 方法。 这让 MongoDB 知道您对对象计数感兴趣,而不是文档本身。 这意味着文档不会成为结果的一部分——数据库将返回的只是计数。 在从游标中检索文档之前,使用游标对象上的方法进一步修改查询,您可以确保只对集合执行您要求的数据库操作。

注意: 执行查询时,MongoDB shell 会自动迭代返回的游标 20 次,以便在屏幕上显示前 20 个结果。 这是特定于 MongoDB shell 的。 以编程方式使用 MongoDB 时,它不会立即从游标中检索任何结果。


另一种使用游标改变结果集的 MongoDB 方法是 limit() 方法。 顾名思义,您可以使用 limit() 来限制查询将返回的结果数量。

运行以下查询,它将仅从集合中检索三个山峰:

db.peaks.find(
    {},
    { "_id": 0, "name": 1, "height": 1 }
).limit(3)

MongoDB shell 将响应三个对象而不是五个,即使查询没有过滤任何数据:

Output{ "name" : "Everest", "height" : 8848 }
{ "name" : "K2", "height" : 8611 }
{ "name" : "Kangchenjunga", "height" : 8586 }

应用于光标的 limit(3) 方法告诉光标在到达前 3 个文件后停止返回更多文档。 像这样对大型集合使用 limit() 游标方法将有助于确保您只检索您需要的结果,而不是更多。

默认情况下,MongoDB 将按插入顺序返回对象,但您可能希望更改该行为。 假设您有兴趣查找数据库中保存的三个最低的山峰。 您可以运行以下查询:

db.peaks.find(
    {},
    { "_id": 0, "name": 1, "height": 1 }
).limit(3).sort({ "height": 1 })

添加的 sort({ "height": 1 }) 导致结果集与前面的示例不同:

Output{ "name" : "Makalu", "height" : 8485 }
{ "name" : "Lhotse", "height" : 8516 }
{ "name" : "Kangchenjunga", "height" : 8586 }

再次,只返回三座山峰。 但是,这一次它们是从具有最低 height 值的那一个升序排序的。

光标上的 sort() 方法接受 JSON 对象 - height - 作为参数,类似于投影文档。 它还接受将用于排序的键列表。 接受的值是 1 用于升序或 -1 用于每个键的降序排序。

结论

通过阅读本文,您熟悉了 MongoDB 用于过滤查询结果的方式。 您针对单个字段、多个条件和复杂结构(例如数组和嵌套文档)过滤了集合文档。 您还学习了仅选择字段子集并使用游标方法对结果进行排序。 这些技术可用于从其他大型集合中仅检索感兴趣的文档。

本教程仅描述了 MongoDB 提出的少数查询运算符,以允许精确的文档查询。 您可以研究官方官方MongoDB文档,了解更多关于不同查询运算符的信息。