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

ordenar por valor de objeto incrustado en Mongodb

Si necesita calcular algo como esto en tiempo de ejecución, con contenido "filtrado" de la matriz que determina el orden de clasificación, entonces es mejor que haga algo con .aggregate() para remodelar y determinar un valor de clasificación como este:

db.collection.aggregate([
    // Pre-filter the array elements
    { "$project": {
        "tags": 1,
        "score": {
            "$setDifference": [
                { "$map": {
                    "input": "$tags",
                    "as": "tag",
                    "in": {
                        "$cond": [
                            { "$eq": [ "$$el.id", "t1" ] },
                            "$$el.score",
                            false
                        ]
                    }
                }},
                [false]
            ]
        }
    }},
    // Unwind to denormalize
    { "$unwind": "$score" },
    // Group back the "max" score
    { "$group": {
        "_id": "$_id",
        "tags": { "$first": "$tags" },
        "score": { "$max": "$score" }
    }},
    // Sort descending by score
    { "$sort": { "score": -1 } }
])

Donde la primera parte de la canalización se usa para "filtrar previamente" el contenido de la matriz (además de mantener el campo original) solo para aquellos valores de "puntuación" donde la identificación es igual a "t1". Esto se hace procesando $map que aplica una condición a cada elemento a través de $cond para determinar si devolver la "puntuación" para ese elemento o false .

El $setDifference la operación hace una comparación con una matriz de un solo elemento [false] que elimina efectivamente cualquier false valores devueltos desde el $map . Como un "conjunto", esto también elimina las entradas duplicadas, pero para el propósito de clasificación aquí es algo bueno.

Con la matriz reducida y remodelada a valores, procesa $unwind listo para la siguiente etapa para tratar los valores como elementos individuales. El $group la etapa esencialmente aplica $max en la "puntuación" para devolver el valor más alto contenido en los resultados filtrados.

Entonces solo es cuestión de aplicar el $sort sobre el valor determinado para ordenar los documentos. Naturalmente, si quisiera esto al revés, use $min y ordenar en orden ascendente en su lugar.

Por supuesto, agregue un $match etapa al principio si todo lo que realmente quiere son documentos que realmente contengan valores "t1" para id dentro de las etiquetas. Pero esa parte es de menor relevancia para la clasificación de los resultados filtrados que desea lograr.

La alternativa al cálculo es hacerlo todo mientras escribe entradas en la matriz en los documentos. Un poco desordenado, pero es algo como esto:

db.collection.update(
    { "_id": docId },
    {
        "$push": { "tags": { "id": "t1", "score": 60 } },
        "$max": { "maxt1score": 60 },
        "$min": { "mint1score": 60 }
    }
)

Aquí el $max El operador de actualización solo establece el valor para el campo especificado si el nuevo valor es mayor que el valor existente o, de lo contrario, aún no existe ninguna propiedad. El caso inverso es cierto para $min , donde solo si es menor que se reemplazará con el nuevo valor.

Por supuesto, esto tendría el efecto de agregar varias propiedades adicionales a los documentos, pero el resultado final es que la clasificación se simplifica enormemente:

db.collection.find().sort({ "maxt1score": -1 })

Y se ejecutará mucho más rápido que calcular con una canalización de agregación.

Así que considere los principios de diseño. Los datos estructurados en matrices en los que desea resultados filtrados y emparejados para ordenar significa calcular en tiempo de ejecución para determinar qué valor ordenar. Agregar propiedades adicionales al documento en .update() significa que simplemente puede hacer referencia a esas propiedades para ordenar directamente los resultados.