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.