JOIN jest jedną z kluczowych cech wyróżniających bazy danych SQL i NoSQL. W bazach danych SQL możemy wykonać JOIN między dwiema tabelami w tej samej lub różnych bazach danych. Jednak nie dotyczy to MongoDB, ponieważ umożliwia ona operacje JOIN między dwiema kolekcjami w tej samej bazie danych.
Sposób, w jaki dane są prezentowane w MongoDB, sprawia, że prawie niemożliwe jest powiązanie ich z jedną kolekcją do drugiej, z wyjątkiem korzystania z podstawowych funkcji zapytań skryptowych. MongoDB albo denormalizuje dane, przechowując powiązane elementy w oddzielnym dokumencie, albo łączy dane w innym oddzielnym dokumencie.
Można powiązać te dane, używając odniesień ręcznych, takich jak pole _id jednego dokumentu, który jest zapisany w innym dokumencie jako odniesienie. Niemniej jednak, aby pobrać niektóre wymagane dane, trzeba wykonać wiele zapytań, co sprawia, że proces jest nieco nużący.
Dlatego postanawiamy skorzystać z koncepcji JOIN, która ułatwia powiązanie danych. Operacja JOIN w MongoDB jest osiągana za pomocą operatora $lookup, który został wprowadzony w wersji 3.2.
$operator wyszukiwania
Główną ideą koncepcji JOIN jest uzyskanie korelacji między danymi w jednym zbiorze a drugim. Podstawowa składnia operatora $lookup to:
{
$lookup:
{
from: <collection to join>,
localField: <field from the input documents>,
foreignField: <field from the documents of the "from" collection>,
as: <output array field>
}
}
Jeśli chodzi o znajomość SQL, to zawsze wiemy, że wynikiem operacji JOIN jest osobny wiersz łączący wszystkie pola z tabeli lokalnej i obcej. W przypadku MongoDB jest to inny przypadek, ponieważ dokumenty wynikowe są dodawane jako tablica lokalnych dokumentów kolekcji. Na przykład miejmy dwie kolekcje; „studenci” i „jednostki”
studenci
{"_id" : 1,"name" : "James Washington","age" : 15.0,"grade" : "A","score" : 10.5}
{"_id" : 2,"name" : "Clinton Ariango","age" : 14.0,"grade" : "B","score" : 7.5}
{"_id" : 3,"name" : "Mary Muthoni","age" : 16.0,"grade" : "A","score" : 11.5}
Jednostki
{"_id" : 1,"Maths" : "A","English" : "A","Science" : "A","History" : "B"}
{"_id" : 2,"Maths" : "B","English" : "B","Science" : "A","History" : "B"}
{"_id" : 3,"Maths" : "A","English" : "A","Science" : "A","History" : "A"}
Możemy pobrać jednostki uczniów z odpowiednimi ocenami za pomocą operatora $lookup z podejściem JOIN .ie
db.getCollection('students').aggregate([{
$lookup:
{
from: "units",
localField: "_id",
foreignField : "_id",
as: "studentUnits"
}
}])
Co da nam poniższe wyniki:
{"_id" : 1,"name" : "James Washington","age" : 15,"grade" : "A","score" : 10.5,
"studentUnits" : [{"_id" : 1,"Maths" : "A","English" : "A","Science" : "A","History" : "B"}]}
{"_id" : 2,"name" : "Clinton Ariango","age" : 14,"grade" : "B","score" : 7.5,
"studentUnits" : [{"_id" : 2,"Maths" : "B","English" : "B","Science" : "A","History" : "B"}]}
{"_id" : 3,"name" : "Mary Muthoni","age" : 16,"grade" : "A","score" : 11.5,
"studentUnits" : [{"_id" : 3,"Maths" : "A","English" : "A","Science" : "A","History" : "A"}]}
Jak wspomniano wcześniej, jeśli wykonamy JOIN przy użyciu koncepcji SQL, zostaniemy zwróceni z osobnymi dokumentami na platformie Studio3T .ie
SELECT *
FROM students
INNER JOIN units
ON students._id = units._id
Jest odpowiednikiem
db.getCollection("students").aggregate(
[
{
"$project" : {
"_id" : NumberInt(0),
"students" : "$$ROOT"
}
},
{
"$lookup" : {
"localField" : "students._id",
"from" : "units",
"foreignField" : "_id",
"as" : "units"
}
},
{
"$unwind" : {
"path" : "$units",
"preserveNullAndEmptyArrays" : false
}
}
]
);
Powyższe zapytanie SQL zwróci poniższe wyniki:
{ "students" : {"_id" : NumberInt(1),"name" : "James Washington","age" : 15.0,"grade" : "A","score" : 10.5},
"units" : {"_id" : NumberInt(1),"Maths" : "A","English" : "A","Science" : "A","History" : "B"}}
{ "students" : {"_id" : NumberInt(2), "name" : "Clinton Ariango","age" : 14.0,"grade" : "B","score" : 7.5 },
"units" : {"_id" : NumberInt(2),"Maths" : "B","English" : "B","Science" : "A","History" : "B"}}
{ "students" : {"_id" : NumberInt(3),"name" : "Mary Muthoni","age" : 16.0,"grade" : "A","score" : 11.5},
"units" : {"_id" : NumberInt(3),"Maths" : "A","English" : "A","Science" : "A","History" : "A"}}
Czas działania będzie oczywiście zależał od struktury zapytania. Na przykład, jeśli masz wiele dokumentów w jednym zbiorze w drugim, powinieneś wykonać agregację ze zbioru z mniejszymi dokumentami, a następnie wyszukać w zbiorze z większą liczbą dokumentów. W ten sposób wyszukiwanie wybranego pola z kolekcji mniejszych dokumentów jest dość optymalne i zajmuje mniej czasu niż wykonywanie wielokrotnych wyszukiwań dla wybranego pola w kolekcji z większą liczbą dokumentów. Dlatego zaleca się, aby najpierw umieścić mniejszą kolekcję.
W przypadku relacyjnej bazy danych kolejność baz danych nie ma znaczenia, ponieważ większość interpreterów SQL ma optymalizatory, które mają dostęp do dodatkowych informacji umożliwiających podjęcie decyzji, która z nich powinna być pierwsza.
W przypadku MongoDB będziemy musieli użyć indeksu, aby ułatwić operację JOIN. Wszyscy wiemy, że wszystkie dokumenty MongoDB mają klucz _id, który dla relacyjnego DBM można uznać za klucz podstawowy. Indeks zapewnia większą szansę na zmniejszenie ilości danych, do których należy uzyskać dostęp, oprócz obsługi operacji, gdy jest używany w kluczu obcym $lookup.
W potoku agregacji, aby użyć indeksu, musimy upewnić się, że $match jest wykonywany w pierwszym etapie, aby odfiltrować dokumenty, które nie spełniają kryteriów. Na przykład, jeśli chcemy pobrać wynik dla ucznia z wartością pola _id równą 1:
select *
from students
INNER JOIN units
ON students._id = units._id
WHERE students._id = 1;
Odpowiednik kodu MongoDB, który otrzymasz w tym przypadku to:
db.getCollection("students").aggregate(
[{"$project" : { "_id" : NumberInt(0), "students" : "$$ROOT" }},
{ "$lookup" : {"localField" : "students._id", "from" : "units", "foreignField" : "_id", "as" : "units"} },
{ "$unwind" : { "path" : "$units","preserveNullAndEmptyArrays" : false } },
{ "$match" : {"students._id" : NumberLong(1) }}
]);
Zwrócony wynik dla powyższego zapytania będzie następujący:
{"_id" : 1,"name" : "James Washington","age" : 15,"grade" : "A","score" : 10.5,
"studentUnits" : [{"_id" : 1,"Maths" : "A","English" : "A","Science" : "A","History" : "B"}]}
Gdy nie używamy etapu $match, a raczej nie na pierwszym etapie, jeśli sprawdzimy za pomocą funkcji wyjaśniania, dostaniemy również etap COLLSCAN. Wykonanie COLLSCAN dla dużego zestawu dokumentów zazwyczaj zajmuje dużo czasu. W ten sposób decydujemy się na użycie pola indeksu, które w funkcji wyjaśniania obejmuje tylko etap IXSCAN. Ta ostatnia ma tę zaletę, że sprawdzamy indeks w dokumentach i nie skanujemy wszystkich dokumentów; zwrócenie wyników nie zajmie dużo czasu. Możesz mieć inną strukturę danych, na przykład:
{ "_id" : NumberInt(1),
"grades" : {"Maths" : "A", "English" : "A", "Science" : "A", "History" : "B"
}
}
Możemy chcieć zwrócić oceny jako różne elementy w tablicy, a nie jako całe osadzone pole ocen.
Po napisaniu powyższego zapytania SQL musimy zmodyfikować wynikowy kod MongoDB. Aby to zrobić, kliknij ikonę kopiowania po prawej stronie, jak poniżej, aby skopiować kod agregacji:
Następnie przejdź do zakładki agregacji i na wyświetlonym panelu znajduje się ikona wklejania, kliknij ją, aby wkleić kod.
Kliknij wiersz $match, a następnie zieloną strzałkę w górę, aby przenieść etap na górę jako pierwszy. Jednak najpierw musisz utworzyć indeks w swojej kolekcji, taki jak:
db.students.createIndex(
{ _id: 1 },
{ name: studentId }
)
Przykładowy kod otrzymasz poniżej:
db.getCollection("students").aggregate(
[{ "$match" : {"_id" : 1.0}},
{ "$project" : {"_id" : NumberInt(0),"students" : "$$ROOT"}},
{ "$lookup" : {"localField" : "students._id","from" : "units","foreignField" : "_id","as" : "units"}},
{ "$unwind" : {"path" : "$units", "preserveNullAndEmptyArrays" : false}}
]
Kilkadziesiąt — Zostań administratorem baz danych MongoDB — wprowadzenie MongoDB do produkcjiDowiedz się, co trzeba wiedzieć, aby wdrażać, monitorować, zarządzać i skalować MongoDB. Pobierz za darmo Za pomocą tego kodu otrzymamy poniższy wynik:
{ "students" : {"_id" : NumberInt(1), "name" : "James Washington","age" : 15.0,"grade" : "A", "score" : 10.5},
"units" : {"_id" : NumberInt(1), "grades" : {"Maths" : "A", "English" : "A", "Science" : "A", "History" : "B"}}}
Ale wszystko, czego potrzebujemy, to mieć oceny jako oddzielną jednostkę dokumentu w zwróconym dokumencie, a nie jak w powyższym przykładzie. Dlatego dodamy etap $addfields, stąd kod jak poniżej.
db.getCollection("students").aggregate(
[{ "$match" : {"_id" : 1.0}},
{ "$project" : {"_id" : NumberInt(0),"students" : "$$ROOT"}},
{ "$lookup" : {"localField" : "students._id","from" : "units","foreignField" : "_id","as" : "units"}},
{ "$unwind" : {"path" : "$units", "preserveNullAndEmptyArrays" : false}},
{ "$addFields" : {"units" : "$units.grades"} }]
Otrzymane dokumenty będą wtedy:
{
"students" : {"_id" : NumberInt(1), "name" : "James Washington", "grade" : "A","score" : 10.5},
"units" : {"Maths" : "A", "English" : "A", "Science" : "A", "History" : "B"}
}
Zwrócone dane są całkiem porządne, ponieważ usunęliśmy osadzone dokumenty z kolekcji jednostek jako osobne pole.
W naszym następnym samouczku przyjrzymy się zapytaniom z kilkoma sprzężeniami.