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 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?
El nuevo operador de proyección de MongoDB 2.2 $elemMatch
proporciona 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 $filter
operador 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"
}
]
}
]
El nuevo marco de agregación en MongoDB 2.2+ proporciona una alternativa a Map/Reduce. El $unwind
operador se puede utilizar para separar su shapes
matriz 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
}
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 if
condición que se encuentra en $cond
; si coincide, mantendrá el contenido ( $$DESCEND
) o lo eliminará ( $$PRUNE
).
En el ejemplo anterior, primero $match
devuelve la shapes
matriz 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 $redact
no encuentra un color
campo en el nivel superior, devolverá un resultado false
que podría eliminar todo el documento que no queremos.
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()
Es mejor que pueda consultar el elemento de matriz coincidente usando $slice
si 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 que cualquier elemento de la matriz coincida con sus criterios. Puede devolver el elemento coincidente con el $
operador.
db.getCollection('aj').find({"shapes.color":"red"},{"shapes.$":1})
SALIDAS
{
"shapes" : [
{
"shape" : "circle",
"color" : "red"
}
]
}