资讯专栏INFORMATION COLUMN

【mongoDB高级篇①】聚集运算之group与aggregate

Taste / 494人阅读

摘要:语法按什么字段进行分组进行分组前变量初始化该处声明的变量可以在以下回调函数中作为的属性使用类似中的分组后的查询返回先迭代出分组然后再迭代分组中的文档即变量就代表当前分组中此刻迭代到的文档变量就代表当前分组。

group 语法
db.collection.group({
    key:{field:1},//按什么字段进行分组
    
    initial:{count:0},//进行分组前变量初始化,该处声明的变量可以在以下回调函数中作为result的属性使用
    
    cond:{},//类似mysql中的having,分组后的查询返回
    
    reduce: function ( curr, result ) { }, //The function takes two arguments: the current document and an aggregation result document for that group.先迭代出分组,然后再迭代分组中的文档,即curr变量就代表当前分组中此刻迭代到的文档,result变量就代表当前分组。
   
   keyf:function(doc){},//keyf和key二选一,传入的参数doc代表当前文档,如果分组的字段是经过运算后的字段用到,作用类似mysql中的group by left("2015-09-12 14:05:22",10);
   
   finalize:function(result) {}//该result也就是reduce的result,都是代表当前分组,这个函数是在走完当前分组结束后回调;
})

除了分组的key字段外,就只返回有result参数的回调函数中的操作的属性字段;

实例
# 表结构如下
{
  _id: ObjectId("5085a95c8fada716c89d0021"),
  ord_dt: ISODate("2012-07-01T04:00:00Z"),
  ship_dt: ISODate("2012-07-02T04:00:00Z"),
  item: { sku: "abc123",
          price: 1.99,
          uom: "pcs",
          qty: 25 }
}
#Example1
SELECT ord_dt, item_sku
FROM orders
WHERE ord_dt > "01/01/2012"
GROUP BY ord_dt, item_sku
↓↓↓↓
db.orders.group(
   {
     key: { ord_dt: 1, "item.sku": 1 },
     cond: { ord_dt: { $gt: new Date( "01/01/2012" ) } },
     reduce: function ( curr, result ) { },
     initial: { }
   }
)

#Example2
SELECT ord_dt, item_sku, SUM(item_qty) as total
FROM orders
WHERE ord_dt > "01/01/2012"
GROUP BY ord_dt, item_sku
↓↓↓↓
db.orders.group(
   {
     key: { ord_dt: 1, "item.sku": 1 },
     cond: { ord_dt: { $gt: new Date( "01/01/2012" ) } },
     reduce: function( curr, result ) {
                 result.total += curr.item.qty;
             },
     initial: { total : 0 }
   }
)

#Example3
db.orders.group(
   {
     keyf: function(doc) {
               return { day_of_week: doc.ord_dt.getDay() };
           },
     cond: { ord_dt: { $gt: new Date( "01/01/2012" ) } },
    reduce: function( curr, result ) {
                result.total += curr.item.qty;
                result.count++;
            },
    initial: { total : 0, count: 0 },
    finalize: function(result) {
                  var weekdays = [
                       "Sunday", "Monday", "Tuesday",
                       "Wednesday", "Thursday",
                       "Friday", "Saturday"
                      ];
                  result.day_of_week = weekdays[result.day_of_week];
                  result.avg = Math.round(result.total / result.count);
              }
   }
)

[
  { "day_of_week" : "Sunday", "total" : 70, "count" : 4, "avg" : 18 },
  { "day_of_week" : "Friday", "total" : 110, "count" : 6, "avg" : 18 },
  { "day_of_week" : "Tuesday", "total" : 70, "count" : 3, "avg" : 23 }
]
工作中用到的实例
#查询每个栏目最贵的商品价格, max()操作
{
  key:{cat_id:1},
  cond:{},
  reduce:function(curr , result) {
      if(curr.shop_price > result.max) {
          result.max = curr.shop_price;
      }
  },
  initial:{max:0}
}

#查询每个栏目下商品的平均价格
{
  key:{cat_id:1},
  cond:{},
  reduce:function(curr , result) {
      result.cnt += 1;
      result.sum += curr.shop_price;
  },
  initial:{sum:0,cnt:0},
  finalize:function(result) {
      result.avg = result.sum/result.cnt; //在每次分组完毕后进行运算
  }
}

group其实略微有点鸡肋,因为既然用到了mongodb,那复制集和分片是避无可免的,而group是不支持分片的运算

Aggregation

聚合管道是一个基于数据处理管道概念的框架。通过使用一个多阶段的管道,将一组文档转换为最终的聚合结果。

语法

参考手册: http://docs.mongoing.com/manual-zh/core/aggregation-pipeline.html

db.collection.aggregate(pipeline, options);

pipeline Array

# 与mysql中的字段对比说明
$project # 返回哪些字段,select,说它像select其实是不太准确的,因为aggregate是一个阶段性管道操作符,$project是取出哪些数据进入下一个阶段管道操作,真正的最终数据返回还是在group等操作中;

$match # 放在group前相当于where使用,放在group后面相当于having使用

$sort # 排序1升-1降 sort一般放在group后,也就是说得到结果后再排序,如果先排序再分组没什么意义;

$limit # 相当于limit m,不能设置偏移量

$skip # 跳过第几个文档

$unwind # 把文档中的数组元素打开,并形成多个文档,参考Example1

$group: { _id: , : {  :  }, ...  # 按什么字段分组,注意所有字段名前面都要加$,否则mongodb就为以为不加$的是普通常量,其中accumulator又包括以下几个操作符
# $sum,$avg,$first,$last,$max,$min,$push,$addToSet
#如果group by null就是 count(*)的效果

$geoNear # 取某一点的最近或最远,在LBS地理位置中有用

$out # 把结果写进新的集合中。注意1,不能写进一个分片集合中。注意2,不能写进
实例

Example1: unwind

> db.test.insert({ "_id" : 1, "item" : "ABC1", sizes: [ "S", "M", "L"] });
WriteResult({ "nInserted" : 1 })
> db.test.aggregate( [ { $unwind : "$sizes" } ] )
{ "_id" : 1, "item" : "ABC1", "sizes" : "S" }
{ "_id" : 1, "item" : "ABC1", "sizes" : "M" }
{ "_id" : 1, "item" : "ABC1", "sizes" : "L" }

db.test.insert({ "_id" : 2, "item" : "ABC1", sizes: [ "S", "M", "L",["XXL","XL"]] });
WriteResult({ "nInserted" : 1 })
> db.test.aggregate( [ { $unwind : "$sizes" } ] )
{ "_id" : 1, "item" : "ABC1", "sizes" : "S" }
{ "_id" : 1, "item" : "ABC1", "sizes" : "M" }
{ "_id" : 1, "item" : "ABC1", "sizes" : "L" }
{ "_id" : 2, "item" : "ABC1", "sizes" : "S" }
{ "_id" : 2, "item" : "ABC1", "sizes" : "M" }
{ "_id" : 2, "item" : "ABC1", "sizes" : "L" }
{ "_id" : 2, "item" : "ABC1", "sizes" : [ "XXL", "XL" ] } # 只能打散一维数组
Example2
#数据源
{ "_id" : 1, "item" : "abc", "price" : 10, "quantity" : 2, "date" : ISODate("2014-03-01T08:00:00Z") }
{ "_id" : 2, "item" : "jkl", "price" : 20, "quantity" : 1, "date" : ISODate("2014-03-01T09:00:00Z") }
{ "_id" : 3, "item" : "xyz", "price" : 5, "quantity" : 10, "date" : ISODate("2014-03-15T09:00:00Z") }
{ "_id" : 4, "item" : "xyz", "price" : 5, "quantity" : 20, "date" : ISODate("2014-04-04T11:21:39.736Z") }
{ "_id" : 5, "item" : "abc", "price" : 10, "quantity" : 10, "date" : ISODate("2014-04-04T21:23:13.331Z") }

# 综合示例
db.sales.aggregate([
  # 由上到下,分阶段的进行,注意该数组中的顺序是有意义的
  {
    $project:{item:1,price:1,quantity:1} # 1.取出什么元素待操作;
  },
  {
    $group:{ # 2. 对已取出的元素进行聚合运算;
      _id:"$item", # 根据什么来分组
      quantityCount:{$sum:"$quantity"},
      priceTotal:{$sum:"$price"}
    }
  },
  {
    $sort:{
      quantityCount:1 #3.升序
    }
  },

  # 4.基于上面的结果,取倒数第二名
  {
    $skip: 2
  },
  {
    $limit:1
  },

  # 5.然后把结果写到result集合中
  {
    $out:"result"
  }
])

#表达式$month,$dayOfMonth,$year,$sum,$avg
db.sales.aggregate(
   [
      {
        $group : {
           _id : { month: { $month: "$date" }, day: { $dayOfMonth: "$date" }, year: { $year: "$date" } }, #按月日年分组
           totalPrice: { $sum: { $multiply: [ "$price", "$quantity" ] } },
           averageQuantity: { $avg: "$quantity" },
           count: { $sum: 1 }
        }
      }
   ]
)

#结果
{ "_id" : { "month" : 3, "day" : 15, "year" : 2014 }, "totalPrice" : 50, "averageQuantity" : 10, "count" : 1 }
{ "_id" : { "month" : 4, "day" : 4, "year" : 2014 }, "totalPrice" : 200, "averageQuantity" : 15, "count" : 2 }
{ "_id" : { "month" : 3, "day" : 1, "year" : 2014 }, "totalPrice" : 40, "averageQuantity" : 1.5, "count" : 2 }

#
#
# 表达式$push
db.sales.aggregate(
   [
     {
       $group:
         {
           _id: { day: { $dayOfYear: "$date"}, year: { $year: "$date" } },
           itemsSold: { $push:  { item: "$item", quantity: "$quantity" } }
         }
     }
   ]
)

# result
{
   "_id" : { "day" : 46, "year" : 2014 },
   "itemsSold" : [
      { "item" : "abc", "quantity" : 10 },
      { "item" : "xyz", "quantity" : 10 },
      { "item" : "xyz", "quantity" : 5 },
      { "item" : "xyz", "quantity" : 10 }
   ]
}
{
   "_id" : { "day" : 34, "year" : 2014 },
   "itemsSold" : [
      { "item" : "jkl", "quantity" : 1 },
      { "item" : "xyz", "quantity" : 5 }
   ]
}
{
   "_id" : { "day" : 1, "year" : 2014 },
   "itemsSold" : [ { "item" : "abc", "quantity" : 2 } ]
}

#
#
# 表达式$addToSet
db.sales.aggregate(
   [
     {
       $group:
         {
           _id: { day: { $dayOfYear: "$date"}, year: { $year: "$date" } },
           itemsSold: { $addToSet: "$item" }
         }
     }
   ]
)

#result
{ "_id" : { "day" : 46, "year" : 2014 }, "itemsSold" : [ "xyz", "abc" ] }
{ "_id" : { "day" : 34, "year" : 2014 }, "itemsSold" : [ "xyz", "jkl" ] }
{ "_id" : { "day" : 1, "year" : 2014 }, "itemsSold" : [ "abc" ] }

#
#
# 表达式 $first
db.sales.aggregate(
   [
     { $sort: { item: 1, date: 1 } },
     {
       $group:
         {
           _id: "$item",
           firstSalesDate: { $first: "$date" }
         }
     }
   ]
)

# result
{ "_id" : "xyz", "firstSalesDate" : ISODate("2014-02-03T09:05:00Z") }
{ "_id" : "jkl", "firstSalesDate" : ISODate("2014-02-03T09:00:00Z") }
{ "_id" : "abc", "firstSalesDate" : ISODate("2014-01-01T08:00:00Z") }

Example3

db.sales.aggregate(
   [
      {
        $group : {
           _id : null, # 如果为null,就统计出全部
           totalPrice: { $sum: { $multiply: [ "$price", "$quantity" ] } },
           averageQuantity: { $avg: "$quantity" },
           count: { $sum: 1 }
        }
      }
   ]
)

Example4

# 数据源
{ "_id" : 8751, "title" : "The Banquet", "author" : "Dante", "copies" : 2 }
{ "_id" : 8752, "title" : "Divine Comedy", "author" : "Dante", "copies" : 1 }
{ "_id" : 8645, "title" : "Eclogues", "author" : "Dante", "copies" : 2 }
{ "_id" : 7000, "title" : "The Odyssey", "author" : "Homer", "copies" : 10 }
{ "_id" : 7020, "title" : "Iliad", "author" : "Homer", "copies" : 10 }

# 根据作者分组,获得其著多少书籍
db.books.aggregate(
   [
     { $group : { _id : "$author", books: { $push: "$title" } } }
   ]
)

# result
{ "_id" : "Homer", "books" : [ "The Odyssey", "Iliad" ] }
{ "_id" : "Dante", "books" : [ "The Banquet", "Divine Comedy", "Eclogues" ] }

# 通过系统变量$$ROOT(当前的根文档)来分组
db.books.aggregate(
   [
     { $group : { _id : "$author", books: { $push: "$$ROOT" } } }
   ]
)

# result
{
  "_id" : "Homer",
  "books" :
     [
       { "_id" : 7000, "title" : "The Odyssey", "author" : "Homer", "copies" : 10 },
       { "_id" : 7020, "title" : "Iliad", "author" : "Homer", "copies" : 10 }
     ]
}
{
  "_id" : "Dante",
  "books" :
     [
       { "_id" : 8751, "title" : "The Banquet", "author" : "Dante", "copies" : 2 },
       { "_id" : 8752, "title" : "Divine Comedy", "author" : "Dante", "copies" : 1 },
       { "_id" : 8645, "title" : "Eclogues", "author" : "Dante", "copies" : 2 }
     ]
}

邮政编码数据集的聚合实例: http://docs.mongoing.com/manual-zh/tutorial/aggregation-zip-code-data-set.html

对用户爱好数据做聚合实例:
http://docs.mongoing.com/manual-zh/tutorial/aggregation-with-user-preference-data.html

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/18803.html

相关文章

  • mongoDB高级②】大数据聚集运算mapReduce(映射化简)

    摘要:简述从字面上来理解就是两个过程映射以及化简。在映射化简的过程都是每台服务器自己的在运算,大量的服务器同时来进行运算工作,这就是大数据基本理念。映射操作输出了键值对结果。在中,所有的映射化简函数都是使用编写,并且运行在进程中。 简述 mapReduce从字面上来理解就是两个过程:map映射以及reduce化简。是一种比较先进的大数据处理方法,其难度不高,从性能上来说属于比较暴力的(通过N...

    madthumb 评论0 收藏0
  • MongoDB 高级查询

    摘要:格式为查询条件显示与否的选项如排序可以按指定的某些字段排序,字段标记为的为升序,标记为的为降序。查询条件消除重复使用函数。格式为集合名指定字段查询条件如聚合管道是特有的一种管道型聚合查询方式。 参考官方文档(图文并茂非常好看):Getting Started - MongoDB Documentation MongoDB的查询功能非常强大,同时有些地方也会有点复杂。所以需要下点功夫学习...

    Lin_YT 评论0 收藏0
  • mongoDB查询进阶】聚合管道(二) -- 阶段操作符

    摘要:当在中使用时,累加器是针对每个分组使用的当在中使用时,累加器则是针对每个字面量起作用,具体用法下一篇文章阐述。另外再加以配合表达式操作符组成的表达式或者在或中使用累加器能查询统计的内容会更加的多样化。 上篇最后说到管道操作符,本篇文章将详细说一下管道操作符。 mongoDB查询进阶--聚合管道(一)回顾 什么是管道操作符(Aggregation Pipeline Operators) ...

    brianway 评论0 收藏0
  • mongoDB查询进阶】聚合管道(二) -- 阶段操作符

    摘要:当在中使用时,累加器是针对每个分组使用的当在中使用时,累加器则是针对每个字面量起作用,具体用法下一篇文章阐述。另外再加以配合表达式操作符组成的表达式或者在或中使用累加器能查询统计的内容会更加的多样化。 上篇最后说到管道操作符,本篇文章将详细说一下管道操作符。 mongoDB查询进阶--聚合管道(一)回顾 什么是管道操作符(Aggregation Pipeline Operators) ...

    flybywind 评论0 收藏0

发表评论

0条评论

Taste

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<