Solución:
Las primeras cosas a considerar aquí es que el marco de agregación funciona con una “tubería” de etapas que se aplicarán para obtener un resultado. Si está familiarizado con el procesamiento de cosas en la “línea de comandos” o “shell” de su sistema operativo, es posible que tenga algo de experiencia con la “tubería” o |
operador.
Aquí hay un modismo común de Unix:
ps -ef | grep mongod | tee "out.txt"
En este caso, la salida del primer comando aquí ps -ef
se está “canalizando” al siguiente comando grep mongod
que a su vez tiene su salida “canalizada” a la tee out.txt
que ambas salidas a la terminal, así como el nombre de archivo especificado. Se trata de una “canalización” en la que cada etapa “alimenta” a la siguiente y en el “orden” de la secuencia en la que están escritas.
Lo mismo ocurre con la canalización de agregación. Una “tubería” aquí es de hecho una “matriz”, que es un conjunto ordenado de instrucciones que se pasan al procesar los datos para obtener un resultado.
db.classtest.aggregate([
{ "$group": {
"_id": "$name",
"total": { "$sum": "$marks"}
}},
{ "$sort": { "name": 1 } },
{ "$limit": 5 }
])
Entonces, lo que sucede aquí es que todos los elementos de la colección son procesados primero por $group
para obtener sus totales. No hay un “orden” específico para la agrupación, por lo que no tiene mucho sentido ordenar los datos por adelantado. Tampoco tiene sentido hacerlo porque aún no ha llegado a sus etapas posteriores.
Entonces lo harías $sort
los resultados y también $limit
según sea necesario.
Para su próxima “página” de datos, lo ideal sería $match
en el último nombre único encontrado, así:
db.classtest.aggregate([
{ "$match": { "name": { "$gt": lastNameFound } }},
{ "$group": {
"_id": "$name",
"total": { "$sum": "$marks"}
}},
{ "$sort": { "name": 1 } },
{ "$limit": 5 }
])
No es la mejor solución, pero realmente no existen alternativas para este tipo de agrupaciones. Sin embargo, se volverá notablemente “más rápido” con cada iteración hacia el final. Alternativamente, almacenar todos los nombres únicos (o leerlos de otra colección) y “paginar” a través de esa lista con una “consulta de rango” en cada declaración de agregación puede ser una opción viable, si sus datos lo permiten.
Algo como:
db.classtest.aggregate([
{ "$match": { "name": { "$gte": "Allan", "$lte": "David" } }},
{ "$group": {
"_id": "$name",
"total": { "$sum": "$marks"}
}},
{ "$sort": { "name": 1 } },
])
Desafortunadamente, no hay una opción de “agrupación límite hasta x resultados”, por lo que, a menos que pueda trabajar con otra lista, básicamente está agrupando todo (y posiblemente un conjunto gradualmente más pequeño cada vez) con cada consulta de agregación que envíe.
He resuelto el problema sin necesidad de mantener otra colección o incluso sin que $ group atraviese toda la colección, por lo que publiqué mi propia respuesta.
Como han señalado otros:
-
$group
no mantiene el orden, por lo que la clasificación temprana no es de mucha ayuda. -
$group
no realiza ninguna optimización, incluso si hay un seguimiento$limit
, es decir, corre$group
en toda la colección.
Mi caso de uso tiene las siguientes características únicas, que me ayudaron a resolverlo:
- Habrá un máximo de 10 registros por cada estudiante (mínimo de 1).
-
No soy muy exigente con el tamaño de la página. El front-end capaz de manejar diferentes tamaños de página. El siguiente es el comando de agregación que he usado.
db.classtest.aggregate( [ {$sort: {name: 1}}, {$limit: 5 * 10}, {$group: {_id: '$name', total: {$sum: '$marks'}}}, {$sort: {_id: 1}} ])
Explicando lo anterior.
- si
$sort
precede inmediatamente$limit
, el marco optimiza la cantidad de datos que se enviarán a la siguiente etapa. Consulte aquí - Para obtener un mínimo de 5 registros (tamaño de página), necesito pasar al menos 5 (tamaño de página) * 10 (registros máximos por estudiante) = 50 registros al
$group
escenario. Con esto, el tamaño del resultado final puede estar entre 0 y 50. - Si el resultado es menor que 5, entonces no se requiere más paginación.
- Si el tamaño del resultado es mayor que 5, es posible que exista la posibilidad de que el último registro del estudiante no se procese por completo (es decir, que no se agrupen todos los registros del estudiante), por lo que descarto el último registro del resultado.
-
Luego, el nombre en el último registro (entre los resultados retenidos) se usa como $ criterios de coincidencia en la solicitud de página siguiente, como se muestra a continuación.
db.classtest.aggregate( [ {$match: {name: {$gt: lastRecordName}}} {$sort: {name: 1}}, {$limit: 5 * 10}, {$group: {_id: '$name', total: {$sum: '$marks'}}}, {$sort: {_id: 1}} ])
En lo anterior, el marco aún optimizará $match, $sort and $limit
juntos como una sola operación, que he confirmado a través del plan de explicación.
-
“
$group
lo hace no pedir sus documentos de salida “. Consulte http://docs.mongodb.org/manual/reference/operator/aggregation/group/ -
$limit
limita el número de elementos procesados de un$sort
operación, no solo el número de elementos pasados a la siguiente etapa. Consulte la nota en http://docs.mongodb.org/manual/reference/operator/aggregation/limit/
Para la primera pregunta que hizo, no estoy seguro, pero parece (ver 1.) que una etapa n + 1 puede influir en el comportamiento de la etapa n: el límite será límite la operación de clasificación a sus primeros n elementos, y la operación de clasificación no se completará como si la siguiente etapa límite no existiera.