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