MongoDb进阶实践之八 MongoDB的聚合初探

一、引言

      好久没有写东西了,MongoDB系列的文章也丢下好长时间了。今天终于有时间了,就写了一篇有关聚合的文章。一说到“聚合”,用过关系型数据库的人都应该知道它是一个什么东西。关系型数据库有“聚合”的概念,我们的MongoDB数据库也有聚合的概念,今天我就来抛砖引玉,简单的说一说MongoDB文档数据库中的“聚合”的概念。

二、简介

      管道是MongoDB2.2版本引入新的功能 ,它是数据聚合的一个新框架,其概念类似于数据处理的管道。 每个文档通过一个由多个节点组成的管道,每个节点有自己特殊的功能(分组、过滤等),文档经过管道处理后,最后输出相应的结果。管道基本的功能有两个:一是对文档进行“过滤”,也就是筛选出符合条件的文档;二是对文档进行“变换”,也就是改变文档的输出形式。其他的一些功能还包括按照某个指定的字段分组和排序等。而且在每个阶段还可以使用表达式操作符计算平均值和拼接字符串等相关操作。管道提供了一个MapReduce 的替代方案,MapReduce使用相对来说比较复杂,而管道拥有固定的接口(操作符表达),使用比较简单,对于大多数的聚合任务管道一般来说是首选方法。


三、管道的相关操作


      管道操作符作为“键”,所对应的“值”叫做管道表达式。它可以看做是管道操作符的操作数(Operand),每个管道表达式是一个文档结构,它是由字段名、字段值、和一些表达式操作符组成的,

      每个管道表达式只能作用于处理当前正在处理的文档,而不能进行跨文档的操作。管道表达式对文档的处理都是在内存中进行的。除了能够进行累加计算的管道表达式外,其他的表达式都是无状态的,也就是不会保留上下文的信息。累加性质的表达式操作符通常和$group操作符一起使用,来统计该组内最大值、最小值等,例如上面的例子中我们在$group管道操作符中使用了具有累加的$sum来计算总和。

    1、管道表达式使用详解

      语法:aggregate() 方法的基本语法格式如下所示:

      >db.collectionName.aggregate(aggregateOperation)

      参数说明:

          collectionName:该参数表示的是集合的名称,就是在哪个集合上进行聚合操作。

          aggregateOperation:具体索要执行的聚合操作的名称

    

      下面列出了一些聚合的管道表达式:

      表达式        描述                        实例

      $sum        计算总和。                    db.users.aggregate([{$group : {_id : "$author",num_tutorial : {$sum : "$likes"}}}])

      $avg        计算平均值                    db.users.aggregate([{$group : {_id : "$author",num_tutorial : {$avg : "$likes"}}}])

      $min        获取集合中所有文档对应值得最小值。        db.users.aggregate([{$group : {_id : "$author",num_tutorial : {$min : "$likes"}}}])

      $max        获取集合中所有文档对应值得最大值。        db.users.aggregate([{$group : {_id : "$author",num_tutorial : {$max : "$likes"}}}])

      $push        在结果文档中插入值到一个数组中。        db.users.aggregate([{$group : {_id : "$author",url : {$push: "$url"}}}])

      $addToSet    在结果文档中插入值到一个数组中,但不创建副本。    db.users.aggregate([{$group : {_id : "$author",url : {$addToSet : "$url"}}}])

      $first        根据资源文档的排序获取第一个文档数据。        db.users.aggregate([{$group : {_id : "$author",first_url : {$first : "$url"}}}])

      $last        根据资源文档的排序获取最后一个文档数据        db.users.aggregate([{$group : {_id : "$author",last_url : {$last : "$url"}}}])



        1.1、【 $sum】 的用法

 1          //原始数据:
 2         > db.users.find()
 3         { "_id" : ObjectId(5b1e044733091e826f7c2c74"),title" : MongoDb Overview",1)">descriptionMongodb is not sql databaseauthorhuangFeiHongurlhttp://www.huangfeihong.comtags" : [ mongodbdatabaseNoSql" ],1)">likes100 }
 4         { 5b1e044733091e826f7c2c75NoSql OverviewNo sql database is very fast10 5         { 5b1e044733091e826f7c2c76Log4Net Overrivewlog4net is not sql databaselingChonghttp://www.linchong.comlognetNoSQL750 6         >
 7 
 8 
 9         1】、类似sql语句: select author,count(*) from users group by author
10         
11             > db.users.aggregate([{$group:{":$authorbooks":{$sum:1}}}]);
12             { 13             { 214 
15 
16 
17         2】、类似sql语句: select count(1) as count  users
18         
19             > db.users.count();
20             3
21 
22             > db.users.aggregate([{$group:{":null,count:{$sum23             { " : count324 
25 
26 
27         3】、类似sql语句: select sum(likes) as total 28         
29             > db.users.aggregate([{$group:{total":{$likes"}}}])
30             { 86031 
32 
33              类似sql语句: 34 
35             > db.users.aggregate([{$group:{36             { 37             { 110 }


        1.2、【 $max】 的用法

         原始数据:
        > db.users.find()
        {  }
        {  }
        >


         users group by author,我们通过相同的作者来进行分组,然后查询相同作者的书最多的阅读详情
        
            > db.users.aggregate([{$group:{max$max}}}]);
            {  }
            { 100 }

 


        1.3、【 $min】 的用法

         users group by author,我们通过相同的作者来进行分组,然后查询相同作者的书最少的阅读详情
        
            > db.users.aggregate([{$group:{min$min10 }


        1.4、【 $avg】 的用法

         as average  users group by author,我们可以根据相同的作者分类算出相同作者阅读数量的平均值
        
            > db.users.aggregate([{$group:{average$avg}}}])
            { 55 }


        select avg(likes) users

            > db.users.aggregate([{$group:{286.6666666666667 }
            > 


        1.5、【 $push】 的用法,将指定的表达式的值添加到一个数组中,就算是有重复的值也会显示,这个值不要超过16M,不然会出现错误

        100,1)">quantity410,1)">12Log4Net OverviewlinChong750,1)">65b1e1bfb33091e826f7c2c77MongoDb Higher120,1)">235b1e1bfb33091e826f7c2c78NoSql Redis Overviewredis30,1)">335b1e1bfb33091e826f7c2c79Memcached OverrivewMemcached is sql databasewuSonghttp://www.wusong.commemcachedcache50,1)">】、我们通过相同的作者来进行分组,然后查询每个相同作者卖出的数量放在数组里面
     
             db.users.aggregate({$group:{quantities$push$quantity}}});
            {  ] }
            { 6,1)">4,1)">12,1)"> ] }

        】、我们通过相同的作者来进行分组,然后查询每个相同作者卖出的数量放在数组里面,并且显示书名和其销量

            > db.users.aggregate({$group:{$title}}}});
            { " : [ {  } ] }
            { " },{ 33,1)">23,1)"> } ] }
            >

        3】、我们通过相同的作者来进行分组,然后查询每个相同作者卖出的数量放在数组里面,并且显示书名和其销量,有重复值,也会显示【{ " },{  }】

            > db.users.aggregate({$group:{ } ] }
            > 


        1.6、【 $addToSet】 的用法,将表达式的值添加到一个数组中(无重复值),这个值不要超过16M,不然会出现错误

         }

        1】、我们通过相同的作者来进行分组,然后查询每个相同作者卖出的数量放在数组里面,有重复值,只显示一个4,【{  ] } 】
        
             > db.users.aggregate({$group:{$addToSet ] }


         } ] }
            > 


        1.7、【 $first】 的用法,返回每组第一个文档,如果有排序,按照排序,如果没有按照默认的存储的顺序的第一个文档。

         }
     >

        】、按作者进行分组,获取分组的第一条记录
        
             > db.users.aggregate({$groupquantityFrist$first}}})
            { 4 }


        1.8、【 $last】 的用法,返回每组最后一个文档,如果有排序,按照排序,如果没有按照默认的存储的顺序的最后个文档。

         }
>

        】、按作者进行分组,获取分组的最后一条记录
        
            > db.users.aggregate({quantityLast$last4 }


    2、管道操作符使用详解

      管道是由一个个功能节点组成的,这些节点用管道操作符来进行表示。聚合管道以一个集合中的所有文档作为开始,然后这些文档从一个操作节点流向下一个节点 ,每个操作节点对文档做相应的操作。这些操作可能会创建新的文档或者过滤掉一些不符合条件的文档,在管道中可以对文档进行重复操作。管道在Unix和Linux中一般用于将当前命令的输出结果作为下一个命令的参数。    MongoDB的聚合管道将MongoDB文档在一个管道处理完毕后将结果传递给下一个管道处理。管道操作是可以重复的。

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

        2.1、【$project】:数据投影,主要用于重命名、增加和删除字段,也可以用于创建计算结果以及嵌套文档。

         原始数据
        > db.users.find({},{_id:0})
        {  }
        >

        隐藏_id字段,并且只显示title和author字段
        > db.users.aggregate({$project0,1)">1,1)">}});
        { " }


        2.2、【$match】:用于过滤数据,只输出符合条件的文档。$match使用MongoDB的标准查询操作。$match的语法和查询表达式(db.collection.find())的语法相同

         }
        >


        获取销售数量大于4并且小于等于12的作者,并按作者分组统计
        > db.users.aggregate([{$match$gt$lte12}}},{}}}]);
        {  }
               {  }


                获取销售数量大于4并且小于等于33的作者,并按作者分组统计
           > db.users.aggregate([{33}}},1)">}}}]);
           {  }
           {  }
            >

                获取销售数量大于等于4并且小于等于33的作者,并按作者分组统计
            > db.users.aggregate([{$gte3 }


         注意: 1、不能在$match操作符中使用$where表达式操作符。

                    2、$match尽量出现在管道的前面,这样可以提早过滤文档,加快聚合速度。

                    3、如果$match出现在最前面的话,可以使用索引来加快查询。



        2.3、【$limit】:用来限制MongoDB聚合管道返回的文档数。

        只显示前两条
        > db.users.aggregate({$limit }

        只显示前两条,并且隐藏_id字段
        > db.users.aggregate({2},1)">}})
        { 只显示前四条,并且隐藏_id字段
        > db.users.aggregate({4},1)"> }
        > 


        2.4、【$skip】:在聚合管道中跳过指定数量的文档,并返回余下的文档。

        跳过前2条,从第三条记录开始显示
        > db.users.aggregate({$skip跳过前2条,从第三条记录开始显示,并结合$project 隐藏_id字段
        > db.users.aggregate({通过$skip和$limit分页显示,显示第一页,每页显示2条记录,并隐藏_id和url字段
        > db.users.aggregate({0},url:通过$skip和$limit分页显示,显示第二页,每页显示2条记录,并隐藏_id和url字段
        > db.users.aggregate({ }
        > 


        2.5、【$unwind】:将文档中的某一个数组类型字段拆分成多条,每条包含数组中的一个值。

        把数组tags字段独立开来,每个数组元素是一条独立的记录,这是全部字段都显示的
        > db.users.aggregate({$unwind$tags把数组tags字段独立开来,每个数组元素是一条独立的记录,这里进行了字段的减少
        > db.users.aggregate({"},1)">10 }


          注意:1、$unwind:"$tags"})不要忘了$符号

                    2、如果$unwind目标字段不存在的话,那么该文档将被忽略过滤掉,例如:

                           > db.article.aggregate({$project:{author:1,title:1,tags:1}},{$unwind:"$tag"})
                            { "result" : [ ],"ok" : 1 }
                          将$tags改为$tag因不存在该字段,该文档被忽略,输出的结果为空

                    3、如果$unwind目标字段不是一个数组的话,将会产生错误,例如:

                        > db.article.aggregate({$project:{author:1,{$unwind:"$title"})
                        Error: Printing Stack Trace
                           at printStackTrace (src/mongo/shell/utils.js:37:15)
                           at DBCollection.aggregate (src/mongo/shell/collection.js:897:9)
                           at (shell):1:12
                           Sat Nov 16 19:16:54.488 JavaScript execution failed: aggregate failed: {
                            "errmsg" : "exception: $unwind:  value at end of field path must be an array",
                            "code" : 15978,1)">                            "ok" : 0
                           } at src/mongo/shell/collection.js:L898

                   4、如果$unwind目标字段数组为空的话,该文档也将会被忽略。


        2.6、【$group】:将集合中的文档分组,可用于统计结果。

        根据作者分类统计
        > db.users.aggregate({}}})
        {  }


        根据作者分类统计,隐藏_id字段
        > db.users.aggregate([{1}}},1)">}}])
        { 3 }


        注意:  1、$group的输出是无序的。

                     2、$group操作目前是在内存中进行的,所以不能用它来对大量个数的文档进行分组。


        2.7、【$sort】:将输入文档排序后输出。

        根据quantity字段按升序排列
        > db.users.aggregate([{$sort1}},1)">}}]);
        {  }



        根据quantity字段按降序排列
        > db.users.aggregate([{":- }
        > 


          注意:1. 如果将$sort放到管道前面的话可以利用索引,提高效率

                   2. MongoDB 24.对内存做了优化,在管道中如果$sort出现在$limit之前的话,$sort只会对前$limit个文档进行操作,这样在内存中也只会保留前$limit个文档,从而可以极大的节省内存

                   3. $sort操作是在内存中进行的,如果其占有的内存超过物理内存的10%,程序会产生错误

        2.8、【$goNear】:$goNear会返回一些坐标值,这些值以按照距离指定点距离由近到远进行排序

            原始数据
            > db.geoInstance.insert([{loc:[3]},{loc:[3,1)">4]},-2]},{loc:{x:9,y:5}},{loc:{lng:-9.2,lat:21.3}}])
            BulkWriteResult({
                    writeErrors : [ ],writeConcernErrorsnInserted,1)">nUpsertednMatchednModifiednRemovedupserted : [ ]
            })


            > db.geoInstance.find()
            { 5b1f4d21cf30d7cba03d5e03loc5b1f4d21cf30d7cba03d5e045b1f4d21cf30d7cba03d5e055b1f4d21cf30d7cba03d5e06" : [ -5b1f4d21cf30d7cba03d5e07" : { xy5 } }
            { 5b1f4d21cf30d7cba03d5e08lng" : -lat } }
            >

            注意:一定要建立2d索引,否则不能计算距离(我在这里掉坑了)
            > db.geoInstance.ensureIndex({2d});
            {
                    createdCollectionAutomaticallyfalsenumIndexesBeforenumIndexesAfterok
            }

            执行结果
            > db.geoInstance.aggregate({$geoNear":{near:[2],distanceField:distance}})
            { 5 },1)">3.16227766016837954 ],1)">7.2801098892805183 ],1)">9.05538513813741711.1803398874989492 ],1)">1621.3 },1)">27.223702907576698 }
            >


         注意: 1、使用$goNear只能在管道处理的开始第一个阶段进行

                     2、必须指定distanceField,该字段用来决定是否包含距离字段

                     3、$gonNear和geoNear命令比较相似,但是也有一些不同:distanceField在$geoNear中是必选的,而在geoNear中是可选的;includeLocs在$geoNear中是string类型,而在geoNear中是boolen类型。


五、结束

      好了,就是这些了。学习是一个渐进的过程,今天只是初步涉及“聚合”的操作,还有有差距,有些概念要好好的理解一下。不忘初心,继续努力吧。

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


文章浏览阅读752次。关系型数据库关系型数据库是一个结构化的数据库,创建在关系模型(二维表模型)基础上,一般面向于记录SQL语句(标准数据查询语言)就是一种基于关系型数据库的语言,用于执行对关系型数据库中数据的检索和操作主流的关系数据库包括Oracle、Mysql、SQL Server、Microsoft Access、DB2等非关系型数据库NoSQL(nOSQL=Not Only SQL),意思是“不仅仅是SQL”,是非关系型数据库的总称。除了主流的关系型数据库外的数据库,都认为是非关系型主流的NoSQ.._redis是非关系型数据库吗
文章浏览阅读687次,点赞2次,收藏5次。商城系统中,抢购和秒杀是很常见的营销场景,在一定时间内有大量的用户访问商场下单,主要需要解决的问题有两个:1. 高并发对数据库产生的压力;2. 竞争状态下如何解决商品库存超卖;高并发对数据库产生的压力对于第一个问题,使用缓存来处理,避免直接操作数据库,例如使用 Redis。竞争状态下如何解决商品库存超卖对于第二个问题,需要重点说明。常规写法:查询出对应商品的库存,判断库存数量否大于 0,然后执行生成订单等操作,但是在判断库存是否大于 0 处,如果在高并发下就会有问题,导致库存_php库存结余并发
文章浏览阅读1.4k次。MongoTemplate开发spring-data-mongodb提供了MongoTemplate和MongoRepository两种方式访问MongoDB,MongoRepository的方式访问较为简单,MongoTemplate方式较为灵活,这两种方式在Java对于MongoDB的运用中相辅相成。_springboot插入指定的mongodb数据库
文章浏览阅读887次,点赞10次,收藏19次。1.背景介绍1. 背景介绍NoSQL数据库是一种非关系型数据库,它的特点是可以存储非结构化的数据,并且可以处理大量的数据。HBase是一个分布式、可扩展的列式存储系统,它是基于Google的Bigtable设计的。HBase是一个开源的NoSQL数据库,它的核心功能是提供高性能的随机读写访问。在本文中,我们将对比HBase与其他NoSQL数据库,例如Redis、MongoDB、Cass...
文章浏览阅读819次。MongoDB连接失败记录_edentialmechanisn-scram-sha-1
文章浏览阅读470次。mongodb抽取数据到ES,使用ELK内部插件无法获取数据,只能试试monstache抽取mongodb数据,但是monstache需要mongodb replica set 模式才能采集数据。############monstache-compose文件。#replicas set 启动服务。# 默认备份节点不能读写,可以设置。# mydb指的是需要同步的数据库。#登录主mongodb初始化rs。#primary 创建用户。# ip地址注意要修改。# ip地址注意要修改。_monstache csdn
文章浏览阅读913次,点赞4次,收藏5次。storage:fork: trueadmin登录切换数据库注意: use 代表创建并使用,当库中没有数据时默认不显示这个库删除数据库查看表清单> show tables # 或者 > show collections表创建db.createCollection('集合名称', [options])table1字段类型描述capped布尔(可选)如果为 true,则创建固定集合。固定集合是指有着固定大小的集合,当达到最大值时,它会自动覆盖最早的文档。_mongodb5
文章浏览阅读862次。Centos7.9设置MongoDB开机自启(超全教程,一条龙)_mongodb centos开机启动脚本
文章浏览阅读1.3k次,点赞6次,收藏21次。NoSQL数据库使用场景以及架构介绍
文章浏览阅读856次,点赞21次,收藏20次。1.背景介绍1. 背景介绍NoSQL数据库是一种非关系型数据库,它的设计目标是为了解决传统关系型数据库(如MySQL、Oracle等)在处理大量不结构化数据方面的不足。NoSQL数据库可以处理大量数据,具有高性能、高可扩展性和高可用性。但是,与关系型数据库不同,NoSQL数据库没有固定的模式,数据结构也不一定是表格。在NoSQL数据库中,数据存储和查询都是基于键值对、列族、图形等不同的...
文章浏览阅读416次。NoSQL定义:非关系型、分布式、开放源码和具有横向扩展能力的下一代数据库。由c++编写的开源、高性能、无模式的基于分布式文件存储的文档型数据库特点:高性能、高可用性、高扩展性、丰富的查询支持、可替换已完场文档某个指定的数据字段应用场景:社交场景:使用mongodb存储用户信息游戏场景:用户信息,装备积分物流场景:订单信息,订单状态场景操作特点:数据量大;读写操作频繁;价值较低的数据,对事物性要求不高开源、c语言编写、默认端口号6379、key-value形式存在,存储非结构化数据。_nosql
文章浏览阅读1.5k次,点赞3次,收藏2次。Exception in thread "main" redis.clients.jedis.exceptions.JedisConnectionException: Failed to create socket. at redis.clients.jedis.DefaultJedisSocketFactory.createSocket(DefaultJedisSocketFactory.java:110) at redis.clients.jedis.Connection.connect(Conne_redis.clients.jedis.exceptions.jedisconnectionexception: failed to create so
文章浏览阅读6.5k次,点赞3次,收藏12次。readAnyDatabase(在所有数据库上都有读取数据的权限)、readWriteAnyDatabase(在所有数据库上都有读写数据的权限)、userAdminAnyDatabase(在所有数据库上都有管理user的权限)、dbAdminAnyDatabase(管理所有数据库的权限);:clusterAdmin(管理机器的最高权限)、clusterManager(管理和监控集群的权限)、clusterMonitor(监控集群的权限)、hostManager( 管理Server);_mongodb创建用户密码并授权
文章浏览阅读593次。Redis是一个基于内存的键值型NoSQL数据库,在实际生产中有着非常广泛的用处_搭建本地redis
文章浏览阅读919次。Key 的最佳实践[业务名]:[数据名]:[id]足够简短:不超过 44 字节不包含特殊字符Value 的最佳实践:合理的拆分数据,拒绝 BigKey选择合适数据结构Hash 结构的 entry 数量不要超过 1000(默认是 500,如果达到上限则底层会使用哈希表而不是 ZipList,内存占用较多)设置合理的超时时间批量处理的方案:原生的 M 操作Pipeline 批处理注意事项:批处理时不建议一次携带太多命令。Pipeline 的多个命令之间不具备原子性。_redis高级实战
文章浏览阅读1.2k次。MongoDB 递归查询_mongodb数据库 递归
文章浏览阅读1.2k次。通过实际代码例子介绍:如何通过MongoTemplate和MongoRepository操作数据库数据_springboot操作mongodb
文章浏览阅读687次,点赞7次,收藏2次。首先欢迎大家阅读此文档,本文档主要分为三个模块分别是:Redis的介绍及安装、RedisDesktopManager可视化工具的安装、主从(哨兵)模式的配置。_redis 主从配置工具
文章浏览阅读764次。天下武功,无坚不摧,唯快不破!我的名字叫 Redis,全称是 Remote Dictionary Server。有人说,组 CP,除了要了解她外,还要给机会让她了解你。那么,作为开发工程师的你,是否愿意认真阅读此心法抓住机会来了解我,运用到你的系统中提升性能。我遵守 BSD 协议,由意大利人 Salvatore Sanfilippo 使用 C 语言编写的一个基于内存实现的键值型非关系(NoSQL)..._redis 7.2 源码
文章浏览阅读2k次。MongoDB 的增删改查【1】_mongodb $inc