Mongodb(2)—聚合操作(1)原生命令

Aggregation简单来说,就是将文档统计。分析、分类的方法。

Aggregation接收指定collection的数据集,通过计算后返回result数据的过程。它中间会经历多个stages。整体而言就是一个pipeline(管道)。

image.png

图片来源

由上图可知,文档(数据)经过筛选、分组、统计等过程最终输出数据。

语法:

db.collection.aggregate(pipeline,options);

参数说明:

参数 类型 描述
pipeline array 一系列数据聚合操作。详见聚合管道操作符,在版本2.6后,该方法可以接收pipeline作为参数而非数组,但若不指定数组,则不能指定options参数
options document 可选,aggregate()传递给聚合命令的其他选项。2.6版本中新增功能:仅当将管道指定为数组时才可使用。
  • db.collection.aggregate()可以用多个构件创建一个管道,对文档进行一连串的处理。这些构件包括:筛选(match)、映射(project)、分组(group)、排序(sort)、限制(limit)、跳跃(skip)。

  • 每个阶段的管道限制为100M内存,如果一个节点管道超过这个极限,MongoDB会产生一个错误,为了处理大型数据集,可以设置options参数(allowDiskUse=true)将聚合管道节点的数据写入临时文件。这样就可以解决100M的内存限制。

  • db.collection.aggregate()输出结果只能保存在一个文档中,BSON Document大小限制为16M。可以通过返回指针解决。版本2.6后,返回一个指针,可以返回任何数据集大小。

官网描述

1. 聚合操作

原始数据.png

$count:统计数量

   db.stus.aggregate([
     {
         $match:{"scope":{$gte:60}}
     },{
         $count:"count"   //统计函数,计算数量
     }
     ],{
        allowDiskUse:true  //options(可选)即允许磁盘上缓存
     })
显示结果.png

注意$count统计数量,上述有两个操作。

  1. $match阶段,相当于where操作,将大于等于60的scope传递给下一个=阶段。
  2. $count阶段是返回聚合管道中剩余的文档计数,并将值分配给名为count的字段。

相当于sql:select count(*) as count from stus where scope>=60;

$group分组

按指定的表达式对文档进行分组,并将每个不同分组的文档输出到一个阶段。输出文档包含_id字段,文档以_id的值进行分组。

输出文档还可以包含计算字段,该字段保存由$group_id字段分组的一些accumulator表达式的值。$group不会输出具体的文档而只是统计信息。

语法:

{$group:{_id:,:{ : },...}}
  • _id字段是必填的:但是,_id:null可以用来为整个输出文档计算累计值。
  • 剩余字段是可选的,并使用运算符进行计算。
  • _id和表达式可以接受任何有效的表达式。
    db.stus.aggregate([
     {
        $group:{
            "_id":"$age",
            "sum":{"$sum":"$scope"},  //计算总和。
            "avg":{"$avg":"$scope"},  //计算平均值。
            "min":{"$min":"$scope"},  //获取集合中所有文档对应值得最小值。
            "max":{$max:"$scope"},    //获取集合中所有文档对应值得最大值。
            "first":{$first:"$scope"}, //返回每组第一个文档,如果有排序,按照排序,如果没有按照默认的存储的顺序的第一个文档。
            "last":{$last:"$scope"},   //返回每组最后一个文档,如果有排序,按照排序,如果没有按照默认的存储的顺序的最后个文档。
            "push":{$push:"$name"},  //将指定的表达式的值添加到一个数组中。
            "addToSet":{$addToSet:"$scope"} //将表达式的值添加到一个集合中(无重复值,无序)。
        }
     }
    ]);
输出结果.png

输出参数类型:

{
  "_id" : "14",
  "sum" : 126,
  "avg" : 63,
  "min" : 59,
  "max" : 67,
  "first" : 59,
  "last" : 67,
  "push" : ["数组4", "数组10"],
  "addToSet" : [59, 67]
}

注意:

  1. $group阶段的内存限制为100M。默认情况下,如果stage超过此限制,$group将产生错误。但是,要允许处理大型数据集,请将allowDiskUse选项设置为true。
  2. mongodb类型值不会进行隐式转换,即使值相同,但是不是相同的类型,也是不同的组。
  3. accumulator表达式只能处理number类型。

练习2:

插入数据:

    db.items.insert([
    { "_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") }
    ]);

1. 按date的月份、日期、年份对文档进行分组,并计算total priceaverage quantity,并汇总各文档的数量。

表达式.png

$group可以对多个字段进行分组。

最终执行结果如下图所示。

/* 1 */
{
  "_id" : {
    "month" : 3,
    "day" : 1,
    "year" : 2014
  },
  "totalPrice" : 40,
  "avg" : 1.5,
  "count" : 2
}

/* 2 */
{
  "_id" : {
    "month" : 4,
    "day" : 4,
    "year" : 2014
  },
  "totalPrice" : 200,
  "avg" : 15,
  "count" : 2
}

/* 3 */
{
  "_id" : {
    "month" : 3,
    "day" : 15,
    "year" : 2014
  },
  "totalPrice" : 50,
  "avg" : 10,
  "count" : 1
}

2. 那么group null是什么效果呢?

group null的效果.png

可以看出,实际上是对所有文档进行统计。

3. 如何查询distinct values的数据

使用group分组完成去重的功能。

image.png

4. 数据转换

1)将集合中的数据按price分组并转换成item的数组。

数据转换

price相同的item在一个数组中

2)使用系统变量$$ROOT按price对文档进行分组,生成的文档不得超过BSON文档大小限制。

    db.items.aggregate([{
        $group:{_id:"$price",items:{$push:"$$ROOT"}}
     }]);

执行结果:

/* 1 */
{
  "_id" : 5,
  "items" : [{
      "_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")
    }]
}

/* 2 */
{
  "_id" : 20,
  "items" : [{
      "_id" : 2,
      "item" : "jkl",
      "price" : 20,
      "quantity" : 1,
      "date" : ISODate("2014-03-01T09:00:00Z")
    }]
}

/* 3 */
{
  "_id" : 10,
  "items" : [{
      "_id" : 1,
      "item" : "abc",
      "price" : 10,
      "quantity" : 2,
      "date" : ISODate("2014-03-01T08:00:00Z")
    }, {
      "_id" : 5,
      "item" : "abc",
      "price" : 10,
      "quantity" : 10,
      "date" : ISODate("2014-04-04T21:23:13.331Z")
    }]
}

$match过滤操作

过滤文档,仅将符合指定条件的文档传递到下一个管道阶段。

$match接受一个指定查询条件的文档。

语法:

{ $match: {  } }

管道优化:
$match用于对文档进行筛选,之后可以得到文档子集上做聚合,$match可以使用除了地理空间之外的所有常规查询操作符。在实际应用中尽可能将$match放在管道的前面位置

  1. 可以快速将不需要的文档过滤掉,以减少管道的工作量;
  2. 如果在$project$group之前执行$match,查询可以使用索引。:

案例:

  1. 插入数据:
     db.views.insert([
     { "_id" : ObjectId("512bc95fe835e68f199c8686"), "author" : "dave", "score" : 80, "views" : 100 },
     { "_id" : ObjectId("512bc962e835e68f199c8687"), "author" : "dave", "score" : 85, "views" : 521 },
     { "_id" : ObjectId("55f5a192d4bede9ac365b257"), "author" : "ahn", "score" : 60, "views" : 1000 },
     { "_id" : ObjectId("55f5a192d4bede9ac365b258"), "author" : "li", "score" : 55, "views" : 5000 },
     { "_id" : ObjectId("55f5a1d3d4bede9ac365b259"), "author" : "annT", "score" : 60, "views" : 50 },
     { "_id" : ObjectId("55f5a1d3d4bede9ac365b25a"), "author" : "li", "score" : 94, "views" : 999 },
     { "_id" : ObjectId("55f5a1d3d4bede9ac365b25b"), "author" : "ty", "score" : 95, "views" : 1000 }
     ]);

1. 使用$match做简单的匹配查询

案例.png

2. 使用$match管道处理文档,然后将结果分组

统计数量.png

$match使用MongoDB的标准查询操作,放在group前相当于where,放在group后相当having。

unwind

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

语法:

{$unwind:}

注:要指定字段的路径,在字段名称前加上$符并使用引号

在3.2后增加的新语法

{
  $unwind:{
       path:,
       includeArrayIndex:, //可选。一个新字段的名称用于存放元素的数组索引,该名称不能以$开头。
       preserveNullAndEmptyArrays: #可选default :false,若为true,如果路径为空,缺少或为空数组,则$unwind输出文档
    }
}

如果为输出文档汇总不存在的字段指定路径,或者该字段为空数组,则$unwind默认会忽略输入文档,并且不会输出该文档的文档。

案例1:简单的$unwind命令

     db.testunwind.insert(
        { "_id" : 1, "item" : "ABC1", sizes: [ "S", "M", "L"] }
     );

使用$unwind为sizes数组中的每个元素输出一个文档。

image.png

每个文档与输入文档相同,除了sizes字段的值是元素sizes数组的值。

案例二:使用3.2新增的属性字段

     db.testunwind2.insert([
     { "_id" : 1, "item" : "ABC", "sizes": [ "S", "M", "L"] },
     { "_id" : 2, "item" : "EFG", "sizes" : [ ] },
     { "_id" : 3, "item" : "IJK", "sizes": "M" },
     { "_id" : 4, "item" : "LMN" },
     { "_id" : 5, "item" : "XYZ", "sizes" : null }
     ]);

1)使用includeArrayIndex参数

     db.testunwind2.aggregate([
        {$unwind:{path:"$sizes",includeArrayIndex:"arrayIndex"}}
     ]);

最终结果是将含有sizes数组的文档进行输出,且携带着数组元素下标。

{ "_id" : 1, "item" : "ABC", "sizes" : "S", "arrayIndex" : NumberLong(0) }
{ "_id" : 1, "item" : "ABC", "sizes" : "M", "arrayIndex" : NumberLong(1) }
{ "_id" : 1, "item" : "ABC", "sizes" : "L", "arrayIndex" : NumberLong(2) }
{ "_id" : 3, "item" : "IJK", "sizes" : "M", "arrayIndex" : null }

2)使用preserveNullAndEmptyArrays参数

image.png

$project映射操作

可以将其看做sql中的select操作。

$project可以在文档中选择想要的字段和不想要的字段。也可以通过管道表达式进行一些复杂的操作。如数学操作、日期操作、字符串操作、逻辑操作。

语法:

{ $project: {  } }

$project管道符的作用是选择字段(指定字段、添加字段、不显示字段,排除字段等),重命名字段,派生字段。

specification有以下形式:

  1. :<1 or true>是否包含该字段,field:1/0,表示选择/不选择field。
  2. _id:<0 or false>是否指定_id字段。
  3. :添加新字段或重置现有字段的值,在版本3.6中更改:Mongodb3.6添加变量REMOVE。如果表达式的计算结果为$$REMOVE,则该字段将排除在输出外。
  4. 投影或添加/重置嵌入文档的字段时,可以使用.符号,例如:
"contact.address.country": <1 or 0 or expression>
或
contact: { address: { country: <1 or 0 or expression> } }

案例一:

     db.testproject.insert(
      {
         "_id" : 1,
         title: "abc123",
         isbn: "0001122223334",
         author: { last: "zzz", first: "aaa" },
         copies: 5,
         lastModified: "2016-07-28"
       }
     )

1)$project默认输出_id字段。

image.png

2)_id字段默认包含在内,要从$project阶段的输出文档中排除_id字段。

排除_id字段.png

3)在$project阶段从输出中排除lastModified字段:

image.png

4)在嵌套文档中排除字段。

image.png
image.png

5)3.6版本中的新功能:从3.6版本开始,可以从聚合表达式中使用变量REMOVE来有条件地禁止一个字段。

继续插入数据

     db.testproject.insert([
         {
         "_id" : 2,
         title: "Baked Goods",
         isbn: "9999999999999",
         author: { last: "xyz", first: "abc", middle: "" },
         copies: 2,
         lastModified: "2017-07-21"
         },
        {
         "_id" : 3,
         title: "Ice Cream Cakes",
         isbn: "8888888888888",
         author: { last: "xyz", first: "abc", middle: "mmm" },
         copies: 5,
         lastModified: "2017-07-22"
         }
      ])

$project阶段使用REMOVE变量来排除author.middle字段,前提是它等于"":

      db.testproject.aggregate([
      {
        $project:{
            title:1,
            "author.first":1, //嵌套文档输出特别的内容
            "author.middle":{
                $cond:{
                    if:{$eq:["","$author.middle"]},
                    then:"$$REMOVE",
                    else:"$author.middle"
                }
            }
        }
      }
      ])

最终效果:

/* 1 */
{
  "_id" : 1,
  "title" : "abc123",
  "author" : {
    "first" : "aaa"
  }
}

/* 2 */
{
  "_id" : 2,
  "title" : "Baked Goods",
  "author" : {
    "first" : "abc"
  }
}

/* 3 */
{
  "_id" : 3,
  "title" : "Ice Cream Cakes",
  "author" : {
    "first" : "abc",
    "middle" : "mmm"
  }
}

6)只含有嵌套文档的指定字段

     db.testproject1.insert([
        { _id: 1, user: "1234", stop: { title: "book1", author: "xyz", page: 32 } },
        { _id: 2, user: "7890", stop: [ { title: "book2", author: "abc", page: 5 }, { title: "book3", author: "ijk", page: 100 } ] }
     ])
     db.testproject1.aggregate([
     {
        $project:{"stop.title":1}
     }
     ])

返回结果:

{ "_id" : 1, "stop" : { "title" : "book1" } }
{ "_id" : 2, "stop" : [ { "title" : "book2" }, { "title" : "book3" } ] }

对字段进行计算
插入数据:

     db.testproject2.insert([
     {
        "_id" : 1,
         title: "abc123",
         isbn: "0001122223334",
         author: { last: "zzz", first: "aaa" },
         copies: 5
     }
     ])
     db.testproject2.aggregate([
        {
            $project:{
                title:1,
                isbn:{
                    prefix:{$substr:["$isbn",0,3]},  //对字段进行处理(截取)
                    checkDigit: { $substr: [ "$isbn", 12, 1] }
                    },
                lastname:"author.last",
                copiesSold: "$copies"
            }
        }
     ])

返回结果:

{
  "_id" : 1,
  "title" : "abc123",
  "isbn" : {
    "prefix" : "000",
    "checkDigit" : "4"
  },
  "lastname" : "author.last",
  "copiesSold" : 5
}

投影出新的数组字段

db.testproject3.insert([
{ "_id" : ObjectId("55ad167f320c6be244eb3b95"), "x" : 1, "y" : 1 }
]);
db.testproject3.aggregate([
    {
        $project:{
            myArray:["$x","$y"]
        }
    }
])
最终返回字段.png

如果数组中包含不存在的字段,则会返回null

$sort:排序,1升,-1降,sort一般在group后,也就是说得到结果在排序,如果先排序在分组没有意义。

    db.pts.aggregate([
        {
        $group:{
            "_id":"$school_id",
            "count":{"$sum":1}
            }  
        },  //先分组
        {
         $sort:{"count":-1}  //对分组内容进行排序
        }
     ])

$limit用来限制Mongodb聚合管道返回的文档数,相当于mysql中的limit m,不能设置偏移量。

    db.pts.aggregate([
        {
            $limit:2  
        }
     ]);

$skip聚合管道中跳过指定数量的文档,并返回余下的文档。

    db.pts.aggregate([
        {
        $skip:2
        }
     ]);

skip(), limilt(), sort()三个放在一起执行的时候,执行的顺序是先 sort(), 然后是 skip(),最后是显示的 limit()。

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

    db.pts.aggregate([
        {
        $unwind:"$items"
        }
     ]);

$group将集合中的文档分组,可用于统计结果,其中_id固定写法。

    db.pts.aggregate([
        {
            $group:{
            "_id":"$school_id",  //以school_id的值分组
            "count":{"$sum":1}  //聚合函数,输出count(1)
            }
        }
     ]);

推荐阅读

https://www.cnblogs.com/duanxz/p/3600052.html

mongodb高级聚合查询

你可能感兴趣的:(Mongodb(2)—聚合操作(1)原生命令)