Recupere solo el elemento consultado en una matriz de objetos en la colección MongoDB

Supongamos que tiene los siguientes documentos en mi colección:

{ "_id":ObjectId("562e7c594c12942f08fe4192"), "shapes":[ { "shape":"square", "color":"blue" }, { "shape":"circle", "color":"red" } ] }, { "_id":ObjectId("562e7c594c12942f08fe4193"), "shapes":[ { "shape":"square", "color":"black" }, { "shape":"circle", "color":"green" } ] } 

Hacer consulta:

 db.test.find({"shapes.color": "red"}, {"shapes.color": 1}) 

O

 db.test.find({shapes: {"$elemMatch": {color: "red"}}}, {"shapes.color": 1}) 

Devuelve un documento coincidente (Documento 1) , pero siempre con TODOS los elementos de la matriz en shapes :

 { "shapes": [ {"shape": "square", "color": "blue"}, {"shape": "circle", "color": "red"} ] } 

Sin embargo, me gustaría obtener el documento (Documento 1) solo con la matriz que contiene color=red :

 { "shapes": [ {"shape": "circle", "color": "red"} ] } 

¿Cómo puedo hacer esto?

El nuevo operador de proyección $elemMatch MongoDB 2.2 proporciona otra forma de alterar el documento devuelto para que contenga solo el primer elemento de shapes coincidentes:

 db.test.find( {"shapes.color": "red"}, {_id: 0, shapes: {$elemMatch: {color: "red"}}}); 

Devoluciones:

 {"shapes" : [{"shape": "circle", "color": "red"}]} 

En 2.2 también puede hacer esto usando el $ projection operator , donde $ en un nombre de campo de objeto de proyección representa el índice del primer elemento de matriz coincidente del campo de la consulta. A continuación se muestran los mismos resultados que arriba:

 db.test.find({"shapes.color": "red"}, {_id: 0, 'shapes.$': 1}); 

Actualización de MongoDB 3.2

Comenzando con la versión 3.2, puede usar el nuevo operador de agregación $filter para filtrar una matriz durante la proyección, que tiene el beneficio de incluir todas las coincidencias, en lugar de solo la primera.

 db.test.aggregate([ // Get just the docs that contain a shapes element where color is 'red' {$match: {'shapes.color': 'red'}}, {$project: { shapes: {$filter: { input: '$shapes', as: 'shape', cond: {$eq: ['$$shape.color', 'red']} }}, _id: 0 }} ]) 

Resultados:

 [ { "shapes" : [ { "shape" : "circle", "color" : "red" } ] } ] 

El nuevo Framework de Agregación en MongoDB 2.2+ proporciona una alternativa a Map / Reduce. El operador $unwind se puede usar para separar su matriz de shapes en una secuencia de documentos que se pueden combinar:

 db.test.aggregate( // Start with a $match pipeline which can take advantage of an index and limit documents processed { $match : { "shapes.color": "red" }}, { $unwind : "$shapes" }, { $match : { "shapes.color": "red" }} ) 

Resultados en:

 { "result" : [ { "_id" : ObjectId("504425059b7c9fa7ec92beec"), "shapes" : { "shape" : "circle", "color" : "red" } } ], "ok" : 1 } 

Otra forma interesante es usar $ redact , que es una de las nuevas funciones de agregación de MongoDB 2.6 . Si está utilizando 2.6, no necesita un $ unwind que podría causarle problemas de rendimiento si tiene matrices grandes.

 db.test.aggregate([ { $match: { shapes: { $elemMatch: {color: "red"} } }}, { $redact : { $cond: { if: { $or : [{ $eq: ["$color","red"] }, { $not : "$color" }]}, then: "$$DESCEND", else: "$$PRUNE" } }}]); 

$redact “restringe el contenido de los documentos según la información almacenada en los documentos” . Por lo tanto, se ejecutará solo dentro del documento . Básicamente escanea la parte superior de su documento hasta la parte inferior, y comprueba si coincide con su condición if que está en $cond , si hay coincidencia guardará el contenido ( $$DESCEND ) o eliminará ( $$PRUNE ).

En el ejemplo anterior, el primer $match devuelve el conjunto de shapes enteras, y $ redact lo reduce al resultado esperado.

Tenga en cuenta que {$not:"$color"} es necesario, ya que escaneará también el documento superior, y si $redact no encuentra un campo de color en el nivel superior, esto arrojará un valor false que podría borrar todo el documento que no quiero

Precaución: esta respuesta proporciona una solución que era relevante en ese momento , antes de que se presentaran las nuevas características de MongoDB 2.2 y posteriores. Vea las otras respuestas si está usando una versión más reciente de MongoDB.

El parámetro del selector de campo está limitado a las propiedades completas. No se puede usar para seleccionar parte de una matriz, solo la matriz completa. Intenté usar el operador posicional $ , pero eso no funcionó.

La forma más fácil es simplemente filtrar las formas en el cliente .

Si realmente necesita la salida correcta directamente desde MongoDB, puede usar un map-reduce para filtrar las formas.

 function map() { filteredShapes = []; this.shapes.forEach(function (s) { if (s.color === "red") { filteredShapes.push(s); } }); emit(this._id, { shapes: filteredShapes }); } function reduce(key, values) { return values[0]; } res = db.test.mapReduce(map, reduce, { query: { "shapes.color": "red" } }) db[res.result].find() 

Mejor puede consultar en el elemento de matriz coincidente usando $slice Es útil devolver el objeto significativo en una matriz.

 db.test.find({"shapes.color" : "blue"}, {"shapes.$" : 1}) 

$slice es útil cuando conoce el índice del elemento, pero a veces desea cualquier elemento de matriz que coincida con sus criterios. Puede devolver el elemento coincidente con el operador $ .

La syntax para encontrar en mongodb es

  db..find(query, projection); 

y la segunda consulta que ha escrito, es decir

  db.test.find( {shapes: {"$elemMatch": {color: "red"}}}, {"shapes.color":1}) 

en esto, ha utilizado el operador $elemMatch en la parte de consulta, mientras que si usa este operador en la parte de proyección, obtendrá el resultado deseado. Puede escribir su consulta como

  db.users.find( {"shapes.color":"red"}, {_id:0, shapes: {$elemMatch : {color: "red"}}}) 

Esto te dará el resultado deseado.

  db.getCollection('aj').find({"shapes.color":"red"},{"shapes.$":1}) 

SALIDAS

 { "shapes" : [ { "shape" : "circle", "color" : "red" } ] } 

Gracias a JohnnyHK .

Aquí solo quiero agregar un uso más complejo.

 // Document { "_id" : 1 "shapes" : [ {"shape" : "square", "color" : "red"}, {"shape" : "circle", "color" : "green"} ] } { "_id" : 2 "shapes" : [ {"shape" : "square", "color" : "red"}, {"shape" : "circle", "color" : "green"} ] } // The Query db.contents.find({ "_id" : ObjectId(1), "shapes.color":"red" },{ "_id": 0, "shapes" :{ "$elemMatch":{ "color" : "red" } } }) //And the Result {"shapes":[ { "shape" : "square", "color" : "red" } ]} 

Solo necesita ejecutar la consulta

 db.test.find( {"shapes.color": "red"}, {shapes: {$elemMatch: {color: "red"}}}); 

salida de esta consulta es

 { "_id" : ObjectId("562e7c594c12942f08fe4192"), "shapes" : [ {"shape" : "circle", "color" : "red"} ] } 

como esperaba, dará el campo exacto de la matriz que coincide con el color: ‘rojo’.

junto con $ project, será más apropiado que otros elementos coincidentes se apalanquen junto con otros elementos en el documento.

 db.test.aggregate( { "$unwind" : "$shapes" }, { "$match" : { "shapes.color": "red" }}, {"$project":{ "_id":1, "item":1 }} )