Wat je hier mist is dat $lookup
produceert een "array" in het uitvoerveld gespecificeerd door as
in zijn argumenten. Dit is het algemene concept van MongoDB "relaties", in die zin dat een "relatie" tussen documenten wordt weergegeven als een "sub-eigenschap" die "binnen" het document zelf is en voor velen een enkelvoud of een "array" is.
Aangezien MongoDB "schemaloos" is, is het algemene vermoeden van $lookup
is dat je "veel" bedoelt en het resultaat is daarom "altijd" een array. Dus zoek je naar het "zelfde resultaat als in SQL" dan moet je $unwind
die array na de $lookup
. Of het "één" of "veel" is, doet er niet toe, aangezien het nog steeds "altijd" een array is:
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 } }
])
Hier moet je de andere dingen die je mist in de vertaling noteren:
Volgorde is "belangrijk"
Aggregatiepijplijnen zijn "samenvattend expressiever" dan SQL. Ze kunnen in feite het best worden beschouwd als "een opeenvolging van stappen" toegepast op de gegevensbron om de gegevens te verzamelen en te transformeren. De beste analogie hiervoor zijn "doorgesluisde" opdrachtregelinstructies, zoals:
ps -ef | grep mongod | grep -v grep | awk '{ print $1 }'
Waar de "pijp" |
kan worden beschouwd als een "pijplijnfase" in een MongoDB-aggregatie-"pijplijn".
Daarom willen we $match
om dingen uit de "bron" -collectie te filteren als onze eerste operatie. En dit is over het algemeen een goede gewoonte, omdat het alle documenten die niet aan de vereiste voorwaarden voldeden, uit de verdere voorwaarden verwijdert. Net zoals wat er gebeurt in ons voorbeeld "command line pipe", waar we "input" en vervolgens "pipe" naar een grep
om te "verwijderen" of "filteren".
Paden zijn belangrijk
Het volgende dat u hier doet, is "meedoen" via $lookup
. Het resultaat is een "array" van de items uit de "from"
collectieargument dat overeenkomt met de opgegeven velden om uit te voeren in de "as"
"veldpad" als een "array".
De hier gekozen naamgeving is belangrijk, aangezien het "document" van de bronverzameling nu beschouwt dat alle items van de "join" nu op dat gegeven pad bestaan. Om dit gemakkelijk te maken, gebruik ik dezelfde "collectie"-naam als de "join" voor het nieuwe "pad".
Dus vanaf de eerste "join" is de uitvoer naar "tb2"
en dat zal alle resultaten van die verzameling bevatten. Er is ook een belangrijk ding om op te merken met de volgende reeks van $unwind
en dan $match
, over hoe MongoDB de query daadwerkelijk verwerkt.
Bepaalde reeksen zijn "echt" belangrijk
Omdat het "lijkt op" zijn er "drie" pijplijnfasen, namelijk $lookup
dan $unwind
en dan $match
. Maar in feite doet MongoDB echt iets anders, wat wordt aangetoond in de uitvoer van { "explain": true }
toegevoegd aan de .aggregate()
commando:
{
"$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
}
}
}
},
Dus afgezien van het eerste punt van "volgorde" van toepassing waar je de $match
. moet plaatsen uitspraken waar ze nodig zijn en het "meest goed" doen, dit wordt eigenlijk "heel belangrijk" met het concept van "joins". Het ding om hier op te merken is dat onze reeksen van $lookup
dan $unwind
en dan $match
, wordt eigenlijk door MongoDB verwerkt als alleen de $lookup
stadia, waarbij de andere bewerkingen "opgerold" zijn in de ene pijplijntrap voor elk.
Dit is een belangrijk onderscheid met andere manieren om de resultaten te "filteren" die worden verkregen door $lookup
. Aangezien in dit geval de feitelijke "query"-voorwaarden op de "join" van $match
worden uitgevoerd op de verzameling om mee te doen "voordat" de resultaten worden teruggestuurd naar de ouder.
Dit in combinatie met $unwind
( wat vertaald wordt in unwinding
) zoals hierboven weergegeven, is hoe MongoDB omgaat met de mogelijkheid dat de "join" zou kunnen resulteren in het produceren van een reeks inhoud in het brondocument waardoor deze de 16 MB BSON-limiet overschrijdt. Dit zou alleen gebeuren in gevallen waarin het resultaat waaraan wordt gekoppeld erg groot is, maar hetzelfde voordeel is waar het "filter" daadwerkelijk wordt toegepast, omdat het zich op de doelverzameling bevindt "voordat" resultaten worden geretourneerd.
Het is dat soort afhandeling dat het beste "correleert" met hetzelfde gedrag als een SQL JOIN. Het is daarom ook de meest effectieve manier om resultaten te verkrijgen van een $lookup
waar er andere voorwaarden van toepassing zijn op de JOIN dan alleen de "lokale" of "buitenlandse" sleutelwaarden.
Merk ook op dat de andere gedragsverandering is van wat in wezen een LEFT JOIN is, uitgevoerd door $lookup
waarbij het "bron"-document altijd zou worden bewaard, ongeacht de aanwezigheid van een overeenkomend document in de "doel"-verzameling. In plaats daarvan de $unwind
voegt hieraan toe door alle resultaten van de "bron" die niets hadden met het "doel" te "weggooien" door de aanvullende voorwaarden in $match
.
Ze worden zelfs van tevoren weggegooid vanwege de impliciete preserveNullAndEmptyArrays: false
die is inbegrepen en zou alles weggooien waar de "lokale" en "buitenlandse" sleutels niet eens overeenkwamen tussen de twee collecties. Dit is een goede zaak voor dit specifieke type zoekopdracht, aangezien de "join" bedoeld is voor de "gelijke" waarde voor die waarden.
Sluiten
Zoals eerder opgemerkt, behandelt MongoDB "relaties" over het algemeen heel anders dan hoe u een "relationele database" of RDBMS zou gebruiken. Het algemene concept van "relaties" is in feite het "inbedden" van de gegevens, hetzij als een enkele eigenschap of als een array.
Misschien wil je zo'n output, wat ook een deel van de reden is waarom dat zonder de $unwind
volg hier de uitvoer van $lookup
is eigenlijk een "array". Maar met behulp van $unwind
is in deze context eigenlijk het meest effectief en geeft tevens de garantie dat de "samengevoegde" data er niet voor zorgt dat de eerder genoemde BSON-limiet door die "join" wordt overschreden.
Als u daadwerkelijk uitvoerarrays wilt, kunt u hier het beste de $group
gebruiken pijplijnfase, en mogelijk als meerdere fasen om de resultaten van $unwind
te "normaliseren" en "ongedaan te maken"
{ "$group": {
"_id": "$_id",
"tb1_field": { "$first": "$tb1_field" },
"tb1_another": { "$first": "$tb1_another" },
"tb3": { "$push": "$tb3" }
}}
Waar je in dit geval in feite alle velden zou vermelden die je nodig hebt van "tb1"
op hun eigenschapsnamen met behulp van $first
om alleen het "eerste" voorkomen te behouden (in wezen herhaald door resultaten van "tb2"
en "tb3"
unwound ) en dan $push
het "detail" van "tb3"
in een "array" om de relatie met "tb1"
weer te geven .
Maar de algemene vorm van de aggregatiepijplijn zoals gegeven is de exacte weergave van hoe resultaten zouden worden verkregen uit de originele SQL, die "gedenormaliseerde" uitvoer is als gevolg van de "join". Of je daarna de resultaten opnieuw wilt "normaliseren" is aan jou.