Qual é o operador $ unfind no MongoDB?

103

Este é meu primeiro dia com o MongoDB, então vá com calma comigo :)

Não consigo entender a $unwindoperadora, talvez porque inglês não seja minha língua nativa.

db.article.aggregate(
    { $project : {
        author : 1 ,
        title : 1 ,
        tags : 1
    }},
    { $unwind : "$tags" }
);

O operador de projeto é algo que posso entender, suponho (é tipo SELECT, não é?). Mas então, $unwind(citando) retorna um documento para cada membro da matriz desenrolada em cada documento de origem .

É como um JOIN? Se sim, como o resultado de $project(com _id, author, titlee tagscampos) pode ser comparado com a tagsmatriz?

NOTA : Peguei o exemplo do site do MongoDB, não conheço a estrutura do tagsarray. Acho que é uma simples matriz de nomes de tag.

Gremo
fonte

Respostas:

235

Em primeiro lugar, bem-vindo ao MongoDB!

A coisa a lembrar é que o MongoDB emprega uma abordagem "NoSQL" para armazenamento de dados, então elimine os pensamentos de seleções, junções, etc. de sua mente. A forma como ele armazena seus dados é na forma de documentos e coleções, o que permite um meio dinâmico de adicionar e obter os dados de seus locais de armazenamento.

Dito isso, para entender o conceito por trás do parâmetro $ unwind, primeiro você deve entender o que o caso de uso que você está tentando citar está dizendo. O documento de exemplo de mongodb.org é o seguinte:

{
 title : "this is my title" ,
 author : "bob" ,
 posted : new Date () ,
 pageViews : 5 ,
 tags : [ "fun" , "good" , "fun" ] ,
 comments : [
             { author :"joe" , text : "this is cool" } ,
             { author :"sam" , text : "this is bad" }
 ],
 other : { foo : 5 }
}

Observe como as tags são na verdade um array de 3 itens, neste caso sendo "divertido", "bom" e "divertido".

O que $ unfind faz é permitir que você retire um documento para cada elemento e retorne o documento resultante. Pensando nisso em uma abordagem clássica, seria o equivalente a "para cada item na matriz de tags, retorne um documento com apenas aquele item".

Assim, o resultado da execução do seguinte:

db.article.aggregate(
    { $project : {
        author : 1 ,
        title : 1 ,
        tags : 1
    }},
    { $unwind : "$tags" }
);

retornaria os seguintes documentos:

{
     "result" : [
             {
                     "_id" : ObjectId("4e6e4ef557b77501a49233f6"),
                     "title" : "this is my title",
                     "author" : "bob",
                     "tags" : "fun"
             },
             {
                     "_id" : ObjectId("4e6e4ef557b77501a49233f6"),
                     "title" : "this is my title",
                     "author" : "bob",
                     "tags" : "good"
             },
             {
                     "_id" : ObjectId("4e6e4ef557b77501a49233f6"),
                     "title" : "this is my title",
                     "author" : "bob",
                     "tags" : "fun"
             }
     ],
     "OK" : 1
}

Observe que a única coisa que muda na matriz de resultado é o que está sendo retornado no valor das tags. Se você precisar de uma referência adicional sobre como isso funciona, incluí um link aqui . Espero que isso ajude e boa sorte com sua incursão em um dos melhores sistemas NoSQL que encontrei até agora.

HGS Labs
fonte
44

$unwind duplica cada documento no pipeline, uma vez por elemento da matriz.

Portanto, se o pipeline de entrada contiver um documento de artigo com dois elementos em tags, {$unwind: '$tags'}o pipeline será transformado em dois documentos de artigo iguais, exceto pelo tagscampo. No primeiro doc, tagsconteria o primeiro elemento da matriz do doc original e, no segundo doc, tagsconteria o segundo elemento.

JohnnyHK
fonte
22

Vamos entender por um exemplo

É assim que o documento da empresa se parece:

documento original

O $unwindnos permite pegar documentos como entrada que têm um campo com valor de array e produzir documentos de saída, de forma que haja um documento de saída para cada elemento do array. fonte

O estágio $ unfind

Portanto, vamos voltar aos exemplos de nossas empresas e dar uma olhada no uso dos estágios de desenrolamento. Esta consulta:


db.companies.aggregate([
    { $match: {"funding_rounds.investments.financial_org.permalink": "greylock" } },
    { $project: {
        _id: 0,
        name: 1,
        amount: "$funding_rounds.raised_amount",
        year: "$funding_rounds.funded_year"
    } }
])

produz documentos que possuem matrizes para quantidade e ano.

saída do projeto

Porque estamos acessando o valor arrecadado e o ano financiado para cada elemento dentro da matriz de rodadas de financiamento. Para corrigir isso, podemos incluir um estágio de desenrolamento antes do estágio de nosso projeto neste pipeline de agregação e parametrizar isso dizendo que queremos a unwindmatriz de rodadas de financiamento:


db.companies.aggregate([
    { $match: {"funding_rounds.investments.financial_org.permalink": "greylock" } },
    { $unwind: "$funding_rounds" },
    { $project: {
        _id: 0,
        name: 1,
        amount: "$funding_rounds.raised_amount",
        year: "$funding_rounds.funded_year"
    } }
])

o desenrolamento tem o efeito de enviar para o próximo estágio mais documentos do que recebe como entrada

Se olharmos para o funding_roundsarray, sabemos que para cada um funding_roundsexiste um raised_amounte um funded_yearcampo. Portanto, unwindpara cada um dos documentos que são elementos do funding_roundsarray, será gerado um documento de saída. Agora, neste exemplo, nossos valores são strings. Porém, independente do tipo de valor para os elementos de um array, unwindirá produzir um documento de saída para cada um desses valores, de forma que o campo em questão terá apenas aquele elemento. No caso de funding_rounds, esse elemento será um desses documentos como o valor funding_roundspara cada documento que for repassado ao nosso projectestágio. O resultado, então, de ter executado isso, é que agora temos um amounte umyear . Um para cada rodada de financiamento para cada empresaem nossa coleção. O que isso significa é que nossa correspondência produziu muitos documentos da empresa e cada um desses documentos da empresa resulta em muitos documentos. Um para cada rodada de financiamento em cada documento da empresa. unwindrealiza esta operação utilizando os documentos que lhe são entregues no matchpalco. E todos esses documentos para cada empresa são então passados ​​para o projectpalco.

saída de desenrolar

Portanto, todos os documentos em que o financiador foi a Greylock (como no exemplo de consulta) serão divididos em vários documentos, igual ao número de rodadas de financiamento para cada empresa que corresponda ao filtro $match: {"funding_rounds.investments.financial_org.permalink": "greylock" }. E cada um desses documentos resultantes será então repassado ao nosso project. Agora, unwindproduz uma cópia exata para cada um dos documentos que recebe como entrada. Todos os campos têm a mesma chave e valor, com uma exceção: o funding_roundscampo, em vez de ser uma matriz de funding_roundsdocumentos, tem um valor que é um único documento, que é uma rodada de financiamento individual. Então, uma empresa que tem 4 rodadas de financiamento resultará na unwindcriação de 4documentos. Onde cada campo é uma cópia exata, exceto o valor individual e ano para cada rodada de financiamento para cada empresafunding_rounds, que em vez de ser uma matriz para cada uma dessas cópias, será um elemento individual da funding_roundsmatriz do documento da empresa que unwindestá sendo processado no momento. Portanto, e um campo. Assim, receberá vários documentos para cada empresa do filtro e poderá, portanto, processar cada um dos documentos individualmente e identificar umunwindtem o efeito de enviar para o próximo estágio mais documentos do que recebe como entrada. O que isso significa é que nosso projectestágio agora obtém um funding_roundscampo que, novamente, não é um array, mas sim um documento aninhado que possui um .raised_amountfunded_yearprojectmatch

Zameer
fonte
2
usar o mesmo documento será melhor.
Jeb50,
1
Como primeiro caso de uso para $ unfind, eu tinha um conjunto aninhado bastante complicado de conjuntos aninhados. Indo entre mongo docs e stackowerflow, sua resposta finalmente me ajudou a entender $ project e $ unbind melhor. Obrigado @Zameer!
sete de
3

De acordo com a documentação oficial do mongodb:

$ unfind Desconstrói um campo de array dos documentos de entrada para gerar um documento para cada elemento. Cada documento de saída é o documento de entrada com o valor do campo da matriz substituído pelo elemento.

Explicação por meio de exemplo básico:

Um inventário de coleção contém os seguintes documentos:

{ "_id" : 1, "item" : "ABC", "sizes": [ "S", "M", "L"] }
{ "_id" : 2, "item" : "EFG", "sizes" : [ ] }
{ "_id" : 3, "item" : "IJK", "sizes": "M" }
{ "_id" : 4, "item" : "LMN" }
{ "_id" : 5, "item" : "XYZ", "sizes" : null }

As seguintes operações $ unfind são equivalentes e retornam um documento para cada elemento no campo tamanhos . Se o campo de tamanhos não resolver para uma matriz, mas não estiver ausente, nulo ou uma matriz vazia, $ unfind tratará o operando não-matriz como uma matriz de elemento único.

db.inventory.aggregate( [ { $unwind: "$sizes" } ] )

ou

db.inventory.aggregate( [ { $unwind: { path: "$sizes" } } ] 

Acima da saída da consulta:

{ "_id" : 1, "item" : "ABC", "sizes" : "S" }
{ "_id" : 1, "item" : "ABC", "sizes" : "M" }
{ "_id" : 1, "item" : "ABC", "sizes" : "L" }
{ "_id" : 3, "item" : "IJK", "sizes" : "M" }

Por que é necessário?

$ unfind é muito útil ao realizar agregação ele quebra o documento complexo / aninhado em um documento simples antes de realizar várias operações, como classificação, pesquisa, etc.

Para saber mais sobre $ unfind:

https://docs.mongodb.com/manual/reference/operator/aggregation/unwind/

Para saber mais sobre agregação:

https://docs.mongodb.com/manual/reference/operator/aggregation-pipeline/

Amitesh Bharti
fonte
2

considere o exemplo abaixo para entender esses dados em uma coleção

{
        "_id" : 1,
        "shirt" : "Half Sleeve",
        "sizes" : [
                "medium",
                "XL",
                "free"
        ]
}

Consulta - db.test1.aggregate ([{$ unfind: "$ tamanhos"}]);

resultado

{ "_id" : 1, "shirt" : "Half Sleeve", "sizes" : "medium" }
{ "_id" : 1, "shirt" : "Half Sleeve", "sizes" : "XL" }
{ "_id" : 1, "shirt" : "Half Sleeve", "sizes" : "free" }
Pravin
fonte
1

Deixe-me explicar de uma forma correlacionada com a forma RDBMS. Esta é a declaração:

db.article.aggregate(
    { $project : {
        author : 1 ,
        title : 1 ,
        tags : 1
    }},
    { $unwind : "$tags" }
);

para aplicar ao documento / registro :

{
 title : "this is my title" ,
 author : "bob" ,
 posted : new Date () ,
 pageViews : 5 ,
 tags : [ "fun" , "good" , "fun" ] ,
 comments : [
             { author :"joe" , text : "this is cool" } ,
             { author :"sam" , text : "this is bad" }
 ],
 other : { foo : 5 }
}

O $ project / Select simplesmente retorna esses campos / colunas como

SELECIONE autor, título, tags DO artigo

A próxima é a parte divertida do Mongo, considere este array tags : [ "fun" , "good" , "fun" ]como outra tabela relacionada (não pode ser uma tabela de consulta / referência porque os valores têm alguma duplicação) chamada "tags". Lembre-se de que SELECT geralmente produz coisas verticais, então desenrolar as "tags" é dividir () verticalmente em "tags" de tabela.

O resultado final de $ project + $ unfind: insira a descrição da imagem aqui

Traduza a saída para JSON:

{ "author": "bob", "title": "this is my title", "tags": "fun"},
{ "author": "bob", "title": "this is my title", "tags": "good"},
{ "author": "bob", "title": "this is my title", "tags": "fun"}

Porque não dissemos ao Mongo para omitir o campo "_id", então ele é adicionado automaticamente.

A chave é torná-lo semelhante a uma tabela para realizar a agregação.

Jeb50
fonte
Ou outra maneira de pensar nisso é UNION ALL
Jeb50,