mongodb聚合查询
发布于 6 年前 作者 Andyliwr 4387 次浏览 来自 分享

前言

mongo如何根据每条记录的时间分组查询数据?比如查询用户每一天上传了哪些图片,最终返回结果是时间作为key值,value值是一个多张图片的数组。 简单的想的话可以先查询到用户上传的所有图片,然后前端再做数组便利,将这些图片分组。那再数据量很大的时候又该怎么处理呢? mongodb有提供一个aggregate方法,就是用来应对这种分组查询的。

数据准备

假设现在有一张名叫histories的表,它的数据结构如下,点击这里下载全部数据:

{
    "_id" : ObjectId("59ffb36d662314030cdad763"),
    "filename" : "1509929837131.png",
    "old_filename" : "mysql_system_path.PNG",
    "filesize" : "1509929837.126",
    "userid" : ObjectId("59f7d33009bcb137983415f6"),
    "tmp_url" : "D:\\PROJECT\\ldk-upload-img\\uploads\\upload_1053904e8f5135f8641505a9f5c5ef72.PNG",
    "remote_url" : "http://oyh0gj8ht.bkt.clouddn.com/1509929837131.png",
    "time" : ISODate("2017-11-05T00:57:17.228Z"),
    "__v" : 0
}

我们要做的就是查找出userid为“59f7d33009bcb137983415f6”的用户每一天上传了那些图片,也就是old_filenameremote_url这两个字段。

学习aggregate

先来学习聚合常用的操作:

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

然后浏览下聚合一些常用的表达式

  • $sum 计算总和 统计每个用户上传图片数量总数
> db.histories.aggregate([{$group : {_id : "$userid", upload_num : {$sum : 1}}}])
{ "_id" : ObjectId("59f8600b06445e4780bb0a69"), "upload_num" : 6 }
{ "_id" : ObjectId("59f7d33009bcb137983415f6"), "upload_num" : 2 }

统计每个用户上传图片的总大小

> db.histories.aggregate([{$group : {_id : "$userid", upload_num : {$sum : "$filesize"}}}])
{ "_id" : ObjectId("59f8600b06445e4780bb0a69"), "upload_num" : 10569636779.855999 }
{ "_id" : ObjectId("59f7d33009bcb137983415f6"), "upload_num" : 3019860521.908 }
  • $avg 计算平均值 统计每个用户上传图片平均大小,这个有些无聊>_<
> db.histories.aggregate([{$group : {_id : "$userid", upload_num : {$avg : "$filesize"}}}])
{ "_id" : ObjectId("59f8600b06445e4780bb0a69"), "upload_num" : 1509948111.4079998 }
{ "_id" : ObjectId("59f7d33009bcb137983415f6"), "upload_num" : 1509930260.954 }
  • $max$min 取得最大值和最小值 计算得到用户所有上传历史里文件大小最大和最小的数值
> db.histories.aggregate([{$group : {_id : "$userid", upload_max : {$max : "$filesize"}, upload_min : {$min : "$filesize"}}}])
{ "_id" : ObjectId("59f8600b06445e4780bb0a69"), "upload_max" : 1509951997.92, "upload_min" : 1509947349.678 }
{ "_id" : ObjectId("59f7d33009bcb137983415f6"), "upload_max" : 1509930684.782, "upload_min" : 1509929837.126 }
  • $push 很重要的一个计算表达式,比如我要统计用户每天内上传了那些图片
> db.histories.aggregate([
   {
     $project: {
       day: {
           $substr: [{ "$add": ["$time", 28800000] }, 0, 10]
       },
       "old_filename": 1,
       "remote_url": 1
     }
   },
   {
     $group: {
       _id: "$day",
       "upload_arr": { $push: { name: "$old_filename", link: "$remote_url" } }
     }
   }
 ])

$project就是定义会有那些字段参与到统计,中途可以创建全新的字段,或者对字段做格式化,比如为了拿到某一天的日期,我们需要将原本以UTC格式的存储的时间字段$time加上8*60*60*1000毫秒就是8天的时间,这样时间就变成了北京时间。然后使用$substr操作截取字段的前11位,比如"2017-11-06 15:28:00"就变成了"2017-11-06"。 另外由于在最终的统计结果中用到了文件名old_filename,以及远程地址remote_url,所以我们需要在$project中将它的值设置为1,表示它需要被保留。 在$group中就可以对保留下来的数据做分组处理,_id是定义根据什么来分组,这里显然是根据时间,也就是新生成的day字段,最后新建一个upload_arr字段,用来保存所有在这一天内的上传的图片的信息。这下你明白$push的作用了吧,就是在结果文档中插入值到一个数组中。 结果如下:

{
  "_id": "2017-11-06",
  "upload_arr": [
      {"name": "mysql_system_path.PNG","link": "http://oyh0gj8ht.bkt.clouddn.com/1509930684790.png"},
      {"name": "github.PNG","link": "http: //oyh0gj8ht.bkt.clouddn.com/1509947349681.png"},
      {"name": "mysql_start.png","link": "http://oyh0gj8ht.bkt.clouddn.com/1509947447592.png"},
      {"name": "mysql_system_path.PNG","link": "http: //oyh0gj8ht.bkt.clouddn.com/1509947455689.png"},
      {"name": "Capture.jpg","link": "http://oyh0gj8ht.bkt.clouddn.com/1509947466105.jpg"},
      {"name": "andyliwr-ftp.png","link": "http://oyh0gj8ht.bkt.clouddn.com/1509947471882.png"},
      {"name": "post20170912_01.png","link": "http://oyh0gj8ht.bkt.clouddn.com/1509947591007.png"},
      {"name": "check.png","link": "http: //oyh0gj8ht.bkt.clouddn.com/1509951997921.png"
    }
  ]
},
{
  "_id": "2017-11-05",
  "upload_arr": [{"name": "mysql_system_path.PNG","link": "http://oyh0gj8ht.bkt.clouddn.com/1509929837131.png"}]
}
  • 其他一些表达式:
  1. $addToSet, 在结果文档中插入值到一个数组中,但不创建副本。
  2. $first,根据资源文档的排序获取第一个文档数据。
  3. $last,根据资源文档的排序获取最后一个文档数据。

最后贴下我的最终代码:

.aggregate([
  {
    $match: {
        "userid": ObjectId("59f7d33009bcb137983415f6")
    }
  },
  {
    $project: {
      day: {
          $substr: [{ "$add": ["$time", 28800000] }, 0, 10]
      },
      "old_filename": 1,
      "remote_url": 1
    }
  },
  {
    $group: {
      _id: "$day",
      "upload_arr": { $push: { name: "$old_filename", link: "$remote_url" } }
    }
  }
])

详情可以参考我的博客地址 http://www.andylistudio.com/2017/11/06/mongodb_aggregate/

1 回复

很详细,统计数据用聚合很爽

回到顶部