This commit is contained in:
heibaiying 2019-07-31 14:39:37 +08:00
parent d96bc11786
commit d09df055c8
10 changed files with 361 additions and 55 deletions

View File

@ -54,10 +54,11 @@ TODO
#### 4.MongoDB #### 4.MongoDB
+ MongoDB 简介及基本原理 + [MongoDB 基础](https://github.com/heibaiying/Full-Stack-Notes/blob/master/notes/MongoDB_基础.md)
+ MongoDB数据类型分析 + [MongoDB 索引](https://github.com/heibaiying/Full-Stack-Notes/blob/master/notes/MongoDB_索引.md)
+ MongoDB 聚合、索引及基本执行命令 + [MongoDB 聚合](https://github.com/heibaiying/Full-Stack-Notes/blob/master/notes/MongoDB_聚合.md)
+ MongoDB数据分片、转存及恢复策略 + [MongoDB 复制](https://github.com/heibaiying/Full-Stack-Notes/blob/master/notes/MongoDB_复制.md)
+ [MongoDB 分片](https://github.com/heibaiying/Full-Stack-Notes/blob/master/notes/MongoDB_分片.md)

View File

@ -1,5 +1,22 @@
# MongoDB 基础 # MongoDB 基础
<nav>
<a href="#一数据类型">一、数据类型</a><br/>
<a href="#二新增数据">二、新增数据</a><br/>
<a href="#三查询数据">三、查询数据</a><br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#31-基本-API">3.1 基本 API</a><br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#32-等值查询">3.2 等值查询</a><br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#33-范围查询">3.3 范围查询</a><br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#34-逻辑查询">3.4 逻辑查询</a><br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#35-集合查询">3.5 集合查询</a><br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#36-额外操作">3.6 额外操作</a><br/>
<a href="#四修改数据">四、修改数据</a><br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#41-基本-API">4.1 基本 API</a><br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#42-常规修改器">4.2 常规修改器</a><br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#43-数组修改器">4.3 数组修改器</a><br/>
<a href="#五删除数据">五、删除数据</a><br/>
</nav>
## 一、数据类型 ## 一、数据类型
### 1.1 BSON ### 1.1 BSON
@ -56,7 +73,7 @@ db.collection.insertMany()
新增单条数据的示例如下。额外需要说明的是,在插入文档前,文档所属的集合不必预先创建,程序会自动创建: 新增单条数据的示例如下。额外需要说明的是,在插入文档前,文档所属的集合不必预先创建,程序会自动创建:
```json ```shell
db.user.insertOne({ db.user.insertOne({
name: "heibai", name: "heibai",
age: 26, age: 26,
@ -68,7 +85,7 @@ db.user.insertOne({
新增多条数据的示例如下: 新增多条数据的示例如下:
```json ```shell
db.user.insertMany([ db.user.insertMany([
{ {
name: "hei", name: "hei",
@ -109,7 +126,7 @@ db.collection.find(<query>, <projection>)
+ `<query>`:用于指定查询条件,不加任何条件则默认查询集合中全部数据; + `<query>`:用于指定查询条件,不加任何条件则默认查询集合中全部数据;
+ `<projection>`可选操作用于自定查询的返回字段1 表示该字段包含在返回结果中0 表示不返回,示例如下: + `<projection>`可选操作用于自定查询的返回字段1 表示该字段包含在返回结果中0 表示不返回,示例如下:
```json ```shell
db.user.find({},{name: 1, ObjectId:-1})} db.user.find({},{name: 1, ObjectId:-1})}
``` ```
@ -123,19 +140,19 @@ db.user.find({name:"heibai"})
上面的语法实际上是 `$eq` 操作的简写形式,如下: 上面的语法实际上是 `$eq` 操作的简写形式,如下:
```json ```shell
db.user.find({name: {$eq: "heibai"}}) db.user.find({name: {$eq: "heibai"}})
``` ```
所以如果你想要进行非等值查询,则可以使用 `$ne` 操作符,代表 not equal ,示例如下: 所以如果你想要进行非等值查询,则可以使用 `$ne` 操作符,代表 not equal ,示例如下:
```json ```shell
db.user.find({name: {$ne: "heibai"}}) db.user.find({name: {$ne: "heibai"}})
``` ```
特别的,如果你想允许某个字段等于多个值,可以使用 `$in` 操作符,示例如下: 特别的,如果你想允许某个字段等于多个值,可以使用 `$in` 操作符,示例如下:
```json ```shell
db.user.find({name: {$in:["heibai","ying"]} }) db.user.find({name: {$in:["heibai","ying"]} })
``` ```
@ -143,7 +160,7 @@ db.user.find({name: {$in:["heibai","ying"]} })
Mongodb 提供了比较操作符 `$lt``$lte``$gt``$gte` ,分别对应 <<=、 >和 >= ,主要用于范围查查询,示例如下: Mongodb 提供了比较操作符 `$lt``$lte``$gt``$gte` ,分别对应 <<=、 >和 >= ,主要用于范围查查询,示例如下:
```json ```shell
db.user.find({age: {$gt: 20, $lt: 40}}) db.user.find({age: {$gt: 20, $lt: 40}})
``` ```
@ -153,18 +170,18 @@ Mongodb 提供了逻辑操作符 `$or`、`$and`、`$not`、`$nor` ,用于处
查询姓名为 heibai 或者年龄大于 30 岁的所有用户,此时可以使用 $or 操作符: 查询姓名为 heibai 或者年龄大于 30 岁的所有用户,此时可以使用 $or 操作符:
```json ```shell
db.user.find( { $or: [{ name: "heibai" }, { age: { $gt: 30 } }] }) db.user.find( { $or: [{ name: "heibai" }, { age: { $gt: 30 } }] })
``` ```
查询所有姓名不是以 hei 开头的所有用户,此时可以使用 $not 操作符来配合正则表达式: 查询所有姓名不是以 hei 开头的所有用户,此时可以使用 $not 操作符来配合正则表达式:
```json ```shell
db.user.find({name: {$not: /^hei*/}}) db.user.find({name: {$not: /^hei*/}})
``` ```
如果文档中存在 name 字段,则它的值不能为 heibai如果文档中存在 age 字段,则它的值不能大于 30 ,只有满足以上两个条件的文档才会被查询出来,示例如下: 如果文档中存在 name 字段,则它的值不能为 heibai如果文档中存在 age 字段,则它的值不能大于 30 ,只有满足以上两个条件的文档才会被查询出来,示例如下:
```json ```shell
db.user.find( { $nor: [{ name: "heibai" }, { age: { $gt: 30 } }] }) db.user.find( { $nor: [{ name: "heibai" }, { age: { $gt: 30 } }] })
``` ```
@ -174,22 +191,22 @@ $and 操作符的使用率比较低,因为此时更好的方式是把多个条
如果需要查询个人爱好中有 football 的所有用户,即只要集合 Hobby 中存在 football 即可,对应的查询方法如下: 如果需要查询个人爱好中有 football 的所有用户,即只要集合 Hobby 中存在 football 即可,对应的查询方法如下:
```json ```shell
db.user.find({Hobby: "football"}) db.user.find({Hobby: "football"})
``` ```
如果想要获取集合中指定位置等于指定值的文档,对应的查询方法如下: 如果想要获取集合中指定位置等于指定值的文档,对应的查询方法如下:
```json ```shell
db.user.find({"Hobby.2": "football"}) db.user.find({"Hobby.2": "football"})
``` ```
如果想要约束集合必须包含多个指定值,此时可以使用 $all 操作符: 如果想要约束集合必须包含多个指定值,此时可以使用 $all 操作符:
```json ```shell
db.user.find({Hobby:{ $all: ["football", "tennis"]}}) db.user.find({Hobby:{ $all: ["football", "tennis"]}})
``` ```
查询时如果只想返回集合的部分内容,则可以使用 $slice $slice 接收一个参数 n正数表示获取集合的前 n 个参数,负数表示获取集合末尾的 n 个参数,示例如下: 查询时如果只想返回集合的部分内容,则可以使用 $slice $slice 接收一个参数 n正数表示获取集合的前 n 个参数,负数表示获取集合末尾的 n 个参数,示例如下:
```json ```shell
db.user.find({name: "heibai"},{Hobby:{$slice: 2}}) db.user.find({name: "heibai"},{Hobby:{$slice: 2}})
``` ```
@ -220,7 +237,7 @@ db.collection.updateMany(<filter>, <update>, <options>)
+ `<update>`:更改操作或新文档数据; + `<update>`:更改操作或新文档数据;
+ ` <options>`:可选操作,常用的可选操作是 `upsert` ,当其为 true 时,代表如果按照过滤条件没有找到对应的文档,则将待更改的数据插入到集合中;当其为 false 时,如果没有找到数据,则不执行任何操作。示例如下: + ` <options>`:可选操作,常用的可选操作是 `upsert` ,当其为 true 时,代表如果按照过滤条件没有找到对应的文档,则将待更改的数据插入到集合中;当其为 false 时,如果没有找到数据,则不执行任何操作。示例如下:
```json ```shell
db.user.replaceOne( db.user.replaceOne(
{ _id: ObjectId("5d3d00a4ad383d3becc7b03a")}, { _id: ObjectId("5d3d00a4ad383d3becc7b03a")},
{ {
@ -242,7 +259,7 @@ db.user.replaceOne(
用于修改具体的字段,如果待修改的字段不存在,则会新增该字段。示例如下: 用于修改具体的字段,如果待修改的字段不存在,则会新增该字段。示例如下:
```json ```shell
db.user.updateOne( db.user.updateOne(
{ name: "danrenying"}, { name: "danrenying"},
{ $set: {age: 66} } { $set: {age: 66} }
@ -253,7 +270,7 @@ db.user.updateOne(
用于对指定字段的值进行增加或减少,示例如下: 用于对指定字段的值进行增加或减少,示例如下:
```json ```shell
db.user.updateOne( db.user.updateOne(
{ name: "danrenying"}, { name: "danrenying"},
{ $inc: {age: -10} } { $inc: {age: -10} }
@ -268,7 +285,7 @@ db.user.updateOne(
用于往数组中新增数据,示例如下。使用 `$each` 可以一次添加多个元素: 用于往数组中新增数据,示例如下。使用 `$each` 可以一次添加多个元素:
```json ```shell
db.user.updateOne( db.user.updateOne(
{ name: "danrenying"}, { name: "danrenying"},
{ $push: {"Hobby": {$each: ["film","music"]}} } { $push: {"Hobby": {$each: ["film","music"]}} }
@ -279,7 +296,7 @@ db.user.updateOne(
该修改器可以把数组当做集 (set) 来使用,即只能添加当前数组中不存在的数据,示例如下: 该修改器可以把数组当做集 (set) 来使用,即只能添加当前数组中不存在的数据,示例如下:
```json ```shell
db.user.updateOne( db.user.updateOne(
{ name: "danrenying"}, { name: "danrenying"},
{ $addToSet: {"Hobby": {$each: ["film","music"]}} } { $addToSet: {"Hobby": {$each: ["film","music"]}} }
@ -290,7 +307,7 @@ db.user.updateOne(
该修改器可以从数组任意一端删除元素,`-1` 代表从数组头删除元素,`1` 代表从数组尾删除元素,示例如下: 该修改器可以从数组任意一端删除元素,`-1` 代表从数组头删除元素,`1` 代表从数组尾删除元素,示例如下:
```json ```shell
db.user.updateOne( db.user.updateOne(
{ name: "danrenying"}, { name: "danrenying"},
{ $pop: {"Hobby": -1} } { $pop: {"Hobby": -1} }
@ -319,7 +336,7 @@ db.collection.deleteOne()
使用示例如下: 使用示例如下:
```json ```shell
db.user.deleteOne( db.user.deleteOne(
{ name: "danrenying"} { name: "danrenying"}
) )

View File

@ -1,5 +1,27 @@
# MongoDB 索引 # MongoDB 索引
<nav>
<a href="#一索引简介">一、索引简介</a><br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#11-创建索引">1.1 创建索引</a><br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#12-查看索引">1.2 查看索引</a><br/>
<a href="#二索引的类型">二、索引的类型</a><br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#21-单字段索引">2.1 单字段索引</a><br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#22-复合索引">2.2 复合索引</a><br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#23-多键索引">2.3 多键索引</a><br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#24-哈希索引">2.4 哈希索引</a><br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#25-地理空间索引">2.5 地理空间索引</a><br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#26-文本索引">2.6 文本索引</a><br/>
<a href="#三索引的性质">三、索引的性质</a><br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#31-唯一索引">3.1 唯一索引</a><br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#32-稀疏性">3.2 稀疏性</a><br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#33-部分索引">3.3 部分索引</a><br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#34-TTL-索引">3.4 TTL 索引</a><br/>
<a href="#四删除索引">四、删除索引</a><br/>
<a href="#五EXPLAIN">五、EXPLAIN</a><br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#51-输出参数">5.1 输出参数</a><br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#52-覆盖索引">5.2 覆盖索引</a><br/>
</nav>
## 一、索引简介 ## 一、索引简介
### 1.1 创建索引 ### 1.1 创建索引
@ -90,8 +112,7 @@ db.user.find({}).sort({name:1})
当前大多数数据库都支持双向遍历索引,这和存储结构有关 (如下图)。在 B-Tree 结构的叶子节点上,存储了索引键的值及其对应文档的位置信息,而每个叶子节点间则类似于双向链表,所以如下图既可以从值为 4 的索引遍历到值为 92 的索引,反之亦然。 当前大多数数据库都支持双向遍历索引,这和存储结构有关 (如下图)。在 B-Tree 结构的叶子节点上,存储了索引键的值及其对应文档的位置信息,而每个叶子节点间则类似于双向链表,所以如下图既可以从值为 4 的索引遍历到值为 92 的索引,反之亦然。
![b-tree](D:\Full-Stack-Notes\pictures\b-tree.png) <div align="center"> <img src="https://github.com/heibaiying/Full-Stack-Notes/blob/master/pictures/b-tree.png"/> </div>
### 2.2 复合索引 ### 2.2 复合索引
@ -104,7 +125,7 @@ db.user.createIndex( { name: -1,birthday: 1} )
需要注意的是 MongoDB 的复合索引具备前缀索引的特征,即如果你创建了索引 `{ a:1, b: 1, c: 1, d: 1 }`,那么等价于在该集合上,还存在了以下三个索引,这三个隐式索引同样可以用于优化查询和排序操作: 需要注意的是 MongoDB 的复合索引具备前缀索引的特征,即如果你创建了索引 `{ a:1, b: 1, c: 1, d: 1 }`,那么等价于在该集合上,还存在了以下三个索引,这三个隐式索引同样可以用于优化查询和排序操作:
```json ```shell
{ a: 1 } { a: 1 }
{ a: 1, b: 1 } { a: 1, b: 1 }
{ a: 1, b: 1, c: 1 } { a: 1, b: 1, c: 1 }

View File

@ -1,5 +1,22 @@
# MongoDB 聚合操作 # MongoDB 聚合操作
<nav>
<a href="#一聚合简述">一、聚合简述</a><br/>
<a href="#二聚合管道">二、聚合管道</a><br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#11-$match">1.1 $match</a><br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#12-$project">1.2 $project</a><br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#13-$group">1.3 $group</a><br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#14-$unwind">1.4 $unwind</a><br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#15-$sort">1.5 $sort</a><br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#16-$limit">1.6 $limit</a><br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#17-$skip">1.7 $skip</a><br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#18-$lookup">1.8 $lookup</a><br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#19-$out">1.9 $out</a><br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#110-自动优化">1.10 自动优化</a><br/>
<a href="#三MapReduce">三、MapReduce</a><br/>
<a href="#四单用途聚合方法">四、单用途聚合方法</a><br/>
</nav>
## 一、聚合简述 ## 一、聚合简述
在日常开发中我们通常需要对存储数据进行聚合分析后再返回给客户端。MongoDB提供了三种聚合的方式分别是聚合管道map-reduce 函数和单用途聚合方法。 在日常开发中我们通常需要对存储数据进行聚合分析后再返回给客户端。MongoDB提供了三种聚合的方式分别是聚合管道map-reduce 函数和单用途聚合方法。
@ -59,7 +76,7 @@ db.employees.aggregate([
所以最后的输出结果如下: 所以最后的输出结果如下:
```json ```shell
{ {
"_id" : ObjectId("5d3fe6488ba16934ccce999d"), "_id" : ObjectId("5d3fe6488ba16934ccce999d"),
"fullName" : "GeorgiFacello" "fullName" : "GeorgiFacello"
@ -70,7 +87,7 @@ db.employees.aggregate([
} }
``` ```
在当前最新的 MongoDB 4.x 中MongoDB 提供了将近 30 个管道阶段,用于满足不同数据处理的需求。以下主要介绍常用几个管道阶段,如果想要了解全部的管道阶段,可以参见官方文档:[Aggregation Pipeline Stages](https://docs.mongodb.com/manual/reference/operator/aggregation-pipeline/) 在当前最新的 MongoDB 4.x 中MongoDB 提供了将近 30 个管道阶段,用于满足不同数据处理的需求。以下主要介绍常用几个管道阶段,如果想要了解全部的管道阶段,可以参见官方文档:[Aggregation Pipeline Stages](https://docs.mongodb.com/manual/reference/operator/aggregation-pipeline/)
### 1.1 $match ### 1.1 $match
@ -117,13 +134,13 @@ db.employees.aggregate([
]) ])
``` ```
这里判断当文档的 hobby 属性为空数组时,其 hobby 属性不会被输出到下一个管道阶段。 这里判断当文档的 hobby 属性为空数组时,其 hobby 属性不会被输出到下一个管道阶段。
### 1.3 $group ### 1.3 $group
$group 管道阶段和大多数关系型数据库中的 group by 字句功能类似,都是用于分组计算。示例如下: $group 管道阶段和大多数关系型数据库中的 group by 字句功能类似,都是用于分组计算。示例如下:
```json ```shell
db.employees.aggregate( db.employees.aggregate(
[ [
{ $group : { { $group : {
@ -199,7 +216,7 @@ $unwind 将文档按照数组中的每一个元素进行拆分,类似于大多
+ **preserveNullAndEmptyArrays**:如果用于展开的字段值为 null 或空数组时,则对应的文档不会被输出到下一阶段。如果想要输出到下一阶段则需要将该属性设置为 true。示例语句如下 + **preserveNullAndEmptyArrays**:如果用于展开的字段值为 null 或空数组时,则对应的文档不会被输出到下一阶段。如果想要输出到下一阶段则需要将该属性设置为 true。示例语句如下
```json ```shell
db.employees.aggregate( [ db.employees.aggregate( [
{$project: {_id: 0, emp_no: 1, hobby:1}}, {$project: {_id: 0, emp_no: 1, hobby:1}},
{ $unwind: { $unwind:
@ -228,7 +245,7 @@ db.employees.aggregate( [
### 1.5 $sort ### 1.5 $sort
$sort 主要用于排序操作,需要注意的是如果可以,应当尽量将该操作放置在管道的第一阶段,从而可以利用索引进行排序,否则就需要使用内存进行排序,这时排序操作就会变得相当昂贵,需要额外的内存和计算资源的开销 $sort 主要用于排序操作,需要注意的是如果可以,应当尽量将该操作放置在管道的第一阶段,从而可以利用索引进行排序,否则就需要使用内存进行排序,这时排序操作就会变得相当昂贵,需要额外消耗内存和计算资源
示例如下: 示例如下:
@ -250,6 +267,29 @@ db.employees.aggregate([
### 1.8 $lookup ### 1.8 $lookup
#### 1. 关联查询
$lookup 类似与大多数关系型数据库中的 left outer join ,用于实现不同集合之间的连接。其基本的语法如下:
```shell
{
$lookup:
{
from: <collection to join>,
localField: <field from the input documents>,
foreignField: <field from the documents of the "from" collection>,
as: <output array field>
}
}
```
+ **from**:指定同一数据库中的集合以进行连接操作;
+ **localField**:连接集合中用于进行连接的字段;
+ **foreignField**:待连接集合中用于进行连接的字段;
+ **as**:指定用于存放匹配文档的新数组字段的名称。如果指定的字段已存在,则进行覆盖。
为了 $lookup 管道,我们需要额外添加一个集合,用于储存员工的职位信息 (一个员工可以有多个职位头衔) ,语法如下:
```shell ```shell
db.titles.insertMany([ db.titles.insertMany([
{ {
@ -270,11 +310,13 @@ db.titles.insertMany([
}, },
{ {
emp_no: 10004, emp_no: 10004,
title: "Senior Engineer " title: "Senior Engineer"
} }
]) ])
``` ```
执行连接查询,连接条件为员工工号:
```shell ```shell
db.employees.aggregate([ db.employees.aggregate([
{ {
@ -287,8 +329,177 @@ db.employees.aggregate([
} }
} }
]) ])
```
输出结果如下,为节省篇幅和突出重点,这里只显示部分内容,下文亦同。从输出中可以看到员工的职位信息都作为内嵌文档插入到指定的 emp_title 字段下,等价于执行了 left outer join 操作。
```shell
{
"_id" : ObjectId("5d3ffaaa8ba16934ccce99a1"),
"emp_no" : 10001,
........
"emp_title" : [
{
"_id" : ObjectId("5d4011728ba16934ccce99a5"),
"emp_no" : 10001,
"title" : "Senior Engineer"
}
]
},
........
{
"_id" : ObjectId("5d3ffaaa8ba16934ccce99a4"),
"emp_no" : 10004,
........
"emp_title" : [
{
"_id" : ObjectId("5d4011728ba16934ccce99a8"),
"emp_no" : 10004,
"title" : "Engineer"
},
{
"_id" : ObjectId("5d4011728ba16934ccce99a9"),
"emp_no" : 10004,
"title" : "Senior Engineer"
}
]
}
```
#### 2. 非相关查询
除了关联查询外MongoDB 从 3.6 开始,还支持非相关查询。其基本语法如下:
```shell
{
$lookup:
{
from: <collection to join>,
let: { <var_1>: <expression>, …, <var_n>: <expression> },
pipeline: [ <pipeline to execute on the collection to join> ],
as: <output array field>
}
}
```
- **from**:指定同一数据库中的集合以进行连接操作;
- **let**:可选操作。用于指定在管道阶段中使用的变量,需要注意的是访问 let 变量必须使用 $expr 运算符。
- **pipeline**:必选值,用于指定要在已连接集合上运行的管道。需要注意的是该管道无法直接访问输入文档中的字段,需要先在 let 中进行定义,然后再引用。
- **as**:指定用于存放匹配文档的新数组字段的名称。如果指定的字段已存在,则进行覆盖。
这里我们分别基于三种场景来讲解不相关查询:
**场景一**:假设每个员工都需要了解其他员工的职位信息,以便进行沟通,则对应的查询语法如下:
```shell
db.employees.aggregate([
{
$lookup:
{
from: "titles",
pipeline: [],
as: "emps_title"
}
}
])
```
此时输出如下,可以看到每个员工的 emps_title 字段都是相同的,都包含了所有员工的职位信息。因为查询中并没有指定任何字段进行关联,所以这种查询也被称为非相关查询。
```shell
{
"_id" : ObjectId("5d3ffaaa8ba16934ccce99a1"),
"emp_no" : 10001,
.......
],
"emps_title" : [
{
"_id" : ObjectId("5d4011728ba16934ccce99a5"),
"emp_no" : 10001,
"title" : "Senior Engineer"
},
{
"_id" : ObjectId("5d4011728ba16934ccce99a6"),
"emp_no" : 10002,
"title" : "Staff"
},
{
"_id" : ObjectId("5d4011728ba16934ccce99a7"),
"emp_no" : 10003,
"title" : "Senior Engineer"
},
{
"_id" : ObjectId("5d4011728ba16934ccce99a8"),
"emp_no" : 10004,
"title" : "Engineer"
},
{
"_id" : ObjectId("5d4011728ba16934ccce99a9"),
"emp_no" : 10004,
"title" : "Senior Engineer"
}
]
},
{
"_id" : ObjectId("5d3ffaaa8ba16934ccce99a2"),
.......
"emps_title" : [
{
"_id" : ObjectId("5d4011728ba16934ccce99a5"),
"emp_no" : 10001,
"title" : "Senior Engineer"
},
{
"_id" : ObjectId("5d4011728ba16934ccce99a6"),
"emp_no" : 10002,
"title" : "Staff"
},
{
"_id" : ObjectId("5d4011728ba16934ccce99a7"),
"emp_no" : 10003,
"title" : "Senior Engineer"
},
{
"_id" : ObjectId("5d4011728ba16934ccce99a8"),
"emp_no" : 10004,
"title" : "Engineer"
},
{
"_id" : ObjectId("5d4011728ba16934ccce99a9"),
"emp_no" : 10004,
"title" : "Senior Engineer"
}
]
},
..........
```
**场景二**:假设每个员工都只需要知道办公室职员 (即职位为 Staff ) 的员工的信息,此时就需要利用 pipeline 管道对 titles 集合中的数据进行过滤,对应的查询语法为:
```shell
db.employees.aggregate([
{
$lookup:
{
from: "titles",
pipeline: [
{ $match:
{ $expr: { $eq: [ "$title","Staff"]}}
}
],
as: "emps_title"
}
}
])
```
**场景三**:假设只有男员工需要知道其他职员的信息,此时就需要利用 pipeline 管道对 employees 集合中的数据进行过滤。由于 employees 集合是输入集合,管道无法直接访问其中的字段,此时需要先在 let 中进行定义,然后再引用。语句如下:
```shell
db.employees.aggregate([ db.employees.aggregate([
{ {
$lookup: $lookup:
@ -300,29 +511,14 @@ db.employees.aggregate([
{ $expr: { $eq: [ "$$gender","M"]}} { $expr: { $eq: [ "$$gender","M"]}}
} }
], ],
as: "emp_title" as: "emps_title"
}
}
])
db.employees.aggregate([
{
$lookup:
{
from: "titles",
pipeline: [
{ $match:
{ $expr: { $eq: [ "$title","M"]}}
}
],
as: "emp_title"
} }
} }
]) ])
``` ```
这里需要使用两个 `$` 符号对 gender 变量进行引用,因为使用 let 定义后,其类似于系统变量。
### 1.9 $out ### 1.9 $out
$out 用于将数据写入指定的集合,它必须是管道中的最后一个阶段。如果指定的集合不存在,则会自动新建;如果指定的集合存在,它会覆盖原有集合的数据。其实际的步骤如下: $out 用于将数据写入指定的集合,它必须是管道中的最后一个阶段。如果指定的集合不存在,则会自动新建;如果指定的集合存在,它会覆盖原有集合的数据。其实际的步骤如下:
@ -360,8 +556,79 @@ db.employees.aggregate([
当排序操作在限制操作之前时,如果没有中间阶段会修改文档数量 (例如 $unwind$group),优化器会将 $limit 合并到 $sort中。如果在 $sort 和 $limit 之间存在修改文档数量的管道阶段MongoDB 将不会执行合并。 当排序操作在限制操作之前时,如果没有中间阶段会修改文档数量 (例如 $unwind$group),优化器会将 $limit 合并到 $sort中。如果在 $sort 和 $limit 之间存在修改文档数量的管道阶段MongoDB 将不会执行合并。
了解这些优化策略可以有助于我们在开发中合理设置管道的顺序。想要了解全部的优化策略,可以参阅 MongoDB 的官方文档:[Aggregation Pipeline Optimization](https://docs.mongodb.com/manual/core/aggregation-pipeline-optimization/) 了解这些优化策略可以有助于我们在开发中合理设置管道的顺序。想要了解全部的优化策略,可以参阅 MongoDB 的官方文档:[Aggregation Pipeline Optimization](https://docs.mongodb.com/manual/core/aggregation-pipeline-optimization/)
## 三、MapReduce ## 三、MapReduce
## 四、单用途聚合方法 MongoDB 的 MapReduce 在概念上与 Hadoop MapReduce 类似MongoDB 将 *map* 阶段应用于每个符合条件的每个输入文档,*map* 阶段的输出结果是 key-value 键值对。对于具有多个值的 keyMongoDB 会执行 *reduce* 阶段,该阶段负责收集并聚合数据,然后将结果存储在集合中。
这里我们以按照性别分组计算员工的平均年龄为例,演示 MapReduce 的基本使用,语句如下:
```shell
db.employees.mapReduce(
function(){
emit(this.gender,this.age)
},
function(key,values){
return Array.avg(values)
},
{ out:"age_avg"}
)
```
对应的计算结果会被输出到 age_avg 集合中,其内容如下:
```shell
{
"_id" : "F",
"value" : 33
},
{
"_id" : "M",
"value" : 39
}
```
当前 MongoDB 支持在分片集合执行 map-reduce 操作,也支持将 Map-reduce 操作的结果输出到分片集合。map-reduce 使得开发者可以灵活控制聚合行为,但其性能不如聚合管道,所以两者应适场景而选用。
## 四、单用途聚合方法
出于易用性考虑MongoDB 提供了部分 API用于实现对常见聚合过程的简单访问。如下
### 4.1 count
用于计算符合条件的文档的数量,如果想要计算集合中所有文档的数量,则传入空对象即可:
```shell
db.employees.count({gender:"M"})
```
### 4.2 estimatedDocumentCount
官方更加推荐使用 estimatedDocumentCount 计算集合中所有文档的数量,它会直接获取元数据信息并返回,因此它并不支持传入查询条件,示例如下:
```shell
db.employees.estimatedDocumentCount({})
```
### 4.3 distinct
和大多数数据库中的 distinct 关键字类似,用于返回不重复的数据:
```shell
db.titles.distinct("title")
```
## 参考资料
+ 官方文档:[Aggregation](https://docs.mongodb.com/manual/aggregation/)、[Map-Reduce](https://docs.mongodb.com/manual/core/map-reduce/)
+ 所有聚合管道总览https://docs.mongodb.com/manual/reference/operator/aggregation-pipeline/
+ 聚合管道中所有可选操作符https://docs.mongodb.com/manual/reference/operator/aggregation/