Recuperar 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 el documento coincidente (Document 1) , pero siempre con TODOS los elementos del array en shapes:

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

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

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

¿Cómo puedo hacer esto?

Author: Neil Lunn, 2010-10-21

10 answers

Nuevo MongoDB 2.2$elemMatch operador de proyección proporciona otra forma de alterar el documento devuelto para que contenga primero elemento shapes coincidente:

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

Devuelve:

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

En 2.2 también puede hacer esto usando el $ projection operator, donde el $ en un nombre de campo de 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 arriba:

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

MongoDB 3.2 Actualización

A partir de la versión 3.2, puede utilizar la nueva $filter operador de agregación para filtrar una matriz durante la proyección, 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"
            }
        ]
    }
]
 324
Author: JohnnyHK,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-09-25 18:04:43

El nuevo Marco de agregación en MongoDB 2.2+ proporciona una alternativa a Map/Reduce. Las $unwind el operador se puede usar para separar su matriz shapes en un flujo de documentos que se pueden emparejar:

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
}
 91
Author: Stennie,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-01-28 08:41:36

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

El parámetro selector de campo está limitado a propiedades completas. No se puede usar para seleccionar parte de una matriz, solo la matriz completa. Intenté usar el operador posi 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 de 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()
 28
Author: Niels van der Rest,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-07-19 18:42:45

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 un unwind unwind que podría causarle problemas de rendimiento si tiene arreglos 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 basándose en la información almacenada en los propios documentos" . Así que solo se ejecutará dentro del documento. Básicamente escanea su documento arriba a la parte inferior, y comprueba si coincide con su if condición que está en $cond, si hay coincidencia se va a mantener el contenido($$DESCEND) o eliminar($$PRUNE).

En el ejemplo anterior, primero $match devuelve todo el array shapes, y $redact lo reduce al resultado esperado.

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

 27
Author: anvarik,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-06-04 08:31:35

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

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

$slice es útil cuando conoces el índice del elemento, pero a veces quieres cualquier elemento de matriz coincidió con sus criterios. Puede devolver el elemento correspondiente con el operador $.

 16
Author: Naren Dran,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-09-18 09:14:37

La sintaxis para buscar en mongodb es

    db.<collection name>.find(query, projection);

Y la segunda pregunta que has escrito, es

    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 utiliza 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 le dará el resultado deseado.

 11
Author: Vicky,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-01-31 08:19:11
 db.getCollection('aj').find({"shapes.color":"red"},{"shapes.$":1})

SALIDAS

{

   "shapes" : [ 
       {
           "shape" : "circle",
           "color" : "red"
       }
   ]
}
 11
Author: Viral Patel,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-12-07 06:25:02

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"
    }
]}
 7
Author: Eddy,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-05-23 11:54:50

Solo necesita ejecutar query

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

La salida de esta consulta es

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

Como esperabas, dará el campo exacto de la matriz que coincide con el color:'red'.

 5
Author: Vaibhav Patil,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-11-10 07:50:37

Junto con project project será más apropiado de otro modo los elementos coincidentes se agruparán junto con otros elementos en el documento.

db.test.aggregate(
  { "$unwind" : "$shapes" },
  { "$match" : {
     "shapes.color": "red"
  }},
{"$project":{
"_id":1,
"item":1
}}
)
 2
Author: shakthydoss,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-02-09 15:45:13