2012-10-07 2 views
5

J'ai une collection de documents sous la forme suivante:Tri par pertinence avec MongoDB

{ _id: ObjectId(...) 
, title: "foo" 
, tags: ["bar", "baz", "qux"] 
} 

La requête doit trouver tous les documents avec l'une de ces balises. J'utilise actuellement cette requête:

{ "tags": { "$in": ["bar", "hello"] } } 

Et cela fonctionne; tous les documents marqués "barre" ou "bonjour" sont renvoyés.

Cependant, je veux trier par pertinence, c'est-à-dire plus correspond aux étiquettes le plus tôt le document devrait apparaître dans le résultat. Par exemple, un document étiqueté ["bar", "hello", "baz"] doit être plus élevé dans les résultats qu'un document marqué ["bar", "baz", "boo"] pour la requête ["bar", "hello"]. Comment puis-je atteindre cet objectif?

Répondre

9

MapReduce et le faire clientside va être trop lent - vous . devraient utiliser le cadre d'agrégation (nouveau dans MongoDB 2.2)

Il pourrait ressembler à ceci:

db.collection.aggregate([ 
    { $match : { "tags": { "$in": ["bar", "hello"] } } }, 
    { $unwind : "$tags" }, 
    { $match : { "tags": { "$in": ["bar", "hello"] } } }, 
    { $group : { _id: "$title", numRelTags: { $sum:1 } } }, 
    { $sort : { numRelTags : -1 } } 
    // optionally 
    , { $limit : 10 } 
]) 

Notez les premier et troisième éléments de pipeline chercher identique, c'est intentionnel et nécessaire. Voici ce que les étapes font:

  1. ne transmettre que les documents qui ont le mot-clé "bar" ou "bonjour" en eux.
  2. dénouer les balises array (qui signifie divisé en un seul document par des balises élément
  3. passe sur les étiquettes seulement exactement « bar » ou « bonjour » (c.-à-jeter le reste des balises)
  4. groupe
  5. par titre (il pourrait être aussi par « _id $ » ou toute autre combinaison de document original additionnant le nombre de tags (de « bar » et « bonjour »), il avait sorte
  6. par ordre décroissant par nombre d'étiquettes pertinentes
  7. (en option) limite l'ensemble retourné au top 10.
+0

Je pense que c'est {$ unwind: "$ tags"} plutôt que {$ unwind: {"$ tags"}} –

+0

Que savez-vous - vous avez raison, le premier à remarquer dans quatre ans? :) –

+0

Je suppose. Réponse incroyable btw-- énorme aide. Merci. –

1

Vous pourriez potentiellement utiliser MapReduce pour quelque chose comme ça. Vous devez traiter chaque document à l'étape Carte, déterminer le nombre de balises correspondant à la requête et attribuer un score. Ensuite, vous pouvez trier en fonction de ce score.

http://www.mongodb.org/display/DOCS/MapReduce

0

Quelque chose qui complexe devrait être fait après l'interrogation. Soit côté serveur via db.eval (si votre client le supporte) ou juste côté client. Voici un exemple de ce que vous cherchez.

Il récupère tous les messages avec les tags que vous avez spécifiés, puis les trie en fonction du nombre de correspondances.

retirer la db.eva (partie et de le traduire dans la langue de votre client utilise pour interroger pour obtenir l'effet clientside (

db.eval(function() { 
    var tags = ["a","b","c"]; 
    return db.posts.find({tags:{$in:tags}}).toArray().sort(function(a,b){ 

     var matches_a = 0; 
     var matches_b = 0; 
     a.tags.forEach(function (tag) { 
      for (t in tags) { 
       if (tag == t) { 
        matches_a++; 
       } else { 
        matches_b++; 
       } 
      } 
     }); 

     b.tags.forEach(function(tag) { 
      for (t in tags) { 
       if (tag == t) { 
        matches_b++; 
       } else { 
        matches_a++; 
       } 
      } 
     }); 
     return matches_a - matches_b; 
    }); 
}); 
+1

Ceci est lent pour les grandes collections, je vais essayer de trouver une autre réponse. – arian

Questions connexes