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

limiter et trier chaque groupe par dans mongoDB en utilisant l'agrégation

Votre meilleure option ici est d'exécuter des requêtes séparées pour chaque "pays" (idéalement en parallèle) et de renvoyer les résultats combinés. Les requêtes sont assez simples et renvoient simplement les 2 premières valeurs après avoir appliqué un tri sur la valeur d'évaluation et s'exécuteront assez rapidement même si vous devez effectuer plusieurs requêtes pour obtenir le résultat complet.

Le cadre d'agrégation n'est pas adapté à cela, maintenant et même dans un avenir proche. Le problème est qu'il n'existe aucun opérateur de ce type qui "limite" le résultat d'un regroupement de quelque manière que ce soit. Donc, pour ce faire, vous devez essentiellement $push tout le contenu dans un tableau et en extraire les "top n" valeurs.

Les opérations actuelles nécessaires pour ce faire sont assez horribles, et le problème principal est que les résultats dépasseront probablement la limite BSON de 16 Mo par document sur la plupart des sources de données réelles.

Il y a aussi un n complexité à cela en raison de la façon dont vous auriez à le faire en ce moment. Mais juste pour démontrer avec 2 éléments :

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

Cela donne le résultat mais ce n'est pas une bonne approche et devient beaucoup plus complexe avec des itérations pour des limites plus élevées ou même lorsque les groupements ont peut-être moins de n résultats à renvoyer dans certains cas.

La série de développement actuelle ( 3.1.x ) au moment de l'écriture a un $slice opérateur qui rend cela un peu plus simple, mais qui a toujours le même écueil de "taille":

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

Mais fondamentalement jusqu'à ce que le cadre d'agrégation ait un moyen de "limiter" le nombre d'éléments produits par $push ou un opérateur "limite" de regroupement similaire, alors le cadre d'agrégation n'est pas vraiment la solution optimale pour ce type de problème.

Requêtes simples comme celle-ci :

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

Exécuté pour chaque pays distinct et idéalement en traitement parallèle par boucle d'événement de thread avec un résultat combiné produit l'approche la plus optimale en ce moment. Ils ne récupèrent que ce qui est nécessaire, ce qui est le gros problème que le framework d'agrégation ne peut pas encore gérer dans un tel regroupement.

Cherchez donc de l'aide pour faire ces "résultats de requête combinés" de la manière la plus optimale pour le langage que vous avez choisi, car ce sera beaucoup moins complexe et beaucoup plus performant que de lancer cela sur le cadre d'agrégation.