Loading

5.mongodb的group与sum操作

一、group与sum的概念

1.知识储备:聚合与管道

1.1 MongoDB 聚合:

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

有点类似 SQL 语句中的 count(*)。

介绍一下聚合的中的一些表达式方法:

表达式描述实例
$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 获取集合中所有文档对应值得最小值。 db.mycol.aggregate([{$group : {_id : "$by_user", num_tutorial : {$min : "$likes"}}}])
$max 获取集合中所有文档对应值得最大值。 db.mycol.aggregate([{$group : {_id : "$by_user", num_tutorial : {$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 根据资源文档的排序获取第一个文档数据。 db.mycol.aggregate([{$group : {_id : "$by_user", first_url : {$first : "$url"}}}])
$last 根据资源文档的排序获取最后一个文档数据 db.mycol.aggregate([{$group : {_id : "$by_user", last_url : {$last : "$url"}}}])

1.2 MongoDB 管道:

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

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

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

这里我们介绍一下聚合框架中常用的几个操作:

  • $project:修改输入文档的结构。可以用来重命名、增加或删除域,也可以用于创建计算结果以及嵌套文档。
  • $match:用于过滤数据,只输出符合条件的文档。$match使用MongoDB的标准查询操作。
  • $limit:用来限制MongoDB聚合管道返回的文档数。
  • $skip:在聚合管道中跳过指定数量的文档,并返回余下的文档。
  • $unwind:将文档中的某一个数组类型字段拆分成多条,每条包含数组中的一个值。
  • $group:将集合中的文档分组,可用于统计结果。
  • $sort:将输入文档排序后输出。
  • $geoNear:输出接近某一地理位置的有序文档。

1.3 聚合和管道的合作:

例子:这段将会把文章的分数在70到90之间的所有文章,然后将符合条件的记录送到下一阶段$group管道操作符进行处理。(所以理论上你可以定义一个聚合,在聚合下使用无数个管道)

group将会使用id进行分组,然后sum来计算每一个相同_id的总数并赋值给count。最后打印count的值就是所有符合条件的文章的数目了。

db.articles.aggregate( [
                        { $match : { score : { $gt : 70, $lte : 90 } } },
                        { $group: { _id: null, count: { $sum: 1 } } }
                       ] );

此时看到此处,有的人可能会有疑问:

1.group使用id进行分组,那么id怎么为null;

2.sum进行计算,为什么给sum后面赋值个1;

别急,往下看:

2.正文:

group上面已解释是一种管道,将符合条件的进行分组,sum是聚合的计算方法。

sum可以在下面的管道或者方法中使用:

 

 sum后面通常跟得是一个field(翻译为领域,我把它理解为变量领域,可能不太恰当,但是我找不到其它词去形容它):

1.如果这个变量领域是数值类型的,那么它会根据变量的数值进行计算;

2.如果这个变量领域有数值也有非数值类型的,它会根据那个数值类型的进行计算;

3.如果这个变量领域(不管是不是数值类型),只要他在表中没出现,那么它会返回0;

4.如果这个变量领域是非数值类型的,那么它会返回0;

 看了sum后面的field的介绍,有的人可能马上反应过来,但是,有的人还可能一脸闷逼,那么我继续解释:

1.由于在group中使用_id进行分组,那么mogodb的group管道就会根据_id的具体值进行分组,但是当我们传入_id为:null时,mongodb的group此时就不会根据_id的具体值进行分组,它会把所有符合条件的记录看成一个整体,此时group取出的将是整个符合条件的所有记录。

2.由于sum后面跟的是1的值,那么sum在遍历每一个group组合的成员时,或者说sum在遍历变量领域的每一个成员的时候,由于这个变量领域里的值都是1,它每遍历到一个变量(表里的每条符合条件的记录)都会进行加1操作,此时由于group本身只有一个,就是一整个表中符合条件的所有记录,那么sum遍历整个表的每一条符合条件的记录时就会进行加1操作,随后count保存的就是所有符合条件的值。

 

所以通常情况下,我可以使用group+sum的组合写法,将数据库每条记录进行分类:

我有这样一个数据库:其中除了flag是布尔类型,agree是number类型,其它的都是字符串类型

 

 

 现在我想做的是,如何计算每一条记录中的agree使他们相加起来,再返回给我:

例子:下面这段代码的执行过程:

1.使用group将每一个_id进行分组。

2.计算每一个agree的总和,由于我的_id是不重复的,所以sum的作用其实没有体现出来,不过当你的表里有多个记录是同一个id值,那么sum将会把每一个相同_id下的agree值进行计算,然后打印。

3.把计算后的每一个_id的agree总和作为commentAgreesCount的值。

4.打印输出。

db.article_comments.aggregate(      [{ $group :        {  _id:"$_id",  commentAgreesCount: { $sum: "$agree" }} }]   )
运行结果: {
"_id" : ObjectId("606a70ac298cbe2978047810"), "commentAgreesCount" : 1 } { "_id" : ObjectId("606a60ba3878e73b78a9f5f0"), "commentAgreesCount" : 1 } { "_id" : ObjectId("606a7694298cbe2978047814"), "commentAgreesCount" : 0 } { "_id" : ObjectId("606a5ce13878e73b78a9f5ec"), "commentAgreesCount" : 1 }

但是我的本意并不是想获得每一个分组后的结果,我是要获取所有的记录的agree总和。

所以解决方法:

1.把id定义为null,mongodb内部就会不区分_id,即把所有的_id进行计算,然后输出结果

db.article_comments.aggregate(      [{ $group :        {  _id:null, commentAgreesCount: { $sum: "$agree" }} }]   )
运行结果: {
"_id" : null, "commentAgreesCount" : 3 }

但是sum方法只能计算数值类型的,如果是字符串类型的,它就会默认把值设置为0。这个我上面也有贴官网的图,也有进行相应的解释。

例子:

db.article_comments.aggregate(
     [{$group : 
      {
       _id:null,
       commentAgreesCount: { $sum: "$topic_id" }
    
        }
     }]
  )

结果:

{ "_id" : null, "commentAgreesCount" : 0 }

 

3.后话:

如果你要查询的是非数值类型的总和,打比方:我的agree设置成了String类型,但是我想获得每一条记录中agree加起来的总和。

又或者是我有一个数据库,里面保存着这个月卖出去的所有商品,所有商品的id是字符类型的,我想统计所有商品的卖出情况,比如id:1的所有商品,因为id:1的商品一个月内可能卖出很多次。id:2的所有商品,因为id:2的商品一个月内也可能卖出很多次 ……以此类推,然后我想计算所有id相同的商品卖出去的情况,并使用sum进行计算所有相同id商品的价格*件数=每件商品的营业额

此时再假设你的商品价格也是字符串类型的,那么此时就无法用上面那个sum方法计算了,此时解决方法也有,就是根据数据库查询到的所有记录,再进行foreach循环遍历一下所有商品的价格字符,再把他们转换为数值类型的进行计算。

所以数据库的方法并不是万能的,有时候你无法根据数据库进行查询,获取想要的数据类型时,那么只能将数据库获得的冗余数据,使用其他语言再进行数据处理。

 

posted @ 2021-04-05 13:47  兜里还剩五块出头  阅读(3720)  评论(0编辑  收藏  举报