Consulta agregada de MongoDB
¿Qué es una consulta agregada?
Las operaciones de agregación se utilizan principalmente para procesar datos y devolver resultados de cálculos. Las operaciones agregadas combinan valores de varios documentos, agrupados por una condición, y luego realizan una serie de operaciones (como suma, promedio, máximo, mínimo) para devolver un único resultado.
Consulta agregada en MongoDB
La agregación es el lenguaje de consulta de alto nivel de MongoDB, que nos permite generar nueva información de documentos que no existe en un solo documento transformando y combinando datos de múltiples documentos. Agregado en MongoDB se utiliza principalmente para procesar datos (como promedio estadístico de grupo, suma, valor máximo, etc.) y devolver los resultados de datos calculados, que es algo similar a contar (*) y agrupar por en declaraciones SQL.
En MongoDB, hay dos formas de calcular agregados: Pipeline y MapReduce. La velocidad de consulta de canalización es más rápida que MapReduce, pero el poder de MapReduce radica en su capacidad para ejecutar una lógica de agregación compleja en paralelo en varios servidores. MongoDB no permite que una sola operación agregada de un Pipeline consuma demasiada memoria del sistema.
Método de tubería agregada
El marco de agregación de MongoDB es para ingresar documentos en la canalización de procesamiento, completar las operaciones en los documentos en la canalización y finalmente convertir los documentos en resultados de agregación.La canalización de agregación de MongoDB procesa los documentos de MongoDB en una canalización y pasa los resultados a la siguiente canalización para su procesamiento. Las operaciones de tubería son repetibles.
La etapa de canalización más básica proporciona filtros, que funcionan como consultas y transformaciones de documentos, y pueden modificar la forma de los documentos de salida. Otras operaciones de canalización proporcionan herramientas para agrupar y clasificar documentos por campos específicos y para agregar el contenido de matrices, incluidas matrices de documentos.
Además, los operadores se pueden usar en la etapa de canalización para realizar tareas como calcular promedios o concatenar cadenas. Las canalizaciones de agregación pueden operar en colecciones fragmentadas.
Proceso de agregación
db.collection.aggregate() es una tubería de agregación basada en el procesamiento de datos. Cada documento pasa a través de una tubería que consta de múltiples etapas. La tubería de cada etapa se puede agrupar, filtrar y otras funciones, y luego pasar a través de una serie de procesos y emitir el resultado correspondiente.
El flujo del método de canalización de agregación se muestra en la siguiente figura
La operación de agregación en la figura anterior es equivalente a la siguiente declaración en MySQL:
select cust_id as _id, sum(amount) as total from orders where status like "%A%" group by cust_id;
Proceso detallado
db.collection.aggregate()
可以用多个构件创建一个管道,对于一连串的文档进行处理。这些构件包括:筛选操作的match
、映射操作的project
、分组操作的group
、排序操作的sort
、限制操作的limit
、和跳过操作的skip
。db.collection.aggregate()
使用了MongoDB内置的原生操作,聚合效率非常高,支持类似于SQL Group By操作的功能,而不再需要用户编写自定义的JavaScript例程。- 每个阶段管道限制为100MB的内存。如果一个节点管道超过这个极限,MongoDB将产生一个错误。为了能够在处理大型数据集,可以设置
allowDiskUse
为true
来在聚合管道节点把数据写入临时文件。这样就可以解决100MB的内存的限制。 db.collection.aggregate()
可以作用在分片集合,但结果不能输在分片集合,MapReduce
可以 作用在分片集合,结果也可以输在分片集合。db.collection.aggregate()
方法可以返回一个指针(cursor
),数据放在内存中,直接操作。跟Mongo shell 一样指针操作。db.collection.aggregate()
输出的结果只能保存在一个文档中,BSON Document
大小限制为16M。可以通过返回指针解决,版本2.6中:DB.collect.aggregate()
方法返回一个指针,可以返回任何结果集的大小。
聚合语法
db.collection.aggregate(pipeline, options)
参数说明
参数 | 类型 | 描述 |
---|---|---|
pipeline | array | 一系列数据聚合操作或阶段。详见聚合管道操作符 在版本2.6中更改:该方法仍然可以将流水线阶段作为单独的参数接受,而不是作为数组中的元素;但是,如果不将管道指定为数组,则不能指定options参数 |
options | document | 可选。 aggregate()传递给聚合命令的其他选项。 2.6版中的新增功能:仅当将管道指定为数组时才可用。 |
注意事项
使用db.collection.aggregate()直接查询会提示错误,但是传一个空数组如db.collection.aggregate([])则不会报错,且会和find一样返回所有文档。
常用聚合管道
与mysql聚合类比
为了便于理解,先将常见的mongo的聚合操作和mysql的查询做下类比
SQL 操作/函数 | mongodb聚合操作 |
---|---|
where | $match |
group by | $group |
having | $match |
select | $project |
order by | $sort |
limit | $limit |
sum() | $sum |
count() | $sum |
join | $lookup |
$count
返回包含输入到stage的文档的计数,理解为返回与表或视图的find()查询匹配的文档的计数。db.collection.count()方法不执行find()操作,而是计数并返回与查询匹配的结果数。
语法
{ $count: <string> }
group+$project的序列:
db.collection.aggregate([
{
"$group": {
"_id": null,
"count": {// 这里count自定义,相当于mysql的select count(*) as tables
"$sum": 1
}
}
},
{
"$project": {// 返回不显示_id字段
"_id": 0
}
}
])
示例
查询人数是
100000
以上的城市的数量
- $match:阶段排除pop小于等于100000的文档,将大于100000的文档传到下个阶段
- $count:阶段返回聚合管道中剩余文档的计数,并将该值分配给名为
count
的字段。
db.zips.aggregate([
{
"$match": {
"pop": {
"$gt": 100000
}
}
},
{
"$count": "count"
}
])
$group
按指定的表达式对文档进行分组,并将每个不同分组的文档输出到下一个阶段。输出文档包含一个_id字段,该字段按键包含不同的组。
输出文档还可以包含计算字段,该字段保存由 group不会输出具体的文档而只是统计信息。
语法
{ $group: { _id: <expression>, <field1>: { <accumulator1> : <expression1> }, ... } }
_id
字段是必填的;但是,可以指定_id值为null来为整个输入文档计算累计值。- 剩余的计算字段是可选的,并使用
<accumulator>
运算符进行计算。 _id
和<accumulator>
表达式可以接受任何有效的表达式。
accumulator操作符
名称 | 描述 | 类比sql |
---|---|---|
$avg | 计算均值 | avg |
$first | 返回每组第一个文档,如果有排序,按照排序,如果没有按照默认的存储的顺序返回第一个文档。 | limit 0,1 |
$last | 返回每组最后一个文档,如果有排序,按照排序,如果没有按照默认的存储的顺序返回最后一个文档。 | - |
$max | 根据分组,获取集合中所有文档对应值的最大值。 | max |
$min | 根据分组,获取集合中所有文档对应值的最小值。 | min |
$push | 将指定的表达式的值添加到一个数组中。 | - |
$addToSet | 将表达式的值添加到一个集合中(无重复值,无序)。 | - |
$sum | 计算总和 | sum |
$stdDevPop | 返回输入值的总体标准偏差(population standard deviation) | - |
$stdDevSamp | 返回输入值的样本标准偏差(the sample standard deviation) | - |
group将产生错误,但是,要允许处理大型数据集,请将allowDiskUse选项设置为true以启用$group操作以写入临时文件。
注意:
- "$addToSet":expr,如果当前数组中不包含expr,那就将它添加到数组中。
- "$push":expr,不管expr是什么值,都将它添加到数组中,返回包含所有值的数组。
示例
按照
state
分组,并计算每一个state分组的总人数,平均人数以及每个分组的数量
db.zips.aggregate([
{
"$group": {
"_id": "$state",
"totalPop": {
"$sum": "$pop"
},
"avglPop": {
"$avg": "$pop"
},
"count": {
"$sum": 1
}
}
}
])
查找不重复的所有的
state
的值
db.zips.aggregate([
{
"$group": {
"_id": "$state"
}
}
])
按照
city
分组,并且分组内的state
字段列表加入到stateItem
并显示
db.zips.aggregate([
{
"$group": {
"_id": "$city",
"stateItem": {
"$push": "$state"
}
}
}
])
下面聚合操作使用系统变量$$ROOT按item对文档进行分组,生成的文档不得超过BSON文档大小限制
db.zips.aggregate([
{
"$group": {
"_id": "$city",
"item": {
"$push": "$$ROOT"
}
}
}
]).pretty();
$match
过滤文档,仅将符合指定条件的文档传递到下一个管道阶段。
$match接受一个指定查询条件的文档,查询语法与读操作查询语法相同。
语法
{ $match: { <query> } }
管道优化
match可以使用除了地理空间之外的所有常规查询操作符,在实际应用中尽可能将$match放在管道的前面位置。这样有两个好处:
- 一是可以快速将不需要的文档过滤掉,以减少管道的工作量;
- 二是如果再投射和分组之前执行$match,查询可以使用索引。
使用限制
- 不能在
$match
查询中使用$
作为聚合管道的一部分。 - 要在
$match
阶段使用$text
,$match
阶段必须是管道的第一阶段。 - 视图不支持文本搜索。
示例
使用 $match做简单的匹配查询,查询缩写是
NY
的城市数据
db.zips.aggregate([
{
"$match": {
"state": "NY"
}
}
]).pretty();
使用 group管道以计算文档的计数
db.zips.aggregate([
{
"$match": {
"state": "NY"
}
},
{
"$group": {
"_id": null,
"sum": {
"$sum": "$pop"
},
"avg": {
"$avg": "$pop"
},
"count": {
"$sum": 1
}
}
}
]).pretty();
$unwind
从输入文档解构数组字段以输出每个元素的文档,简单说就是 可以将数组拆分为单独的文档。
语法
要指定字段路径,在字段名称前加上$符并用引号括起来。
{ $unwind: <field path> }
v3.2+支持如下语法
{
$unwind:
{
path: <field path>,
#可选,一个新字段的名称用于存放元素的数组索引。该名称不能以$开头。
includeArrayIndex: <string>,
#可选,default :false,若为true,如果路径为空,缺少或为空数组,则$unwind输出文档
preserveNullAndEmptyArrays: <boolean>
}
}
如果为输入文档中不存在的字段指定路径,或者该字段为空数组,则$unwind默认会忽略输入文档,并且不会输出该输入文档的文档。
版本3.2中的新功能:要输出数组字段丢失的文档,null或空数组,请使用选项preserveNullAndEmptyArrays。
示例
以下聚合使用$unwind为loc数组中的每个元素输出一个文档:
db.zips.aggregate([
{
"$match": {
"_id": "01002"
}
},
{
"$unwind": "$loc"
}
]).pretty();
$project
$project可以从文档中选择想要的字段,和不想要的字段(指定的字段可以是来自输入文档或新计算字段的现有字段),也可以通过管道表达式进行一些复杂的操作,例如数学操作,日期操作,字符串操作,逻辑操作。
语法
$project 管道符的作用是选择字段(指定字段,添加字段,不显示字段,_id:0,排除字段等),重命名字段,派生字段。
{ $project: { <specification(s)> } }
specifications有以下形式:
<field>
: <1 or true>
是否包含该字段,field:1/0,表示选择/不选择 field
_id
: <0 or false>
是否指定_id字段
<field>
: <expression>
添加新字段或重置现有字段的值。 在版本3.6中更改:MongoDB 3.6添加变量REMOVE。如果表达式的计算结果为$$REMOVE,则该字段将排除在输出中。
<field>
:<0 or false>
v3.4新增功能,指定排除字段
- 默认情况下,_id字段包含在输出文档中。要在输出文档中包含输入文档中的任何其他字段,必须明确指定 project将忽略该字段包含,并且不会将该字段添加到文档中。
- 默认情况下,_id字段包含在输出文档中。要从输出文档中排除_id字段,必须明确指定$project中的_id字段为0。
- v3.4版新增功能-如果指定排除一个或多个字段,则所有其他字段将在输出文档中返回。 如果指定排除_id以外的字段,则不能使用任何其他$project规范表单:即,如果排除字段,则不能指定包含字段,重置现有字段的值或添加新字段。此限制不适用于使用REMOVE变量条件排除字段。
- v3.6版本中的新功能- 从MongoDB 3.6开始,可以在聚合表达式中使用变量REMOVE来有条件地禁止一个字段。
- 要添加新字段或重置现有字段的值,请指定字段名称并将其值设置为某个表达式。
- 要将字段值直接设置为数字或布尔文本,而不是将字段设置为解析为文字的表达式,请使用 project会将数字或布尔文字视为包含或排除该字段的标志。
- 通过指定新字段并将其值设置为现有字段的字段路径,可以有效地重命名字段。
- 从MongoDB 3.2开始,$project阶段支持使用方括号[]直接创建新的数组字段。如果数组规范包含文档中不存在的字段,则该操作会将空值替换为该字段的值。
- 在版本3.4中更改-如果$project 是一个空文档,MongoDB 3.4和更高版本会产生一个错误。
- 投影或添加/重置嵌入文档中的字段时,可以使用点符号
示例
以下$project阶段的输出文档中只包含_id,city和state字段
db.zips.aggregate([
{
"$project": {
"_id": 1,
"city": 1,
"state": 1
}
}
]).pretty();
_id
字段默认包含在内。要从$ project阶段的输出文档中排除_id
字段,请在project文档中将_id
字段设置为0来指定排除_id字段。
db.zips.aggregate([
{
"$project": {
"_id": 0,
"city": 1,
"state": 1
}
}
]).pretty();
以下$ project阶段从输出中排除loc字段
db.zips.aggregate([
{
"$project": {
"loc": 0
}
}
]).pretty();
可以在聚合表达式中使用变量REMOVE来有条件地禁止一个字段,
db.zips.aggregate([
{
"$project": {
"_id": 1,
"city": 1,
"state": 1,
"pop": 1,
"loc": {
"$cond": {
"if": {
"$gt": [
"$pop",
1000
]
},
"then": "$$REMOVE",
"else": "$loc"
}
}
}
}
]).pretty();
我们还可以改变数据,将人数大于1000的城市坐标重置为0
db.zips.aggregate([
{
"$project": {
"_id": 1,
"city": 1,
"state": 1,
"pop": 1,
"loc": {
"$cond": {
"if": {
"$gt": [
"$pop",
1000
]
},
"then": [
0,
0
],
"else": "$loc"
}
}
}
}
]).pretty();
新增字段列
db.zips.aggregate([
{
"$project": {
"_id": 1,
"city": 1,
"state": 1,
"pop": 1,
"desc": {
"$cond": {
"if": {
"$gt": [
"$pop",
1000
]
},
"then": "人数过多",
"else": "人数过少"
}
},
"loc": {
"$cond": {
"if": {
"$gt": [
"$pop",
1000
]
},
"then": [
0,
0
],
"else": "$loc"
}
}
}
}
]).pretty();
$limit
限制传递到管道中下一阶段的文档数
语法
{ $limit: <positive integer> }
示例,此操作仅返回管道传递给它的前5个文档。 $limit对其传递的文档内容没有影响。
db.zips.aggregate({
"$limit": 5
});
注意
当 limit之前立即出现时,$sort操作只会在过程中维持前n个结果,其中n是指定的限制,而MongoDB只需要将n个项存储在内存中。当allowDiskUse为true并且n个项目超过聚合内存限制时,此优化仍然适用。
$skip
跳过进入stage的指定数量的文档,并将其余文档传递到管道中的下一个阶段
语法
{ $skip: <positive integer> }
示例,此操作将跳过管道传递给它的前5个文档, $skip对沿着管道传递的文档的内容没有影响。
db.zips.aggregate({
"$skip": 5
});
$sort
对所有输入文档进行排序,并按排序顺序将它们返回到管道。
语法
{ $sort: { <field1>: <sort order>, <field2>: <sort order> ... } }
$sort指定要排序的字段和相应的排序顺序的文档。
<sort order>
可以具有以下值之一:
- 1指定升序。
- -1指定降序。
- {$meta:“textScore”}按照降序排列计算出的textScore元数据。
示例
要对字段进行排序,请将排序顺序设置为1或-1,以分别指定升序或降序排序,如下例所示:
db.zips.aggregate([
{
"$sort": {
"pop": -1,
"city": 1
}
}
])
$sortByCount
根据指定表达式的值对传入文档分组,然后计算每个不同组中文档的数量。每个输出文档都包含两个字段:包含不同分组值的_id字段和包含属于该分组或类别的文档数的计数字段,文件按降序排列。
语法
{ $sortByCount: <expression> }
使用示例
下面举了一些常用的mongo聚合例子和mysql对比,假设有一条如下的数据库记录(表名:zips)作为例子:
统计所有数据
SQL的语法格式如下
select count(1) from zips;
mongoDB的语法格式
db.zips.aggregate([
{
"$group": {
"_id": null,
"count": {
"$sum": 1
}
}
}
])
对所有城市人数求合
SQL的语法格式如下
select sum(pop) AS tota from zips;
mongoDB的语法格式
db.zips.aggregate([
{
"$group": {
"_id": null,
"total": {
"$sum": "$pop"
}
}
}
])
对城市缩写相同的城市人数求合
SQL的语法格式如下
select state,sum(pop) AS tota from zips group by state;
mongoDB的语法格式
db.zips.aggregate([
{
"$group": {
"_id": "$state",
"total": {
"$sum": "$pop"
}
}
}
])
state重复的城市个数
SQL的语法格式如下
select state,count(1) AS total from zips group by state;
mongoDB的语法格式
db.zips.aggregate([
{
"$group": {
"_id": "$state",
"total": {
"$sum": 1
}
}
}
])
state重复个数大于100的城市
SQL的语法格式如下
select state,count(1) AS total from zips group by state having count(1)>100;
mongoDB的语法格式
db.zips.aggregate([
{
"$group": {
"_id": "$state",
"total": {
"$sum": 1
}
}
},
{
"$match": {
"total": {
"$gt": 100
}
}
}
])
往期干货:
- 怎样才能快速成为一名架构师?
- 如何从Java工程师成长为架构师?
- 六种常用事务解决方案,你方唱罢,我登场(没有最好只有更好)
- 超详细教程,一文入门Istio架构原理及实战应用
- [Código fuente gráfico] Análisis de código fuente de Zookeeper3.7, mecanismo de gestión de sesiones, reglas de votación de elección de líder, proceso de sincronización de datos de clúster
- [Código fuente gráfico] Análisis de código fuente de Zookeeper3.7, incluida la fuente del proceso de inicio del servicio, comunicación de red, solicitud de procesamiento de RequestProcessor
- [Conoce la verdad, conoce la razón] Análisis del código fuente de Apollo del centro de configuración
- [Recomendado] Creo que este es el tutorial de Apollo más completo desde la entrada hasta la maestría.
- Tecnología de sonda - JavaAgent y tecnología de mejora de Bytecode - Byte Buddy
- 100003 palabras, lo llevará a descifrar el sistema de arquitectura del sistema en los escenarios de promoción de comercio electrónico doble 11 y 618
- [Ilustración] JUC de subprocesos múltiples de Java desde la entrada hasta el dominio
- 12437 palabras, lo llevan a explorar el principio de la comunicación RPC en profundidad
- Ejercicio de práctica de ajuste de JVM, mi madre ya no está preocupada por la optimización de mi rendimiento
- 13651 palabras, te explican la destrucción de objetos JVM
- No entiendo el mecanismo de carga de clases de JVM, ¿cómo optimizar el rendimiento de JVM?
- 4859 palabras, 609 líneas, explican el área de datos de ejecución de JVM a la vez
- Deje que el pasante construya un clúster de Redis y casi me pone a mí~~~
- Usé el candado distribuido de Redis para tomar una botella de Maotai, y luego GG~~
- Recién llegado, ¿puede hablarme sobre el mecanismo de persistencia de Redis, cuál puede resolver este problema comercial que encontramos?
Este artículo fue publicado por el equipo de enseñanza e investigación de Chuanzhi Education Boxue Valley-Wild Architect. Si este artículo es útil para usted, síganos y haga clic en Me gusta; si tiene alguna sugerencia, también puede dejar un comentario o una carta privada. Su apoyo es la fuerza que me impulsa a persistir en la creación ¡Por favor indique la fuente!