程序员书籍笔记 程序员书籍笔记
  • JavaScript
  • HTML/CSS
  • PHP
  • Python
  • Go
  • 数据库
  • 容器
  • 微服务
  • 消息队列
  • 搜索引擎
  • 大数据
  • 鸟哥Linux私房菜
其他
  • 大数据
  • 深度学习
APP下载 (opens new window)
GitHub (opens new window)
  • JavaScript
  • HTML/CSS
  • PHP
  • Python
  • Go
  • 数据库
  • 容器
  • 微服务
  • 消息队列
  • 搜索引擎
  • 大数据
  • 鸟哥Linux私房菜
其他
  • 大数据
  • 深度学习
APP下载 (opens new window)
GitHub (opens new window)
  • 数据库

    • MySQL必知必会

    • MongoDB权威指南

      • 入门
      • 文档创建、更新、删除
      • 查询
      • 索引
      • 聚合
        • 旧版内容
          • COUNT
          • distinct
          • group
        • 聚合框架
        • 管道操作符
          • $match 操作符
          • $project
          • $group
          • $unwind
          • $sort
          • $limit 和 $skip
          • 使用管道
  • 容器

  • 微服务

  • 消息队列

  • 搜索引擎

  • 大数据

  • 框架和软件
  • 数据库
  • MongoDB权威指南
小游
2021-05-14

聚合

# 旧版内容

# COUNT

count可以返回集合中文档数量

db.test.find({}).count()
2
1
2

# distinct

distinct用来找出给定键的所有不同的值。使用时必须指定集合和键,这里我们返回了所有的名字

db.runCommand({"distinct":"test","key":"name"})
/* 1 */
{
    "values" : [ 
        "小游", 
        "张三"
    ],
    "ok" : 1.0
}
1
2
3
4
5
6
7
8
9

# group

group做的聚合稍复杂一些。先选定分组所依据的键,而后 MongoDB就会将集合依据选定键值的不同分成若干组。然后可以通过聚合每一组内的文档,产生一个结果。文档如果你熟悉SQL,那么这个 group和SQL中的 GROUP BY差不多

# 聚合框架

使用聚合框架可以对集合中的文档进行变换和组合。基本上,可以用多个构件创建一个管道( pipeline),用于对一连串的文档进行处理。这些构件包括筛选( filtering)、投射( projecting)、分组( grouping)、排序( sorting)、限制( limiting) 和跳过( skipping)。比如我们进行下面这个操作

  • 将每个文章文档中的作者投射出来。
  • 将作者按照名字排序,统计每个名字出现的次数。
  • 将作者按照名字出现次数降序排列。
  • 将返回结果限制为前5个。

这个操作比较复杂。。我这里就直接贴书上的例子吧

image-20210515170046002

image-20210515170054219

image-20210515170106720

image-20210515170120020

image-20210515170137082

# 管道操作符

毎个操作符都会接受一连串的文档,对这些文档做一些类型转换,最后将转换后的文档作为结果传递给下一个操作符(对于最后一个管道操作符,是将结果返回给客户端)。

# $match 操作符

match用于对文档集合进行筛选,之后就可以在筛选得到的文档子集上做聚合。

注意,按照书上的直接加{}在ROBO里面无法使用,所以这里使用了[],后面都按照这个来,不在提醒

db.test.aggregate([{
    "$match":{"name":"小游"}
}])
/* 1 */
{
    "_id" : ObjectId("609bb9f16ac4daf1370a10c6"),
    "name" : "小游",
    "age" : 20,
    "like" : [ 
        "写代码", 
        "看番"
    ]
}
1
2
3
4
5
6
7
8
9
10
11
12
13

# $project

相对于“普通”的查询而言,管道中的投射操作更加强大。使用"$ project"可以从子文档中提取字段,可以重命名字段,还可以在这些字段上进行一些有意思的操作。比如下面这个我们只提取名字

db.test.aggregate([{
    "$project":{"name":1,"_id":0}
}])
/* 1 */
{
    "name" : "小游"
}

/* 2 */
{
    "name" : "张三"
}
1
2
3
4
5
6
7
8
9
10
11
12

我们可以对字段进行重命名(这里的$name指的是数据库里面的name字段)

db.test.aggregate([{
    "$project":{"nickname":"$name","_id":0}
}])
/* 1 */
{
    "nickname" : "小游"
}

/* 2 */
{
    "nickname" : "张三"
}
1
2
3
4
5
6
7
8
9
10
11
12

# 管道表达式

默认我们使用的就是管道表达式

# 数学表达式

算数表达式可以对数值进行操作,我们的原始数据如下

/* 1 */
{
    "_id" : ObjectId("609bb9f16ac4daf1370a10c6"),
    "name" : "小游",
    "age" : 20,
    "count" : 10,
    "like" : [ 
        "写代码", 
        "看番"
    ]
}

/* 2 */
{
    "_id" : ObjectId("609cd4df71a920015b3da7ac"),
    "name" : "张三",
    "age" : 100,
    "count" : 5,
    "like" : []
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

下面我们把 age 和 count 这两个数组相乘

db.test.aggregate([{
    "$project":{"total":{"$multiply":["$age","$count"]}}
}])
/* 1 */
{
    "_id" : ObjectId("609bb9f16ac4daf1370a10c6"),
    "total" : 200
}

/* 2 */
{
    "_id" : ObjectId("609cd4df71a920015b3da7ac"),
    "total" : 500
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14

表达式可以进行任意的嵌套,这里就贴书上的一个复杂的例子

image-20210515172351331

算数表达式里面有下面这几种操作符

image-20210515172415516

# 日期表达式

image-20210515172509572

# 字符串表达式

这个可以对字符串进行各种操作

image-20210515172628636

db.test.aggregate([{
    "$project":{"name":{"$concat":["$name","666"]}}
}])
/* 1 */
{
    "_id" : ObjectId("609bb9f16ac4daf1370a10c6"),
    "name" : "小游666"
}
/* 2 */
{
    "_id" : ObjectId("609cd4df71a920015b3da7ac"),
    "name" : "张三666"
}
1
2
3
4
5
6
7
8
9
10
11
12
13

# 逻辑表达式

逻辑表达式可以控制语句

image-20210515172941534

image-20210515172958393

比如下面我们这里来简单判断一下大小

db.test.aggregate([{
    "$project":{"name":{"$cond":[{"$eq":["$age",20]},"20岁","不是20岁"]},"age":1}
}])
/* 1 */
{
    "_id" : ObjectId("609bb9f16ac4daf1370a10c6"),
    "age" : 20,
    "name" : "20岁"
}

/* 2 */
{
    "_id" : ObjectId("609cd4df71a920015b3da7ac"),
    "age" : 100,
    "name" : "不是20岁"
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

# $group

$group操作可以将文档依据特定字段的不同值进行分组。我们的原始数据如下

/* 1 */
{
    "_id" : ObjectId("609bb9f16ac4daf1370a10c6"),
    "name" : "小游",
    "age" : 20,
    "count" : 10,
    "like" : [ 
        "写代码", 
        "看番"
    ]
}

/* 2 */
{
    "_id" : ObjectId("609cd4df71a920015b3da7ac"),
    "name" : "张三",
    "age" : 100,
    "count" : 5,
    "like" : []
}

/* 3 */
{
    "_id" : ObjectId("609f957aae177b370c33b0cb"),
    "name" : "小游",
    "age" : 66
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27

下面我们使用group来对name进行分组操作

db.test.aggregate([{
    "$group":{"_id":"$name"}
}])
/* 1 */
{
    "_id" : "小游"
}

/* 2 */
{
    "_id" : "张三"
}
1
2
3
4
5
6
7
8
9
10
11
12

# 分组操作符

这些分组操作符允许对每个分组进行计算,得到相应的结果。7.1节介绍过"$sum"分组操作符的作用:分组中每出现一个文档,它就对计算结果加1,这样便可以得到每个分组中的文档数量。

# 算数操作符

包括sum和average,我们先用 $sum 来统计字段的和

db.test.aggregate([{
    "$group":{"_id":"$name","count":{"$sum":"$age"}}
}])
/* 1 */
{
    "_id" : "张三",
    "count" : 100
}

/* 2 */
{
    "_id" : "小游",
    "count" : 86
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14

下面我们来统计平均值

db.test.aggregate([{
    "$group":{"_id":"$name","count":{"$avg":"$age"}}
}])
/* 1 */
{
    "_id" : "小游",
    "count" : 43.0
}

/* 2 */
{
    "_id" : "张三",
    "count" : 100.0
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14

# 极值操作符

极值操作符可以用于获取数据集合中的边缘值,比如求集合中的最大值或者最小值之类的

image-20210515174231363

比如我们获取这些集合中的最大值

db.test.aggregate([{
    "$group":{"_id":"$name","count":{"$max":"$age"}}
}])
/* 1 */
{
    "_id" : "张三",
    "count" : 100
}

/* 2 */
{
    "_id" : "小游",
    "count" : 66
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14

# 数组操作符

image-20210515174325766

比如我们把所有的年龄都加到数组中去

db.test.aggregate([{
    "$group":{"_id":"$name","count":{"$push":"$age"}}
}])
/* 1 */
{
    "_id" : "张三",
    "count" : [ 
        100
    ]
}

/* 2 */
{
    "_id" : "小游",
    "count" : [ 
        20, 
        66
    ]
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

# 分组行为

有两个操作符不能用前面介绍的流式工作方式对文档进行处理,"$ g roup"是其中之一。大部分操作符的工作方式都是流式的,只要有新文档进入,就可以对新文档进行处理,但是"$group"必须要等收到所有的文档之后,才能对文档进行分组,然后才能将各个分组发送给管道中的下一个操作符。这意味着,在分片的情况下,$group"会先在每个分片上执行,然后各个分片上的分组结果会被发送到 mongos再进行最后的统一分组,剩余的管道工作也都是在 mongos(而不是在分片)上运行的。

# $unwind

拆分( unwind)可以将数组中的每一个值拆分为单独的文档。例如,如果有一篇拥有多条评论的博客文章,可以使用$ unwind将每条评论拆分为一个独立的文档,原始文档如下

/* 1 */
{
    "_id" : ObjectId("609bb9f16ac4daf1370a10c6"),
    "name" : "小游",
    "age" : 20,
    "count" : 10,
    "like" : [ 
        "写代码", 
        "看番"
    ]
}

/* 2 */
{
    "_id" : ObjectId("609cd4df71a920015b3da7ac"),
    "name" : "张三",
    "age" : 100,
    "count" : 5,
    "like" : [ 
        "做坏事", 
        "当例子"
    ]
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
db.test.aggregate([{
    "$unwind":"$like"
}])
/* 1 */
{
    "_id" : ObjectId("609bb9f16ac4daf1370a10c6"),
    "name" : "小游",
    "age" : 20,
    "count" : 10,
    "like" : "写代码"
}

/* 2 */
{
    "_id" : ObjectId("609bb9f16ac4daf1370a10c6"),
    "name" : "小游",
    "age" : 20,
    "count" : 10,
    "like" : "看番"
}

/* 3 */
{
    "_id" : ObjectId("609cd4df71a920015b3da7ac"),
    "name" : "张三",
    "age" : 100,
    "count" : 5,
    "like" : "做坏事"
}

/* 4 */
{
    "_id" : ObjectId("609cd4df71a920015b3da7ac"),
    "name" : "张三",
    "age" : 100,
    "count" : 5,
    "like" : "当例子"
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38

# $sort

可以根据任何字段(或者多个字段)进行排序,与在普通查询中的语法相同。如果要对大量的文档进行排序,强烈建议在管道的第一阶段进行排序,这时的排序操作可以使用索引。否则,排序过程就会比较慢,而且会占用大量内存。

db.test.aggregate([{
    "$sort":{"age":-1}
}])
/* 1 */
{
    "_id" : ObjectId("609cd4df71a920015b3da7ac"),
    "name" : "张三",
    "age" : 100,
    "count" : 5,
    "like" : [ 
        "做坏事", 
        "当例子"
    ]
}

/* 2 */
{
    "_id" : ObjectId("609bb9f16ac4daf1370a10c6"),
    "name" : "小游",
    "age" : 20,
    "count" : 10,
    "like" : [ 
        "写代码", 
        "看番"
    ]
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26

# $limit 和 $skip

$Limit会接受一个数字n,返回结果集中的前n个文档。

$skip也是接受一个数字n,丢弃结果集中的前n个文档,将剩余文档作为结果返回。在“普通”査询中,如果需要跳过大量的数据,那么这个操作符的效率会很低。在聚合中也是如此,因为它必须要先匹配到所有需要跳过的文档,然后再将这些文档丢弃。

# 使用管道

编辑 (opens new window)
上次更新: 2021/05/15, 20:39:34
索引
实用工具

← 索引 实用工具→

Theme by Vdoing | Copyright © 2021-2021 小游
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式