MongoDB
 sql >> Base de Dados >  >> NoSQL >> MongoDB

Como aderir a duas coleções adicionais com condições


O que está faltando aqui é que $lookup produz um "array" no campo de saída especificado por as em seus argumentos. Este é o conceito geral de "relações" do MongoDB, em que uma "relação" entre documentos é representada como uma "subpropriedade" que está "dentro" do próprio documento, sendo singular ou um "array" para muitos.

Como o MongoDB é "sem esquema", a presunção geral de $lookup é que você quer dizer "muitos" e o resultado é, portanto, "sempre" uma matriz. Então, procurando o "mesmo resultado que no SQL", você precisa $unwind esse array após o $lookup . Se é "um" ou "muitos" não tem importância, pois ainda é "sempre" uma matriz:
db.getCollection.('tb1').aggregate([
  // Filter conditions from the source collection
  { "$match": { "status": { "$ne": "closed" } }},

  // Do the first join
  { "$lookup": {
    "from": "tb2",
    "localField": "id",
    "foreignField": "profileId",
    "as": "tb2"
  }},

  // $unwind the array to denormalize
  { "$unwind": "$tb2" },

  // Then match on the condtion for tb2
  { "$match": { "tb2.profile_type": "agent" } },

  // join the second additional collection
  { "$lookup": {
    "from": "tb3",
    "localField": "tb2.id",
    "foreignField": "id",
    "as": "tb3"
  }},

  // $unwind again to de-normalize
  { "$unwind": "$tb3" },

  // Now filter the condition on tb3
  { "$match": { "tb3.status": 0 } },

  // Project only wanted fields. In this case, exclude "tb2"
  { "$project": { "tb2": 0 } }
])

Aqui você precisa observar as outras coisas que estão faltando na tradução:

A sequência é "importante"


Os pipelines de agregação são mais "concisos" que o SQL. Na verdade, eles são mais bem considerados como "uma sequência de etapas" aplicado à fonte de dados para agrupar e transformar os dados. O melhor análogo a isso são as instruções de linha de comando "piped", como:
ps -ef  | grep mongod | grep -v grep | awk '{ print $1 }'

Onde o "pipe" | pode ser considerado como um "pipeline stage" em um "pipeline" de agregação do MongoDB.

Como tal, queremos $match para filtrar coisas da coleção "source" como nossa primeira operação. E isso geralmente é uma boa prática, pois remove quaisquer documentos que não atendam às condições exigidas de outras condições. Assim como o que está acontecendo em nosso exemplo "command line pipe", onde levamos "input" e depois "pipe" para um grep para "remover" ou "filtrar".

Os caminhos são importantes


Onde a próxima coisa que você faz aqui é "juntar-se" via $lookup . O resultado é um "array" dos itens do "from" argumento de coleção correspondido pelos campos fornecidos para saída no "as" "caminho de campo" como uma "matriz".

A nomenclatura escolhida aqui é importante, pois agora o "documento" da coleção de origem considera que todos os itens da "junção" agora existem naquele caminho determinado. Para facilitar isso, uso o mesmo nome de "coleção" que o "join" para o novo "caminho".

Então, a partir do primeiro "join", a saída é "tb2" e que conterá todos os resultados dessa coleção. Há também uma coisa importante a ser observada com a seguinte sequência de $unwind e então $match , sobre como o MongoDB realmente processa a consulta.

Certas sequências "realmente" importam


Como "parece" existem "três" estágios de pipeline, sendo $lookup então $unwind e então $match . Mas, de fato, o MongoDB realmente faz outra coisa, o que é demonstrado na saída de { "explain": true } adicionado ao .aggregate() comando:
    {
        "$lookup" : {
            "from" : "tb2",
            "as" : "tb2",
            "localField" : "id",
            "foreignField" : "profileId",
            "unwinding" : {
                "preserveNullAndEmptyArrays" : false
            },
            "matching" : {
                "profile_type" : {
                    "$eq" : "agent"
                }
            }
        }
    }, 
    {
        "$lookup" : {
            "from" : "tb3",
            "as" : "tb3",
            "localField" : "tb2.id",
            "foreignField" : "id",
            "unwinding" : {
                "preserveNullAndEmptyArrays" : false
            },
            "matching" : {
                "status" : {
                    "$eq" : 0.0
                }
            }
        }
    }, 

Então, além do primeiro ponto de aplicação de "sequência", onde você precisa colocar o $match declarações onde eles são necessários e fazem o "mais bem", isso realmente se torna "realmente importante" com o conceito de "junções". A coisa a notar aqui é que nossas sequências de $lookup então $unwind e então $match , na verdade são processados ​​pelo MongoDB como apenas o $lookup estágios, com as outras operações "enroladas" em um estágio de pipeline para cada um.

Esta é uma distinção importante para outras formas de "filtrar" os resultados obtidos pelo $lookup . Como neste caso, as condições reais de "consulta" no "join" de $match são executados na coleção para unir "antes" que os resultados sejam retornados ao pai.

Isso em combinação com $unwind (que é traduzido em unwinding ) como mostrado acima é como o MongoDB realmente lida com a possibilidade de que a "junção" possa resultar na produção de uma matriz de conteúdo no documento de origem que faz com que ele exceda o limite de 16 MB BSON. Isso só aconteceria nos casos em que o resultado que está sendo unido é muito grande, mas a mesma vantagem está em onde o "filtro" é realmente aplicado, estando na coleção de destino "antes" de os resultados serem retornados.

É esse tipo de manipulação que melhor "se correlaciona" com o mesmo comportamento de um SQL JOIN. Portanto, também é a maneira mais eficaz de obter resultados de um $lookup onde há outras condições a serem aplicadas ao JOIN além de simplesmente o "local" de valores de chave "estrangeiros".

Observe também que a outra mudança de comportamento é do que é essencialmente um LEFT JOIN realizado por $lookup onde o documento "origem" sempre seria retido, independentemente da presença de um documento correspondente na coleção "destino". Em vez disso, o $unwind adiciona a isso "descartando" quaisquer resultados da "fonte" que não tenham nada correspondente do "destino" pelas condições adicionais em $match .

Na verdade, eles são descartados de antemão devido ao preserveNullAndEmptyArrays: false implícito que está incluído e descartaria qualquer coisa em que as chaves "local" e "estrangeira" nem sequer correspondessem entre as duas coleções. Isso é bom para esse tipo específico de consulta, pois a "junção" destina-se ao "igual" desses valores.

Concluir


Como observado anteriormente, o MongoDB geralmente trata "relações" de maneira muito diferente de como você usaria um "Banco de Dados Relacional" ou RDBMS. O conceito geral de "relações" é de fato "incorporar" os dados, seja como uma única propriedade ou como um array.

Você pode realmente desejar essa saída, o que também é parte do motivo pelo qual isso sem o $unwind sequencia aqui a saída de $lookup é na verdade uma "matriz". No entanto, usando $unwind neste contexto é realmente a coisa mais eficaz a fazer, além de garantir que os dados "juntados" não façam com que o limite de BSON mencionado seja excedido como resultado dessa "junção".

Se você realmente quer arrays de saída, então a melhor coisa a fazer aqui seria usar o $group estágio de pipeline e possivelmente como vários estágios para "normalizar" e "desfazer os resultados" de $unwind
  { "$group": {
    "_id": "$_id",
    "tb1_field": { "$first": "$tb1_field" },
    "tb1_another": { "$first": "$tb1_another" },
    "tb3": { "$push": "$tb3" }    
  }}

Onde você, de fato, para este caso, listaria todos os campos necessários de "tb1" por seus nomes de propriedade usando $first para manter apenas a "primeira" ocorrência (repetida essencialmente pelos resultados de "tb2" e "tb3" unwound ) e então $push o "detalhe" de "tb3" em um "array" para representar a relação com "tb1" .

Mas a forma geral do pipeline de agregação, conforme fornecido, é a representação exata de como os resultados seriam obtidos do SQL original, que é uma saída "desnormalizada" como resultado da "junção". Se você deseja "normalizar" os resultados novamente depois disso, é com você.