MongoDB
 sql >> Database >  >> NoSQL >> MongoDB

Come aderire a due raccolte aggiuntive con condizioni

Quello che ti manca qui è che $lookup produce un "array" nel campo di output specificato da as nelle sue argomentazioni. Questo è il concetto generale delle "relazioni" di MongoDB, in quanto una "relazione" tra documenti è rappresentata come una "sottoproprietà" che è "all'interno" del documento stesso, essendo per molti singolare o una "matrice".

Poiché MongoDB è "schemaless", la presunzione generale di $lookup è che intendi "molti" e il risultato è quindi "sempre" un array. Quindi, cercando lo "stesso risultato di SQL", devi $unwind quell'array dopo il $lookup . Che sia "uno" o "molti" non ha alcuna conseguenza, poiché è ancora "sempre" un array:

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

Qui devi annotare le altre cose che mancano nella traduzione:

La sequenza è "importante"

Le pipeline di aggregazione sono più "concisamente espressive" di SQL. In effetti, sono meglio considerati come "una sequenza di passaggi" applicato all'origine dati per raccogliere e trasformare i dati. Il miglior analogo a questo sono le istruzioni della riga di comando "convogliate", come:

ps -ef  | grep mongod | grep -v grep | awk '{ print $1 }'

Dove il "tubo" | può essere considerato come una "fase della pipeline" in una "pipeline" di aggregazione MongoDB.

Pertanto, vogliamo $match per filtrare le cose dalla raccolta "source" come prima operazione. E questa è generalmente una buona pratica poiché rimuove tutti i documenti che non soddisfano le condizioni richieste da ulteriori condizioni. Proprio come quello che sta succedendo nel nostro esempio "command line pipe", dove prendiamo "input" e poi "pipe" in un grep per "rimuovere" o "filtrare".

I percorsi contano

Dove la prossima cosa che fai qui è "unirti" tramite $lookup . Il risultato è un "array" degli elementi da "from" argomento di raccolta corrispondente ai campi forniti per l'output nel "as" "percorso campo" come "array".

La denominazione scelta qui è importante, poiché ora il "documento" dalla raccolta di origine considera tutti gli elementi del "join" esistenti in quel determinato percorso. Per semplificare, utilizzo lo stesso nome di "raccolta" del "join" per il nuovo "percorso".

Quindi a partire dal primo "join" l'output è "tb2" e questo conterrà tutti i risultati di quella raccolta. C'è anche una cosa importante da notare con la seguente sequenza di $unwind e poi $match , su come MongoDB elabora effettivamente la query.

Alcune sequenze contano "realmente"

Dato che "sembra" ci sono "tre" fasi della pipeline, essendo $lookup quindi $unwind e poi $match . Ma in "fatti" MongoDB fa davvero qualcos'altro, che è dimostrato nell'output di { "explain": true } aggiunto a .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
                }
            }
        }
    }, 

Quindi, a parte il primo punto di "sequenza" dell'applicazione dove devi mettere il $match affermazioni dove sono necessarie e fanno il "più buono", questo diventa effettivamente "molto importante" con il concetto di "unisce". La cosa da notare qui è che le nostre sequenze di $lookup quindi $unwind e poi $match , in realtà viene elaborato da MongoDB solo come $lookup fasi, con le altre operazioni "arrotolate" in una fase della pipeline per ciascuna.

Questa è un'importante distinzione rispetto ad altri modi di "filtrare" i risultati ottenuti da $lookup . Poiché in questo caso, le effettive condizioni di "interrogazione" sul "join" da $match vengono eseguiti sulla raccolta per unirsi "prima" che i risultati vengano restituiti al genitore.

Questo in combinazione con $unwind (che è tradotto in unwinding ) come mostrato sopra, è il modo in cui MongoDB affronta effettivamente la possibilità che il "join" possa comportare la produzione di una matrice di contenuto nel documento di origine che fa sì che superi il limite BSON di 16 MB. Ciò accadrebbe solo nei casi in cui il risultato a cui si unisce è molto grande, ma lo stesso vantaggio è nel punto in cui viene effettivamente applicato il "filtro", trovandosi sulla raccolta di destinazione "prima" che vengano restituiti i risultati.

È quel tipo di gestione che meglio "correla" allo stesso comportamento di SQL JOIN. È quindi anche il modo più efficace per ottenere risultati da una $lookup dove ci sono altre condizioni da applicare al JOIN oltre ai semplici valori chiave "locali" o "stranieri".

Nota anche che l'altra modifica del comportamento deriva da ciò che è essenzialmente un LEFT JOIN eseguito da $lookup dove il documento "fonte" verrebbe sempre conservato indipendentemente dalla presenza di un documento corrispondente nella raccolta "destinazione". Invece il $unwind aggiunge a questo "scartando" qualsiasi risultato dalla "sorgente" che non aveva nulla che corrispondesse al "target" per le condizioni aggiuntive in $match .

In effetti vengono persino scartati in anticipo a causa del implicito preserveNullAndEmptyArrays: false che è incluso e scarterebbe tutto ciò in cui le chiavi "locale" e "estranea" non corrispondevano nemmeno tra le due raccolte. Questa è una buona cosa per questo particolare tipo di query in quanto "join" è inteso come "uguale" su quei valori.

Concludi

Come notato prima, MongoDB generalmente tratta le "relazioni" in modo molto diverso da come useresti un "database relazionale" o RDBMS. Il concetto generale di "relazioni" è infatti "incorporare" i dati, sia come una singola proprietà che come un array.

Potresti effettivamente desiderare tale output, che è anche parte del motivo per cui ciò senza il $unwind sequenzia qui l'output di $lookup è in realtà un "array". Comunque usando $unwind in questo contesto è effettivamente la cosa più efficace da fare, oltre a garantire che i dati "uniti" non comportino effettivamente il superamento del suddetto limite BSON a seguito di tale "unione".

Se vuoi davvero degli array di output, la cosa migliore da fare qui sarebbe usare il $group fase della pipeline, e possibilmente come più fasi per "normalizzare" e "annullare i risultati" di $unwind

  { "$group": {
    "_id": "$_id",
    "tb1_field": { "$first": "$tb1_field" },
    "tb1_another": { "$first": "$tb1_another" },
    "tb3": { "$push": "$tb3" }    
  }}

Dove in effetti per questo caso elencheresti tutti i campi che hai richiesto da "tb1" dai nomi delle loro proprietà usando $first per mantenere solo la "prima" occorrenza ( essenzialmente ripetuta dai risultati di "tb2" e "tb3" unwound ) e poi $push il "dettaglio" da "tb3" in un "array" per rappresentare la relazione con "tb1" .

Ma la forma generale della pipeline di aggregazione come data è la rappresentazione esatta di come i risultati sarebbero ottenuti dall'SQL originale, che è un output "denormalizzato" come risultato del "join". Se vuoi "normalizzare" di nuovo i risultati dopo questo dipende da te.