To, czego tu brakuje, to ten $lookup
tworzy "tablicę" w polu wyjściowym określonym przez as
w jego argumentach. Jest to ogólna koncepcja „relacji” MongoDB, w której „relacja” między dokumentami jest reprezentowana jako „podwłaściwość”, która znajduje się „w” samym dokumencie, będąc albo w liczbie pojedynczej, albo jako „tablica” dla wielu.
Ponieważ MongoDB jest "bez schematu", ogólne założenie $lookup
jest to, że masz na myśli „wiele”, a wynikiem jest „zawsze” tablica. Więc szukając „tego samego wyniku, co w SQL”, musisz $unwind
ta tablica po $lookup
. Nie ma znaczenia, czy jest to „jeden”, czy „wiele”, ponieważ nadal jest to „zawsze” tablica:
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 } }
])
Tutaj musisz zanotować inne rzeczy, których brakuje w tłumaczeniu:
Sekwencja jest „ważna”
Potoki agregacji są bardziej „zwięzłe” niż SQL. W rzeczywistości najlepiej jest je traktować jako „sekwencję kroków” zastosowane do źródła danych w celu zebrania i przekształcenia danych. Najlepszym odpowiednikiem tego są instrukcje wiersza poleceń „potokowe”, takie jak:
ps -ef | grep mongod | grep -v grep | awk '{ print $1 }'
Gdzie „potok” |
można uznać za „etap potoku” w „potoku” agregacji MongoDB.
W związku z tym chcemy $match
w celu odfiltrowania rzeczy z kolekcji "source" jako naszej pierwszej operacji. I jest to generalnie dobra praktyka, ponieważ usuwa wszelkie dokumenty, które nie spełniały wymaganych warunków z dalszych warunków. Podobnie jak to, co dzieje się w naszym przykładzie „potok wiersza poleceń”, gdzie bierzemy „input”, a następnie „pipe” do grep
„usunąć” lub „przefiltrować”.
Ścieżki mają znaczenie
Następną rzeczą, którą tutaj robisz, jest „dołączanie” przez $lookup
. Wynikiem jest "tablica" elementów z "from"
argument kolekcji dopasowany do dostarczonych pól do wyprowadzenia w "as"
„ścieżka pola” jako „tablica”.
Wybrane tutaj nazewnictwo jest ważne, ponieważ teraz „dokument” z kolekcji źródłowej uwzględnia wszystkie elementy z „połączenia”, które istnieją teraz pod daną ścieżką. Aby to ułatwić, używam tej samej nazwy „kolekcji”, co „dołącz” dla nowej „ścieżki”.
Tak więc zaczynając od pierwszego "dołączenia" wyjściem jest "tb2"
i który będzie zawierał wszystkie wyniki z tej kolekcji. Należy również zwrócić uwagę na następującą sekwencję $unwind
a następnie $match
, jak MongoDB faktycznie przetwarza zapytanie.
Niektóre sekwencje „naprawdę” mają znaczenie
Ponieważ „wygląda na to”, że istnieją „trzy” etapy potoku, czyli $lookup
następnie $unwind
a następnie $match
. Ale w "fakcie" MongoDB naprawdę robi coś innego, co jest pokazane w danych wyjściowych { "explain": true }
dodane do .aggregate()
polecenie:
{
"$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
}
}
}
},
Więc pomijając pierwszy punkt "sekwencji" zastosowania, w którym musisz umieścić $match
Stwierdzenia tam, gdzie są potrzebne i robią „najbardziej dobre”, to faktycznie staje się „naprawdę ważne” z pojęciem „połączeń”. Należy zauważyć, że nasze sekwencje $lookup
następnie $unwind
a następnie $match
, faktycznie są przetwarzane przez MongoDB jako po prostu $lookup
etapy, z innymi operacjami „zwiniętymi” w jeden etap potoku dla każdego.
Jest to ważne rozróżnienie od innych sposobów "filtrowania" wyników uzyskanych przez $lookup
. Ponieważ w tym przypadku, rzeczywiste warunki "zapytanie" przy "dołączeniu" z $match
są wykonywane na kolekcji, aby dołączyć „przed” zwróceniem wyników do rodzica.
To w połączeniu z $unwind
( co jest tłumaczone na unwinding
), jak pokazano powyżej, MongoDB faktycznie radzi sobie z możliwością, że „dołączenie” może spowodować utworzenie tablicy treści w dokumencie źródłowym, która spowoduje przekroczenie limitu 16 MB BSON. Zdarzyłoby się to tylko w przypadkach, gdy wynik, do którego jest dołączony, jest bardzo duży, ale ta sama zaleta polega na tym, że faktycznie zastosowany jest „filtr”, ponieważ znajduje się w zbiorze docelowym „przed” zwróceniem wyników.
Jest to rodzaj obsługi, który najlepiej „koreluje” z tym samym zachowaniem, co SQL JOIN. Jest to również najskuteczniejszy sposób uzyskiwania wyników z $lookup
gdzie istnieją inne warunki, które należy zastosować do JOIN oprócz po prostu „lokalnych” wartości „obcych” kluczy.
Zauważ również, że inna zmiana zachowania pochodzi od tego, co jest zasadniczo LEFT JOIN wykonywanym przez $lookup
gdzie dokument „źródłowy” byłby zawsze zachowywany, niezależnie od obecności pasującego dokumentu w kolekcji „docelowej”. Zamiast tego $unwind
dodaje do tego przez "odrzucenie" wszelkich wyników ze "źródła", które nie mają niczego pasującego do "docelowego" przez dodatkowe warunki w $match
.
W rzeczywistości są one nawet wcześniej odrzucane z powodu domniemanego preserveNullAndEmptyArrays: false
który jest uwzględniony i odrzuca wszystko, w przypadku gdy klucze „lokalne” i „obce” nie pasują nawet do tych dwóch kolekcji. Jest to dobra rzecz w przypadku tego konkretnego typu zapytania, ponieważ „sprzężenie” ma na celu „równe” dla tych wartości.
Zakończ
Jak wspomniano wcześniej, MongoDB generalnie traktuje „relacje” znacznie inaczej niż w przypadku „relacyjnej bazy danych” lub RDBMS. Ogólna koncepcja "relacji" polega w rzeczywistości na "osadzeniu" danych w postaci pojedynczej właściwości lub tablicy.
Możesz rzeczywiście chcieć takich wyników, co jest również jednym z powodów, dla których bez $unwind
sekwencjonować tutaj wyjście $lookup
jest w rzeczywistości „tablicą”. Jednak używając $unwind
w tym kontekście jest to właściwie najskuteczniejsza rzecz do zrobienia, a także danie gwarancji, że „połączone” dane w rzeczywistości nie spowodują przekroczenia wspomnianego limitu BSON w wyniku tego „dołączenia”.
Jeśli rzeczywiście potrzebujesz tablic wyjściowych, najlepszą rzeczą do zrobienia tutaj byłoby użycie $group
etap potoku i prawdopodobnie jako wiele etapów w celu „normalizacji” i „cofnięcia wyników” $unwind
{ "$group": {
"_id": "$_id",
"tb1_field": { "$first": "$tb1_field" },
"tb1_another": { "$first": "$tb1_another" },
"tb3": { "$push": "$tb3" }
}}
Gdzie właściwie w tym przypadku wymieniłbyś wszystkie pola, które były wymagane w "tb1"
według ich nazw właściwości za pomocą $first
aby zachować tylko "pierwsze" wystąpienie (w zasadzie powtarzane przez wyniki "tb2"
i "tb3"
rozwiń ), a następnie $push
"szczegóły" z "tb3"
w "tablicę" reprezentującą relację do "tb1"
.
Ale ogólna forma potoku agregacji, jak podano, jest dokładną reprezentacją tego, w jaki sposób wyniki zostałyby uzyskane z oryginalnego kodu SQL, który jest „zdenormalizowany” wynikiem „połączenia”. Czy chcesz ponownie „znormalizować” wyniki po tym, jak to zależy od Ciebie.