mongodb聚合操作

63 阅读4分钟

介绍

聚合操作组值来自多个文档,可以对分组数据执行各种操作以返回单个结果。

聚合操作包含三类:单一作用聚合、聚合管道、MapReduce。

  • 单一作用聚合:从单个集合聚合文档,做一些简单的操作。
  • 聚合管道:数据聚合的框架,模型基于数据处理流水线的概念。文档进入多级管道,将文档转换为聚合结果。
  • MapReduce:处理每个文档并向每个输入文档发射一个或多个对象的map阶段,以及reduce组合map操作的输出阶段。

单一作用聚合

db.collection.estimatedDocumentCount()返回集合或视图中所有文档的计数,忽略查询条件
db.collection.count()返回与find()集合或视图的查询匹配的文档计数 。等同于 db.collection.find(query).count()构造
db.collection.distinct()在单个集合或视图中查找指定字段的不同值,并在数组中返回结果。

image.png

注意:在分片群集上,如果存在孤立文档或正在进行块迁移,则db.collection.count()没有查询谓词可能导致计数不准确。要避免这些情况,请在分片群集上使用 db.collection.aggregate()方法。

聚合管道

MongoDB 聚合框架(Aggregation Framework)是一个计算框架,它可以:

  1. 作用在一个或几个集合上;
  2. 对集合中的数据进行的一系列运算;
  3. 将这些数据转化为期望的形式; 从效果而言,聚合框架相当于 SQL 查询中的GROUP BY、 LEFT OUTER JOIN 、 AS等。

管道(Pipeline)和阶段(Stage)

整个聚合运算过程称为管道(Pipeline),它是由多个阶段(Stage)组成的, 管道接受一系列文档(原始数据)后,由每个阶段对这些文档进行一系列运算,再将结果文档输出给下一个阶段;

image.png

聚合管道操作语法

pipeline = [$stage1, $stage2, ...$stageN]; 
db.collection.aggregate(pipeline, {options})

# pipelines 一组数据聚合阶段。除$out、$Merge和$geonear阶段之外,每个阶段都可以在管道中出现多次。
# options 可选,聚合操作的其他参数。包含:查询计划、是否使用临时文件、 游标、最大操作时间、读写策略、强制索引等等

常用的管道聚合阶段

聚合管道包含非常丰富的聚合阶段,下面是最常用的聚合阶段

---------------- | ---- | --------------- | | 阶段 | 描述 | SQL等价运算符 | | match筛选条件WHEREmatch | 筛选条件 | WHERE | | project | 投影 | AS | | lookup左外连接LEFTOUTERJOINlookup | 左外连接 | LEFT OUTER JOIN | | sort | 排序 | ORDER BY | | group分组GROUPBYgroup | 分组 | GROUP BY | | skip/limit分页limit | 分页 | | | unwind | 展开数组 | | | graphLookup图搜索graphLookup | 图搜索 | | | facet/$bucket | 分面搜索|

聚合表达式

获取字段信息
$<field>  : 用 $ 指示字段路径
$<field>.<sub field>  : 使用 $  和 .  来指示内嵌文档的路径

常量表达式
$literal :<value> : 指示常量 <value>

系统变量表达式
$$<variable>  使用 $$ 指示系统变量
$$CURRENT  指示管道中当前操作的文档

阶段使用

数据准备

var tags = ["nosql","mongodb","document","developer","popular"];
var types = ["technology","sociality","travel","novel","literature"];
var books=[];
for(var i=0;i<50;i++){
    var typeIdx = Math.floor(Math.random()*types.length);
    var tagIdx = Math.floor(Math.random()*tags.length);
    var tagIdx2 = Math.floor(Math.random()*tags.length);
    var favCount = Math.floor(Math.random()*100);
    var username = "xx00"+Math.floor(Math.random()*10);
    var age = 20 + Math.floor(Math.random()*15);
    var book = {
        title: "book-"+i,
        type: types[typeIdx],
        tag: [tags[tagIdx],tags[tagIdx2]],
        favCount: favCount,
        author: {name:username,age:age}
    };
    books.push(book)
}
db.books.insertMany(books);

$project 将原始字段投影成指定名称,也可以灵活控制输出文档的格式,剔除不需要的字段

db.books.aggregate([{$project:{name:"$title",_id:0,type:1,author:1}}])

# 嵌套文档中排除字段
db.books.aggregate([
    {$project:{name:"$title",_id:0,type:1,"author.name":1}}
])

image.png

**matchmatch** `match`可以使用除了地理空间之外的所有常规查询操作符,在实际应用中尽可能将$match放在管道的前面位置。这样有两个好处:

一、是可以快速将不需要的文档过滤掉,以减少管道的工作量;

二、是如果在投射和分组之前执行$match,查询可以使用索引。

db.books.aggregate([
    {$match:{type:"technology"}},
    {$project:{name:"$title",_id:0,type:1,author:{name:1}}}
])

$count 计数并返回与查询匹配的结果数

db.books.aggregate([
    {$match:{type:"technology"}},
    {$count: "type_count"}
])

image.png

**group按指定的表达式对文档进行分组,并将每个不同分组的文档输出到下一个阶段,输出文档包含一id字段,该字段按键包含不同的组。输出文档还可以包含计算字段,该字段保存由group** 按指定的表达式对文档进行分组,并将每个不同分组的文档输出到下一个阶段,输出文档包含一个_id字段,该字段按键包含不同的组。输出文档还可以包含计算字段,该字段保存由group的_id字段分组的一些accumulator表达式的值。 $group不会输出具体的文档而只是统计信息。

accumulator操作符

------------ | ------------------------------------------- | --------- | |     名称 | 描述 | 类比sql | | avg计算均值   avgavg | 计算均值      | avg | | first | 返回每组第一个文档,如果有排序,按照排序,如果没有按照默认的存储的顺序的第一个文档。 | limit 0,1 | | last返回每组最后一个文档,如果有排序,按照排序,如果没有按照默认的存储的顺序的最后个文档。last | 返回每组最后一个文档,如果有排序,按照排序,如果没有按照默认的存储的顺序的最后个文档。 | - | | max | 根据分组,获取集合中所有文档对应值得最大值。 | max | | min根据分组,获取集合中所有文档对应值得最小值。minmin | 根据分组,获取集合中所有文档对应值得最小值。 | min | | push | 将指定的表达式的值添加到一个数组中。 | - | | addToSet 将表达式的值添加到一个集合中(无重复值,无序)。addToSet  | 将表达式的值添加到一个集合中(无重复值,无序)。 | - | | sum | 计算总和 | sum | | stdDevPop返回输入值的总体标准偏差(populationstandarddeviationstdDevPop | 返回输入值的总体标准偏差(population standard deviation) | - | | stdDevSamp | 返回输入值的样本标准偏差(the sample standard deviation) | -|

group阶段的内存限制为100M。默认情况下,如果stage超过此限制,group阶段的内存限制为100M。默认情况下,如果stage超过此限制,group将产生错误。但是,要允许处理大型数据集,请将allowDiskUse选项设置为true以启用$group操作以写入临时文件

# 统计book的数量,收藏数,收藏平均数
db.books.aggregate([
    {$group:{_id:null,count:{$sum:1},pop:{$sum:"$favCount"},avg:{$avg:"$favCount"}}}
])

# 统计每个作者的所有type
db.books.aggregate([
    {$group:{_id:"$author.name",types:{$addToSet:"$type"}}}
])

image.png

$unwind

可以将文档中的数组字段拆分为多个单独的文档

{
  $unwind:
    {
     #要指定字段路径,在字段名称前加上$符并用引号括起来。
      path: <field path>,
      #可选,指定字段存放元素的数组索引下标,该名称不能以$开头。
      includeArrayIndex: <string>,  
      #可选,default :false,若为true,如果路径为空,缺少或为空数组,则$unwind输出文档
      preserveNullAndEmptyArrays: <boolean> 
     } 
 }
 
 
 # 将tag拆分为多个文档
 db.books.aggregate([
    {$match:{"author.name":"xx006"}},
    {$unwind:"$tag"}
])

# 显示下标
db.books.aggregate([
    {$match:{"author.name":"fox"}},
    {$unwind:{path:"$tag", includeArrayIndex: "arrayIndex"}}
])

# 根据tag拆分文档,tag为空的也返回结果中
db.books.aggregate([
    {$match:{"author.name":"fox"}},
    {$unwind:{path:"$tag", preserveNullAndEmptyArrays: true}}
])

image.png

$limit 限制传递到管道中下一阶段的文档数

仅返回管道传递给它的前5个文档
db.books.aggregate([
    {$limit : 5 }
])

$skip

跳过进入stage的指定数量的文档,并将其余文档传递到管道中的下一个阶段

# 跳过管道传递给它的前5个文档
db.books.aggregate([
    {$skip : 5 }
])

$sort 对所有输入文档进行排序,并按排序顺序将它们返回到管道。

# 按照收藏数降序,名字升序排列
db.books.aggregate([
    {$sort : {favCount:-1,title:1}}
])

$lookup

Mongodb 3.2版本新增,主要用来实现多表关联查询, 相当关系型数据库中多表关联查询。每个输入待处理的文档,经过$lookup 阶段的处理,输出的新文档中会包含一个新生成的数组(可根据需要命名新key )。数组列存放的数据是来自被Join集合的适配文档,如果没有,集合为空(即 为[])

db.collection.aggregate([{
      $lookup: {
             from"<collection to join>",
             localField"<field from the input documents>",
             foreignField"<field from the documents of the from collection>",
             as"<output array field>"
           }
  })
from同一个数据库下等待被Join的集合。
localField源集合中的match值,如果输入的集合中,某文档没有 localField这个Key(Field),在处理的过程中,会默认为此文档含有 localField:null的键值对。
foreignField待Join的集合的match值,如果待Join的集合中,文档没有foreignField值,在处理的过程中,会默认为此文档含有 foreignField:null的键值对。
as为输出文档的新增值命名。如果输入的集合中已存在该值,则会覆盖掉

开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第5天,点击查看活动详情