[nodebr] [off] Ajuda com map de collection (MongoDB)

57 views
Skip to first unread message

Ricardo do Valle

unread,
May 8, 2015, 11:13:08 AM5/8/15
to nod...@googlegroups.com

Pessoal, por gentileza alguém poderia me ajudar, tenho a seguinte collection people:

{"name" : "Arthur", "vehicles": [ {"license" : "AAA1111"} ,  {"license" : "BBB222"}]}
{"name" : "Jose"  , "vehicles": [ {"license" : "CCC3333"}]}

E queria obter uma lista só das license, por exemplo : ["AAA1111", "BBB222", "CCC3333" ]

Qual seria a melhor de fazer?
É possível fazer diretamente no MongoDB ou teria que obter todos os dados e trabalhar neles?

Muito obrigado por qualquer ajuda ou documentação,

Diego Dias

unread,
May 8, 2015, 11:24:16 AM5/8/15
to nod...@googlegroups.com
Ricardo,

Você pode fazer algo como, supondo que você esteja usando Mongoose.
model.find({}).select('vehicles.license');

Mas se for usando MongoDB direto:
model.find({},{'vehicles.license': 1);


Se você quiser saber mais da uma olhada no site do mongodb porque a documentação deles é muito boa.

[]'s


--
Você recebeu essa mensagem porque está inscrito no grupo "Node.js Brasil" dos Grupos do Google.
Para cancelar inscrição nesse grupo e parar de receber e-mails dele, envie um e-mail para nodebr+un...@googlegroups.com.
Para mais opções, acesse https://groups.google.com/d/optout.



--

Diego Dias
Analista Desenvolvedor
Microsoft Certified Applications Developer
Microsoft Certified Technology Specialist WCF .Net 4.0 -
Charter Member
11-8931.1768

Alan Hoffmeister

unread,
May 8, 2015, 11:29:17 AM5/8/15
to nod...@googlegroups.com
Acho que vc pode usar tanto o .mapReduce quanto o .aggregate. Seria interessante deixar o MongoDB gerenciar isso pois transferir os dados e depois manipulá-los vai interferir na performance em geral.

--

Pedro Yamada

unread,
May 8, 2015, 12:35:46 PM5/8/15
to nod...@googlegroups.com
Como o Alan mencionou, você pode fazer com o aggregate. É um bem mais
rápido do que o mapReduce, mas ainda é lento. Idealmente se você
precisar da lista completa das placas, não faça isso em uma rota ou
tenha um cache ou outro documento que é atualizado pela aplicação (ou
uma rotina periódica). Isso é porque fazer um `aggregate` sobre uma
coleção inteira vai percorrer todos os documentos e o array de placas
de cada um.

Vou tentar explicar por cima como fazer isso com o aggregate. O
aggregate trabalha com a ideia de um pipeline. Você chama
`db.collection.aggregate([step1, step2, step3])` e cada passo recebe
os resultados do anterior, sendo que o primeiro recebe os documentos
na collection. Por padrão, as etapas são executadas sem tocar no file
system e se sua collection for grande você pode ter problemas, já que
todos seus resultados precisaram caber na memória.

Essa é uma query um pouco complexa e *muito* lenta. Normalmente, você
pode só usar o `$addToSet` da etapa `$group` do aggregate. A etapa
`$group` serve para criar grupos dos seus documentos e acumular
propriedades. No seu caso ela ajudaria a encontrar um set de
propriedades, mas pode fazer outras coisas, como calcular uma soma ou
média de todos os valores de um field.

O `$addToSet` é um operador que também funciona com
`db.collection.update` e adiciona um item a um array se ele já não
estiver presente. Se seus documentos tivessem essa estrutura mais
simples:
```
{ "name": "nome", "license": "placa" }
```

Bastaria fazer:
```
db.people.aggregate([
{
$group: {
_id: null,
licenses: { '$addToSet': '$license' }
}
}
])
```

Que daria a saída:
```
{ "_id": null, "licenses": [...] }
```

A ideia é que nós vamos criar grupos que tenham um mesmo valor para
"_id" (como só queremos um grupo `_id` é `null`), onde `licenses` vai
ser um set de todos os fields `license`. Além dos operadores, o
aggregate tem essa sintaxe '$fieldName' para pegar o valor de um field
da sua entrada.

No seu caso, as coisas ficam mais complicadas, porque até onde eu sei,
não é possível adicionar todos os elementos de um array para um set
durante um aggregate com esse operador (em um update, é possível fazer
isso usando o operador `$each`). Por isso, você precisa usar a
`$unwind` do aggregate.

A etapa `$unwind` serve justamente para fazer o MongoDB tratar cada
sub-documento como um documento separado. No seu caso, se você só
rodar:
```
db.people.aggregate([
{
$unwind: '$vehicles'
}
])
```

Você terá essa saída:
```
{ "_id" : ObjectId("554cdbd394858216f1db4160"), "name" : "Arthur",
"vehicles" : { "license" : "AAA1111" } }
{ "_id" : ObjectId("554cdbd394858216f1db4160"), "name" : "Arthur",
"vehicles" : { "license" : "BBB222" } }
{ "_id" : ObjectId("554cdbd394858216f1db4161"), "name" : "Jose",
"vehicles" : { "license" : "CCC3333" } }
```

Para cada elemento de `vehicles`, agora há um documento diferente. Com
isso, você pode rodar o `$group` com o `$addToSet` do jeito que estava
no primeiro exemplo:
```
db.people.aggregate([
{
$unwind: '$vehicles'
},
{
$group: {
_id: null,
licenses: {
$addToSet: '$vehicles.license'
}
}
}
])
```

Isso dá a saída:
```
{ "_id" : null, "licenses" : [ "CCC3333", "BBB222", "AAA1111" ] }
```

É possível que a operação seja mais rápida se você remover os dados
desnecessários no começo do pipeline com o `$project`, mas eu mediria
as duas estratégias antes de dizer qualquer coisa. De novo, isso tudo
é bem lento dependendo do tamanho da sua collection.

Dependendo do número de documentos que você tiver, esse aggregate vai
ser mais ou menos rápido do que executar uma query como o Diego
sugeriu. Mas se você estiver usando o Node.js, por exemplo, não é
muito bom bloquear sua única thread manipulando um array que pode ser
arbitrariamente grande. Pra produção, é melhor deixar um worker em
algum lugar executando esse aggregate e construindo um cache dos
resultados.

A documentação do MongoDB é muito boa e eu recomendo o livro "MongoDB:
The Definitive Guide".

Segue um gist com o código: https://gist.github.com/4709c49271387a9a3548
Você pode rodar o setup com `mongo setup.js` e depois brincar com o
aggregate com `mongo < aggregate.js`.

On sex, 8 de mai de 2015 at 12:29 Alan Hoffmeister

Ricardo do Valle

unread,
May 8, 2015, 2:18:11 PM5/8/15
to nod...@googlegroups.com
Muito obrigado a todos,  ajudaram e muito, foi uma aula praticamente para quem esta começando e ajudará outros.

Pedro Yamada, muito obrigado mesmo, utilizei o aggregate que você passou ao final.

Abraços,

--
Você está recebendo esta mensagem porque se inscreveu no grupo "Node.js Brasil" dos Grupos do Google.

Para cancelar inscrição nesse grupo e parar de receber e-mails dele, envie um e-mail para nodebr+un...@googlegroups.com.
Para obter mais opções, acesse https://groups.google.com/d/optout.



--
Ricardo do Valle
Reply all
Reply to author
Forward
0 new messages