Tworzenie pipeline’ów agregacji w MongoDB za pomocą $match, $group i $lookup
Dowiedz się, w jaki sposób etapy agregacji w MongoDB filtrowają, grupują, przekształcają, łączą i sortują dokumenty, oraz jak łączyć je w pipeline, który odpowiada na rzeczywiste pytania analityczne.
Zwykła zapytanie find() nadaje się do pobierania dokumentów spełniających określone warunki, ale nie może obliczyć łącznych przychodów na klienta, dołączyć szczegółów użytkownika do zamówień ani zwrócić raportu uporządkowanego według rankingu. W tym celu MongoDB oferuje framework agregacji. Ten przewodnik wyjaśnia pięć etapów, których najczęściej będziesz używał, małe pułapki w każdym z nich oraz sposób łączenia ich w jeden pipeline, który dostarcza czystego, gotowego do użycia wyniku.
Jak działa pipeline agregacji
Agregacja to uporządkowana lista etapów. Dokumenty trafiają do pierwszego etapu, każdy etap przekształca strumień w określony sposób, a to, co jest emitowane, staje się wejściem dla następnego etapu. Traktowanie tego jak linii montażowej danych ułatwia zrozumienie kolejności etapów.
Krótki pipeline poniżej zachowuje tylko zakończone zamówienia, sumuje kwoty dla każdego klienta i wymienia największych wydawców na początku.
db.orders.aggregate([
{ $match: { status: "completed" } },
{ $group: { _id: "$customerId", total: { $sum: "$amount" } } },
{ $sort: { total: -1 } }
]);
Następne sekcje omawiają każdy etap osobno.
Filtrowanie dokumentów za pomocą $match
$match przyjmuje ten sam rodzaj filtru, który podałbyś do funkcji find(). W tym przykładzie tylko te dokumenty, których wartość pola status jest równa "completed", przechodzą do następnego etapu.
{
$match: {
status: "completed"
}
}
Umieść $match jak najwcześniej. Każdy dokument usunięty na początku oznacza jednego mniej dokumentu, który muszą przetworzyć późniejsze etapy. $match umieszczony na początku pipeline może korzystać z indeksów w kolekcji, podczas gdy późniejsze etapy pracują z wynikami pośrednimi, z którymi indeksy nie mogą pomóc.
Grupowanie i obliczanie sum za pomocą $group
$group zbiera dokumenty, które mają wspólną kluczową wartość, i oblicza wartości dla każdej grupy. W tym przykładzie dla każdego klienta generowany jest jeden dokument z łączną kwotą zamówienia.
{
$group: {
_id: "$customerId",
totalSales: {
$sum: "$amount"
}
}
}
Praca jest wykonywana przez dwie części:
_idokreśla klucz grupowania; w tym przypadku jest to wartość polacustomerIdkażdego dokumentu (przedrostek$oznacza „przeczytaj to pole”).$sumto akumulator, który sumuje wartość polaamountdla wszystkich dokumentów w grupie.
Pozostałe akumulatory działają według tego samego schematu, w tym $avg, $min, $max i $count. Należy pamiętać, że wynik funkcji $group zawiera tylko _id oraz pola, które zostały obliczone; wszystkie pozostałe pola oryginalnych dokumentów znikają.
Wybieranie i kształtowanie pól za pomocą $project
$project decyduje, które pola pojawią się w wyniku i może tworzyć nowe. Ustawienie pola na 1 powoduje jego włączenie, natomiast _id: 0 wyraźnie ukrywa identyfikator, który w przeciwnym razie jest zawarty domyślnie.
{
$project: {
customerId: 1,
totalSales: 1,
_id: 0
}
}
Może on również obliczać wartości. Tutaj $multiply tworzy pole totalWithTax, stosując do totalSales współczynnik podatku w wysokości 18%.
{
$project: {
customerId: 1,
totalWithTax: {
$multiply: ["$totalSales", 1.18]
}
}
}
Ważny jest jeden szczegół, gdy te fragmenty następują po etapie $group: identyfikator klienta znajduje się wtedy w polu _id, a nie w customerId, więc ustawienie customerId: 1 nie spowoduje wyświetlenia żadnych danych. Połączony pipeline na końcu rozwiązuje ten problem, zapisując customerId: „$_id”, co zmienia nazwę pola. Traktuj $project jako etap, który kształtuje ostateczną odpowiedź zwracaną przez twoją API.
Łączenie innej kolekcji za pomocą $lookup
Dokumenty często odnoszą się do danych przechowywanych w innych miejscach. W przypadku kolekcji orders i users, operacja $lookup pobiera odpowiadającego użytkownika dla każdego zamówienia.
{
$lookup: {
from: "users",
localField: "customerId",
foreignField: "_id",
as: "customer"
}
}
Dla każdego zamówienia MongoDB porównuje wartość customerId z wartością _id w kolekcji users i zapisuje wszystkie dopasowania do nowego pola typu tablica o nazwie customer. Jest to najbliższy odpowiednik operacji łączenia w SQL. Wynikiem zawsze jest tablica, nawet gdy dopasowuje się dokładnie jeden użytkownik, dlatego powszechnie stosuje się operację $unwind lub odczytuje pierwszy element. Należy również upewnić się, że oba pola mają ten sam typ: ciąg znaków customerId nie będzie pasował do wartości typu ObjectId w polu _id.
Kierowanie sortowaniem wyników za pomocą $sort
$sort sortuje dokumenty według jednego lub kilku pól. Wartość 1 powoduje sortowanie w porządku rosnącym, a -1 malejącym, więc w przykładzie klienci są uporządkowani od najwyższych wartości totalSales do najniższych.
{
$sort: {
totalSales: -1
}
}
Łączenie etapów w jeden raport
Prawdziwa wartość pojawia się, gdy etapy są ze sobą powiązane. Poniższy proces przekształca surowe zamówienia w uporządkowaną listę klientów oraz ich łącznych sprzedaży:
db.orders.aggregate([
{
$match: {
status: "completed"
}
},
{
$group: {
_id: "$customerId",
totalSales: {
$sum: "$amount"
}
}
},
{
$sort: {
totalSales: -1
}
},
{
$project: {
customerId: "$_id",
totalSales: 1,
_id: 0
}
}
]);
Krok po kroku:
- Zachowuje tylko zakończone zamówienia za pomocą
$match. - Grupuje je według klientów za pomocą
$group. - Sumuje kwoty zamówień każdego klienta, tworząc wartość
totalSales. - Sortuje klientów według tej łącznej kwoty, od najwyższej do najniższej.
- Używa
$project, aby przemienić_idnacustomerIdi usunąć_idz wyniku.
Aby dodać nazwy lub adresy e-mail, można użyć operacji $lookup w odniesieniu do tablicy users. Umieszczenie jej po operacji $group, a także po każdej instrukcji $limit, oznacza, że połączenie danych odbywa się raz na klienta, a nie raz na zamówienie.
Główne wnioski
Kompaktowy sposób na zapamiętanie podstawowych etapów:
$match– filtrowanie.$group– grupowanie i obliczania.$project– wybieranie i przekształcanie danych.$lookup– łączenie kolekcji.$sort– sortowanie wyników.
Razem obejmują one większość elementów, których potrzebują raporty, panele kontrolne, punkty końcowe analityki oraz reguły biznesowe po przekroczeniu podstawowych operacji CRUD. Kolejność ma znaczenie: filtrować wcześnie, grupować raz, łączyć jak najpóźniej, gdy tylko na to pozwalają dane, a formatowanie wyników na końcu. Podczas rozmów kwalifikacyjnych i przeglądów projektu umiejętność uzasadnienia, dlaczego dany etap znajduje się w konkretnym miejscu, jest bardziej przekonująca niż jedynie wymienianie jego funkcji.
Literatura pokrewna
- Zapobieganie utracie aktualizacji w Node.js i MongoDB przy jednoczesnych zapisach — Dowiedz się, jak atomowe aktualizacje warunkowe, optymistyczne blokowanie oparte na wersjach, odpowiedzi 409 oraz transakcje zapobiegają tymczasowemu odrzuceniu danych przez jednoczesne zapisy w MongoDB.