Det du saknar här är $lookup
producerar en "array" i utdatafältet specificerat av as
i sina argument. Detta är det allmänna konceptet för MongoDB "relationer", i det att en "relation" mellan dokument representeras som en "underegenskap" som är "inom" själva dokumentet, och är antingen singular eller en "array" för många.
Eftersom MongoDB är "schemaless", det allmänna antagandet om $lookup
är att du menar "många" och resultatet är därför "alltid" en array. Så letar du efter "samma resultat som i SQL" måste du $unwind
den arrayen efter $lookup
. Om det är "en" eller "många" spelar ingen roll, eftersom det fortfarande "alltid" är en 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 } }
])
Här måste du notera de andra sakerna du saknar i översättningen:
Sekvensen är "viktig"
Aggregationspipelines är mer "uttrycksfulla" än SQL. De anses faktiskt bäst som "en sekvens av steg" tillämpas på datakällan för att sammanställa och transformera data. Den bästa analogen till detta är "piped" kommandoradsinstruktioner, såsom:
ps -ef | grep mongod | grep -v grep | awk '{ print $1 }'
Där "röret" |
kan betraktas som ett "pipeline-steg" i en MongoDB-aggregations-"pipeline".
Som sådana vill vi $match
för att filtrera saker från "källa"-samlingen som vår första operation. Och detta är generellt sett god praxis eftersom det tar bort alla dokument som inte uppfyllde kraven från ytterligare villkor. Precis som vad som händer i vårt "command line pipe"-exempel, där vi tar "input" och sedan "pipe" till en grep
för att "ta bort" eller "filtrera".
Vägarna är viktiga
Där nästa sak du gör här är att "gå med" via $lookup
. Resultatet är en "array" av objekten från "from"
samlingsargument som matchas av de angivna fälten för att matas ut i "as"
"fältsökväg" som en "matris".
Namnet som valts här är viktigt, eftersom nu "dokumentet" från källsamlingen anser att alla objekt från "join" nu finns på den givna vägen. För att göra detta enkelt använder jag samma "samlings" namn som "join" för den nya "sökvägen".
Så från första "join" är utdata till "tb2"
och som kommer att hålla alla resultat från den insamlingen. Det finns också en viktig sak att notera med följande sekvens av $unwind
och sedan $match
, om hur MongoDB faktiskt bearbetar frågan.
Vissa sekvenser spelar "verkligen" roll
Eftersom det "ser ut som" finns det "tre" pipeline-steg, nämligen $lookup
sedan $unwind
och sedan $match
. Men i "faktum" gör MongoDB verkligen något annat, vilket visas i utdata från { "explain": true }
läggs till i .aggregate()
kommando:
{
"$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
}
}
}
},
Så bortsett från den första punkten i "sekvens" som gäller där du behöver sätta $match
uttalanden där de behövs och gör "mest nytta", detta blir faktiskt "riktigt viktigt" med begreppet "ansluter sig". Saken att notera här är att våra sekvenser av $lookup
sedan $unwind
och sedan $match
, faktiskt bearbetas av MongoDB som bara $lookup
steg, med de andra operationerna "upprullade" till ett pipelinesteg för varje.
Detta är en viktig skillnad mot andra sätt att "filtrera" resultaten som erhålls av $lookup
. Eftersom i det här fallet, de faktiska "query"-villkoren på "join" från $match
utförs på samlingen för att gå med "innan" resultaten returneras till föräldern.
Detta i kombination med $unwind
(som översätts till unwinding
) som visas ovan är hur MongoDB faktiskt hanterar möjligheten att "join" kan resultera i att en mängd innehåll produceras i källdokumentet som gör att det överskrider 16MB BSON-gränsen. Detta skulle bara hända i fall där resultatet som kopplas till är mycket stort, men samma fördel är där "filtret" faktiskt tillämpas, att vara på målsamlingen "innan" resultat returneras.
Det är den typen av hantering som bäst "korrelerar" till samma beteende som en SQL JOIN. Det är därför också det mest effektiva sättet att få resultat från en $lookup
där det finns andra villkor att gälla för JOIN förutom bara de "lokala" av "utländska" nyckelvärden.
Observera också att den andra beteendeförändringen är från vad som i huvudsak är en LEFT JOIN utförd av $lookup
där "källdokumentet" alltid skulle behållas oavsett förekomsten av ett matchande dokument i "mål"-samlingen. Istället $unwind
lägger till detta genom att "kassera" alla resultat från "källan" som inte hade något som matchade "målet" enligt de ytterligare villkoren i $match
.
Faktum är att de till och med kasseras i förväg på grund av den underförstådda
preserveNullAndEmptyArrays: false
som ingår och skulle kassera allt där de "lokala" och "utländska" nycklarna inte ens matchade mellan de två samlingarna. Detta är bra för den här typen av fråga eftersom "join" är avsett att vara "lika" på dessa värden.
Avsluta
Som nämnts tidigare behandlar MongoDB generellt "relationer" mycket annorlunda än hur du skulle använda en "Relational Database" eller RDBMS. Det allmänna konceptet "relationer" är i själva verket att "bädda in" data, antingen som en enskild egenskap eller som en array.
Du kanske faktiskt vill ha sådan utdata, vilket också är en del av anledningen till att det utan $unwind
sekvens här utdata från $lookup
är faktiskt en "array". Men med $unwind
i detta sammanhang är faktiskt det mest effektiva att göra, samt ger en garanti för att den "anslutna" data faktiskt inte orsakar att ovannämnda BSON-gräns överskrids som ett resultat av den "anslutna".
Om du verkligen vill ha matriser av utdata, då är det bästa du kan göra här att använda $group
pipeline-steg, och möjligen som flera steg för att "normalisera" och "ångra resultaten" av $unwind
{ "$group": {
"_id": "$_id",
"tb1_field": { "$first": "$tb1_field" },
"tb1_another": { "$first": "$tb1_another" },
"tb3": { "$push": "$tb3" }
}}
Där du faktiskt i det här fallet skulle lista alla fält du behövde från "tb1"
genom deras egenskapsnamn med $first
att bara behålla den "första" förekomsten (i huvudsak upprepad av resultaten av "tb2"
och "tb3"
unwound ) och sedan $push
"detaljen" från "tb3"
till en "array" för att representera relationen till "tb1"
.
Men den generella formen av aggregeringspipelinen som ges är den exakta representationen av hur resultat skulle erhållas från den ursprungliga SQL, som är "denormaliserad" utdata som ett resultat av "join". Om du vill "normalisera" resultaten igen efter detta är upp till dig.