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

Requête Mongo pour trier par nombre distinct

C'est vraiment (toujours) mieux géré par plusieurs requêtes, car MongoDB n'a vraiment "toujours" pas encore les opérateurs vraiment efficaces pour le faire.

Vous pouvez cependant faire quelque chose comme ça avec MongoDB 3.2, mais il y a des « pièges » évidents :

db.Books.aggregate([
    { "$group": {
        "_id": "$company",
        "count": { "$sum": 1 },
        "urls": {
            "$push": "$url"
        }
    }},
    { "$sort": { "count": -1 } },
    { "$limit": 10 },
    { "$project": {
        "count": 1,
        "urls": { "$slice": ["$urls",0, 3] }
    }}
])

Et le problème évident est que quoi qu'il arrive, vous ajoutez toujours tout du contenu "url" dans le tableau groupé. Cela a le potentiel de dépasser la limite BSON de 16 Mo. Ce n'est peut-être pas le cas, mais c'est quand même un peu inutile d'ajouter "tout" le contenu alors que vous n'en voulez que "trois".

Donc, même dans ce cas, il est probablement plus pratique de simplement interroger les "urls" séparément sur chacun des 10 premiers résultats.

Voici une liste pour node.js qui démontre :

var async = require('async'),
    mongodb = require('mongodb'),
    MongoClient = mongodb.MongoClient;

MongoClient.connect("mongodb://localhost/test",function(err,db) {

    if (err) throw err;

    // Get the top 10
    db.collection("Books").aggregate(
        [
            { "$group": {
                "_id": "$company",
                "count": { "$sum": 1 }
             }},
             { "$sort": { "count": -1 } },
             { "$limit": 10 }
        ],function(err,results) {
            if (err) throw err;

            // Query for each result and map query response as urls
            async.map(
                results,
                function(result,callback) {
                    db.collection("Books").find({ 
                       "company": result.company 
                    }).limit(3).toArray(function(err,items) {
                        result.urls = items.map(function(item) { 
                            return item.url;
                        });
                        callback(err,result);
                    })
                },
                function(err,results) {
                    if (err) throw err;
                    // each result entry has 3 urls
                }
            );
        }
     )

});

Oui, c'est plus d'appels à la base de données, mais c'est vraiment seulement dix et donc pas vraiment un problème.

Le vrai la résolution pour cela est couverte dans SERVER-9377 - Étendez $push ou $max pour permettre la collecte de "top " N valeurs par clé _id dans la phase $group . Cela a le statut prometteur "En cours", donc on y travaille activement.

Une fois que cela est résolu, une seule déclaration d'agrégation devient viable, car vous pourrez alors "limiter" les "urls" résultantes dans le $push initial à seulement trois entrées, plutôt que de toutes les supprimer sauf trois après coup.