Cómo filtrar la matriz en el subdocumento con MongoDB

Tengo una matriz en un subdocumento como este

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

¿Puedo filtrar subdocumento para a> 3

Mi resultado esperado a continuación

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

Intento 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 matriz

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

y trato de usar agregado con $match pero no funciona

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

Se devuelve todo el elemento en matriz

 { "_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 esperado?

Usar aggregate es el enfoque correcto, pero necesita $unwind el conjunto de la list antes de aplicar el $match para poder filtrar elementos individuales y luego usar $group para volver a armarlo:

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

productos:

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

Actualización de MongoDB 3.2

Comenzando con la versión 3.2, puede usar el nuevo operador de agregación $filter para hacer esto de manera más eficiente al solo incluir los elementos de la 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]} }} }} ]) 

La solución anterior funciona mejor si se requieren múltiples sub documentos coincidentes. $ elemMatch también es muy útil si se requiere un solo documento secundario coincidente como resultado

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

Resultado:

 { "_id": ObjectId("..."), "list": [{a: 1}] } 

Use $ filter aggregation

Selecciona un subconjunto de la matriz para devolver en función de la condición especificada. Devuelve una matriz con solo aquellos elementos que coinciden con la 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 }} }} ]);