MongoDB
 sql >> Base de données >  >> NoSQL >> MongoDB

trier par valeur d'objet incorporé dans Mongodb

Si vous avez besoin de calculer quelque chose comme ça au moment de l'exécution, avec le contenu "filtré" du tableau déterminant l'ordre de tri, alors vous feriez mieux de faire quelque chose avec .aggregate() pour remodeler et déterminer une valeur de tri comme celle-ci :

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 } }
])

Où la première partie du pipeline est utilisée pour "pré-filtrer" le contenu du tableau (ainsi que pour conserver le champ d'origine) aux seules valeurs de "score" où l'id est égal à "t1". Cela se fait en traitant $map qui applique une condition à chaque élément via $cond pour déterminer s'il faut renvoyer le "score" pour cet élément ou false .

Le $setDifference l'opération effectue une comparaison avec un seul tableau d'éléments [false] qui supprime efficacement tout false valeurs renvoyées par le $map . En tant qu'"ensemble", cela supprime également les entrées en double, mais pour le tri ici, c'est une bonne chose.

Avec le tableau réduit et remodelé en valeurs, vous traitez $unwind prêt pour l'étape suivante pour traiter les valeurs en tant qu'éléments individuels. Le $group l'étape s'applique essentiellement $max sur le "score" pour renvoyer la valeur la plus élevée contenue dans les résultats filtrés.

Ensuite, il suffit d'appliquer le $sort sur la valeur déterminée pour commander les documents. Naturellement, si vous vouliez l'inverse, utilisez $min et trier par ordre croissant à la place.

Bien sûr, ajoutez un $match étape au début si tout ce que vous voulez vraiment, ce sont des documents qui contiennent en fait des valeurs "t1" pour id dans les balises. Mais cette partie est la moins pertinente pour le tri des résultats filtrés que vous souhaitez obtenir.

L'alternative au calcul consiste à tout faire au fur et à mesure que vous écrivez des entrées dans le tableau dans les documents. Un peu désordonné, mais ça donne quelque chose comme ça :

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

Ici le $max L'opérateur de mise à jour définit uniquement la valeur du champ spécifié si la nouvelle valeur est supérieure à la valeur existante ou si aucune propriété n'existe encore. Le cas inverse est vrai pour $min , où seulement s'il est inférieur à il sera remplacé par la nouvelle valeur.

Cela aurait bien sûr pour effet d'ajouter diverses propriétés supplémentaires aux documents, mais le résultat final est le tri est grandement simplifié :

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

Et cela va s'exécuter beaucoup plus rapidement que le calcul avec un pipeline d'agrégation.

Considérez donc les principes de conception. Les données structurées dans des tableaux où vous souhaitez des résultats filtrés et appariés pour le tri signifient un calcul au moment de l'exécution pour déterminer la valeur à trier. Ajout de propriétés supplémentaires au document sur .update() signifie que vous pouvez simplement référencer ces propriétés afin de trier directement les résultats.