dzięki Julianowi Motzowi za pomoc w recenzji tego artykułu.
jedną z największych różnic pomiędzy bazami danych SQL i NoSQL jest JOIN. W relacyjnych bazach danych klauzula SQL JOIN umożliwia łączenie wierszy z dwóch lub więcej tabel przy użyciu wspólnego pola między nimi. Na przykład, jeśli masz tabele books
i publishers
, możesz pisać polecenia SQL, takie jak:
SELECT book.title, publisher.nameFROM bookLEFT JOIN book.publisher_id ON publisher.id;
innymi słowy, tabela book
ma pole publisher_id
, które odwołuje się do pola id
w tabeli publisher
.
jest to praktyczne, ponieważ jeden wydawca może zaoferować tysiące książek. Jeśli kiedykolwiek będziemy musieli zaktualizować dane wydawcy, możemy zmienić pojedynczy rekord. Redundancja danych jest zminimalizowana, ponieważ nie musimy powtarzać informacji wydawcy dla każdej książki. Technika ta jest znana jako normalizacja.
bazy danych SQL oferują szereg funkcji normalizacji i ograniczeń, aby zapewnić utrzymanie relacji.
NoSQL = = brak JOIN?
nie zawsze …
bazy danych zorientowane na dokumenty, takie jak MongoDB, są przeznaczone do przechowywania denormalizowanych danych. Idealnie, nie powinno być relacji między zbiorami. Jeśli te same dane są wymagane w dwóch lub więcej dokumentach, należy je powtórzyć.
może to być frustrujące, ponieważ istnieje kilka sytuacji, w których nigdy nie potrzebujesz danych relacyjnych. Na szczęście MongoDB 3.2 wprowadza nowy operator $lookup
, który może wykonać operację typu LEFT-OUTER-JOIN-like na dwóch lub więcej kolekcjach. Ale jest haczyk …
agregacja MongoDB
$lookup
jest dozwolona tylko w operacjach agregacji. Pomyśl o nich jak o potoku operatorów, które odpytywają, filtrują i grupują wynik. Wyjście jednego operatora jest używane jako Wejście dla następnego.
agregacja jest trudniejsza do zrozumienia niż prostsze zapytania find
i generalnie będzie działać wolniej. Są jednak potężne i nieocenioną opcją dla złożonych operacji wyszukiwania.
agregację najlepiej wyjaśnić na przykładzie. Załóżmy, że tworzymy platformę społecznościową z kolekcją user
. Przechowuje dane każdego użytkownika w oddzielnych dokumentach. Na przykład:
{ "_id": ObjectID("45b83bda421238c76f5c1969"), "name": "User One", "email: "[email protected]", "country": "UK", "dob": ISODate("1999-09-13T00:00:00.000Z")}
możemy dodać tyle pól, ile potrzeba, ale wszystkie dokumenty MongoDB wymagają pola _id
, które ma unikalną wartość. _id
jest podobny do klucza podstawowego SQL i w razie potrzeby zostanie wstawiony automatycznie.
Nasza sieć społecznościowa wymaga teraz kolekcji post
, która przechowuje wiele wnikliwych aktualizacji od użytkowników. Dokumenty przechowują tekst, datę, ocenę i odniesienie do użytkownika, który je napisał w polu user_id
:
{ "_id": ObjectID("17c9812acff9ac0bba018cc1"), "user_id": ObjectID("45b83bda421238c76f5c1969"), "date: ISODate("2016-09-05T03:05:00.123Z"), "text": "My life story so far", "rating": "important"}
chcemy teraz pokazać ostatnie dwadzieścia postów z” ważną ” oceną od wszystkich użytkowników w odwrotnej kolejności chronologicznej. Każdy zwrócony dokument powinien zawierać tekst, czas postu oraz nazwę powiązanego użytkownika i kraj.
zapytanie zbiorcze MongoDB jest przekazywane tablicy operatorów rurociągów, które definiują każdą operację w kolejności. Najpierw musimy wyodrębnić wszystkie dokumenty z kolekcji post
, które mają poprawną ocenę za pomocą filtra $match
:
{ "$match": { "rating": "important" } }
musimy teraz sortować dopasowane elementy w kolejności odwrotnej daty za pomocą operatora $sort
:
{ "$sort": { "date": -1 } }
ponieważ potrzebujemy tylko dwudziestu postów, możemy zastosować etap $limit
, więc MongoDB musi przetwarzać tylko dane, które chcemy:
{ "$limit": 20 }
możemy teraz połączyć dane z kolekcji user
używając nowego operatora $lookup
. Wymaga obiektu o czterech parametrach:
-
localField
: pole wyszukiwania w dokumencie wejściowym -
from
: kolekcja do przyłączenia -
foreignField
: pole do wyszukania w kolekcjifrom
-
as
: Nazwa pola wyjściowego.
nasz operator jest zatem:
{ "$lookup": { "localField": "user_id", "from": "user", "foreignField": "_id", "as": "userinfo"} }
spowoduje to utworzenie nowego pola w naszym wyjściu o nazwie userinfo
. Zawiera tablicę, w której każda wartość jest zgodna z dokumentem user
:
"userinfo":
mamy relację jeden do jednego między post.user_id
i user._id
, ponieważ post może mieć tylko jednego autora. Dlatego nasza tablica userinfo
zawsze będzie zawierać tylko jeden element. Możemy użyć operatora $unwind
do dekonstrukcji go w pod-dokument:
{ "$unwind": "$userinfo" }
wyjście zostanie teraz przekonwertowane na bardziej praktyczny format, który może mieć zastosowanie dalsze operatory:
"userinfo": { "name": "User One", "email: "[email protected]", …}
na koniec możemy zwrócić tekst, czas postu, nazwę użytkownika i kraj za pomocą etapu $project
w rurociągu:
{ "$project": { "text": 1, "date": 1, "userinfo.name": 1, "userinfo.country": 1} }
składając to wszystko
nasze końcowe zapytanie zbiorcze dopasowuje posty, sortuje w kolejności, ogranicza do ostatnich dwudziestu pozycji, łączy dane użytkownika, spłaszcza tablicę użytkownika i zwraca tylko niezbędne pola. Pełna Komenda:
db.post.aggregate();
rezultatem jest zbiór do dwudziestu dokumentów. Na przykład:
Super! W końcu mogę przełączyć się na NoSQL!
MongoDB $lookup
jest użyteczny i potężny, ale nawet ten podstawowy przykład wymaga złożonego zapytania zbiorczego. Nie jest substytutem potężniejszej klauzuli JOIN oferowanej w SQL. MongoDB również nie oferuje ograniczeń; jeśli dokument user
zostanie usunięty, pozostaną osierocone dokumenty post
.
idealnie, operator $lookup
powinien być wymagany rzadko. Jeśli bardzo tego potrzebujesz, prawdopodobnie korzystasz z niewłaściwego magazynu danych…
jeśli masz dane relacyjne, użyj relacyjnej (SQL) bazy danych!
to powiedziawszy, $lookup
jest mile widzianym dodatkiem do MongoDB 3.2. Może przezwyciężyć niektóre z bardziej frustrujących problemów podczas używania małych ilości danych relacyjnych w bazie danych NoSQL.