MongoDB 8.0——聚合
2026/8/2 1:13:25 网站建设 项目流程

聚合

    • 1、聚合方法
    • 2、聚合管道
      • 2.1、管道和阶段
      • 2.2、管道操作符
      • 2.3、管道表达式与表达式操作符
        • 2.3.1、算术表达式操作符
        • 2.3.2、数组表达式操作符
        • 2.3.3、位操作符
        • 2.3.4、布尔表达式操作符
        • 2.3.5、比较表达式操作符
        • 2.3.6、条件表达式操作符
        • 2.3.7、客户端聚合表达式操作符
        • 2.3.8、数据量操作符
        • 2.3.9、日期表达式操作符
        • 2.3.10、对象表达式操作符
        • 2.3.11、设置表达式操作符
        • 2.3.12、字符串表达式操作符
        • 2.3.13、文本表达式操作符
        • 2.3.14、时间戳表达式操作符
        • 2.3.15、三角函数表达式操作符
        • 2.3.16、类型表达式操作符
        • 2.3.17、聚合器操作符
      • 2.4、聚合操作

1、聚合方法

MongoDB的聚合操作是一种高级查询语言,它允许用户通过转换和合并多个文档的数据来生成新的、在单个文档中不存在的文档信息。这种操作通常涉及将记录按条件分组后进行一系列操作,如求最大值、最小值、平均值等简单操作,也可以进行复杂的数据统计和数据挖掘。

在MongoDB中,聚合操作主要通过aggregate()方法实现。aggregate()方法提供了一种灵活且强大的方式来处理数据,可以执行各种复杂的聚合任务。

MongoDB提供了3种主要的聚合方法,各自具有独特的特点和适用场景,同时它们也存在一定的联系。

  • 聚合管道方法:这种方法可以理解为一种流水线处理过程,它将多个聚合阶段连接在一起,每个阶段都对输入文档进行处理,并将结果传递给下一个阶段。基于管道的概念,文档进入一个多段管道,通过一系列的管道阶段(如过滤、分组、排序、计算等)进行转换和处理,最终输出聚合结果。聚合管道利用MongoDB的原生运算来提供高效的数据聚合,是执行数据聚合的首选方法。它特别适合处理大规模的数据集,并且可以通过索引来提高某些阶段的性能。
  • Map-Reduce方法:Map-Reduce是一种编程模型,用于处理和生成大数据集。在MongoDB中,Map-Reduce包括两个主要阶段:Map阶段和Reduce阶段。通过将数据分成小块(Map阶段)​,然后在这些小块上执行并行计算,最后将所有结果合并(Reduce阶段)以生成最终结果。这种方法在处理大规模数据集时非常有效,通常用于处理复杂的聚合计算,特别是当需要在分布式环境中处理大量数据时。然而,Map-Reduce相对于聚合管道来说可能更为复杂,并且性能可能稍逊一筹。
  • 单一目标聚合方法:这种方法提供了一些特定的聚合操作功能,可以直接在集合上执行而无须使用聚合管道或Map-Reduce。这些操作通常更简单、更直接,适用于一些常见的聚合需求。然而,它们的功能相对有限,可能无法满足所有复杂的聚合需求。

在联系方面,这3种聚合方法都是为了实现数据的聚合和转换,以满足不同的查询和分析需求。它们都是MongoDB提供的数据处理工具,可以根据具体的应用场景和需求选择合适的方法。同时,这3种方法也可以相互补充,共同构建出更强大的数据处理能力。

在聚合操作中,主要通过以下两种聚合方法进行处理。

  • db.collection.aggregate():用于聚合管道。
  • db.collection.mapReduce():用于大数据的Map-Reduce聚合。

除以上两种方法外,MongoDB中还有一些方法也涉及聚合操作,这主要在单一目标聚合中使用,如表所示。

总的来说,MongoDB的聚合操作提供了一种灵活且强大的方式来处理和分析数据,可以满足各种复杂的查询和统计需求,在实际应用中需要根据具体需求进行选择和使用。

2、聚合管道

聚合管道(Aggregation Pipeline)是MongoDB 2.2版本引入的一个新功能,它基于数据处理管道模型的数据聚合框架,其概念和工作方式类似于Linux中的管道操作符,主要用于批量数据处理和统计分析,类似于SQL的group by语句。

聚合管道由多个阶段(Stage)组成,每个阶段有自己特定的功能,文档在一个阶段处理完毕后,聚合管道会将处理的结果传递给下一阶段。每个阶段都有相应的阶段操作符来对文档进行特定的处理。待处理的文档会流经这些阶段,最终完成计算,计算结果可以直接输出,也可以存储到集合中。整个过程像一个流水线,每一阶段都是环环相扣的,上一个工作阶段的输出(即工作结果)为下一个工作阶段的输入。这体现了聚合管道的两个功能特点:

  • 对文档进行过滤,筛选符合条件的文档。
  • 对文档进行转换,以指定形式输出文档。

聚合管道通过db.collection.aggregate()方法实现。聚合管道涉及几个常用的概念:管道和阶段、管道操作符和管道表达式。

2.1、管道和阶段

传统意义上的管道是指用管子、管子连接件和阀门等连接成的用于输送气体、液体或带固体颗粒的流体的装置。管道在生活中很常见。例如水管管道、燃气管道、暖气管道,这些管道都有共同点,即管道内的流体是按照从前向后的顺序依次流通的。有些管道,例如水管管道,在一些重要的节点会进行处理,例如过滤、净化、杀菌、消毒等,最后流入住户的都是合格的饮用水。

在MongoDB中,聚合管道也是同样的道理,只不过它的流体为数据。数据经过管道时,会对数据进行筛选、排序、分组、计算等操作,这些操作分属于不同的阶段。前一阶段完成后输出的数据成为下一个阶段的输入数据。最终输出的是符合需求的文档数据。

使用聚合管道进行数据分析的基本步骤如下。

步骤1:构建聚合管道:根据需求选择合适的阶段和操作符,构建聚合管道。每个阶段都定义了数据的处理方式,如筛选、分组、排序等。

步骤2:执行聚合管道:将构建好的聚合管道作为参数传递给MongoDB的aggregate()方法,执行聚合操作。在执行过程中,数据会按照定义的顺序流经每个阶段,每个阶段都会对数据进行相应的处理。

步骤3:处理聚合结果:聚合操作完成后,会得到一个包含聚合结果的游标(Cursor)。开发者可以遍历游标,获取处理后的数据,并进行进一步的分析或展示。

例如,一个聚合管道可能包含以下阶段:首先,从集合中根据条件查找出一批文档数据;接着,对搜索出来的文档数据进行分组统计;最后,对统计数据进行排序。整个过程就像一个流水线式的操作,数据从管道的一端流入,经过几个阶段的处理后,输出一个最终的结果。

在聚合管道中,有些阶段可以使用索引来提高性能,例如match阶段和group阶段。然而,使用索引的条件和限制需要特别注意。

如果要对集合创建聚合管道,可以在MongoDB Shell中使用以下语法:


如果数据库要面对创建聚合管道,可以使用如下语法:

db.aggregate([{<stage>},...])

2.2、管道操作符

管道由一个个阶段组成,区分这些阶段的就是管道操作符。管道操作符的主要作用是将一个阶段的输出作为另一个阶段的输入,从而实现数据的连续处理或转换。常用的管道操作符如表所示。

在管道操作符中,除$out$merge$geoNear$changeStream$changeStreamSplitLargeEvent外,其他的操作符都可以多次出现。

2.3、管道表达式与表达式操作符

前面的管道操作符可以理解为阶段的key,那么这里的管道表达式即为阶段的value。例如{KaTeX parse error: Expected 'EOF', got '}' at position 19: …ch:{title:'活着'}}̲,其中match为管道操作符,{title:‘活着’}为管道表达式。通常情况下,管道表达式类似于一个携带参数的函数,形式上为一个文档,结构如下:

{<operator>:[<argument1>,<argument2>...]}

如果只有一个参数的话,可以简化为如下格式:

{<operator>:<argument>}

在使用管道表达式时,需要结合表达式的操作符。MongoDB提供了多种类型的表达式操作符,涵盖多种数据类型和计算方法。

2.3.1、算术表达式操作符

在MongoDB的聚合管道中,算术表达式操作符用于执行数学运算。这些操作符可以在$project$group等阶段中使用,以计算和转换字段的值。一些常用的算术表达式操作符如表所示。


这些算术表达式操作符可以在聚合管道的各个阶段灵活使用,以进行复杂的数学计算和数据处理。例如,在$group阶段,可以使用$sum来计算某个字段的总和,或者使用$avg来计算平均值。在$project阶段,可以使用$multiply$divide来创建新的字段,其值是基于现有字段的计算结果。

注意,当在聚合管道中使用这些操作符时,通常需要将它们放在表达式的上下文中,例如{ $sum:"$field" },其中$field是要对其执行数学运算的字段名。

2.3.2、数组表达式操作符

在MongoDB的聚合管道中,数组表达式操作符用于处理数组字段,执行诸如过滤、转换和计算数组元素等操作。一些常用的数组表达式操作符如表所示。


这些数组表达式操作符在聚合管道的各个阶段都非常有用,特别是在处理包含数组字段的文档时。读者可以根据需要在$project$group或其他阶段中使用它们,以执行复杂的数组操作和数据转换。

2.3.3、位操作符

在MongoDB的聚合管道中,位操作符允许对数值字段进行位运算。这些操作符对于需要执行位级别操作的数据处理任务非常有用。一些常用的位操作符如表所示。

2.3.4、布尔表达式操作符

在布尔表达式操作符中,null、0以及未定义的数据值均视为false,非0数值和数组均视为true。常用的布尔表达式操作符如表所示。

2.3.5、比较表达式操作符

在所有的比较表达式操作符中,除$cmp返回数值类型外,其他均返回布尔型。常用的比较表达式操作符如表所示。

2.3.6、条件表达式操作符

MongoDB中的条件表达式操作符允许用户根据特定的条件来过滤查询结果。一些常用的MongoDB条件表达式操作符如表所示。

2.3.7、客户端聚合表达式操作符

客户端聚合表达式操作符是在版本4.4之后才引入的操作符,用来定义聚合函数和客户端函数,如表所示。

2.3.8、数据量操作符

数据量操作符用来返回数据的大小,如表所示。

2.3.9、日期表达式操作符

日期表达式操作符是MongoDB操作中使用频率较高的操作符。MongoDB提供了多个日期表达式操作符,用于在查询和聚合中对日期和时间字段进行各种操作。一些常用的MongoDB日期表达式操作符如表所示。

2.3.10、对象表达式操作符

MongoDB提供了3个对象表达式操作符,用来操作文档对象,如表所示。

2.3.11、设置表达式操作符

在MongoDB中,设置表达式操作符通常用于更新文档时,通过这些操作符可以修改字段的值或结构。这些操作符可以在update、updateOne、updateMany等方法的更新文档中使用,如表所示。

2.3.12、字符串表达式操作符

在MongoDB中,处理字符串字段时,可以使用一系列的表达式操作符来执行各种字符串比较和操作。一些常用的字符串表达式操作符如表所示。

2.3.13、文本表达式操作符

文本表达式操作符主要用于访问文本搜索相关的元数据,如表所示。

2.3.14、时间戳表达式操作符

时间戳表达式操作符主要用于基于时间戳返回对应的值,如表所示。

2.3.15、三角函数表达式操作符

MongoDB提供了常用的三角函数表达式操作符,如表所示。

2.3.16、类型表达式操作符

MongoDB中的类型表达式操作符主要用于在查询中根据字段的数据类型进行过滤。常用的类型表达式操作符如表所示。

2.3.17、聚合器操作符

在MongoDB中,聚合器操作符用于处理和转换集合中的数据,以便执行复杂的分析操作。在聚合操作中,通过这些操作符可以对数据进行分组、过滤、排序、计算等。一些常用的MongoDB聚合器操作符如表所示。

这些操作符可以根据需要在查询、更新和聚合操作中进行组合和使用,以实现复杂的数据处理和分析任务。注意,随着MongoDB版本的更新,有些操作符可能会被废弃或替换,因此建议查阅最新的官方文档以获取最准确的信息。

2.4、聚合操作

下面通过聚合来操作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:'Fiction',averageGenreRating:9.333333333333334},{_id:'Novel',averageGenreRating:9.375},{_id:'Science Fiction',averageGenreRating:9.5}]

第四阶段$sort:按评分从高到低排列,最终得到的数据如下所示。

[{_id:'Science Fiction',averageGenreRating:9.5},{_id:'Novel',averageGenreRating:9.375},{_id:'Fiction',averageGenreRating:9.333333333333334}]

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的基本语法:

参数说明:

  • map:映射函数,生成键值对序列,作为reduce函数的参数。
  • reduce:分解函数,reduce函数的任务是将key-values转换为key-value,也就是把values数组中的多个值合并为一个单一的值value。
  • out:指定统计结果存放的集合。
  • query:筛选条件,只有满足该条件的文档才会调用map函数。
  • sort:和limit结合使用的排序参数(在文档传递给map函数之前进行排序)​,可以优化分组机制。
  • limit:传递给map函数的文档数量的上限。

在集合orders中查找status:'‘A’'的数据,并根据cust_id进行分组,并计算amount字段的总和,如图所示。

考虑使用以下文档结构存储用户的文章,文档存储了用户的user_name和文章的status字段:

db.posts.insertMany([{"post_text":"奔月教程,最全的技术文档。","user_name":"mark","status":"active"},{"post_text":"奔月教程,最全的技术文档。","user_name":"mark","status":"active"},{"post_text":"奔月教程,最全的技术文档。","user_name":"mark","status":"active"},{"post_text":"奔月教程,最全的技术文档。","user_name":"mark","status":"active"},{"post_text":"奔月教程,最全的技术文档。","user_name":"mark","status":"disabled"},{"post_text":"奔月教程,最全的技术文档。","user_name":"runoon","status":"disabled"},{"post_text":"奔月教程,最全的技术文档。","user_name":"runoon","status":"disabled"},{"post_text":"奔月教程,最全的技术文档。","user_name":"runoon","status":"active"}])

现在,我们在posts集合中使用mapReduce函数来选取已发布的文章(status:“active”),并通过user_name分组,计算每个用户的文章数:

db.posts.mapReduce(function(){emit(this.user_name,1)},function(key,values){returnArray.sum(values)},{query:{status:"active"},out:"post_total"})

以上mapReduce输出结果为:

结果表明,共有5个符合查询条件(status:“active”)的文档,在map函数中生成了5个键值对文档,最后使用reduce函数将相同的键值分为两组。

参数说明:

  • result:存储结果的集合名称,这是一个临时集合,当mapReduce的连接关闭后,该集合将自动被删除。
  • timeMillis:执行所花费的时间,以毫秒为单位。
  • input:满足条件被发送到map函数的文档个数。
  • emit:在map函数中emit被调用的次数,也就是所有集合中的数据总量。
  • output:结果集合中的文档个数(count对调试非常有帮助)​。
  • ok:是否成功,成功时返回1。
  • err:如果失败,这里会显示失败原因。不过从经验来看,失败原因可能比较模糊,参考价值不大。

使用find操作符来查看mapReduce的查询结果:

db.posts.mapReduce(function(){emit(this.user_name,1)},function(key,values){returnArray.sum(values)},{query:{status:"active"},out:"post_total"}).find()

查询结果如下:

{"_id":"mark","value":4}{"_id":"runoon","value":1}

用类似的方式,mapReduce可以被用来构建大型复杂的聚合查询。map函数和reduce函数可以使用JavaScript来实现,使得mapReduce的使用非常灵活和强大。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询