sql >> Base de Datos >  >> NoSQL >> MongoDB

limitar y ordenar cada grupo en mongoDB usando agregación

Su mejor opción aquí es ejecutar consultas separadas para cada "País" (idealmente en paralelo) y devolver los resultados combinados. Las consultas son bastante simples y solo devuelven los 2 valores principales después de aplicar una ordenación en el valor de calificación y se ejecutarán con bastante rapidez incluso si necesita realizar varias consultas para obtener el resultado completo.

El marco de agregación no es una buena opción para esto, ahora e incluso en el futuro cercano. El problema es que no existe tal operador que "limite" el resultado de cualquier agrupación de ninguna manera. Entonces, para hacer esto, básicamente necesitas $push todo el contenido en una matriz y extraer los valores "top n" de eso.

Las operaciones actuales necesarias para hacer eso son bastante horribles, y el problema principal es que es probable que los resultados excedan el límite de BSON de 16 MB por documento en la mayoría de las fuentes de datos reales.

También hay un n complejidad a esto debido a cómo tendrías que hacerlo ahora mismo. Pero solo para demostrar con 2 elementos:

db.collection.aggregate([
    // Sort content by country and rating
    { "$sort": { "Country": 1, "rating": -1 } },

    // Group by country and push all items, keeping first result
    { "$group": {
        "_id": "$Country",
        "results": {
            "$push": {
                "name": "$name", 
                "rating": "$rating",
                "id": "$id"
            }
        },
        "first": { 
            "$first": {
                "name": "$name", 
                "rating": "$rating",
                "id": "$id"
            }
        }
    }},

    // Unwind the array
    { "$unwind": "results" },

    // Remove the seen result from the array
    { "$redact": {
        "$cond": {
            "if": { "$eq": [ "$results.id", "$first.id" ] },
            "then": "$$PRUNE",
            "else": "$$KEEP"
        }
    }},

    // Group to return the second result which is now first on stack
    { "$group": {
        "_id": "$_id",
        "first": { "$first": "$first" },
        "second": { 
            "$first": {
                "name": "$results.name", 
                "rating": "$results.rating",
                "id": "$results.id"
            }
        }
    }},

    // Optionally put these in an array format
    { "$project": {
        "results": { 
            "$map": {
                "input": ["A","B"],
                "as": "el",
                "in": {
                    "$cond": {
                        "if": { "$eq": [ "$$el", "A" ] },
                        "then": "$first",
                        "else": "$second"
                    }
                }
            }
        }
    }}
])

Eso obtiene el resultado, pero no es un gran enfoque y se vuelve mucho más complejo con iteraciones para límites más altos o incluso donde las agrupaciones posiblemente tengan menos de n resultados para devolver en algunos casos.

La serie de desarrollo actual ( 3.1.x ) al momento de escribir tiene un $slice operador que hace esto un poco más simple, pero aún tiene el mismo escollo de "tamaño":

db.collection.aggregate([
    // Sort content by country and rating
    { "$sort": { "Country": 1, "rating": -1 } },

    // Group by country and push all items, keeping first result
    { "$group": {
        "_id": "$Country",
        "results": {
            "$push": {
                "name": "$name", 
                "rating": "$rating",
                "id": "$id"
            }
        }
    }},
    { "$project": {
        "results": { "$slice": [ "$results", 2 ] }
    }}
])

Pero básicamente hasta que el marco de agregación tenga alguna forma de "limitar" la cantidad de elementos producidos por $push o un operador de "límite" de agrupación similar, entonces el marco de agregación no es realmente la solución óptima para este tipo de problema.

Consultas simples como esta:

db.collection.find({ "Country": "USA" }).sort({ "rating": -1 }).limit(1)

Ejecutar para cada país distinto e idealmente en procesamiento paralelo por ciclo de evento de subproceso con un resultado combinado produce el enfoque más óptimo en este momento. Solo obtienen lo que se necesita, que es el gran problema que el marco de agregación aún no puede manejar en dicha agrupación.

Así que busque soporte para hacer estos "resultados de consulta combinados" de la manera más óptima para su idioma elegido, ya que será mucho menos complejo y mucho más eficaz que lanzar esto en el marco de agregación.