Det du mangler her er $lookup
producerer et "array" i outputfeltet specificeret af as
i sine argumenter. Dette er det generelle koncept for MongoDB "relationer", idet en "relation" mellem dokumenter er repræsenteret som en "underegenskab", der er "inden for" selve dokumentet, idet den enten er ental eller en "array" for mange.
Da MongoDB er "skemaløs", den generelle formodning om $lookup
er, at du mener "mange", og resultatet er derfor "altid" et array. Så leder du efter det "samme resultat som i SQL", så skal du $unwind
det array efter $lookup
. Om det er "en" eller "mange" er uden betydning, da det stadig "altid" er en matrix:
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 } }
])
Her skal du notere de andre ting, du mangler i oversættelsen:
Sekvensen er "vigtig"
Aggregationspipelines er mere "præcise udtryksfulde" end SQL. De betragtes faktisk bedst som "en sekvens af trin" anvendt på datakilden for at sammensætte og transformere dataene. Den bedste analog til dette er "piped" kommandolinjeinstruktioner, såsom:
ps -ef | grep mongod | grep -v grep | awk '{ print $1 }'
Hvor "røret" |
kan betragtes som et "pipeline-stadium" i en MongoDB-aggregations-"pipeline".
Som sådan ønsker vi at $match
for at filtrere ting fra "kilde"-samlingen som vores første operation. Og dette er generelt god praksis, da det fjerner alle dokumenter, der ikke opfyldte de krævede betingelser, fra yderligere betingelser. Ligesom hvad der sker i vores "command line pipe" eksempel, hvor vi tager "input" og derefter "pipe" til en grep
for at "fjerne" eller "filtrere".
Stier betyder noget
Hvor det næste du gør her er at "join" via $lookup
. Resultatet er en "array" af elementerne fra "from"
samlingsargument matchet af de medfølgende felter til output i "as"
"feltsti" som et "array".
Navngivningen, der er valgt her, er vigtig, da "dokumentet" fra kildesamlingen nu betragter alle elementer fra "sammenføjningen" for at eksistere på den givne vej. For at gøre dette nemt bruger jeg det samme "samlings" navn som "join" for den nye "sti".
Så fra den første "join" er outputtet til "tb2"
og det vil indeholde alle resultaterne fra den samling. Der er også en vigtig ting at bemærke med følgende sekvens af $unwind
og derefter $match
, om hvordan MongoDB faktisk behandler forespørgslen.
Visse sekvenser betyder "virkelig"
Da det "ligner" er der "tre" pipeline-stadier, nemlig $lookup
derefter $unwind
og derefter $match
. Men i "faktisk" gør MongoDB virkelig noget andet, hvilket er demonstreret i outputtet af { "explain": true }
tilføjet til .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å bortset fra det første punkt i "sekvens", der gælder, hvor du skal placere $match
udsagn, hvor de er nødvendige og gør "mest gavn", dette bliver faktisk "rigtig vigtigt" med begrebet "joins". Det, der skal bemærkes her, er, at vores sekvenser af $lookup
derefter $unwind
og derefter $match
, bliver faktisk behandlet af MongoDB som blot $lookup
trin, med de andre operationer "rullet op" til én pipeline-fase for hver.
Dette er en vigtig forskel fra andre måder at "filtrere" resultaterne opnået ved $lookup
. Da i dette tilfælde, de faktiske "forespørgsel"-betingelser på "join" fra $match
udføres på samlingen for at deltage "før" resultaterne returneres til forælderen.
Dette i kombination med $unwind
(som er oversat til unwinding
) som vist ovenfor er, hvordan MongoDB faktisk håndterer muligheden for, at "join" kunne resultere i at producere en række indhold i kildedokumentet, som får det til at overskride 16MB BSON-grænsen. Dette ville kun ske i tilfælde, hvor resultatet, der sammenføjes, er meget stort, men den samme fordel er, hvor "filteret" faktisk anvendes, idet det er på målsamlingen "før" resultaterne returneres.
Det er den slags håndtering, der bedst "korrelerer" til den samme adfærd som en SQL JOIN. Det er derfor også den mest effektive måde at opnå resultater fra en $lookup
hvor der er andre betingelser, der skal gælde for JOIN bortset fra blot de "lokale" af "fremmede" nøgleværdier.
Bemærk også, at den anden adfærdsændring er, hvad der i det væsentlige er en LEFT JOIN udført af $lookup
hvor "kilde"-dokumentet altid ville blive bibeholdt uanset tilstedeværelsen af et matchende dokument i "mål"-samlingen. I stedet for $unwind
tilføjer til dette ved at "kassere" alle resultater fra "kilden", som ikke havde noget, der matchede fra "målet" af de yderligere betingelser i $match
.
Faktisk er de endda kasseret på forhånd på grund af de underforståede preserveNullAndEmptyArrays: false
som er inkluderet og ville kassere alt, hvor de "lokale" og "fremmede" nøgler ikke engang matchede mellem de to samlinger. Dette er en god ting for denne særlige type forespørgsel, da "join" er beregnet til "lige" på disse værdier.
Afslut
Som nævnt før behandler MongoDB generelt "relationer" meget anderledes end, hvordan du ville bruge en "Relational Database" eller RDBMS. Det generelle koncept for "relationer" er faktisk "indlejring" af data, enten som en enkelt egenskab eller som en matrix.
Du kan faktisk ønske et sådant output, hvilket også er en del af grunden til, at det uden $unwind
sekvens her outputtet af $lookup
er faktisk en "array". Men ved at bruge $unwind
i denne sammenhæng er faktisk den mest effektive ting at gøre, samt at give en garanti for, at de "joined" data faktisk ikke forårsager, at den førnævnte BSON-grænse overskrides som følge af den "join".
Hvis du rent faktisk vil have arrays af output, så ville den bedste ting at gøre her være at bruge $group
pipeline-fase, og muligvis som flere faser for at "normalisere" og "fortryde resultaterne" af $unwind
{ "$group": {
"_id": "$_id",
"tb1_field": { "$first": "$tb1_field" },
"tb1_another": { "$first": "$tb1_another" },
"tb3": { "$push": "$tb3" }
}}
Hvor du faktisk i dette tilfælde ville liste alle de felter, du krævede fra "tb1"
ved deres ejendomsnavne ved hjælp af $first
kun at beholde den "første" forekomst (i det væsentlige gentaget af resultaterne af "tb2"
og "tb3"
unwound ) og derefter $push
"detaljen" fra "tb3"
ind i et "array" for at repræsentere relationen til "tb1"
.
Men den generelle form for aggregeringspipelinen som givet er den nøjagtige repræsentation af, hvordan resultater ville blive opnået fra den originale SQL, som er "denormaliseret" output som et resultat af "join". Om du vil "normalisere" resultaterne igen efter dette er op til dig.