centos mongodb 第十八节课 聚合
centos mongodb 第十八节课 聚合
聚合
MongoDB 的聚合操作是一种高级查询语言,它允许用户通过转换和合并多个文档的数据来生成新的、在单个文档中不存在的文档信息。
这种操作通常涉及将记录按条件分组后进行一系列操作,如求最大值、最小值、平均值等简单操作,也可以进行复杂的数据统计和数据挖掘。
在 MongoDB 中,聚合操作主要通过 aggregate () 方法实现。aggregate () 方法提供了一种灵活且强大的方式来处理数据,可以执行各种复杂的聚合任务。
MongoDB 提供了 3 种主要的聚合方法,各自具有独特的特点和适用场景,同时它们也存在一定的联系。
【1】聚合管道方法(aggregate ()):这种方法可以理解为一种流水线处理过程,它将多个聚合阶段连接在一起,每个阶段都对输入文档进行处理,并将结果传递给下一个阶段。
基于管道的概念,文档进入一个多段管道,通过一系列的管道阶段(如过滤、分组、排序、计算等)进行转换和处理,最终输出聚合结果。聚合管道利用 MongoDB 的原生运算来提供高效的数据聚合,是执行数据聚合的首选方法。它特别适合处理大规模的数据集,并且可以通过索引来提高某些阶段的性能。
【2】Map-Reduce方法(在 mongo 5.0 过时):Map-Reduce 是一种编程模型,用于处理和生成大数据集。
MongoDB 中,Map-Reduce 包含两个主要阶段:Map 阶段和 Reduce 阶段。
通过将数据分成小块(Map 阶段),然后在这些小块上执行并行计算,最后将所有结果合并(Reduce 阶段)以生成最终结果。这种方法在处理大规模数据集时非常有效,通常用于处理复杂的聚合计算,特别是当需要在分布式环境中处理大量数据时。
然而,Map-Reduce 相对于聚合管道来说可能更为复杂,并且性能稍逊一筹。
【3】单一目标聚合方法:这种方法提供了一些特定的聚合操作功能,可以直接在集合上执行而无须使用聚合管道或 Map-Reduce。这些操作通常更简单、更直接,适用于一些常见的聚合需求。然而,它们的功能相对有限,可能无法满足所有复杂的聚合需求。
在联系方面,这 3 种聚合方法都是为了实现数据的聚合和转换,以满足不同的查询和分析需求。它们都是 MongoDB 提供的数据处理工具,可以根据具体的应用场景和需求选择合适的方法。
同时,这 3 种方法也可以相互补充,共同构建出更强大的数据处理能力。
在聚合操作中,主要通过以下两种聚合方法进行处理。
db.collection.aggregate ():用于聚合管道。
db.collection.mapReduce ():用于大数据的 Map-Reduce 聚合。
单一目标聚合命令
Count:计算集合或视图的文档数
Distinct:查找集合或视图中特定 key 的去重值
聚合管道
聚合管道(Aggregation Pipeline)是 MongoDB 2.2版本引入的一个新功能,它基于数据处理管道模型的数据聚合框架,
其概念和工作方式类似于 Linux 中的管道操作符,主要用于批量数据处理和统计分析,类似于 SQL 的 group by 语句。
聚合管道由多个阶段(Stage)组成,每个阶段有自己特定的功能,文档在一个阶段处理完毕后,聚合管道会将处理的结果传递给下一阶段。每个阶段都有相应的阶段操作符来对文档进行特定的处理。待处理的文档会流经这些阶段,最终完成计算,计算结果可以直接输出,也可以存储到集合中。整个过程像一个流水线,每一阶段都是环环相扣的,上一个工作阶段的输出(即工作结果)为下一个工作阶段的输入。
这体现了聚合管道的两个功能特点:
对文档进行过滤,筛选符合条件的文档。
对文档进行转换,以指定形式输出文档。
聚合管道通过 db.collection.aggregate() 方法实现。聚合管道涉及几个常用的概念:管道和阶段、管道操作符和管道表达式。
数据经过管道时,会对数据进行筛选、排序、分组、计算等操作,这些操作分属于不同的阶段。
前一阶段完成后输出的数据成为下一个阶段的输入数据。最终输出的是符合需求的文档数据。
使用聚合管道进行数据分析的基本步骤如下。
步骤 01 构建聚合管道:根据需求选择合适的阶段和操作符,构建聚合管道。每个阶段都定义了数据的处理方式,如筛选、分组、排序等。
步骤 02 执行聚合管道:将构建好的聚合管道作为参数传递给 MongoDB 的 aggregate() 方法,执行聚合操作。
在执行过程中,数据会按照定义的顺序流经每个阶段,每个阶段都会对数据进行相应的处理。
步骤 03 处理聚合结果:聚合操作完成后,会得到一个包含聚合结果的游标(Cursor)。开发者可以遍历游标,获取处理后的数据,并进行进一步的分析或展示。
db.<collection>.aggregate([ { <$stage1> }, { <$stage2> } ... ])
管道操作符
管道由一个个阶段组成,区分这些阶段的就是管道操作符。管道操作符的主要作用是将一个阶段的输出作为另一个阶段的输入,从而实现数据的连续处理或转换
表 6.2 常用的管道操作符
$project 修改输入文档的结构。它可以用来重命名、增加或删除字段,也可以用于创建计算结果以及嵌套文档。例如,project: { title: 1, _id: 0 }将只返回 title 字段,并排除 _id 字段
$match 用于过滤数据,只输出符合条件的文档。它等价于 find() 查询操作,可以在管道中使用 MongoDB 的标准查询操作符。例如,match: { author: "余华" } 将筛选出所有 author 字段为 "余华" 的文档。通常建议将$match 放在管道的前面,以减少后续管道阶段的工作量
$redact 基于文档中存储的信息限制每个文档的内容,从而修改输入文档的结构,结合$project和$match 来实现。对于每个输入文档,输出一个或零个文档
$limit 输出指定数量的文档。这可以用于限制聚合管道返回的文档数,主要用于分页查询。例如,limit: 10 将只返回前 10 个文档
$skip 跳过指定数量的文档,并返回余下的文档,主要用于分页查询。例如,skip: 10 将跳过前 10 个文档
$unwind 将数组字段拆分成多条文档
$group 将集合中的文档分组,并用于统计结果。例如,group: { _id: "orderNum", count: { $sum: 1 } }将按照orderNum字段对文档进行分组,并统计每个组的文档数量。在$group 中,可以使用各种算术操作符对分组后的文档进行求和、求平均数等操作
$sort 将文档排序后输出。例如,sort: { height: -1 }将按照 height 字段的降序对文档进行排序
$geoNear 按照与指定点的距离由近到远地返回一些坐标值
$out 将聚合管道的结果文档写入集合。$out 只能出现在管道中的最后一个阶段
$addFields 添加新字段
聚合操作
下面通过聚合来操作 books 集合,对书籍表中的书籍进行聚合分析,找出按照体裁分类的作品评分排行榜。
首先给 books 集合添加如下数据: db.books.insertMany([ { title: "平凡的世界", genres: [ "Novel","Fiction"], words: 1040000, year: 1986, author: [ "路遥" ], characters: [ "孙少平", "孙少安", "田晓霞", "田润叶"], country: "中国", douban: {rating:9.0,votes:320319,star:5} }, { title: "活着", genres: [ "Novel","Fiction"], words: 132000, year: 1992, author: [ "余华" ], characters: [ "徐福贵", "家珍", "凤霞", "有庆"], country: "中国", douban: {rating:9.4,votes:813308,star:5} }, { title: "红楼梦", genres: [ "Novel","Fiction"], words: 960000, year: 1760, author: [ "曹雪芹","高鹗" ], characters: [ "林黛玉", "贾宝玉", "薛宝钗"], country: "中国", douban: {rating:9.6,votes:813308,star:5} }, { "title": "三体", "genres": [ "Novel", "Science Fiction" ], "words": 800000, "year": [2006, 2008, 2010], "author": [ "刘慈欣" ], "characters": [ "叶文洁", "汪淼", "史强", "逻辑" ], "country": "中国", "douban": { "rating": 9.5, "votes": 180721, "star": 5 } } ])
然后通过 4 个管道阶段对以上数据进行聚合统计,以得到不同体裁作品的平均豆瓣评分。语句如下:
db.books.aggregate([ // 第一阶段 { $project: { _id: 0, genres: 1, title: 1, douban: 1 } }, // 第二阶段 { $unwind: "$genres" }, // 第三阶段 { $group: { _id: "$genres", averageGenreRating: { $avg: "$douban.rating" } } }, // 第四阶段 { $sort: { averageGenreRating: -1 } } ]) 第一阶段$project:过滤出包含genres、title、words字段的文档传递到第二阶段。此阶段筛选出的数据如下: [ { "title": "平凡的世界", "genres": [ "Novel", "Fiction" ], "douban": { "rating": 9, "votes": 320319, "star": 5 } }, { "title": "活着", "genres": [ "Novel", "Fiction" ], "douban": { "rating": 9.4, "votes": 813308, "star": 5 } }, { "title": "红楼梦", "genres": [ "Novel", "Fiction" ], "douban": { "rating": 9.6, "votes": 813308, "star": 5 } }, { "title": "三体" "genres": [ "Novel", "Science Fiction" ], "douban": { "rating": 9.5, "votes": 180721, "star": 5 } } ] 第二阶段$unwind:将第一阶段产生的数据,以 genres 数组中的每一个元素为准,分别拆分,然后将数据传递到第三阶段。 第二阶段筛选出的数据如下: [ { "title": "平凡的世界", "genres": "Novel", "douban": { "rating": 9, "votes": 320319, "star": 5 } }, { "title": "平凡的世界", "genres": "Fiction", "douban": { "rating": 9, "votes": 320319, "star": 5 } }, { "title": "活着", "genres": "Novel", "douban": { "rating": 9.4, "votes": 813308, "star": 5 } }, { "title": "活着", "genres": "Fiction", "douban": { "rating": 9.4, "votes": 813308, "star": 5 } }, { "title": "红楼梦", "genres": "Novel", "douban": { "rating": 9.6, "votes": 813308, "star": 5 } }, { "title": "红楼梦", "genres": "Fiction", "douban": { "rating": 9.6, "votes": 813308, "star": 5 } }, { "title": "三体", "genres": "Novel", "douban": { "rating": 9.5, "votes": 180721, "star": 5 } }, { "title": "三体", "genres": "Science Fiction", "douban": { "rating": 9.5, "votes": 180721, "star": 5 } } ] 第三阶段$group:以去重后的 genres 的每个元素进行分组统计,使用_id字段标记分组关键字,添加一个新的字段averageWords,统计每一个体裁的作品平均字数。 筛选出的数据如下: [ { _id: 'Novel', averageGenreRating: 9.375 }, { _id: 'Fiction', averageGenreRating: 9.333333333333334 }, { _id: 'Science Fiction', averageGenreRating: 9.5 } ] 第四阶段$sort:按评分从高到低排列,最终得到的数据如图 6.1 所示。 > db.books.aggregate([ //第一阶段 { $project: { _id: 0, genres: 1, title: 1 ,douban:1} }, // 第二阶段 { $unwind: "$genres" }, // 第三阶段 { $group: { _id: "$genres", averageGenreRating: { $avg: "$douban.rating" } } }, //第四阶段 { $sort: { averageGenreRating: -1 } } ]) [ { "_id": "Science Fiction", "averageGenreRating": 9.5 }, { "_id": "Novel", "averageGenreRating": 9.375 }, { "_id": "Fiction", "averageGenreRating": 9.333333333333334 } ]
Map-Reduce
Map-Reduce 是一种计算模型,简单来说就是将大批量的工作(数据)分解(Map)执行,然后将结果合并成最终结果(Reduce)。
MongoDB 提供的 Map-Reduce 非常灵活,对于大规模数据分析也相当实用。
然而,在 MongoDB 5.0 之后,该方法已被标记为过时,官方推荐使用聚合管道来替换。
通过聚合管道的阶段,例如$group、$merge等重写 mapReduce 函数,结合$accumulator和$function操作符,
实现自定义客户端的处理逻辑。
使用 mapReduce 函数时,要先实现 map 函数和 reduce 函数。map 函数通过调用emit(key,value),遍历集合中所有的记录,将 key 与 value 传递给 reduce 函数进行处理。map 函数必须调用emit(key, value)返回键值对。
以下是 mapReduce 的基本语法:
db.collection.mapReduce( function() {emit(key,value);}, //map函数 function(key,values) {return reduceFunction;}, //reduce函数 { out: collection, query: document, sort: document, limit: number } )
参数说明:
map: 映射函数,生成键值对序列,作为 reduce 函数的参数。
reduce: 分解函数,reduce 函数的任务是将 key-values 转换为 key-value,也就是把 values 数组中的多个值合并为一个单一的值 value。
out: 指定统计结果存放的集合。
query: 筛选条件,只有满足该条件的文档才会调用 map 函数。
sort: 和 limit 结合使用的排序参数(在文档传递给 map 函数之前进行排序),可以优化分组机制。
limit: 传递给 map 函数的文档数量的上限。
在集合 orders 中查找 status:"A" 的数据,并根据 cust_id 进行分组,并计算 amount 字段的总和
db.orders.mapReduce( map function() { emit( this.cust_id, this.amount ); }, reduce function(key, values) { return Array.sum(values) }, { query: { status: "A" }, out: "order_totals" } )
f

浙公网安备 33010602011771号