MongoDB 快速入门实战教程基础篇 一 :文档的 CR操作

在本文中我们将学习流式聚合操作,并深入了解语句的执行效率。然后深入学习能够提高数据服务可用性的复制集。接着了解 MongoDB 的水平扩展能力,学习 MongoDB 数据的备份与还原方法,并为数据服务开启访问控制。

基础篇 一 文档的 CRUD 操作

CRUD 操作指的是对文档进行 createreadupdate and delete 操作,即增删改查。文档 CRUD 操作的内容将分为 Create Operations, Read Operations, Update Operations, Delete OperationsCursor 等 5 个部分进行介绍。

Create Operations

创建操作或者插入操作会向集合添加新的文档。之前有提到过,如果插入时集合不存在,插入操作会创建对应的集合。MongoDB 提供了 3 个插入文档的方法:

插入单个文档

其中,db.collection.insertOne() 用于向集合插入单个文档。而 db.collection.insertMany()db.collection.insert() 可以向集合插入多个文档。db.collection.insertOne() 示例如下:

> db.zenrust.insertOne({
... nickname: "Rust 之禅",
... name: "zenrust",
... types: "订阅号",
... descs:"超酷人生,我用 Rust"
... })

自动命令执行后会返回一个结果文档,文档输出如下:

{
    "acknowledged" : true,
    "insertedId" : ObjectId("5d157fe26fcb85935e9cb786")
}

这说明文档插入成功。其中,acknowledged 代表本次操作的操作状态,状态值包括 truefalseinsertedId 即该文档的 _id

提示:示例中的省略号是 MongoShell 的换行标识符。换行标识符对命令输入和执行并没有影响,所以本文也不会注重风格的统一,即示例中有时会带有换行符,有时则不带有换行符。

插入多个文档

db.collection.insertMany() 示例如下:

> db.zenrust.insertMany([
... {nickname: "Rust 之禅", name: "zenrust", types: "订阅号", descs: "超酷人生,我用 Rust"},
... {nickname: "进击的 Coder", name: "FightingCoder", types: "订阅号", descs: "分享爬虫技术和机器学习方面的编程经验"}
... ])

由于本次插入了 2 个文档,所以返回的结果文档会显示两个 _id。返回文档内容如下:

{
    "acknowledged" : true,
    "insertedIds" : [
        ObjectId("5d1582136fcb85935e9cb787"),
        ObjectId("5d1582136fcb85935e9cb788")
    ]
}

db.collection.insert() 示例如下:

> db.zenrust.insert({title: "全面认识 RUST,掌控未来的雷电"})

示例演示的是单个文档的插入,实际上插入多个文档也是没问题的。db.collection.insert() 插入单个文档时返回的是一个带有操作状态的 WriteResult 对象:WriteResult({ "nInserted" : 1 }) 。其中,nInserted 表明了插入文档的总数。但如果插入操作遇到错误,那么 WriteResult 对象将包含错误提示信息。

db.collection.insert() 插入多个文档的示例如下:

> db.zenrust.insert([{nickname: "进击的 Coder"}, {nickname: "Rust 之禅"}])
BulkWriteResult({
    "writeErrors" : [ ],
    "writeConcernErrors" : [ ],
    "nInserted" : 2,
    "nUpserted" : 0,
    "nMatched" : 0,
    "nModified" : 0,
    "nRemoved" : 0,
    "upserted" : [ ]
})

可以看到,db.collection.insert() 插入多个文档和插入单个文档得到的返回结果是不同的。

Read Operations

MongoDB 提供了 db.collection.find() 方法从集合中读取文档。在开始练习之前,需要准备用于练习的基础数据。在 MongoShell 中执行以下文档插入操作:

> db.inven.insertMany([
   { name: "詹姆斯", number: 6, attribute: { h: 203, w: 222, p: "前锋" }, status: "A" },
   { name: "韦德", number: 3, attribute: { h: 193, w: 220, p: "得分后卫" }, status: "R" },
   { name: "科比", number: 24, attribute: { h: 198, w: 212, p: "得分后卫" }, status: "R" },
   { name: "姚明", number: 11, attribute: { h: 226, w: 308, p: "中锋" }, status: "R" },
   { name: "乔丹", number: 23, attribute: { h: 198, w: 216, p: "得分后卫" }, status: "R" }
])

查询文档

将一个空位当作为查询过滤器参数传递给 db.collection.find() 方法就可以得到所有文档,对应示例如下:

> db.inven.find({})

或者什么都不传,直接使用 find(),对应示例如下:

> db.inven.find()

这等效于 SQL 中的 SELECT * FROM inven

指定等式条件

如果要指定相等条件,可以使用 {<field1>: <value1>, ...} 这样的过滤表达式,例如过滤出已退役球员("R" 代表退役)的查询语句如下:

> db.inven.find({status: "R"})
{ "_id" : ObjectId("5d159e794d3d891430a2512e"), "name" : "韦德", "number" : 3, "attribute" : { "h" : 193, "w" : 220, "p" : "得分后卫" }, "status" : "R" }
{ "_id" : ObjectId("5d159e794d3d891430a2512f"), "name" : "科比", "number" : 24, "attribute" : { "h" : 198, "w" : 212, "p" : "得分后卫" }, "status" : "R" }
{ "_id" : ObjectId("5d159e794d3d891430a25130"), "name" : "姚明", "number" : 11, "attribute" : { "h" : 226, "w" : 308, "p" : "中锋" }, "status" : "R" }
{ "_id" : ObjectId("5d159e794d3d891430a25131"), "name" : "乔丹", "number" : 23, "attribute" : { "h" : 198, "w" : 216, "p" : "得分后卫" }, "status" : "R" }

这等效于 SQL 中的 SELECT * FROM inven WHERE status = "R"

根据嵌套文档字段查询

我们还可以根据嵌入式文档中的字段进行查询,例如过滤出球员属性中身高为 193 的球员,对应示例如下:

> db.inven.find({"attribute.h": 193})
{ "_id" : ObjectId("5d159e794d3d891430a2512e"), "name" : "韦德", "number" : 3, "attribute" : { "h" : 193, "w" : 220, "p" : "得分后卫" }, "status" : "R" }

要注意的是,访问嵌入式文档中的字段时使用的并不是 attribute.h,而是使用 "attribute.h"

查询与投影操作

查询的情况非常复杂,MongoDB 提供了多种查询操作符来应对这些问题。MongoDB 提供的查询操作符分为以下几类:

  • 比较查询操作符
  • 逻辑查询操作符
  • 元素查询操作符
  • 评估查询操作符
  • 地理空间查询操作符
  • 数组查询操作符
  • 按位查询操作符

接下来,我们将学习每一种查询操作符的规则和语法。

比较查询操作符

比较是最常见的操作之一,它分为同类型比较和非同类型比较。在面对不同的 BSON 类型值时,比较的并不是值的大小,而是值的类型,即按类比较。MongoDB 使用以下比较顺序,顺序从低到高:

  • MinKey (internal type)
  • Null
  • Numbers (ints, longs, doubles, decimals)
  • Symbol, String
  • Object
  • Array
  • BinData
  • ObjectId
  • Boolean
  • Date
  • Timestamp
  • Regular Expression
  • MaxKey (internal type)

同类型比较的情况则稍微复杂一些。数字类型比较的是值的大小,例如 5 大于 3。字符串类型比较的是其值的二进制,例如 R 大于 A 是因为 R 的二进制值 0101 0010 大于 A 的二进制值 0100 0001。数组的小于比较或者升序排序比较的是数组中的最小元素,大于比较或降序排序比较的是数组中的最大元素。我们可以通过一个例子来了解这些知识。准备如下数据:

> db.arrs.insertMany([ 
... {name: "James", attr: [5, 6, 7]}, 
... {name: "Wade", attr: [1, 7, 8]}, 
... {name: "Kobe", attr: [1, 9, 9]}, 
... {name: "Bosh", attr: [2, 9, 9]}
... ])

假设要将文档按 name 升序排序,即字符串升序排序。对应示例如下:

> db.arrs.find().sort({name: 1})
{ "_id" : ObjectId("5d1eb7fef91d329d7e731d4f"), "name" : "Bosh", "attr" : [ 2, 9, 9 ] }
{ "_id" : ObjectId("5d1eb7fef91d329d7e731d4c"), "name" : "James", "attr" : [ 5, 6, 7 ] }
{ "_id" : ObjectId("5d1eb7fef91d329d7e731d4e"), "name" : "Kobe", "attr" : [ 1, 9, 9 ] }
{ "_id" : ObjectId("5d1eb7fef91d329d7e731d4d"), "name" : "Wade", "attr" : [ 1, 7, 8 ] }

排序结果为 Bosh- James - Kobe - Wade,那么字符串降序排序的结果一定是 Wade - Kobe - James - Bosh。对应示例如下:

> db.arrs.find().sort({name: -1})
{ "_id" : ObjectId("5d1eb7fef91d329d7e731d4d"), "name" : "Wade", "attr" : [ 1, 7, 8 ] }
{ "_id" : ObjectId("5d1eb7fef91d329d7e731d4e"), "name" : "Kobe", "attr" : [ 1, 9, 9 ] }
{ "_id" : ObjectId("5d1eb7fef91d329d7e731d4c"), "name" : "James", "attr" : [ 5, 6, 7 ] }
{ "_id" : ObjectId("5d1eb7fef91d329d7e731d4f"), "name" : "Bosh", "attr" : [ 2, 9, 9 ] }

现有:[ 5, 6, 7 ], [ 1, 7, 8 ], [ 1, 9, 9 ], [ 2, 9, 9 ] 4 个数组,上面提到,数组升序排序比较的是最小元素。4 个数组中最小的值分别是 5, 1, 1, 2,其中数组 [1, 7, 8] 和数组 [1, 9, 9] 的最小值相同,则比较第二小的值,即 7, 9。那么正确的升序排序结果因该是 [ 1, 7, 8 ], [ 1, 9, 9 ], [ 2, 9, 9 ], [5, 6, 7]。数组升序排序命令如下:

> db.arrs.find().sort({attr: 1})
{ "_id" : ObjectId("5d1eb7fef91d329d7e731d4d"), "name" : "Wade", "attr" : [ 1, 7, 8 ] }
{ "_id" : ObjectId("5d1eb7fef91d329d7e731d4e"), "name" : "Kobe", "attr" : [ 1, 9, 9 ] }
{ "_id" : ObjectId("5d1eb7fef91d329d7e731d4f"), "name" : "Bosh", "attr" : [ 2, 9, 9 ] }
{ "_id" : ObjectId("5d1eb7fef91d329d7e731d4c"), "name" : "James", "attr" : [ 5, 6, 7 ] }

排序结果与分析结果一致。数组降序排序比较的是最大元素。4 个数组中最大的值分别是 7, 8, 9, 9,其中数组 [1, 9, 9][2, 9, 9] 的最大值和第二大的值相同,则比较第三大的值,即 1, 2。那么正确的降序排序结果应该是 [2, 9, 9], [1, 9, 9], [1, 7, 8], [5, 6, 7]。数组降序排序命令如下:

> db.els.find().sort({attr: -1})
{ "_id" : ObjectId("5d1edd28eb81ddef9df74fff"), "name" : "Kobe", "attr" : [ 1, 9, 9 ] }
{ "_id" : ObjectId("5d1edd28eb81ddef9df75000"), "name" : "Bosh", "attr" : [ 2, 9, 9 ] }
{ "_id" : ObjectId("5d1edd28eb81ddef9df74ffe"), "name" : "Wade", "attr" : [ 1, 7, 8 ] }
{ "_id" : ObjectId("5d1edd28eb81ddef9df74ffd"), "name" : "James", "attr" : [ 5, 6, 7 ] }

排序结果和分析结果并不同,这是为什么呢?难道不是按最大元素比较大小吗?

文档中并没有提到,但我们可以通过例子寻找答案。准备以下数据:

> db.parts.insertMany([
... {name: 1, attr: [9, 9, 0, 5]},
... {name: 2, attr: [9, 9, 0, 1]},
... {name: 3, attr: [9, 0, 9, 0]},
... {name: 4, attr: [9, 8, 7, 6]},
... {name: 5, attr: [5, 2, 3, 6]},
... {name: 6, attr: [9, 0, 0, 0]},
... {name: 7, attr: [30, 0]},
... {name: 8, attr: [22, 0]}, 
... {name: 9, attr: [30, 5]},
... {name: 10, attr: [30, 3]}
... ])

数组降序排序示例如下:

> db.parts.find().sort({attr: -1})
{ "_id" : ObjectId("5d1eef27882bacc4ec4cb121"), "name" : 7, "attr" : [ 30, 0 ] }
{ "_id" : ObjectId("5d1eef27882bacc4ec4cb123"), "name" : 9, "attr" : [ 30, 5 ] }
{ "_id" : ObjectId("5d1eef27882bacc4ec4cb124"), "name" : 10, "attr" : [ 30, 3 ] }
{ "_id" : ObjectId("5d1eef27882bacc4ec4cb122"), "name" : 8, "attr" : [ 22, 0 ] }
{ "_id" : ObjectId("5d1eef27882bacc4ec4cb11b"), "name" : 1, "attr" : [ 9, 9, 0, 5 ] }
{ "_id" : ObjectId("5d1eef27882bacc4ec4cb11c"), "name" : 2, "attr" : [ 9, 9, 0, 1 ] }
{ "_id" : ObjectId("5d1eef27882bacc4ec4cb11d"), "name" : 3, "attr" : [ 9, 0, 9, 0 ] }
{ "_id" : ObjectId("5d1eef27882bacc4ec4cb11e"), "name" : 4, "attr" : [ 9, 8, 7, 6 ] }
{ "_id" : ObjectId("5d1eef27882bacc4ec4cb120"), "name" : 6, "attr" : [ 9, 0, 0, 0 ] }
{ "_id" : ObjectId("5d1eef27882bacc4ec4cb11f"), "name" : 5, "attr" : [ 5, 2, 3, 6 ] }

数组升序排序示例如下:

> db.parts.find().sort({attr: 1})
{ "_id" : ObjectId("5d1eef27882bacc4ec4cb11b"), "name" : 1, "attr" : [ 9, 9, 0, 5 ] }
{ "_id" : ObjectId("5d1eef27882bacc4ec4cb11c"), "name" : 2, "attr" : [ 9, 9, 0, 1 ] }
{ "_id" : ObjectId("5d1eef27882bacc4ec4cb11d"), "name" : 3, "attr" : [ 9, 0, 9, 0 ] }
{ "_id" : ObjectId("5d1eef27882bacc4ec4cb120"), "name" : 6, "attr" : [ 9, 0, 0, 0 ] }
{ "_id" : ObjectId("5d1eef27882bacc4ec4cb121"), "name" : 7, "attr" : [ 30, 0 ] }
{ "_id" : ObjectId("5d1eef27882bacc4ec4cb122"), "name" : 8, "attr" : [ 22, 0 ] }
{ "_id" : ObjectId("5d1eef27882bacc4ec4cb11f"), "name" : 5, "attr" : [ 5, 2, 3, 6 ] }
{ "_id" : ObjectId("5d1eef27882bacc4ec4cb124"), "name" : 10, "attr" : [ 30, 3 ] }
{ "_id" : ObjectId("5d1eef27882bacc4ec4cb123"), "name" : 9, "attr" : [ 30, 5 ] }
{ "_id" : ObjectId("5d1eef27882bacc4ec4cb11e"), "name" : 4, "attr" : [ 9, 8, 7, 6 ] }

根据以上结果,我们可以推测出排序规律:如果被比较的值相同,那么就按照插入顺序(即 ObjectId)排序。降序排序比较的是最大元素,即 30 - 30 - 30 - 22 -9 - 9 - 9 - 9 - 9 - 6,其中 309 均有重复。可以发现:

  • 第一个 30 对应的 name 值为 7,第二个 30 对应的 name 值为 9,第三个 30 对应的 name 值为 10
  • 第一个 9 对应的 name 值为 1,第二个 9 对应的 name 值为 2,第三个 9 对应的 name 值为 3,第四个 9 对应的 name 值为 4,第五个 9 对应的 name 值为 6

升序排序比较的是最小元素,即 0 - 0 - 0 - 0 - 0 - 0 - 2 - 3 - 5 - 6,其中重复的只有 00 对应的 name 值依次为 1, 2, 3, 6, 7, 8 。无论是升序排序还是降序排序,实际得到的结果与我们推测出来的规律相同,这说明我们推测出来的规律是正确的。其他类型的比较或排序规则可查阅官方文档 comparison-sort-order

MongoDB 提供了一系列用于比较的比较符,它们分别是:

名称 描述
$eq 匹配等于指定值的值
$gt 匹配大于指定值的值
$gte 匹配大于或等于指定值的值
$in 匹配数组中指定的任何值
$lt 匹配小于指定值的值
$lte 匹配小于或等于指定值的值
$ne 匹配所有不等于指定值的值
$nin 不匹配数组中指定的任何值

其中,$eq$gte$lt$lte$gt$ne 的语法是相同的。以 $eq 为例,其语法格式如下:

{ <field>: { $eq: <value> } }

假设要匹配集合 els 中名称为 James 的文档,对应示例如下:

> db.els.find({name: {$eq: "James"}})
{ "_id" : ObjectId("5d1edd28eb81ddef9df74ffd"), "name" : "James", "attr" : [ 5, 6, 7 ] }

这等效于 SQL 中的 SELECT * FROM els WHERE name = "James"

$in$nin 的语法相同。以 $in 为例,其格式如下:

{ field: { $in: [<value1>, <value2>, ... <valueN> ] } }

假设要过滤出集合 els 中文档字段 attr 中包含 6 或者 9 的文档,对应示例如下:

> db.els.find({attr: {$in: [6, 9]}})
{ "_id" : ObjectId("5d1edd28eb81ddef9df74ffd"), "name" : "James", "attr" : [ 5, 6, 7 ] }
{ "_id" : ObjectId("5d1edd28eb81ddef9df74fff"), "name" : "Kobe", "attr" : [ 1, 9, 9 ] }
{ "_id" : ObjectId("5d1edd28eb81ddef9df75000"), "name" : "Bosh", "attr" : [ 2, 9, 9 ] }

另外,$in$nin 均支持正则表达式。例如要过滤出集合 invenname 字段值以 或者 开头的文档,对应示例如下:

> db.inven.find({name: {$in: [/^詹/, /^韦/]}})
{ "_id" : ObjectId("5d200b986c39176e3a421af2"), "name" : "詹姆斯", "number" : 6, "attribute" : { "h" : 203, "w" : 222, "p" : "前锋" }, "status" : "A" }
{ "_id" : ObjectId("5d200b986c39176e3a421af3"), "name" : "韦德", "number" : 3, "attribute" : { "h" : 193, "w" : 220, "p" : "得分后卫" }, "status" : "R" }

反过来,过滤出集合 invenname 字段值非 或者非 开头的文档。对应示例如下:

> db.inven.find({name: {$nin: [/^詹/, /^韦/]}})
{ "_id" : ObjectId("5d200b986c39176e3a421af4"), "name" : "科比", "number" : 24, "attribute" : { "h" : 198, "w" : 212, "p" : "得分后卫" }, "status" : "R" }
{ "_id" : ObjectId("5d200b986c39176e3a421af5"), "name" : "姚明", "number" : 11, "attribute" : { "h" : 226, "w" : 308, "p" : "中锋" }, "status" : "R" }
{ "_id" : ObjectId("5d200b986c39176e3a421af6"), "name" : "乔丹", "number" : 23, "attribute" : { "h" : 198, "w" : 216, "p" : "得分后卫" }, "status" : "R" }

以上就是比较查询操作符的相关知识,更多关于比较查询操作符的知识可查阅官方文档 Comparison Query Operators

逻辑查询操作符

MongoDB 中的逻辑查询操作符共有 4 种,它们是:

名称 描述
$and 匹配符合多个条件的文档
$not 匹配不符合条件的文档
$nor 匹配不符合多个条件的文档
$or 匹配符合任一条件的文档

其中,$and$nor$or 语法格式相同:

{ $keyword: [ { <expression1> }, { <expression2> } , ... , { <expressionN> } ] }

语法中的 keyword 代表 and/nor/or。而 $not 语法格式如下

{ field: { $not: { <operator-expression> } } }

$and 是隐式的,这意味着我们不必在查询语句中表明 andAND 。 假设要过滤出集合 inven球衣号大于 10退役球员,此时有两个条件:球衣号大于 10退役球员。对应示例如下:

> db.inven.find({status: "R", number: {$gt: 10}})
{ "_id" : ObjectId("5d159e794d3d891430a2512f"), "name" : "科比", "number" : 24, "attribute" : { "h" : 198, "w" : 212, "p" : "得分后卫" }, "status" : "R" }
{ "_id" : ObjectId("5d159e794d3d891430a25130"), "name" : "姚明", "number" : 11, "attribute" : { "h" : 226, "w" : 308, "p" : "中锋" }, "status" : "R" }
{ "_id" : ObjectId("5d159e794d3d891430a25131"), "name" : "乔丹", "number" : 23, "attribute" : { "h" : 198, "w" : 216, "p" : "得分后卫" }, "status" : "R" }

这等效于 SQL 中的 SELECT * FROM inven WHERE status = "R" AND number > 10。当然,也可以采用显式写法,对应示例如下:

> db.inven.find({$and: [{status: "R"}, {number: {$gt: 10}}]})
{ "_id" : ObjectId("5d159e794d3d891430a2512f"), "name" : "科比", "number" : 24, "attribute" : { "h" : 198, "w" : 212, "p" : "得分后卫" }, "status" : "R" }
{ "_id" : ObjectId("5d159e794d3d891430a25130"), "name" : "姚明", "number" : 11, "attribute" : { "h" : 226, "w" : 308, "p" : "中锋" }, "status" : "R" }
{ "_id" : ObjectId("5d159e794d3d891430a25131"), "name" : "乔丹", "number" : 23, "attribute" : { "h" : 198, "w" : 216, "p" : "得分后卫" }, "status" : "R" }

$or$not$nor 均采用显式写法。假设要过滤出集合 inven球衣号大于 10 或者 退役球员 的文档,对应示例如下:

> db.inven.find({$or: [{status: "R"}, {number: {$gt: 10}}]})
{ "_id" : ObjectId("5d159e794d3d891430a2512e"), "name" : "韦德", "number" : 3, "attribute" : { "h" : 193, "w" : 220, "p" : "得分后卫" }, "status" : "R" }
{ "_id" : ObjectId("5d159e794d3d891430a2512f"), "name" : "科比", "number" : 24, "attribute" : { "h" : 198, "w" : 212, "p" : "得分后卫" }, "status" : "R" }
{ "_id" : ObjectId("5d159e794d3d891430a25130"), "name" : "姚明", "number" : 11, "attribute" : { "h" : 226, "w" : 308, "p" : "中锋" }, "status" : "R" }
{ "_id" : ObjectId("5d159e794d3d891430a25131"), "name" : "乔丹", "number" : 23, "attribute" : { "h" : 198, "w" : 216, "p" : "得分后卫" }, "status" : "R" }

这等效于 SQL 中的 SELECT * FROM inven WHERE status = "R" OR number > 10

假设要过滤出集合 invennumber 不等于 11number 不等于 23 的文档,对应示例如下:

> db.inven.find({$nor: [{number: 23}, {number: 11}]})
{ "_id" : ObjectId("5d200b986c39176e3a421af2"), "name" : "詹姆斯", "number" : 6, "attribute" : { "h" : 203, "w" : 222, "p" : "前锋" }, "status" : "A" }
{ "_id" : ObjectId("5d200b986c39176e3a421af3"), "name" : "韦德", "number" : 3, "attribute" : { "h" : 193, "w" : 220, "p" : "得分后卫" }, "status" : "R" }
{ "_id" : ObjectId("5d200b986c39176e3a421af4"), "name" : "科比", "number" : 24, "attribute" : { "h" : 198, "w" : 212, "p" : "得分后卫" }, "status" : "R" }

假设要过滤出集合 invennumber 不大于 20 的文档,对应示例如下:

> db.inven.find({number: {$not: {$gt: 20}}})
{ "_id" : ObjectId("5d200b986c39176e3a421af2"), "name" : "詹姆斯", "number" : 6, "attribute" : { "h" : 203, "w" : 222, "p" : "前锋" }, "status" : "A" }
{ "_id" : ObjectId("5d200b986c39176e3a421af3"), "name" : "韦德", "number" : 3, "attribute" : { "h" : 193, "w" : 220, "p" : "得分后卫" }, "status" : "R" }
{ "_id" : ObjectId("5d200b986c39176e3a421af5"), "name" : "姚明", "number" : 11, "attribute" : { "h" : 226, "w" : 308, "p" : "中锋" }, "status" : "R" }

这个例子将比较查询操作符和逻辑查询操作符结合使用,实现了更细致的查询。

更多关于逻辑查询操作符的知识可查阅官方文档 Logical Query Operators

元素查询操作符

MongoDB 中的元素查询操作符只有 2 种,它们是:

名称 描述
$exists 匹配具有指定字段的文档
$type 匹配字段值符合类型的文档

exists

在开始学习 $exists 前,我们需要准备以下数据:

> db.elem.insertMany([
... {title: "湖人今夏交易频繁", author: "Asyncins", date: "2019-07-01", article: "..."},
... {title: "詹姆斯现身 MIA-CHN 比赛现场", date: "2019-07-06", article: "..."},
... {title: "伦纳德迟迟不肯表态", author: "Asyncins"}
... ])

$exists 语法格式如下:

{ field: { $exists: <boolean> } }

假设要过滤出集合 elem 中包含 author 字段的文档,对应示例如下:

> db.elem.find({author: {$exists: true}})
{ "_id" : ObjectId("5d203f1a6c39176e3a421af7"), "title" : "湖人今夏交易频繁", "author" : "Asyncins", "date" : "2019-07-01", "article" : "..." }
{ "_id" : ObjectId("5d203f1a6c39176e3a421af9"), "title" : "伦纳德迟迟不肯表态", "author" : "Asyncins" }

反过来,要过滤出集合 elem 中不包含 author 字段的文档,对应示例如下:

> db.elem.find({author: {$exists: false}})
{ "_id" : ObjectId("5d203f1a6c39176e3a421af8"), "title" : "詹姆斯现身 MIA-CHN 比赛现场", "date" : "2019-07-06", "article" : "..." }

type

在开始学习 $type 前,我们需要准备如下数据:

> db.ops.insertMany([
...     {title: "北京高温持续,注意避暑", weight: 5, rec: false},
...     {title: "广西持续降雨,最大降雨量 200 ml", weight: 5, rec: false},
...     {title: "高考分数线已出,高分学子增多", weight: "hot", rec: true},
...     {title: "秋老虎是真是假?", weight: 3, rec: false}
... ])

$type 语法如下:

{ field: { $type: <BSON type> } }

它也支持阵列写法:

{ field: { $type: [ <BSON type1> , <BSON type2>, ... ] } }

假设要过滤出 weight 值类型为 String 的文档,对应示例如下:

> db.ops.find({weight: {$type: "string"}})
{ "_id" : ObjectId("5d1838eb51b88758035de5b7"), "title" : "高考分数线已出,高分学子增多", "weight" : "hot", "rec" : true }

同理,过滤出 weight 值类型为 Number 的文档的对应示例如下:

> db.ops.find({weight: {$type: "number"}})
{ "_id" : ObjectId("5d1838eb51b88758035de5b5"), "title" : "北京高温持续,注意避暑", "weight" : 5, "rec" : false }
{ "_id" : ObjectId("5d1838eb51b88758035de5b6"), "title" : "广西持续降雨,最大降雨量 200 ml", "weight" : 5, "rec" : false }
{ "_id" : ObjectId("5d1838eb51b88758035de5b8"), "title" : "秋老虎是真是假?", "weight" : 3, "rec" : false }

阵列写法中的 BSON 类型为 or 关系,例如要过滤出 weight 值类型为 String 或者 Number 的文档,对应示例如下:

> db.ops.find({weight: {$type: ["string", "number"]}})
{ "_id" : ObjectId("5d1838eb51b88758035de5b5"), "title" : "北京高温持续,注意避暑", "weight" : 5, "rec" : false }
{ "_id" : ObjectId("5d1838eb51b88758035de5b6"), "title" : "广西持续降雨,最大降雨量 200 ml", "weight" : 5, "rec" : false }
{ "_id" : ObjectId("5d1838eb51b88758035de5b7"), "title" : "高考分数线已出,高分学子增多", "weight" : "hot", "rec" : true }
{ "_id" : ObjectId("5d1838eb51b88758035de5b8"), "title" : "秋老虎是真是假?", "weight" : 3, "rec" : false }

这等效于 SELECT * FROM ops WHERE weight.type = string OR weight.type = number 这样的 SQL 伪代码表示。要注意的是,$type 支持所有 BSON 类型的字符串标识符和整数标识符,例如 String 类型的字符串标识符 sting 及其整数标识符 2,即 {$type: "string"} 等效于 {$type: 2}

更多关于元素查询操作符的知识可查阅官方文档 Element Query Operators

评估查询操作符

MongoDB 中的评估查询操作符共有 6 种,它们是:

名称 描述
$expr 允许在查询语句中使用聚合表达式
$jsonSchema 根据给定的 JSON 模式验证文档
$mod 对字段的值执行模运算,并选择具有指定结果的文档
$regex 匹配与正则表达式规则相符的文档
$text 执行文本搜索
$where 匹配满足 JavaScript 表达式的文档

expr

在学习 $expr 前,我们需要准备以下数据:

> db.acbook.insertMany([
... {_id: 1, category: "衣", 预算: 300, 开支: 600},
... {_id: 2, category: "食", 预算: 1000, 开支: 600},
... {_id: 3, category: "住", 预算: 800, 开支: 800},
... {_id: 4, category: "行", 预算: 220, 开支: 360},
... {_id: 5, category: "医", 预算: 200, 开支: -50}
... ])

$expr 语法格式如下:

{ $expr: { <expression> } }

假设要过滤出超出预算的文档,对应示例如下:

> db.acbook.find({$expr: {$gt: ["$开支", "$预算"]}})
{ "_id" : 1, "category" : "衣", "预算" : 300, "开支" : 600 }
{ "_id" : 4, "category" : "行", "预算" : 220, "开支" : 360 }

示例中使用了 $gt 表达式,用于比较 开支预算。我们也可以使用 $lt 表达式,对应命令如下:

> db.acbook.find({$expr: {$lt: ["$预算", "$开支"]}})
{ "_id" : 1, "category" : "衣", "预算" : 300, "开支" : 600 }
{ "_id" : 4, "category" : "行", "预算" : 220, "开支" : 360 }

$expr 支持的表达式非常多,详见官方文档 Expressions

mod

$mod 的作用是对字段的值执行模运算,并选择具有指定结果的文档。其语法格式如下:

{ field: { $mod: [ divisor, remainder ] } }

假设要过滤出集合 acbook 中满足 mod(开支, 6) = 0 的文档。对应示例如下:

> db.acbook.find({开支: {$mod: [6, 0]}})
{ "_id" : 1, "category" : "衣", "预算" : 300, "开支" : 600 }
{ "_id" : 2, "category" : "食", "预算" : 1000, "开支" : 600 }
{ "_id" : 4, "category" : "行", "预算" : 220, "开支" : 360 }

mod(600, 6) = 0mod(360, 6) = 0。同理,要过滤出集合 acbook 中满足 mod(开支, 6) = 2 的文档,对应示例如下:

> db.acbook.find({开支: {$mod: [6, 2]}})
{ "_id" : 3, "category" : "住", "预算" : 800, "开支" : 800 }

mod(800, 6) = 2。要注意的是,$mod 只接受 2 个参数:divisorremainder。如果只传入 1 个参数,例如 db.acbook.find({开支: {$mod: [6]}}), 就会得到如下错误提示:

Error: error: {
    "ok" : 0,
    "errmsg" : "malformed mod, not enough elements",
    "code" : 2,
    "codeName" : "BadValue"
}

不传入值或传入多个值也是不被允许的,在返回文档中的 errmsg 处会给出对应的提示。例如不传入值对应的提示为 malformed mod, not enough elements,而多个值对应的提示为 malformed mod, too many elements

提示:在 2.6 版本中,传入单个值时会默认补上 0,传入多个值时会忽略多余的值。例如 db.acbook.find({开支: {$mod: [6]}})db.acbook.find({开支: {$mod: [6, 2, 3, 5]}}) 等效于 db.acbook.find({开支: {$mod: [6, 0]}})。但 4.0 版本不允许这样做。

regex

MongoDB 提供的 $regex 让开发者可以在查询语句中使用正则表达式,这实在是令人惊喜。MongoDB 中的正则表达式是 PCRE,即 Perl 语言兼容的正则表达式。$regex 语法格式如下:

{ <field>: { $regex: /pattern/, $options: '<options>' } }
{ <field>: { $regex: 'pattern', $options: '<options>' } }
{ <field>: { $regex: /pattern/<options> } }

三种格式任选其一,特定语法的使用限制可参考 $regex vs./pattern/Syntax 。也可以用下面这种语法:

{ <field>: /pattern/<options> }

正则表达式中有一些特殊选项(又称模式修正符),例如不区分大小写或允许使用点字符等,MongoDB 中支持的选项如下:

选项 描述 语法限制
i 不区分大小写字母。
m 支持多行匹配。
x 忽略空格和注释(#),注释以 \n 结尾。 必须使用 $option
s 允许点(.)字符匹配括换行符在内的所有字符,也可以理解为允许点(.)字符匹配换行符后面的字符。 必须使用 $option

在开始学习之前,准备以下数据:

> db.regexs.insertMany([
... {_id: 1, nickname: "abc123", desc: "Single Line Description."},
... {_id: 2, nickname: "abc299", desc: "First line \nSecond line"},
... {_id: 3, nickname: "xyz5566", desc: "Many spaces before    line"},
... {_id: 4, nickname: "xyz8205", desc: "Multiple\nline description"}
... ])

假设要过滤出 nickname 值结尾为 299 的文档,对应示例如下:

> db.regexs.find({nickname: {$regex: /299$/}})
{ "_id" : 2, "nickname" : "abc299", "desc" : "First line \nSecond line" }

这相当于 SQL 中的模糊查询,对应的 SQL 语句为 SELECT * FROM regexs WHERE nickname like "%299"。接下来使用模式修正符 i 实现不区分大小写的匹配,对应示例如下:

> db.regexs.find({nickname: {$regex: /^aBc/i}})
{ "_id" : 1, "nickname" : "abc123", "desc" : "Single Line Description." }
{ "_id" : 2, "nickname" : "abc299", "desc" : "First line \nSecond line" }

这个语句的作用是过滤出集合 regexsnickname 字段值由 aBc 开头的文档,并在匹配时忽略大小写字母。接下来我们再通过一个例子了解模式修正符 m 的用法和作用,对应示例如下:

> db.regexs.find({desc: {$regex: /^s/, $options: "im"}})
{ "_id" : 1, "nickname" : "abc123", "desc" : "Single Line Description." }
{ "_id" : 2, "nickname" : "abc299", "desc" : "First line \nSecond line" }

这个语句的作用是过滤出集合 regexsdesc 字段值由 s 开头的文档,匹配时忽略大小写字母,并进行多行匹配。虽然 _id 为 2 的文档中的 desc 并不是 sS 开头,但由于使用了模式修正符 m,所以能够匹配到 \n 符号后面的 Second。如果没有使用模式修正符 m,那么匹配结果将会是 { "_id" : 1, "nickname" : "abc123", "desc" : "Single Line Description." }

点字符和星号在正则表达式中是最常用的组合,MongoDB 也支持这个组合。假设要过滤出集合 regexsdesc 字段值由 m 开头且 line 结尾的文档,对应示例如下:

> db.regexs.find({desc: {$regex: /m.*line/, $options: "is"}})
{ "_id" : 3, "nickname" : "xyz5566", "desc" : "Many spaces before    line" }
{ "_id" : 4, "nickname" : "xyz8205", "desc" : "Multiple\nline description" }

如果不使用模式修正符 s.* 组合也是可用的,但无法匹配到换行符后面的内容,那么匹配结果将会是 { "_id" : 3, "nickname" : "xyz5566", "desc" : "Many spaces before line" }

模式修正符 x 的描述为:“忽略空格和注释(#),注释以 \n 结尾”。这理解起来有些困难,但你不用担心,只要跟着本文指引和案例,就能够掌握模式修正符 x 的正确用法。示例如下:

> var pattern = "abc #category code\n123 #item number"
> db.regexs.find({nickname: {$regex: pattern, $options: "x"}})
{ "_id" : 1, "nickname" : "abc123", "desc" : "Single Line Description." }

正则规则为 abc #category code\n123 #item number,根据模式修正符 x 的描述,我们可以将其转换为 abc123。即过滤出集合 regexsnickname 值为 abc123 的文档,所以执行结果为:

{ "_id" : 1, "nickname" : "abc123", "desc" : "Single Line Description." }

再来看一个示例:

> var pattern = "abc #category code\n xyz#item number"
> db.regexs.find({nickname: {$regex: pattern, $options: "x"}})

还有 55% 的精彩内容
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
支付 ¥15.99 继续阅读
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,185评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,445评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,684评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,564评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,681评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,874评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,025评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,761评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,217评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,545评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,694评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,351评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,988评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,778评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,007评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,427评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,580评论 2 349