不能对包含聚合或子查询的表达式执行聚合函数_3.3.1 聚合 管道

383443b59ddf3fda8072d76a02f76e15.png

参考官方文档:

https://docs.mongodb.com/manual/aggregation/

聚合操作处理数据记录并返回计算结果。 聚合操作将来自多个文档的值组合在一起,并且可以对分组数据执行各种操作以返回单个结果。 MongoDB提供了三种执行聚合的方式:聚合管道,map-reduce函数和单目标聚合方法。

聚合管道

MongoDB的聚合框架以管道处理数据概念为蓝本。 文档进入多阶段管道,将文档转换为聚合结果。

最基本的管道阶段提供过滤器,其操作类似于查询和文档转换,可以修改输出文档的样式。

其他管道操作提供了按特定字段或字段对文档进行分组和排序的工具,以及用于聚合数组内容(包括文档数组)的工具。 此外,管道阶段可以使用运算符执行任务,例如计算平均值或连接字符串。

该管道使用MongoDB中的本机操作提供高效的数据聚合,是MongoDB中数据聚合的首选方法。

聚合管道可以在分片集合上运行。

聚合管道可以使用索引来改善其某些阶段的性能。 此外,聚合管道具有内部优化阶段。 有关详细信息,请参阅 Pipeline Operators and Indexes 和 Aggregation Pipeline Optimization 。

聚合管道对值类型和结果大小有一些限制。

管道

MongoDB聚合管道由阶段组成。 每个阶段在文档通过管道时转换文档。 管道阶段不需要为每个输入文档生成一个输出文档; 例如,某些阶段可以生成新文档或过滤掉文档。 管道阶段可以在管道中多次出现。

管道表达式

某些管道阶段将管道表达式作为操作数。 管道表达式指定要应用于输入文档的转换。 表达式具有文档结构,可以包含其他表达式。

管道表达式只能对管道中的当前文档进行操作,并且不能引用其他文档中的数据:表达式操作提供文档的内存中转换。

通常,表达式是无状态的,只有在聚合过程看到时才会进行求值,但有一个例外:累加器表达式。

随着文档在管道中演化,$group阶段中使用的累加器保持其状态(例如总数,最大值,最小值和相关数据)。

版本3.2中更改:$project阶段提供了一些累加器; 但是,在$project阶段使用时,累加器不会跨文档维护其状态。

聚合管道行为

在MongoDB中,aggregate命令对单个集合进行操作,从逻辑上将整个集合传递到聚合管道。 要尽可能优化操作,请使用以下策略以避免扫描整个集合。

管道操作和索引

$match和$sort管道运算符可以在管道开头出现时利用索引。

$geoNear管道运算符利用地理空间索引。 使用$geoNear时,$geoNear管道操作必须显示为聚合管道中的第一个阶段。

在3.2版中更改:从MongoDB 3.2开始,索引可以涵盖聚合管道。 在MongoDB 2.6和3.0中,索引无法覆盖聚合管道,因为即使管道使用索引,聚合仍然需要访问实际文档。

早期过滤

如果聚合操作仅需要集合中的一部分数据,请使用$match,$limit和$skip阶段来限制在管道开头输入的文档。 放置在管道的开头时,$match操作使用合适的索引来仅扫描集合中的匹配文档。

在管道开始处放置$match管道阶段后跟$sort阶段在逻辑上等同于具有排序的单个查询并且可以使用索引。 如果可能,将$match运算符放在管道的开头。

聚合管道优化器

聚合管道操作有一个优化器解析,来重塑管道以提高性能。

要查看优化程序如何转换特定聚合管道,请在db.collection.aggregate()方法中包含explain选项。

优化可能会在不同版本之间发生变化。

Projection 优化器

聚合管道可以确定它是否仅需要文档中的字段的子集来获得结果。 如果是这样,管道将只使用那些必需的字段,减少通过管道的数据量。

管道序列优化器

$project 或者$addFields + $match 序列优化器

对于包含投影阶段($project或$addFields)后跟$match阶段的聚合管道,MongoDB会将$match阶段中不需要在投影阶段计算的值的任何过滤器在投影之前移动到新的$match阶段。

如果聚合管道包含多个投影和/或$match阶段,MongoDB会为每个$match阶段执行此优化,在筛选器不依赖的所有投影阶段之前移动每个$match过滤器。

考虑以下阶段的管道:

{ $addFields: {

maxTime: { $max: "$times" },

minTime: { $min: "$times" }

} },

{ $project: {

_id: 1, name: 1, times: 1, maxTime: 1, minTime: 1,

avgTime: { $avg: ["$maxTime", "$minTime"] }

} },

{ $match: {

name: "Joe Schmoe",

maxTime: { $lt: 20 },

minTime: { $gt: 5 },

avgTime: { $gt: 7 }

} }

优化器将$ match阶段分成四个单独的过滤器,一个用于$ match查询文档中的每个键。 然后优化器在尽可能多的投影阶段之前移动过滤器,根据需要创建新的$ match阶段。 在此示例中,优化程序生成以下优化管道:

{ $match: { name: "Joe Schmoe" } },

{ $addFields: {

maxTime: { $max: "$times" },

minTime: { $min: "$times" }

} },

{ $match: { maxTime: { $lt: 20 }, minTime: { $gt: 5 } } },

{ $project: {

_id: 1, name: 1, times: 1, maxTime: 1, minTime: 1,

avgTime: { $avg: ["$maxTime", "$minTime"] }

} },

{ $match: { avgTime: { $gt: 7 } } }

$match过滤器{avgTime:{$gt:7}}取决于$project阶段来计算avgTime字段。 $project阶段是此管道中的最后一个投影阶段,因此无法移动avgTime上的$ match过滤器。

maxTime和minTime字段在$addFields阶段计算,但不依赖于$project阶段。 优化器为这些字段上的过滤器创建了一个新的$match阶段,并将其放在$ project阶段之前。

$match过滤器{name:“Joe Schmoe”}不使用$project或$addFields阶段中计算的任何值,因此在投影阶段之前将其移动到新的$match阶段。

优化后,过滤器{name:“Joe Schmoe”}位于管道开头的$ match阶段。 这具有额外的好处,即允许聚合在最初查询集合时在name字段上使用索引。

$sort+$match 序列优化器

当你有一个$sort后跟$match的序列时,$match会移动到$sort最小画对象排序对象数之前。 例如,如果管道包含以下阶段:

{ $sort: { age : -1 } },

{ $match: { status: 'A' } }

在优化阶段,优化程序将序列转换为以下内容:

{ $match: { status: 'A' } },

{ $sort: { age : -1 } }

$redact + $match 序列优化器

如果可能,当管道的$redact阶段紧跟$match阶段时,聚合有时可以在$redact阶段之前添加$match阶段的一部分。 如果添加的$match阶段位于管道的开头,则聚合可以使用索引以及查询集合来限制进入管道的文档数。

例如,如果管道包含以下阶段:

{ $redact: { $cond: { if: { $eq: [ "$level", 5 ] }, then: "$$PRUNE", else: "$$DESCEND" } } },

{ $match: { year: 2014, category: { $ne: "Z" } } }

优化器会在 $redact阶段前加上相同的 $match 阶段

{ $match: { year: 2014 } },

{ $redact: { $cond: { if: { $eq: [ "$level", 5 ] }, then: "$$PRUNE", else: "$$DESCEND" } } },

{ $match: { year: 2014, category: { $ne: "Z" } } }

$project + $skip 序列优化器

如果你有一个$ project后跟$ skip的序列,$skip会移动到$project之前。 例如,如果管道包含以下阶段

{ $sort: { age : -1 } },

{ $project: { status: 1, name: 1 } },

{ $skip: 5 }

在优化器解析时,优化器会转换为如下序列:

{ $sort: { age : -1 } },

{ $skip: 5 },

{ $project: { status: 1, name: 1 } }

$sort + $limit 合并

当$sort在$limit之前时,如果没有中间阶段修改文档数量(例如$unwind,$group),优化器可以将$limit合并到$sort中。 如果在改变$sort和$limit阶段之间有改变文档数量的管道阶段,MongoDB将不会将$limit合并到$sort中

例如,考虑下列阶段:

{ $sort : { age : -1 } },

{ $project : { age : 1, status : 1, name : 1 } },

{ $limit: 5 }

在优化器解析的时候,优化器合并以下序列:

{

"$sort" : {

"sortKey" : {

"age" : -1

},

"limit" : NumberLong(5)

}

},

{ "$project" : {

"age" : 1,

"status" : 1,

"name" : 1

}

}

$limit+$limit 联合

当$limit立即跟随另一个$limit时,这两个阶段可以合并为单个$limit,其中限制数是两个初始限制数中的较小者。 例如,管道包含以下序列:

{ $limit: 100 },

{ $limit: 10 }

第二个$limit阶段可以合并到第一个$limit阶段,结果是100和10中的最小的限制数:

{ $limit: 10 }

$skip+$skip合并

当 $skip后面立即接一个$skip时,这2个阶段可以合并成一个,数目为2个skip之和。例如,一个管道有下列序列:

{ $skip: 5 },

{ $skip: 2 }

然后第二个$skip阶段可以合并到第一个$skip阶段并形成单个$skip阶段,其中skip的数量7是两个初始限制5和2的总和。

{ $skip: 7 }

$match+$match 合并

当$match紧跟另一个$match时,这两个阶段可以合并成一个$match,将条件与$and组合。 例如,管道包含以下序列:

{ $match: { year: 2014 } },

{ $match: { status: "A" } }

可以合并如下;

{ $match: { $and: [ { "year" : 2014 }, { "status" : "A" } ] } }

$lookup+$unwind 合并

当$unwind紧跟在另一个$lookup之后,$unwind在$lookup的as字段上运行时,优化器可以将$unwind合并到$lookup阶段。 这避免了创建大型中间文档。

例如,管道包含下列序列:

{

$lookup: {

from: "otherCollection",

as: "resultingArray",

localField: "x",

foreignField: "y"

}

},

{ $unwind: "$resultingArray"}

合并后的结果如下:

{

$lookup: {

from: "otherCollection",

as: "resultingArray",

localField: "x",

foreignField: "y",

unwinding: { preserveNullAndEmptyArrays: false }

}

}

聚合管道限制

使用aggregate命令进行聚合操作具有以下限制。

结果大小 限制

在版本3.6中更改:MongoDB 3.6删除了aggregate命令的选项,以将其结果作为单个文档返回。

aggregate命令可以返回游标或将结果存储在集合中。 返回游标或将结果存储在集合中时,结果集中的每个文档都受BSON文档大小限制,目前为16兆字节; 如果任何单个文档超出BSON文档大小限制,该命令将产生错误。 该限制仅适用于退回的文件; 在管道处理期间,文档可能超过此大小。 db.collection.aggregate()方法默认返回游标。

内存限制

管道阶段的RAM限制为100兆字节。 如果某个阶段超过此限制,MongoDB将产生错误。 要允许处理大型数据集,请使用allowDiskUse选项启用聚合管道阶段以将数据写入临时文件。

$graphLookup阶段必须保持在100 M字节的内存限制内。 如果为aggregate()操作指定了allowDiskUse:true,则$graphLookup阶段将忽略该选项。 如果aggregate()操作中还有其他阶段,则allowDiskUse:true选项对这些其他阶段有效。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值