Cómo filtrar array en subdocument con MongoDB [duplicar]


Esta pregunta ya tiene una respuesta aquí:

Tengo matriz en subdocumento como este

{
    "_id" : ObjectId("512e28984815cbfcb21646a7"),
    "list" : [
        {
            "a" : 1
        },
        {
            "a" : 2
        },
        {
            "a" : 3
        },
        {
            "a" : 4
        },
        {
            "a" : 5
        }
    ]
}

Puedo filtrar subdocumento para un > 3

Mi resultado esperado debajo

{
    "_id" : ObjectId("512e28984815cbfcb21646a7"),
    "list" : [
        {
            "a" : 4
        },
        {
            "a" : 5
        }
    ]
}

Trato de usar $elemMatch pero devuelve el primer elemento coincidente en la matriz

Mi consulta:

db.test.find( { _id" : ObjectId("512e28984815cbfcb21646a7") }, { 
    list: { 
        $elemMatch: 
            { a: { $gt:3 } 
            } 
    } 
} )

El resultado devuelve un elemento en el array

{ "_id" : ObjectId("512e28984815cbfcb21646a7"), "list" : [ { "a" : 4 } ] }

Y trato de usar agregado con $match pero no trabajo

db.test.aggregate({$match:{_id:ObjectId("512e28984815cbfcb21646a7"), 'list.a':{$gte:5}  }})

Devuelve todos los elementos en el array

{
    "_id" : ObjectId("512e28984815cbfcb21646a7"),
    "list" : [
        {
            "a" : 1
        },
        {
            "a" : 2
        },
        {
            "a" : 3
        },
        {
            "a" : 4
        },
        {
            "a" : 5
        }
    ]
}

¿Puedo filtrar el elemento en la matriz para obtener el resultado como esperar el resultado?

Author: Neil Lunn, 2013-02-27

3 answers

Usar aggregate es el enfoque correcto, pero necesita $unwind el array list antes de aplicar el $match para que pueda filtrar elementos individuales y luego usar $group para volver a unirlos:

db.test.aggregate(
    { $match: {_id: ObjectId("512e28984815cbfcb21646a7")}},
    { $unwind: '$list'},
    { $match: {'list.a': {$gt: 3}}},
    { $group: {_id: '$_id', list: {$push: '$list.a'}}})

Salidas:

{
  "result": [
    {
      "_id": ObjectId("512e28984815cbfcb21646a7"),
      "list": [
        4,
        5
      ]
    }
  ],
  "ok": 1
}

MongoDB 3.2 Actualización

A partir de la versión 3.2, puede utilizar la nueva $filter operador de agregación para hacer esto de manera más eficiente al incluir solo los elementos list que desea durante un $project:

db.test.aggregate([
    { $match: {_id: ObjectId("512e28984815cbfcb21646a7")}},
    { $project: {
        list: {$filter: {
            input: '$list',
            as: 'item',
            cond: {$gt: ['$$item.a', 3]}
        }}
    }}
])
 87
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
2015-12-08 17:30:13

La solución anterior funciona mejor si se requieren varios documentos secundarios coincidentes. el elemMatch también es muy útil si se requiere un único sub documento coincidente como salida

db.test.find({list: {$elemMatch: {a: 1}}}, {'list.$': 1})

Resultado:

{
  "_id": ObjectId("..."),
  "list": [{a: 1}]
}
 13
Author: keshav,
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-02-27 05:32:19

Use aggregation agregación de filtros

Selecciona un subconjunto de la matriz a devolver basado en el especificado condición. Devuelve un array con solo los elementos que coinciden con condición. Los elementos devueltos están en el orden original.

db.test.aggregate([
    {$match: {"list.a": {$gt:3}}}, // <-- match only the document which have a matching element
    {$project: {
        list: {$filter: {
            input: "$list",
            as: "list",
            cond: {$gt: ["$$list.a", 3]} //<-- filter sub-array based on condition
        }}
    }}
]);
 5
Author: Rahul,
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-03-18 15:48:42