三、MongoDB的聚合操作-笔记

MongoDB的聚合操作用于处理数据并返回计算后的结果,包括单一作用聚合、聚合管道和MapReduce。聚合管道是数据处理流水线,通过多个阶段如$match筛选、$group分组、$unwind展开和$project投影等进行数据转换。文章还提供了多个案例展示了如何使用聚合操作进行数据统计和分析。
摘要由CSDN通过智能技术生成

一、MongoDB聚合


  • MongoDB 中聚合(aggregate)主要用于处理数据(诸如统计平均值,求和等),并返回计算后的数据结果。

类似 S Q L 语句中的 c o u n t ( ∗ ) 。 \color{red}{类似 SQL 语句中的 count(*)。} 类似SQL语句中的count()

  • 聚合操作处理数据记录并返回计算结果 \color{red}{聚合操作处理数据记录并返回计算结果} 聚合操作处理数据记录并返回计算结果。聚合操作组值来自多个文档,可以对分组数据执行各种操作以返回单个结果
  • 聚合操作包含三类: 单一作用聚合、聚合管道、 M a p R e d u c e 。 \color{red}{单一作用聚合、聚合管道、MapReduce。} 单一作用聚合、聚合管道、MapReduce
    • 单一作用聚合:提供了对常见聚合过程的简单访问, 操作单个集合聚合文档 \color{red}{操作单个集合聚合文档} 操作单个集合聚合文档
    • 聚合管道是一个数据聚合的框架,模型基于数据处理流水线的概念 \color{red}{聚合管道是一个数据聚合的框架,模型基于数据处理流水线的概念} 聚合管道是一个数据聚合的框架,模型基于数据处理流水线的概念。文档进入多级管道,将文档转换为聚合结果
    • MapReduce操作具有两个阶段: 处理每个文档并向每个输入文档发射一个或多个对象的 m a p 阶段 \color{red}{处理每个文档并向每个输入文档发射一个或多个对象的map阶段} 处理每个文档并向每个输入文档发射一个或多个对象的map阶段 以及 r e d u c e 组合 m a p 操作的输出阶段。 \color{red}{以及reduce组合map操作的输出阶段。} 以及reduce组合map操作的输出阶段。

语法

db.COLLECTION_NAME.aggregate(AGGREGATE_OPERATION)

二、单一作用聚合

MongoDB提供 db.collection.estimatedDocumentCount(), db.collection.count(), db.collection.distinct() 这类单一作用的聚合函数。
所有这些操作都聚合来自单个集合的文档。
虽然这些操作提供了对公共聚合过程的简单访问,但它们缺乏聚合管道和map-Reduce的灵活性和功能。

函数功能
db.collection.estimatedDocumentCount()返回集合或视图中所有文档的计数
db.collection.count()返回与find()集合或视图的查询匹配的文档计数
等同于 db.collection.find(query).count()构造
db.collection.distinct()在单个集合或视图中查找指定字段的不同值,并在数组中返回结果
//检索集合中所有文档的计数
testdb> db.testCollection.estimatedDocumentCount();
49
//计算与查询匹配的所有文档
testdb> db.testCollection.count({favCount:{$gt:50}});
DeprecationWarning: Collection.count() is deprecated. Use countDocuments or estimatedDocumentCount.
32
testdb> db.testCollection.countDocuments({favCount:{$gt:50}});
32
//返回不同type的数组
testdb> db.testCollection.distinct("type")
[ 'literature', 'none', 'novel', 'sociality', 'technology', 'travel' ]
返回收藏数小于等于80的文档不同type的数组
testdb> db.testCollection.distinct("type",{favCount:{$lte:80}})
[ 'literature', 'none', 'novel', 'sociality', 'technology', 'travel' ]
testdb>

三、聚合管道

3.1 什么是MongoDB聚合框架

M o n g o D B 聚合框架( A g g r e g a t i o n F r a m e w o r k )是一个计算框架 \color{red}{MongoDB 聚合框架(Aggregation Framework)是一个计算框架} MongoDB聚合框架(AggregationFramework)是一个计算框架

  • 作用在一个或几个集合上 \color{red}{作用在一个或几个集合上} 作用在一个或几个集合上
  • 对集合中的数据进行的一系列运算 , 将这些数据转化为期望的形式 \color{red}{对集合中的数据进行的一系列运算,将这些数据转化为期望的形式} 对集合中的数据进行的一系列运算,将这些数据转化为期望的形式

从效果而言,聚合框架相当于 SQL 查询中的GROUP BY、 LEFT OUTER JOIN 、 AS等

3.2 管道(Pipeline)和阶段(Stage)

管道在Unix和Linux中一般用于将当前命令的输出结果作为下一个命令的参数。

MongoDB的聚合管道将MongoDB文档在一个管道处理完毕后将结果传递给下一个管道处理。管道操作是可以重复的。

表达式:处理输入文档并输出。表达式是无状态的,只能用于计算当前聚合管道的文档,不能处理其它的文档。

整个聚合运算过程称为管道( P i p e l i n e ),它是由多个阶段( S t a g e )组成的 \color{red}{整个聚合运算过程称为管道(Pipeline),它是由多个阶段(Stage)组成的} 整个聚合运算过程称为管道(Pipeline),它是由多个阶段(Stage)组成的

  • 接受一系列文档(原始数据) \color{red}{接受一系列文档(原始数据)} 接受一系列文档(原始数据)
  • 每个阶段对这些文档进行一系列运算 \color{red}{每个阶段对这些文档进行一系列运算} 每个阶段对这些文档进行一系列运算
  • 结果文档输出给下一个阶段 \color{red}{结果文档输出给下一个阶段} 结果文档输出给下一个阶段
    在这里插入图片描述

聚合管道操作语法

pipeline = [$stage1, $stage2, ...$stageN];
db.collection.aggregate(pipeline, {options})
  • pipelines 一组数据聚合阶段。除$out、$Merge和$geonear阶段之外,每个阶段都可以在管道中出现多次。
  • options 可选,聚合操作的其他参数。包含:查询计划、是否使用临时文件、 游标、最大操作时间、读写策略、强制索引等等
    在这里插入图片描述

3.3 常用的管道聚合阶段

阶段描述SQL等价运算符
$match筛选条件WHERE
$project投影AS
$lookup左外连接LEFT OUTER JOIN
$sort排序ORDER BY
$sum计算总和db.mycol.aggregate([{$group : {_id : “$by_user”, num_tutorial : {$sum : “$likes”}}}])
$avg计算平均值db.mycol.aggregate([{$group : {_id : “$by_user”, num_tutorial : {$avg : “$likes”}}}])
$min/max获取集合中所有文档对应值得最小/最大值。db.mycol.aggregate([{$group : {_id : “$by_user”, num_tutorial : {$min/max : “$likes”}}}])
$push将值加入一个数组中,不会判断是否有重复的值db.mycol.aggregate([{$group : {_id : “$by_user”, url : {$push: “$url”}}}])
$addToSet将值加入一个数组中,会判断是否有重复的值,若相同的值在数组中已经存在了,则不加入。db.mycol.aggregate([{$group : {_id : “$by_user”, url : {$addToSet : “$url”}}}])
$first/last根据资源文档的排序获取第一个/最后一个文档数据db.mycol.aggregate([{$group : {_id : “$by_user”, url : {$first : “$url”}}}])
$group分组GROUP BY
$skip/$limit分页
$unwind展开数组
$graphLookup图搜索
$facet/$bucket分面搜索

管道聚合文档:

3.3.1 聚合表达式

  • 获取字段信息

    $<field> : 用 $ 指示字段路径
    $<field>.<subfield> : 使用 $ 和 . 来指示内嵌文档的路径

  • 常量表达式

$literal :<value> : 指示常量

  • 系统变量表达式

$$<variable> 使用 $$ 指示系统变量
$$CURRENT 指示管道中当前操作的文档

准备数据

var tags = ["nosql","mongodb","document","developer","popular"];
var types = ["technology","sociality","travel","novel","literature"];
var books=[];
for(var i=0;i<500;i++){
    var typeIdx = Math.floor(Math.random()*types.length);
    var tagIdx = Math.floor(Math.random()*tags.length);
    var tagIdx2 = Math.floor(Math.random()*tags.length);
    var favCount = Math.floor(Math.random()*100);
    var username = "xx00"+Math.floor(Math.random()*10);
    var age = 30 + Math.floor(Math.random()*15);
    var book = {
        title: "book-"+i,
        type: types[typeIdx],
        tag: [tags[tagIdx],tags[tagIdx2]],
        favCount: favCount,
        author: {name:username,age:age}
    };
    books.push(book)
}
db.books.insertMany(books);

// 加载js
testdb> load("C:\\xx\\xxxx\\xxx\\test.js")

3.3.2 $project

投影操作,将原始字段投影成指定名称 \color{red}{投影操作, 将原始字段投影成指定名称} 投影操作,将原始字段投影成指定名称, 如将集合中的 title 投影成 name

testdb> db.testCollection.aggregate([{$project:{name:"$title"}}])
[
  { _id: ObjectId("63f884d67b9fcb1f8bf84df1") },
  { _id: ObjectId("63f88c0b7b9fcb1f8bf84df4"), name: 'book-2' },
  { _id: ObjectId("63f88c0b7b9fcb1f8bf84df5"), name: 'book-3' },
  { _id: ObjectId("63f88c0b7b9fcb1f8bf84df6"), name: 'book-4' },
  { _id: ObjectId("63f88c0b7b9fcb1f8bf84df7"), name: 'book-5' },
  { _id: ObjectId("63f88c0b7b9fcb1f8bf84df8"), name: 'book-6' },
  { _id: ObjectId("63f88c0b7b9fcb1f8bf84df9"), name: 'book-7' },
  { _id: ObjectId("63f88c0b7b9fcb1f8bf84dfa"), name: 'book-8' },
  { _id: ObjectId("63f88c0b7b9fcb1f8bf84dfb"), name: 'book-9' },
  { _id: ObjectId("63f88c0b7b9fcb1f8bf84dfc"), name: 'book-10' },
  { _id: ObjectId("63f88c0b7b9fcb1f8bf84dfd"), name: 'book-11' },
  { _id: ObjectId("63f88c0b7b9fcb1f8bf84dfe"), name: 'book-12' },
  { _id: ObjectId("63f88c0b7b9fcb1f8bf84dff"), name: 'book-13' },
  { _id: ObjectId("63f88c0b7b9fcb1f8bf84e00"), name: 'book-14' },
  { _id: ObjectId("63f88c0b7b9fcb1f8bf84e01"), name: 'book-15' },
  { _id: ObjectId("63f88c0b7b9fcb1f8bf84e02"), name: 'book-16' },
  { _id: ObjectId("63f88c0b7b9fcb1f8bf84e03"), name: 'book-17' },
  { _id: ObjectId("63f88c0b7b9fcb1f8bf84e04"), name: 'book-18' },
  { _id: ObjectId("63f88c0b7b9fcb1f8bf84e05"), name: 'book-19' },
  { _id: ObjectId("63f88c0b7b9fcb1f8bf84e06"), name: 'book-20' }
]
Type "it" for more

$project 可以灵活控制输出文档的格式,也可以剔除不需要的字段 \color{red}{可以灵活控制输出文档的格式,也可以剔除不需要的字段} 可以灵活控制输出文档的格式,也可以剔除不需要的字段

testdb> db.testCollection.aggregate([{$project:{name:"$title",_id:0,type:1,author:1}}])
[
  {},
  { type: 'novel', author: 'xxw2', name: 'book-2' },
  { type: 'travel', author: 'xxw3', name: 'book-3' },
  { type: 'novel', author: 'xxw4', name: 'book-4' },
  { type: 'sociality', author: 'xxw5', name: 'book-5' },
  { type: 'novel', author: 'xxw6', name: 'book-6' },
  { type: 'novel', author: 'xxw7', name: 'book-7' },
  { type: 'sociality', author: 'xxw8', name: 'book-8' },
  { type: 'sociality', author: 'xxw9', name: 'book-9' },
  { type: 'travel', author: 'xxw10', name: 'book-10' },
  { type: 'travel', author: 'xxw11', name: 'book-11' },
  { type: 'literature', author: 'xxw12', name: 'book-12' },
  { type: 'travel', author: 'xxw13', name: 'book-13' },
  { type: 'sociality', author: 'xxw14', name: 'book-14' },
  { type: 'travel', author: 'xxw15', name: 'book-15' },
  { type: 'novel', author: 'xxw16', name: 'book-16' },
  { type: 'travel', author: 'xxw17', name: 'book-17' },
  { type: 'technology', author: 'xxw18', name: 'book-18' },
  { type: 'novel', author: 'xxw19', name: 'book-19' },
  { type: 'novel', author: 'xxw20', name: 'book-20' }
]
Type "it" for more

从嵌套文档中排除字段 \color{red}{从嵌套文档中排除字段} 从嵌套文档中排除字段

testdb> db.books.aggregate([
...     {$project:{name:"$title",_id:0,type:1,"author.name":1}}
... ])
[
  { type: 'literature', author: { name: 'xx000' }, name: 'book-0' },
  { type: 'literature', author: { name: 'xx009' }, name: 'book-1' },
  { type: 'technology', author: { name: 'xx007' }, name: 'book-2' },
  { type: 'technology', author: { name: 'xx007' }, name: 'book-3' },
  { type: 'sociality', author: { name: 'xx007' }, name: 'book-4' },
  { type: 'travel', author: { name: 'xx002' }, name: 'book-5' },
  { type: 'travel', author: { name: 'xx007' }, name: 'book-6' },
  { type: 'literature', author: { name: 'xx004' }, name: 'book-7' },
  { type: 'travel', author: { name: 'xx002' }, name: 'book-8' },
  { type: 'travel', author: { name: 'xx002' }, name: 'book-9' },
  { type: 'sociality', author: { name: 'xx009' }, name: 'book-10' },
  { type: 'novel', author: { name: 'xx003' }, name: 'book-11' },
  { type: 'travel', author: { name: 'xx008' }, name: 'book-12' },
  { type: 'novel', author: { name: 'xx006' }, name: 'book-13' },
  { type: 'novel', author: { name: 'xx007' }, name: 'book-14' },
  { type: 'travel', author: { name: 'xx001' }, name: 'book-15' },
  { type: 'technology', author: { name: 'xx001' }, name: 'book-16' },
  { type: 'literature', author: { name: 'xx004' }, name: 'book-17' },
  { type: 'novel', author: { name: 'xx008' }, name: 'book-18' },
  { type: 'travel', author: { name: 'xx003' }, name: 'book-19' }
]


testdb> db.books.aggregate([
...     {$project:{name:"$title",_id:0,type:1,author:{name:1}}}
... ])
[
  { type: 'literature', author: { name: 'xx000' }, name: 'book-0' },
  { type: 'literature', author: { name: 'xx009' }, name: 'book-1' },

3.3.2 $match

$match用于对文档进行筛选,之后可以在得到的文档子集上做聚合,
$match 可以使用除了地理空间之外的所有常规查询操作符 \color{red}{可以使用除了地理空间之外的所有常规查询操作符} 可以使用除了地理空间之外的所有常规查询操作符

在实际应用中尽可能将$match放在管道的前面位置。
这样有两个好处:

  • 可以快速将不需要的文档过滤掉,以减少管道的工作量;
  • 如果再投射和分组之前执行$match,查询可以使用索引。
db.books.aggregate([{$match:{type:"technology"}}])

筛选管道操作和其他管道操作配合时候时,尽量放到开始阶段 \color{red}{筛选管道操作和其他管道操作配合时候时,尽量放到开始阶段} 筛选管道操作和其他管道操作配合时候时,尽量放到开始阶段,这样可以 减少后续管道操作符要操作的文档数,提升效率 \color{red}{减少后续管道操作符要操作的文档数,提升效率} 减少后续管道操作符要操作的文档数,提升效率

db.books.aggregate([
    {$match:{type:"technology"}},
    {$project:{name:"$title",_id:0,type:1,author:{name:1}}}
])

3.3.2 $count

计数并返回与查询匹配的结果数

testdb> db.books.aggregate([
{$match:{type:"travel"}},//$match阶段筛选出type匹配travel的文档,并传到下一阶段;
{$count:"travel_count"}])//$count阶段返回聚合管道中剩余文档的计数,并将该值分配给travel_count
[ { travel_count: 99 } ]

3.3.3 $group

  • 按指定的表达式对文档进行分组,并将每个不同分组的文档输出到下一个阶段 \color{red}{按指定的表达式对文档进行分组,并将每个不同分组的文档输出到下一个阶段} 按指定的表达式对文档进行分组,并将每个不同分组的文档输出到下一个阶段
  • 输出文档包含一个_id字段,该字段按键包含不同的组。
  • 输出文档还可以包含计算字段,该字段保存由$group的_id字段分组的一些accumulator表达式的值。
  • $group 不会输出具体的文档而只是统计信息 \color{red}{不会输出具体的文档而只是统计信息} 不会输出具体的文档而只是统计信息
{ $group: { _id: <expression>, <field1>: { <accumulator1> : <expression1> }, ... } }
  • _ i d 字段是必填的 ; 但是,可以指 定 i d 值为 n u l l 来为整个输入文档计算累计 \color{red}{\_id字段是必填的;但是,可以指定_id值为null来为整个输入文档计算累计} _id字段是必填的;但是,可以指id值为null来为整个输入文档计算累计
  • 剩余的计算字段是可选的,并使用运算符进行计算
  • _id和表达式可以接受任何有效的表达式
    在这里插入图片描述
    $group 阶段的内存限制为 100 M \color{red}{阶段的内存限制为100M} 阶段的内存限制为100M 默认情况下,如果 s t a g e 超过此限制, \color{red}{默认情况下,如果stage超过此限制,} 默认情况下,如果stage超过此限制, $group 将产生错误 \color{red}{将产生错误} 将产生错误。但是,要允许处理大型数据集,请将 a l l o w D i s k U s e 选项设置为 t r u e 以启用 \color{red}{allowDiskUse选项设置为true以启用} allowDiskUse选项设置为true以启用 $group 操作以写入临时文件 \color{red}{操作以写入临时文件} 操作以写入临时文件

book的数量,收藏总数和平均值

testdb> db.books.aggregate([{$match:{type:"travel"}},{$count:"travel_count"}])
[ { travel_count: 99 } ]
testdb> db.books.aggregate([
...     {$group:{_id:null,count:{$sum:1},pop:{$sum:"$favCount"},avg:{$avg:"$favCount"}}}
... ])
[ { _id: null, count: 500, pop: 25999, avg: 51.998 } ]

统计每个作者的book收藏总数

testdb> db.books.aggregate([
{
$group:{_id:"$author.name",pop:{$sum:"$favCount"}}
}
])
[
  { _id: 'xx004', pop: 2320 },
  { _id: 'xx003', pop: 3199 },
  { _id: 'xx001', pop: 2572 },
  { _id: 'xx007', pop: 2825 },
  { _id: 'xx005', pop: 2578 },
  { _id: 'xx009', pop: 2755 },
  { _id: 'xx006', pop: 2698 },
  { _id: 'xx002', pop: 2093 },
  { _id: 'xx008', pop: 2269 },
  { _id: 'xx000', pop: 2690 }
]

统计每个作者的每本book的收藏数

testdb> db.books.aggregate({$group:{_id:{name:"$author.name",title:"$title"},pop:{$sum:"$favCount"}}})
[
  { _id: { name: 'xx003', title: 'book-19' }, pop: 84 },
  { _id: { name: 'xx008', title: 'book-450' }, pop: 28 },
  { _id: { name: 'xx006', title: 'book-201' }, pop: 18 },
  { _id: { name: 'xx004', title: 'book-207' }, pop: 89 },
  { _id: { name: 'xx009', title: 'book-400' }, pop: 47 },
  { _id: { name: 'xx004', title: 'book-330' }, pop: 80 },
  { _id: { name: 'xx005', title: 'book-448' }, pop: 24 },
  { _id: { name: 'xx001', title: 'book-159' }, pop: 50 },
  { _id: { name: 'xx002', title: 'book-63' }, pop: 19 },
  { _id: { name: 'xx003', title: 'book-301' }, pop: 94 },
  { _id: { name: 'xx000', title: 'book-308' }, pop: 82 },
  { _id: { name: 'xx005', title: 'book-395' }, pop: 37 },
  { _id: { name: 'xx002', title: 'book-220' }, pop: 5 },
  { _id: { name: 'xx007', title: 'book-127' }, pop: 39 },
  { _id: { name: 'xx008', title: 'book-50' }, pop: 90 },
  { _id: { name: 'xx003', title: 'book-303' }, pop: 12 },
  { _id: { name: 'xx003', title: 'book-37' }, pop: 38 },
  { _id: { name: 'xx004', title: 'book-17' }, pop: 82 },
  { _id: { name: 'xx007', title: 'book-252' }, pop: 76 },
  { _id: { name: 'xx001', title: 'book-445' }, pop: 29 }
]

每个作者的book的type合集

testdb> db.books.aggregate(
... [
... {$group:{_id:"$author.name",typeSet:{$addToSet:"$type"}}}
... ]
... )
[
  {
    _id: 'xx005',
    typeSet: [ 'technology', 'literature', 'novel', 'sociality', 'travel' ]
  }
]

3.3.4 $unwind

可以将数组拆分为单独的文档
v3.2+支持如下语法:

{
  $unwind:
    {
     #要指定字段路径,在字段名称前加上$符并用引号括起来。
      path: <field path>,
      #可选,一个新字段的名称用于存放元素的数组索引。该名称不能以$开头。
      includeArrayIndex: <string>,  
      #可选,default :false,若为true,如果路径为空,缺少或为空数组,则$unwind输出文档
      preserveNullAndEmptyArrays: <boolean> 
 } }

姓名为xx006的作者的book的tag数组拆分为多个文档

db.books.aggregate([
    {$match:{"author.name":"xx006"}},
    {$unwind:"$tag"}
])

db.books.aggregate([
    {$match:{"author.name":"xx006"}}
])

每个作者的book的tag合集

db.books.aggregate([
    {$unwind:"$tag"},
    {$group:{_id:"$author.name",types:{$addToSet:"$tag"}}}
])

案例

db.books.insert([
{
	"title" : "book-51",
	"type" : "technology",
	"favCount" : 110,
     "tag":[],
	"author" : {
		"name" : "weljy",
		"age" : 30
	}
},{
	"title" : "book-52",
	"type" : "technology",
	"favCount" : 150,
	"author" : {
		"name" : "weljy",
		"age" : 30
	}
},{
	"title" : "book-53",
	"type" : "technology",
	"tag" : [
		"nosql",
		"document"
	],
	"favCount" : 20,
	"author" : {
		"name" : "weljy",
		"age" : 30
	}
}])

测试

// 使用includeArrayIndex选项来输出数组元素的数组索引
testdb> db.books.aggregate([{$match:{"author.name":"weljy"}},{$unwind:{path:"$tag",includeArrayIndex:"arrayIndex"}}])
[
  {
    _id: ObjectId("63fd6e507b9fcb1f8bf8501a"),
    title: 'book-53',
    type: 'technology',
    tag: 'nosql',
    favCount: 20,
    author: { name: 'weljy', age: 30 },
    arrayIndex: Long("0")
  },
  {
    _id: ObjectId("63fd6e507b9fcb1f8bf8501a"),
    title: 'book-53',
    type: 'technology',
    tag: 'document',
    favCount: 20,
    author: { name: 'weljy', age: 30 },
    arrayIndex: Long("1")
  }
]
//使用preserveNullAndEmptyArrays选项在输出中包含缺少size字段,null或空数组的文档
testdb> db.books.aggregate([
...     {$match:{"author.name":"weljy"}},
...     {$unwind:{path:"$tag", preserveNullAndEmptyArrays: true}}
... ])
[
  {
    _id: ObjectId("63fd6e507b9fcb1f8bf85018"),
    title: 'book-51',
    type: 'technology',
    favCount: 110,
    author: { name: 'weljy', age: 30 }
  },
  {
    _id: ObjectId("63fd6e507b9fcb1f8bf85019"),
    title: 'book-52',
    type: 'technology',
    favCount: 150,
    author: { name: 'weljy', age: 30 }
  },
  {
    _id: ObjectId("63fd6e507b9fcb1f8bf8501a"),
    title: 'book-53',
    type: 'technology',
    tag: 'nosql',
    favCount: 20,
    author: { name: 'weljy', age: 30 }
  },
  {
    _id: ObjectId("63fd6e507b9fcb1f8bf8501a"),
    title: 'book-53',
    type: 'technology',
    tag: 'document',
    favCount: 20,
    author: { name: 'weljy', age: 30 }
  }
]

3.3.5 $limit

限制传递到管道中下一阶段的文档数

db.books.aggregate([
    {$limit : 5 }
])

此操作仅返回管道传递给它的前5个文档。$limit对其传递的文档内容没有影响。
注意:当$sort在管道中的$limit之前立即出现时,$sort操作只会在过程中维持前n个结果,其中n是指定的限制,而MongoDB只需要将n个项存储在内存中。

3.3.6 $skip

跳过进入stage的指定数量的文档,并将其余文档传递到管道中的下一个阶段

db.books.aggregate([
    {$skip : 5 }
])

此操作将跳过管道传递给它的前5个文档。 $skip对沿着管道传递的文档的内容没有影响。

3.3.7 $sort

对所有输入文档进行排序,并按排序顺序将它们返回到管道。
语法:

{ $sort: { <field1>: <sort order>, <field2>: <sort order> ... } }

要对字段进行排序,请将排序顺序设置为1或-1,以分别指定升序或降序排序,如下例所示:

db.books.aggregate([
    {$sort : {favCount:-1,title:1}}
])

3.3.8 $lookup

Mongodb 3.2版本新增, 主要用来实现多表关联查询 \color{red}{主要用来实现多表关联查询} 主要用来实现多表关联查询, 相当关系型数据库中多表关联查询。每个输入待处理的文档,经过$lookup 阶段的处理,输出的新文档中会包含一个新生成的数组(可根据需要命名新key )。数组列存放的数据是来自被Join集合的适配文档,如果没有,集合为空(即 为[ ])

语法:

db.collection.aggregate([{
      $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 同一个数据库下等待被Join的集合。
  • localField 源集合中的match值,如果输入的集合中,某文档没有 localField这个Key(Field),在处理的过程中,会默认为此文档含有 localField:null的键值对。
  • foreignField 待Join的集合的match值,如果待Join的集合中,文档没有foreignField值,在处理的过程中,会默认为此文档含有 foreignField:null的键值对。as为输出文档的新增值命名。如果输入的集合中已存在该值,则会覆盖掉

注意: n u l l = n u l l 此为真 \color{red}{ 注意:null = null 此为真} 注意:null=null此为真
其语法功能类似于下面的伪SQL语句:

SELECT *, <output array field>
FROM collection
WHERE <output array field> IN (SELECT *
                               FROM <collection to join>
                               WHERE <foreignField>= <collection.localField>);

案例 数据准备

db.customer.insert({customerCode:1,name:"customer1",phone:"13112345678",address:"test1"})
db.customer.insert({customerCode:2,name:"customer2",phone:"13112345679",address:"test2"})

db.order.insert({orderId:1,orderCode:"order001",customerCode:1,price:200})
db.order.insert({orderId:2,orderCode:"order002",customerCode:2,price:400})

db.orderItem.insert({itemId:1,productName:"apples",qutity:2,orderId:1})
db.orderItem.insert({itemId:2,productName:"oranges",qutity:2,orderId:1})
db.orderItem.insert({itemId:3,productName:"mangoes",qutity:2,orderId:1})
db.orderItem.insert({itemId:4,productName:"apples",qutity:2,orderId:2})
db.orderItem.insert({itemId:5,productName:"oranges",qutity:2,orderId:2})
db.orderItem.insert({itemId:6,productName:"mangoes",qutity:2,orderId:2})

关联查询

testdb> db.customer.aggregate([{$lookup:{from:"order",localField:"customerCode",foreignField:"customerCode",as:"customerOrder"}}])
[
  {
    _id: ObjectId("63fd78e27b9fcb1f8bf8501b"),
    customerCode: 1,
    name: 'customer1',
    phone: '13112345678',
    address: 'test1',
    customerOrder: [
      {
        _id: ObjectId("63fd78e27b9fcb1f8bf8501d"),
        orderId: 1,
        orderCode: 'order001',
        customerCode: 1,
        price: 200
      }
    ]
  },
  {
    _id: ObjectId("63fd78e27b9fcb1f8bf8501c"),
    customerCode: 2,
    name: 'customer2',
    phone: '13112345679',
    address: 'test2',
    customerOrder: [
      {
        _id: ObjectId("63fd78e27b9fcb1f8bf8501e"),
        orderId: 2,
        orderCode: 'order002',
        customerCode: 2,
        price: 400
      }
    ]
  }
]
testdb> db.customer.aggregate([{$lookup:{from:"order",localField:"customerCode",foreignField:"customerCode",as:"customerOrder"}},{$lookup:{from:"orderItem",localField:"orderId",foreignField:"orderId",as:"orderItem"}}])
[
  {
    _id: ObjectId("63fd78e27b9fcb1f8bf8501b"),
    customerCode: 1,
    name: 'customer1',
    phone: '13112345678',
    address: 'test1',
    customerOrder: [
      {
        _id: ObjectId("63fd78e27b9fcb1f8bf8501d"),
        orderId: 1,
        orderCode: 'order001',
        customerCode: 1,
        price: 200
      }
    ],
    orderItem: []
  },
  {
    _id: ObjectId("63fd78e27b9fcb1f8bf8501c"),
    customerCode: 2,
    name: 'customer2',
    phone: '13112345679',
    address: 'test2',
    customerOrder: [
      {
        _id: ObjectId("63fd78e27b9fcb1f8bf8501e"),
        orderId: 2,
        orderCode: 'order002',
        customerCode: 2,
        price: 400
      }
    ],
    orderItem: []
  }
]

3.4 聚合操作案例1

统计每个分类的book文档数量

testdb> db.books.aggregate([{$group:{_id:"$type",total:{$sum:1}}},{$sort:{total:-1}}])
[
  { _id: 'technology', total: 112 },
  { _id: 'literature', total: 105 },
  { _id: 'travel', total: 99 },
  { _id: 'novel', total: 99 },
  { _id: 'sociality', total: 88 }
]

标签的热度排行,标签的热度则按其关联book文档的收藏数(favCount)来计算

testdb> db.books.aggregate(
{$match:{favCount:{$gt:0}}},
{$unwind:"$tag"},
{$group:{_id:"$tag",total:{$sum:"$favCount"}}},
{$sort:{total:-1}})
[
  { _id: 'nosql', total: 11861 },
  { _id: 'document', total: 10766 },
  { _id: 'popular', total: 10141 },
  { _id: 'developer', total: 10016 },
  { _id: 'mongodb', total: 9254 }
]
  • $match阶段:用于过滤favCount=0的文档。
  • $unwind阶段:用于将标签数组进行展开,这样一个包含3个标签的文档会被拆解为3个条目。
  • $group阶段:对拆解后的文档进行分组计算,$sum:"$favCount"表示按favCount字段进行累加。
  • $sort阶段:接收分组计算的输出,按total得分进行排序

统计book文档收藏数[0,10),[10,60),[60,80),[80,100),[100,+∞)

testdb> db.books.aggregate({$bucket:{groupBy:"$favCount",boundaries:[0,10,60,80,100],default:"other",output:{"count":{$sum:1}}}})
[
  { _id: 0, count: 48 },
  { _id: 10, count: 234 },
  { _id: 60, count: 103 },
  { _id: 80, count: 116 },
  { _id: 'other', count: 2 }
]

3.5 聚合操作案例2

邮政编码数据
MongoDB Database Tools

使用mongoimport工具导入数据

mongoimport -h 192.168.65.174 -d test -u weljy -p weljy --authenticationDatabase=admin -c zips --file C:\ProgramData\zips.json  

h,–host :代表远程连接的数据库地址,默认连接本地Mongo数据库;
–port:代表远程连接的数据库的端口,默认连接的远程端口27017;
-u,–username:代表连接远程数据库的账号,如果设置数据库的认证,需要指定用户账号;
-p,–password:代表连接数据库的账号对应的密码;
-d,–db:代表连接的数据库;
-c,–collection:代表连接数据库中的集合;
-f, --fields:代表导入集合中的字段;
–type:代表导入的文件类型,包括csv和json,tsv文件,默认json格式;
–file:导入的文件名称
–headerline:导入csv文件时,指明第一行是列名,不需要导入

3.5.1 返回人口超过500万的州

test> db.zips.aggregate([
{$group:{_id:"$state",totalPop:{$sum:"$pop"}}},
{$match:{totalPop:{$gte:10000*500}}}
])
[
  { _id: 'MI', totalPop: 9295297 },
  { _id: 'PA', totalPop: 11881643 },
  { _id: 'NC', totalPop: 6628637 },
  { _id: 'OH', totalPop: 10846517 },
  { _id: 'VA', totalPop: 6181479 },
  { _id: 'IL', totalPop: 11427576 },
  { _id: 'FL', totalPop: 12686644 },
  { _id: 'IN', totalPop: 5544136 },
  { _id: 'GA', totalPop: 6478216 },
  { _id: 'MO', totalPop: 5110648 },
  { _id: 'NJ', totalPop: 7730188 },
  { _id: 'NY', totalPop: 17990402 },
  { _id: 'MA', totalPop: 6016425 },
  { _id: 'TX', totalPop: 16984601 },
  { _id: 'CA', totalPop: 29754890 }
]

这个聚合操作的等价SQL是:

SELECT state, SUM(pop) AS totalPop
FROM zips
GROUP BY state
HAVING totalPop >= (10000*500)

3.5.2 返回各州平均城市人口

db.zips.aggregate( [
   { $group: { _id: { state: "$state", city: "$city" }, cityPop: { $sum: "$pop" } } }
] )
[
  { _id: { state: 'MI', city: 'BELLEVILLE' }, cityPop: 35436 },
  { _id: { state: 'MI', city: 'GRAND JUNCTION' }, cityPop: 2100 },
  { _id: { state: 'CA', city: 'SEIAD VALLEY' }, cityPop: 311 },
  { _id: { state: 'OR', city: 'NORTH POWDER' }, cityPop: 571 },
  { _id: { state: 'WY', city: 'COLTER BAY' }, cityPop: 9078 },
  { _id: { state: 'GA', city: 'CRAWFORDVILLE' }, cityPop: 1915 },
  { _id: { state: 'CA', city: 'DUNLAP' }, cityPop: 94 },
  { _id: { state: 'IL', city: 'COLLISON' }, cityPop: 421 },
  { _id: { state: 'MI', city: 'PLEASANT RIDGE' }, cityPop: 2895 },
  { _id: { state: 'NJ', city: 'ROSELLE' }, cityPop: 20159 },
  { _id: { state: 'PA', city: 'ALUM BANK' }, cityPop: 2175 },
  { _id: { state: 'WI', city: 'IRON BELT' }, cityPop: 265 },
  { _id: { state: 'NY', city: 'HEWLETT' }, cityPop: 8023 },
  { _id: { state: 'OH', city: 'MONTPELIER' }, cityPop: 7569 },
  { _id: { state: 'WA', city: 'TENINO' }, cityPop: 6451 },
  { _id: { state: 'MI', city: 'BENTON HARBOR' }, cityPop: 37550 },
  { _id: { state: 'KY', city: 'BRANDENBURG' }, cityPop: 6480 },
  { _id: { state: 'NJ', city: 'CRANFORD' }, cityPop: 22866 },
  { _id: { state: 'MO', city: 'BERKELEY' }, cityPop: 20546 },
  { _id: { state: 'AR', city: 'NATURAL DAM' }, cityPop: 497 }
]


test> db.zips.aggregate([{$group:{_id:{state:"$state",city:"$city"},cityPop:{$sum:"$pop"}}},{$group:{_id:"$_id.state",avgCityPop:{$avg:"$cityPop"}}}])
[
  { _id: 'NV', avgCityPop: 18209.590909090908 },
  { _id: 'OK', avgCityPop: 6155.743639921722 },
  { _id: 'MI', avgCityPop: 12087.512353706112 },
  { _id: 'PA', avgCityPop: 8679.067202337472 },
  { _id: 'OH', avgCityPop: 12700.839578454332 },
  { _id: 'NC', avgCityPop: 10622.815705128205 },
  { _id: 'SC', avgCityPop: 11139.626198083068 },
  { _id: 'VA', avgCityPop: 8526.177931034483 },
  { _id: 'LA', avgCityPop: 10465.496277915632 },
  { _id: 'NM', avgCityPop: 5872.360465116279 },
  { _id: 'AZ', avgCityPop: 20591.16853932584 },
  { _id: 'OR', avgCityPop: 8262.561046511628 },
  { _id: 'SD', avgCityPop: 1839.6746031746031 },
  { _id: 'IA', avgCityPop: 3123.0821147356583 },
  { _id: 'WV', avgCityPop: 2771.4775888717154 },
  { _id: 'WI', avgCityPop: 7323.00748502994 },
  { _id: 'VT', avgCityPop: 2315.8765432098767 },
  { _id: 'HI', avgCityPop: 15831.842857142858 },
  { _id: 'KS', avgCityPop: 3819.884259259259 },
  { _id: 'DE', avgCityPop: 14481.91304347826 }
]

按州返回最大和最小的城市

test> db.zips.aggregate([
...     {$group:{_id:{state:"$state",city:"$city"},pop:{$sum:"$pop"}}},
...     {$sort:{pop:1}},
...     {$group:
...             {
...                     _id:"$_id.state",
...                     biggestCity:{$last:"$_id.city"},
...                     biggestPop:{ $last: "$pop" },
...                     smallestCity:{ $first: "$_id.city" },
...                     smallestPop:{ $first: "$pop" }
...             }
...     },
...     { $project:
...             { _id: 0,
...               state: "$_id",
...               biggestCity:  { name: "$biggestCity",  pop: "$biggestPop" },
...               smallestCity: { name: "$smallestCity", pop: "$smallestPop" }
...             }
...     }
... ])
[
  {
    biggestCity: { name: 'DES MOINES', pop: 148155 },
    smallestCity: { name: 'DOUDS', pop: 15 },
    state: 'IA'
  },
  {
    biggestCity: { name: 'HUNTINGTON', pop: 75343 },
    smallestCity: { name: 'MOUNT CARBON', pop: 0 },
    state: 'WV'
  },
  {
    biggestCity: { name: 'SIOUX FALLS', pop: 102046 },
    smallestCity: { name: 'ZEONA', pop: 8 },
    state: 'SD'
  },
  {
    biggestCity: { name: 'PORTLAND', pop: 518543 },
    smallestCity: { name: 'ODELL', pop: 0 },
    state: 'OR'
  },
  {
    biggestCity: { name: 'MANCHESTER', pop: 106452 },
    smallestCity: { name: 'WEST NOTTINGHAM', pop: 27 },
    state: 'NH'
  },
  {
    biggestCity: { name: 'BALTIMORE', pop: 733081 },
    smallestCity: { name: 'ANNAPOLIS JUNCTI', pop: 32 },
    state: 'MD'
  },
  {
    biggestCity: { name: 'ALBUQUERQUE', pop: 449584 },
    smallestCity: { name: 'ALGODONES', pop: 0 },
    state: 'NM'
  },
  {
    biggestCity: { name: 'VIRGINIA BEACH', pop: 385080 },
    smallestCity: { name: 'WALLOPS ISLAND', pop: 0 },
    state: 'VA'
  },
  {
    biggestCity: { name: 'NEW ORLEANS', pop: 496937 },
    smallestCity: { name: 'FORDOCHE', pop: 0 },
    state: 'LA'
  },
  {
    biggestCity: { name: 'COLUMBIA', pop: 269521 },
    smallestCity: { name: 'QUINBY', pop: 0 },
    state: 'SC'
  },
  {
    biggestCity: { name: 'PHILADELPHIA', pop: 1610956 },
    smallestCity: { name: 'HAMILTON', pop: 0 },
    state: 'PA'
  },
  {
    biggestCity: { name: 'CHARLOTTE', pop: 465833 },
    smallestCity: { name: 'GLOUCESTER', pop: 0 },
    state: 'NC'
  },
  {
    biggestCity: { name: 'DETROIT', pop: 963243 },
    smallestCity: { name: 'LELAND', pop: 0 },
    state: 'MI'
  },
  {
    biggestCity: { name: 'PHOENIX', pop: 890853 },
    smallestCity: { name: 'HUALAPAI', pop: 2 },
    state: 'AZ'
  },
  {
    biggestCity: { name: 'LAS VEGAS', pop: 597557 },
    smallestCity: { name: 'TUSCARORA', pop: 1 },
    state: 'NV'
  },
  {
    biggestCity: { name: 'TULSA', pop: 389072 },
    smallestCity: { name: 'SOUTHARD', pop: 8 },
    state: 'OK'
  },
  {
    biggestCity: { name: 'CLEVELAND', pop: 536759 },
    smallestCity: { name: 'ISLE SAINT GEORG', pop: 38 },
    state: 'OH'
  },
  {
    biggestCity: { name: 'ANCHORAGE', pop: 183987 },
    smallestCity: { name: 'SELAWIK', pop: 0 },
    state: 'AK'
  },
  {
    biggestCity: { name: 'INDIANAPOLIS', pop: 348868 },
    smallestCity: { name: 'WESTPOINT', pop: 145 },
    state: 'IN'
  },
  {
    biggestCity: { name: 'MIAMI', pop: 825232 },
    smallestCity: { name: 'CECIL FIELD NAS', pop: 0 },
    state: 'FL'
  }
]

四、MapReduce操作

MapReduce操作将大量的数据处理工作拆分成多个线程并行处理,然后将结果合并在一起。MongoDB提供的Map-Reduce非常灵活,对于大规模数据分析也相当实用。

MapReduce具有两个阶段:

  1. 将具有相同Key的文档数据整合在一起的map阶段
  2. 组合map操作的结果进行统计输出的reduce阶段

**MapReduce的基本语法 **

db.collection.mapReduce(
   function() {emit(key,value);},  //map 函数
   function(key,values) {return reduceFunction},   //reduce 函数
   {
      out: <collection>,
      query: <document>,
      sort: <document>,
      limit: <number>,
     finalize: <function>, 
     scope: <document>,
     jsMode: <boolean>,
     verbose: <boolean>,
     bypassDocumentValidation: <boolean>
   }
)


  • map,将数据拆分成键值对,交给reduce函数
  • reduce,根据键将值做统计运算
  • out,可选,将结果汇入指定表
  • quey,可选筛选数据的条件,筛选的数据送入map
  • sort,排序完后,送入map
  • limit,限制送入map的文档数
  • finalize,可选,修改reduce的结果后进行输出
  • scope,可选,指定map、reduce、finalize的全局变量
  • jsMode,可选,默认false。在mapreduce过程中是否将数 据转换成bson格式。
  • verbose,可选,是否在结果中显示时间,默认false
  • bypassDocmentValidation,可选,是否略过数据校验

统计type为travel的不同作者的book文档收藏数

db.books.mapReduce(
    function(){emit(this.type,this.favCount)},
    function(key,values){return Array.sum(values)},
    {
        query:{type:"travel"},
        out: "books_favCount"
    }
 )

从MongoDB 5.0开始,map-reduce操作已被弃用。 聚合管道比映射-reduce操作提供更好的性能和可用性。Map-reduce操作可以使用聚合管道操作符重写,例如 g r o u p 、 group、 groupmerge等。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值