79 votes

MongoDB select count(distinct x) sur une colonne indexée - compter les résultats uniques pour les grands ensembles de données

J'ai parcouru plusieurs articles et exemples, mais je n'ai pas encore trouvé de moyen efficace d'effectuer cette requête SQL dans MongoDB (où il y a des millions d'enregistrements). rangées documents)

Première tentative

(par exemple, à partir de cette question qui fait presque double emploi). L'équivalent Mongo de SELECT DISTINCT de SQL ? )

db.myCollection.distinct("myIndexedNonUniqueField").length

J'ai évidemment obtenu cette erreur car mon jeu de données est énorme.

Thu Aug 02 12:55:24 uncaught exception: distinct failed: {
        "errmsg" : "exception: distinct too big, 16mb cap",
        "code" : 10044,
        "ok" : 0
}

Deuxième tentative

J'ai décidé d'essayer de faire un groupe

db.myCollection.group({key: {myIndexedNonUniqueField: 1},
                initial: {count: 0}, 
                 reduce: function (obj, prev) { prev.count++;} } );

Mais j'ai eu ce message d'erreur à la place :

exception: group() can't handle more than 20000 unique keys

Troisième tentative

Je n'ai pas encore essayé mais il y a plusieurs suggestions qui impliquent mapReduce

par exemple

Aussi

Il semble qu'il y ait une demande de modification sur GitHub qui corrige le problème. .distinct pour mentionner qu'il ne devrait retourner qu'un compte, mais c'est toujours ouvert : https://github.com/mongodb/mongo/pull/34

Mais à ce stade, j'ai pensé qu'il valait la peine de demander ici, quelles sont les dernières nouvelles sur le sujet ? Dois-je passer à SQL ou à une autre base de données NoSQL pour les comptes distincts ? ou existe-t-il un moyen efficace ?

Mise à jour :

Ce commentaire sur la documentation officielle de MongoDB n'est pas encourageant, est-ce exact ?

http://www.mongodb.org/display/DOCS/Aggregation#comment-430445808

Mise à jour2 :

Il semble que le nouveau Aggregation Framework réponde au commentaire ci-dessus... (MongoDB 2.1/2.2 et supérieur, aperçu de développement disponible, pas pour la production)

http://docs.mongodb.org/manual/applications/aggregation/

74voto

William Z Points 4328

1) La façon la plus simple de le faire est d'utiliser le cadre d'agrégation. Cela nécessite deux commandes "$group" : la première regroupe par valeurs distinctes, la seconde compte toutes les valeurs distinctes.

pipeline = [ 
    { $group: { _id: "$myIndexedNonUniqueField"}  },
    { $group: { _id: 1, count: { $sum: 1 } } }
];

//
// Run the aggregation command
//
R = db.runCommand( 
    {
    "aggregate": "myCollection" , 
    "pipeline": pipeline
    }
);
printjson(R);

2) Si vous voulez faire cela avec Map/Reduce, vous pouvez le faire. Il s'agit également d'un processus en deux phases : dans la première phase, nous créons une nouvelle collection avec une liste de toutes les valeurs distinctes de la clé. Dans la deuxième phase, nous effectuons un count() sur la nouvelle collection.

var SOURCE = db.myCollection;
var DEST = db.distinct
DEST.drop();

map = function() {
  emit( this.myIndexedNonUniqueField , {count: 1});
}

reduce = function(key, values) {
  var count = 0;

  values.forEach(function(v) {
    count += v['count'];        // count each distinct value for lagniappe
  });

  return {count: count};
};

//
// run map/reduce
//
res = SOURCE.mapReduce( map, reduce, 
    { out: 'distinct', 
     verbose: true
    }
    );

print( "distinct count= " + res.counts.output );
print( "distinct count=", DEST.count() );

Notez que vous ne pouvez pas renvoyer le résultat du map/reduce en ligne, car cela risquerait de dépasser la limite de 16 Mo pour la taille des documents. Vous pouvez consulter le site peut enregistrer le calcul dans une collection et ensuite compter() la taille de la collection, ou vous pouvez obtenir le nombre de résultats à partir de la valeur de retour de mapReduce().

37voto

Stackee007 Points 518
db.myCollection.aggregate( 
   {$group : {_id : "$myIndexedNonUniqueField"} }, 
   {$group: {_id:1, count: {$sum : 1 }}});

directement au résultat :

db.myCollection.aggregate( 
   {$group : {_id : "$myIndexedNonUniqueField"} }, 
   {$group: {_id:1, count: {$sum : 1 }}})
   .result[0].count;

2voto

Munib mir Points 45

La solution suivante a fonctionné pour moi

db.test.distinct('user') ; [ "alex", "England", "France", "Australia" ]

db.countries.distinct('country').length 4

0voto

Sérgio Points 854

Je le fais en 2 phases : premièrement, je sélectionne des valeurs distinctes, deuxièmement, je les compte.

res = posts.find({ "geography": { "$regex": '/europe/', "$options": 'i'}} ).distinct("geography")
res.sort()
for line in res: 
    res2 = posts.find("geography": line )
    print line, res2.count()

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X