2011-12-23 11 views
8

Data la seguente raccolta MongoDB di documenti:Ottenere i documenti con le etichette in lista, in ordine di numero totale di partite

{ 
title : 'shirt one' 
tags : [ 
    'shirt', 
    'cotton', 
    't-shirt', 
    'black' 
] 
}, 
{ 
title : 'shirt two' 
tags : [ 
    'shirt', 
    'white', 
    'button down collar' 
] 
}, 
{ 
title : 'shirt three' 
tags : [ 
    'shirt', 
    'cotton', 
    'red' 
] 
}, 
... 

Come si recupera un elenco di elementi che corrispondono a un elenco di tag, ordinato dal numero totale di tag corrispondenti? Ad esempio, dato questo elenco di tag come input:

['shirt', 'cotton', 'black'] 

che vorrei recuperare gli elementi ordinati in ordine decrescente per numero totale di tag corrispondenti:

item   total matches 
--------  -------------- 
Shirt One  3 (matched shirt + cotton + black) 
Shirt Three 2 (matched shirt + cotton) 
Shirt Two  1 (matched shirt) 

In uno schema relazionale, tag sarebbe una tabella separata e potresti unirti a quella tabella, contare le partite e ordinare per conteggio.

Ma, in Mongo ...?

Sembra questo approccio potrebbe funzionare,

  • pausa i tag input in più "IN" dichiarazioni
  • ricerca per articolo da "o" 'ing insieme ingressi tag
    • cioè dove (' shirt' iN items.tags) OR ('cotone' iN items.tags)
    • questo sarebbe tornato, per esempio, tre casi di "camicia One", 2 istanze di "camicia tre", ecc
  • map/ridurre quell'output
    • map: emit (this._id, {...});
    • ridurre: contare occorrenze totali di _id
    • finalizzare: ordina per contato totale

Ma io non sono chiare su come implementare questa come una query Mongo, o se questo è anche il approccio più efficiente.

+0

sembra semplice lavoro M/R. –

+1

No M/R è semplice nel codice di produzione poiché l'implementazione corrente manca del parallelismo corretto. In effetti, si può fare un buon esempio per evitare m/r nel complesso in situazioni di alto rendimento. –

risposta

5

Al momento, non è possibile farlo a meno che non si utilizzi MapReduce. L'unico problema con MapReduce è che è lento (rispetto a una query normale).

Il framework di aggregazione è previsto per 2.2 (quindi dovrebbe essere disponibile in 2.1 dev release) e dovrebbe rendere questo tipo di cosa molto più semplice da fare senza MapReduce.

Personalmente, non penso che l'utilizzo di M/R sia un modo efficiente per farlo. Preferisco interrogare tutti i documenti e fare questi calcoli dal lato dell'applicazione. È più semplice ed economico scalare i server delle app piuttosto che ridimensionare i server del database in modo che i server delle app eseguano il numero di crunch. Di questi, questo approccio potrebbe non funzionare per te dato i tuoi modelli di accesso ai dati e requisiti.

Un approccio ancora più semplice potrebbe essere quella di includere solo una proprietà count in ciascuno dei vostri oggetti di tag e ogni volta che si $push un nuovo tag alla matrice, è anche $inc la proprietà count. Questo è un modello comune nel mondo MongoDB, almeno fino al quadro di aggregazione.

+1

Includere una proprietà count quando $ pushing di un nuovo tag all'array non sarebbe di aiuto dato questo problema, dato che wount potrebbe semplicemente indicare i tag totali (non i tag totali corrispondenti all'ingresso). – Matt

+0

Ah, giusto, mi sono anticipato. –

1

In secondo luogo @Bryan nel dire che MapReduce è l'unico modo possibile al momento (ed è tutt'altro che perfetto).Ma, nel caso in cui hai disperatamente bisogno, qui si va :-)

var m = function() { 
     var searchTerms = ['shirt', 'cotton', 'black']; 
     var me = this; 
     this.tags.forEach(function(t) { 
      searchTerms.forEach(function(st) { 
       if(t == st) { 
        emit(me._id, {matches : 1}); 
       } 
      }) 
     }) 
    }; 

    var r = function(k, vals) { 
     var result = {matches : 0}; 
     vals.forEach(function(v) { 
      result.matches += v.matches; 
     }) 
     return result; 
    }; 

    db.shirts.mapReduce(m, r, {out: 'found01'}); 

    db.found01.find(); 
+0

Grazie, questo è un buon inizio. Ma, piuttosto che eseguire la mappa/ridurre su * tutti * gli elementi della raccolta, non sarebbe più rapido fare una ricerca iniziale facendo OR insieme a tag di input? Ciò ridurrebbe la dimensione dell'insieme elaborato in m(), e r() potrebbe semplicemente restituire vals.length come totale corrisponde? – Matt

7

Come ho risposto in In MongoDB search in an array and sort by number of matches

E 'possibile utilizzando aggregazione Framework.

Ipotesi attributo

  • tags è un insieme (senza elementi ripetuti)

Query forze

questo approccio a rilassarvi i risultati e rivalutare il predicato match con risultati non svolti, quindi è davvero inefficiente.

db.test_col.aggregate(
    {$match: {tags: {$in: ["shirt","cotton","black"]}}}, 
    {$unwind: "$tags"}, 
    {$match: {tags: {$in: ["shirt","cotton","black"]}}}, 
    {$group: { 
     _id:{"_id":1}, 
     matches:{$sum:1} 
    }}, 
    {$sort:{matches:-1}} 
); 

Risultati attesi

{ 
    "result" : [ 
     { 
      "_id" : { 
       "_id" : ObjectId("5051f1786a64bd2c54918b26") 
      }, 
      "matches" : 3 
     }, 
     { 
      "_id" : { 
       "_id" : ObjectId("5051f1726a64bd2c54918b24") 
      }, 
      "matches" : 2 
     }, 
     { 
      "_id" : { 
       "_id" : ObjectId("5051f1756a64bd2c54918b25") 
      }, 
      "matches" : 1 
     } 
    ], 
    "ok" : 1 
} 
+0

Samuel La risposta è corretta. Ho appena disputato le informazioni aggiuntive che è inefficiente. Al fine di abbinare qualcuno dovrà svolgere i tag comunque eseguendo questa operazione nella pipeline di aggregazione potrebbe essere l'approccio più veloce per le query ad hoc – rat

+0

Questa risposta ha funzionato bene per me tuttavia ho dovuto fare una piccola modifica nell'oggetto '$ gruppo' per fare questo lavoro in Mongo 3.0. e usa questo per l'ID '_id: {" _ id ":" $ _ id "}' – Binarytales

+0

Sì, davvero. Il formato _id di raggruppamento è stato modificato nella versione 3.0 e ora puoi usare quel formato o quello annidato ma anche con il simbolo $. –

Problemi correlati