2011-12-23 7 views
8

se tenga lo siguiente colección MongoDB de documentos:Obtener documentos con etiquetas en la lista, ordenada por el número total de partidos

{ 
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' 
] 
}, 
... 

¿Cómo recuperar una lista de elementos que coincidan con una lista de etiquetas, ordenados por el número total de etiquetas coincidentes? Por ejemplo, teniendo en cuenta esta lista de etiquetas como entrada:

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

que me gustaría recuperar los elementos clasificados en orden descendente por el número total de etiquetas coincidentes:

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

En un esquema relacional, etiquetas sería una tabla separada, y usted podría unirse contra esa mesa, contar las coincidencias y ordenar por el conteo.

Pero, en Mongo ...?

parece que este enfoque podría trabajar,

  • ruptura de las etiquetas de entrada en varias "en" declaraciones
  • consulta de artículos por el "OR" 'ing juntas las entradas etiqueta
    • es decir, donde (' camisa' IN items.tags) OR ('algodón' IN items.tags)
    • este volvería, por ejemplo, tres casos de "camisa One", 2 casos de "camisa tres", etc
  • mapa/reducir esa salida
    • mapa: emitir (this._id, {...});
    • reducir: contar los casos totales de _id
    • de finalización: ordenar por conté total de

Pero no me queda claro sobre cómo implementar esto como una consulta Mongo, o si esto es aún la enfoque más eficiente.

+0

que parece ser simple trabajo de M/R. –

+1

No M/R es simple en el código de producción ya que la implementación actual carece del paralelismo adecuado. De hecho, se puede hacer un buen caso para evitar m/r en situaciones de alto rendimiento. –

Respuesta

5

En este momento, no es posible hacerlo a menos que use MapReduce. El único problema con MapReduce es que es lento (en comparación con una consulta normal).

El marco de agregación está programado para 2.2 (por lo que debería estar disponible en la versión 2.1 dev) y debería hacer este tipo de cosas mucho más fácil sin MapReduce.

Personalmente, no creo que el uso de M/R sea una forma eficiente de hacerlo. Prefiero buscar todos los documentos y hacer esos cálculos en el lado de la aplicación. Es más fácil y más económico escalar los servidores de su aplicación que escalar los servidores de la base de datos, así que permita que los servidores de la aplicación hagan el cálculo del número. De ellos, este enfoque puede no funcionar para usted dados sus patrones y requisitos de acceso a datos.

Un enfoque aún más simple puede ser que acaba de incluir una propiedad count en cada uno de los objetos de la etiqueta y cada vez que $push una nueva etiqueta a la matriz, también $inc la propiedad count. Este es un patrón común en el mundo MongoDB, al menos hasta el marco de agregación.

+1

Incluir una propiedad de conteo cuando $ push'ing una nueva etiqueta en la matriz no ayudaría dado este problema, ya que la carga podría indicar simplemente las etiquetas totales (no las etiquetas totales que coinciden con la entrada). – Matt

+0

Ah, cierto, me adelanté allí. –

1

Voy a segundo @Bryan diciendo que MapReduce es la única manera posible en este momento (y está lejos de ser perfecto).Pero, en caso de que lo necesite desesperadamente, aquí tienes :-)

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

Gracias, este es un buen comienzo. Pero, en lugar de ejecutar el mapa/reducir en * todos * los elementos en la colección, ¿no sería más rápido hacer un hallazgo inicial al ordenar las etiquetas de entrada? Esto reduciría el tamaño del conjunto procesado en m(), yr() podría simplemente devolver vals.length como el total coincide? – Matt

7

mientras respondía en In MongoDB search in an array and sort by number of matches

Es posible utilizar Marco de agregación.

Supuestos

  • tags atributo es un conjunto (no hay elementos repetidos)

consulta

Este enfoque obliga a desenrollar los resultados y volver a evaluar el predicado de comparación con resultados no desvinculados, por lo que es realmente ineficiente.

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

Resultados esperados

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

Samuel La respuesta es correcta. Solo cuestiono la información adicional que es ineficiente. Para que coincida con alguien tendrá que desenrollar las etiquetas de todos modos realizar esta tarea en la tubería de agregación puede ser el enfoque más rápido para consultas adhoc – rat

+0

Esta respuesta funcionó muy bien para mí, pero tuve que hacer un pequeño cambio en el objeto '$ group' para hacer que esto funcione en Mongo 3.0. y use esto para la ID '_id: {" _ id ":" $ _ id "}' – Binarytales

+0

Sí, de hecho. El formato _id de agrupación ha cambiado en la versión 3.0, y ahora puede usar ese formato o el anidado, pero también con el símbolo $. –

Cuestiones relacionadas