Recuperar apenas o elemento consultado em uma matriz de objects na coleção do MongoDB

Suponha que você tenha os seguintes documentos em minha coleção:

{ "_id":ObjectId("562e7c594c12942f08fe4192"), "shapes":[ { "shape":"square", "color":"blue" }, { "shape":"circle", "color":"red" } ] }, { "_id":ObjectId("562e7c594c12942f08fe4193"), "shapes":[ { "shape":"square", "color":"black" }, { "shape":"circle", "color":"green" } ] } 

Fazer consulta:

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

Ou

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

Retorna o documento correspondente (Documento 1) , mas sempre com TODOS os itens da matriz em shapes :

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

No entanto, gostaria de obter o documento (Documento 1) somente com a matriz que contém color=red :

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

Como posso fazer isso?

O novo operador de projeção $elemMatch MongoDB 2.2 fornece outra maneira de alterar o documento retornado para conter apenas o primeiro elemento de shapes correspondidas:

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

Retorna:

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

Na versão 2.2, você também pode fazer isso usando o $ projection operator , em que o $ em um nome de campo de object de projeção representa o índice do primeiro elemento de matriz correspondente do campo da consulta. O seguinte retorna os mesmos resultados acima:

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

Atualização do MongoDB 3.2

A partir da versão 3.2, você pode usar o novo operador de agregação $filter para filtrar uma matriz durante a projeção, que tem a vantagem de include todas as correspondências, em vez de apenas a primeira.

 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" } ] } ] 

O novo Aggregation Framework no MongoDB 2.2+ fornece uma alternativa para Mapear / Reduzir. O operador $unwind pode ser usado para separar sua matriz de shapes em um stream de documentos que podem ser correspondidos:

 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" }} ) 

Resulta em:

 { "result" : [ { "_id" : ObjectId("504425059b7c9fa7ec92beec"), "shapes" : { "shape" : "circle", "color" : "red" } } ], "ok" : 1 } 

Outra maneira interessante é usar $ redact , que é um dos novos resources de agregação do MongoDB 2.6 . Se você estiver usando o 2.6, não precisará de um $ unwind que possa causar problemas de desempenho se você tiver grandes matrizes.

 db.test.aggregate([ { $match: { shapes: { $elemMatch: {color: "red"} } }}, { $redact : { $cond: { if: { $or : [{ $eq: ["$color","red"] }, { $not : "$color" }]}, then: "$$DESCEND", else: "$$PRUNE" } }}]); 

$redact “restringe o conteúdo dos documentos com base nas informações armazenadas nos próprios documentos” . Então, ele será executado apenas dentro do documento . Basicamente, ele varre o documento de cima para baixo e verifica se ele corresponde à sua condição if que está em $cond . Se houver correspondência, ele manterá o conteúdo ( $$DESCEND ) ou removerá ( $$PRUNE ).

No exemplo acima, a primeira $match retorna a matriz de shapes inteira e $ redact redige até o resultado esperado.

Note que {$not:"$color"} é necessário, porque ele irá escanear o documento principal também, e se $redact não encontrar um campo de color no nível superior, isso retornará um false que pode remover todo o documento que nós não quero.

Cuidado: Esta resposta fornece uma solução que era relevante naquele momento , antes que os novos resources do MongoDB 2.2 e acima fossem introduzidos. Veja as outras respostas se você estiver usando uma versão mais recente do MongoDB.

O parâmetro do seletor de campo é limitado a propriedades completas. Não pode ser usado para selecionar parte de uma matriz, apenas a matriz inteira. Eu tentei usar o operador $ posicional , mas isso não funcionou.

A maneira mais fácil é apenas filtrar as formas no cliente .

Se você realmente precisa da saída correta diretamente do MongoDB, você pode usar um map-reduce para filtrar as 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() 

Melhor você pode consultar no elemento de matriz correspondente usando $slice é útil para retornar o object significativo em uma matriz.

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

$slice é útil quando você conhece o índice do elemento, mas às vezes você quer que o elemento da matriz corresponda aos seus critérios. Você pode retornar o elemento correspondente com o operador $ .

A syntax para encontrar no mongodb é

  db..find(query, projection); 

e a segunda consulta que você escreveu, ou seja,

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

Nesse caso, você usou o operador $elemMatch na parte de consulta, ao passo que, se você usar esse operador na parte de projeção, obterá o resultado desejado. Você pode escrever sua consulta como

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

Isso lhe dará o resultado desejado.

  db.getCollection('aj').find({"shapes.color":"red"},{"shapes.$":1}) 

SAÍDAS

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

Obrigado ao JohnnyHK .

Aqui eu só quero adicionar um uso mais complexo.

 // 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" } ]} 

Você só precisa executar a consulta

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

saída desta consulta é

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

como você esperava, ele fornece o campo exato da matriz que corresponde à cor: ‘vermelho’.

Juntamente com $ project, será mais apropriado que outros elementos correspondentes sejam agrupados com outros elementos no documento.

 db.test.aggregate( { "$unwind" : "$shapes" }, { "$match" : { "shapes.color": "red" }}, {"$project":{ "_id":1, "item":1 }} )