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

Resuelto Sebtm asked hace 13 años • 20 respuestas

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 el 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?

Sebtm avatar Oct 21 '10 14:10 Sebtm
Aceptado

El nuevo operador de proyección de MongoDB 2.2 $elemMatchproporciona otra forma de modificar el documento devuelto para que contenga solo el primershapes elemento coincidente :

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 $ projection operator, donde el $nombre del campo en un objeto de proyección representa el índice del primer elemento de matriz coincidente del campo de la consulta. Lo siguiente devuelve los mismos resultados que el anterior:

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

Actualización de MongoDB 3.2

A partir de la versión 3.2, puede utilizar el nuevo $filteroperador de agregación para filtrar una matriz durante la proyección, lo que tiene la ventaja 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"
            }
        ]
    }
]
JohnnyHK avatar Sep 03 '2012 04:09 JohnnyHK

El nuevo marco de agregación en MongoDB 2.2+ proporciona una alternativa a Map/Reduce. El $unwindoperador se puede utilizar para separar su shapesmatriz en un flujo de documentos que pueden coincidir:

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
}
Stennie avatar Sep 03 '2012 03:09 Stennie

Otra forma interesante es usar $redact , que es una de las nuevas características de agregación de MongoDB 2.6 . Si está utilizando 2.6, no necesita $unwind, lo 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 en función de la información almacenada en los propios documentos" . Por lo tanto, se ejecutará sólo dentro del documento . Básicamente, escanea su documento de arriba a abajo y verifica si coincide con su ifcondición que se encuentra en $cond; si coincide, mantendrá el contenido ( $$DESCEND) o lo eliminará ( $$PRUNE).

En el ejemplo anterior, primero $matchdevuelve la shapesmatriz completa y $redact la reduce al resultado esperado.

Tenga en cuenta que {$not:"$color"}es necesario, porque también escaneará el documento superior y, si $redactno encuentra un colorcampo en el nivel superior, devolverá un resultado falseque podría eliminar todo el documento que no queremos.

anvarik avatar Jun 04 '2014 08:06 anvarik

Precaución: esta respuesta proporciona una solución que era relevante en ese momento , antes de que se introdujeran las nuevas funciones de MongoDB 2.2 y posteriores. Consulte las otras respuestas si está utilizando una versión más reciente de MongoDB.

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

La forma más sencilla 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()
Niels van der Rest avatar Oct 21 '2010 09:10 Niels van der Rest

Es mejor que pueda consultar el elemento de matriz coincidente usando $slicesi es útil devolver el objeto significativo en una matriz.

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

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

Narendran avatar Sep 18 '2014 08:09 Narendran
 db.getCollection('aj').find({"shapes.color":"red"},{"shapes.$":1})

SALIDAS

{

   "shapes" : [ 
       {
           "shape" : "circle",
           "color" : "red"
       }
   ]
}
Viral Patel avatar Dec 07 '2016 06:12 Viral Patel