Jak používat $lookup v MongoDB

MongoDB je oblíbená NoSQL databáze, která organizuje data do sbírek. Tyto sbírky sestávají z jednotlivých dokumentů, jež obsahují data ve formátu JSON. Dokumenty jsou obdobou řádků v tradičních relačních SQL databázích, zatímco kolekce se podobají tabulkám.

Jednou z klíčových funkcí databází je schopnost provádět dotazy na uložená data. Dotazování umožňuje vyhledávání specifických informací, provádění datové analýzy, vytváření reportů a integraci dat do různých systémů.

Pro efektivní dotazování je důležité umět spojit data z více tabulek v SQL databázích, nebo z více kolekcí v NoSQL databázích, do jedné výsledné sady.

V MongoDB, operátor $lookup umožňuje kombinovat informace z dvou různých kolekcí při dotazování. Tato operace provádí ekvivalent levého vnějšího spojení, jak je známé z SQL databází.

Využití a účel operátoru $lookup

Klíčovou funkcí databází je schopnost zpracovávat surová data a transformovat je na užitečné informace.

Představte si například, že vlastníte restauraci. Potřebujete analyzovat data o tržbách, abyste zjistili, kolik vyděláte denně, které pokrmy jsou populární o víkendech, nebo třeba kolik šálků kávy prodáte za hodinu.

Pro tyto potřeby nepostačí jednoduché databázové dotazy. Musíte provádět sofistikovanější dotazy na data, která máte uložená. MongoDB proto nabízí nástroj zvaný agregační kanál.

Agregační kanál představuje systém operací, neboli fází, které lze skládat za sebou a provádět komplexní datové transformace za účelem získání finálního souhrnu. Mezi příklady fází agregačního kanálu patří $sort, $match, $group, $merge, $count a právě $lookup.

Tyto fáze se dají skládat v libovolném pořadí v rámci agregačního kanálu. V každé fázi jsou data, která kanálem procházejí, upravena specifickým způsobem.

Operátor $lookup je tedy jednou z fází agregačního kanálu v MongoDB. Slouží k provedení levého vnějšího spojení mezi dvěma kolekcemi v databázi. Levé vnější spojení kombinuje všechny dokumenty z „levé“ kolekce s odpovídajícími dokumenty z „pravé“ kolekce.

Podívejme se na následující dvě kolekce, které jsou pro přehlednost zobrazeny v tabulkové formě:

objednávky_výběr:

order_id customer_id order_date total_amount
1 100 2022-05-01 50.00
2 101 2022-05-02 75.00
3 102 2022-05-03 100.00

Zákazníci_kolekce:

customer_num customer_name customer_email customer_phone
100 John [email protected] [email protected]

Pokud provedeme levé vnější spojení těchto dvou kolekcí pomocí pole customer_id z kolekce order_collection (která je „levou“ kolekcí) a pole customer_num z kolekce customers_collection (která je „pravou“ kolekcí), výsledkem bude sada obsahující všechny dokumenty z kolekce objednávek a ty dokumenty z kolekce zákazníků, které mají customer_num shodující se s customer_id některého záznamu v kolekci objednávek.

Finální výsledek operace levého vnějšího spojení na kolekcích objednávek a zákazníků vypadá takto:

order_id customer_id order_date total_amount customer_num customer_name customer_email customer_phone
1 100 2022-05-01 50.00 100 John [email protected] [email protected]
2 101 2022-05-02 75.00 null null null null
3 102 2022-05-03 100.00 null null null null

Všimněte si, že pro zákazníka s customer_id 101 z kolekce objednávek, který neměl odpovídající customer_num v kolekci zákazníků, jsou chybějící hodnoty z tabulky zákazníků nahrazeny hodnotou null.

Operátor $lookup provádí porovnání na základě rovnosti mezi zadanými poli a načítá celý dokument, který se shoduje, nikoliv jen pole, která se shodují.

Syntaxe operátoru $lookup

Syntaxe operátoru $lookup je následující:

{
   $lookup:
     {
       from: <kolekce ke spojení>,
       localField: <pole z vstupních dokumentů>,
       foreignField: <pole z dokumentů kolekce "from">,
       as: <výstupní pole pole>
     }
}

Operátor $lookup má čtyři parametry:

  • from – určuje kolekci, ze které chceme vyhledávat dokumenty. V našem předchozím příkladu s kolekcemi orders_collection a customers_collection bychom jako hodnotu from zadali customers_collection.
  • localField – je pole v aktuální, tedy „primární“ kolekci, které používáme k porovnávání s poli v „cizí“ kolekci (v našem případě customer_collection). V uvedeném příkladu by localField bylo customer_id z kolekce orders_collection.
  • foreignField – je pole v „cizí“ kolekci, se kterým chceme provést porovnání. V našem příkladu by to bylo pole customer_num z kolekce customer_collection, kterou jsme specifikovali v parametru from.
  • as – je nový název pole, které se objeví ve výstupním dokumentu a bude obsahovat pole dokumentů odpovídajících shodám mezi localField a foreignField. Pokud nejsou žádné shody, bude toto pole obsahovat prázdné pole.

Na základě našich dvou předchozích kolekcí bychom použili následující kód pro provedení operace $lookup, přičemž orders_collection je naše pracovní nebo primární kolekce:

{
    $lookup: {
      from: "customers_collection",
      localField: "customer_id",
      foreignField: "customer_num",
      as: "customer_info"
    }
}

Název pole as může být libovolná řetězcová hodnota. Nicméně, pokud zvolíte název, který již v pracovním dokumentu existuje, bude toto pole přepsáno.

Spojování dat z více kolekcí

V MongoDB je $lookup velmi užitečná fáze agregačního kanálu. Ačkoli použití $lookup není v agregačním kanálu nutné, je tato fáze klíčová pro provádění složitějších dotazů, které vyžadují spojování dat z několika kolekcí.

Fáze $lookup provádí levé vnější spojení dvou kolekcí, což má za následek vytvoření nového pole (nebo přepsání stávajícího) obsahujícího pole dokumentů z jiné kolekce.

Tyto dokumenty jsou vybírány na základě shody hodnot mezi poli, která se porovnávají. Výsledkem je pole obsahující pole dokumentů v případě nalezení shod, nebo prázdné pole v případě, že shody nalezeny nejsou.

Představte si kolekce zaměstnanců a projektů:

Pro spojení těchto dvou kolekcí můžeme použít následující kód:

db.projects.aggregate([
   {
      $lookup: {
         from: "employees",
         localField: "employees",
         foreignField: "_id",
         as: "assigned_employees"
      }
   }
])

Výsledkem této operace je kombinace obou kolekcí. Ke každému projektu jsou přidruženi všichni zaměstnanci, kteří jsou mu přiřazeni. Tito zaměstnanci jsou uvedeni v poli.

Fáze kanálu, které lze použít společně s $lookup

Jak již bylo zmíněno, $lookup je fází agregačního kanálu v MongoDB a lze ji používat společně s dalšími fázemi. Abychom ukázali, jak lze tyto fáze kombinovat, použijeme pro ilustraci následující dvě kolekce.

V MongoDB jsou uloženy ve formátu JSON. Takto vypadají výše uvedené kolekce v MongoDB:

Mezi příklady fází agregačního kanálu, které lze použít společně s $lookup patří:

$match

$match je fáze agregačního kanálu, která se používá k filtrování dokumentů tak, aby do další fáze kanálu mohly postoupit pouze dokumenty, které splňují danou podmínku. Nejvhodnější je používat tuto fázi na začátku procesu, aby se vyloučily dokumenty, které nebudou potřeba, a tím se optimalizoval agregační kanál.

S pomocí dvou předchozích kolekcí můžeme kombinovat $match a $lookup následujícím způsobem:

db.users.aggregate([
   {
      $match: {
         country: "USA"
      }
   },
   {
      $lookup: {
         from: "orders",
         localField: "_id",
         foreignField: "user_id",
         as: "orders"
      }
   }
])

$match se používá k filtrování uživatelů z USA. Výsledek z $match se pak kombinuje s $lookup pro získání podrobností o objednávkách těchto uživatelů. Výsledek této operace je uveden níže:

$project

$project je fáze používaná k transformaci dokumentů určením, která pole se mají zahrnout, vyloučit nebo přidat do dokumentů. Například, pokud pracujete s dokumenty, které obsahují deset polí, ale pouze čtyři pole obsahují relevantní data, můžete pomocí $project odfiltrovat nepotřebná pole.

To vám umožní odesílat do další fáze kanálu pouze potřebná data.

Můžeme kombinovat $lookup a $project následujícím způsobem:

db.users.aggregate([
   {
      $lookup: {
         from: "orders",
         localField: "_id",
         foreignField: "user_id",
         as: "orders"
      }
   },
   {
      $project: {
         name: 1,
         _id: 0,
         total_spent: { $sum: "$orders.price" }
      }
   }
])

Výše uvedený kód kombinuje kolekce uživatelů a objednávek pomocí $lookup, následně se pomocí $project zobrazí pouze jméno každého uživatele a částka, kterou utratil. $project se také používá k odstranění pole _id z výsledku. Výsledek této operace je uveden níže:

$unwind

$unwind je agregační fáze sloužící k dekonstrukci neboli „rozbalení“ pole, čímž vytvoří nové dokumenty pro každý prvek v daném poli. To je užitečné, když chcete provádět agregace s hodnotami prvků v poli.

Například, v případě že chcete provádět agregaci na poli zájmů (hobbies), to není možné, protože se jedná o pole. Nicméně, můžete jej rozbalit pomocí $unwind a poté provádět agregace na výsledných dokumentech.

S použitím kolekcí uživatelů a objednávek můžeme kombinovat $lookup a $unwind následujícím způsobem:

db.users.aggregate([
   {
      $lookup: {
         from: "orders",
         localField: "_id",
         foreignField: "user_id",
         as: "orders"
      }
   },
   {
      $unwind: "$orders"
   }
])

Ve výše uvedeném kódu vrací $lookup pole s názvem „orders“. $unwind pak slouží k rozbalení tohoto pole. Výsledkem této operace je následující: Všimněte si, že Alice se objeví dvakrát, protože má dvě objednávky.

Příklady případů použití operátoru $lookup

Při zpracování dat je operátor $lookup velmi užitečný nástroj. Můžete mít například dvě kolekce, které chcete spojit na základě polí s podobnými daty. K tomu můžete použít jednoduchou fázi $lookup a přidat nové pole do primárních kolekcí, které budou obsahovat dokumenty z jiné kolekce.

Vezměme v úvahu kolekce uživatelů a objednávek:

Tyto dvě kolekce je možné zkombinovat s pomocí $lookup a získat výsledek uvedený níže:

$lookup lze také použít k provádění složitějších spojení. $lookup se neomezuje pouze na spojování dvou kolekcí. Můžete implementovat více fází $lookup pro provedení spojení na více než dvou kolekcích. Představte si tři následující kolekce:

Následující kód nám umožňuje provést složitější spojení napříč třemi kolekcemi a získat tak všechny objednávky a podrobnosti o objednaných produktech:

db.orders.aggregate([
   {
      $lookup: {
         from: "order_items",
         localField: "_id",
         foreignField: "order_id",
         as: "order_items"
      }
   },
   {
      $unwind: "$order_items"
   },
   {
      $lookup: {
         from: "products",
         localField: "order_items.product_id",
         foreignField: "_id",
         as: "product_details"
      }
   },
   {
      $group: {
         _id: "$_id",
         customer: { $first: "$customer" },
         total: { $sum: "$order_items.price" },
         products: { $push: "$product_details" }
      }
   }
])

Výsledek této operace je uveden níže:

Závěr

Při zpracování dat zahrnujících více kolekcí je $lookup užitečný, protože umožňuje spojovat data a vyvozovat závěry na základě dat uložených ve více kolekcích. Zpracování dat zřídkakdy závisí pouze na jedné kolekci.

Pro vyvození smysluplných závěrů z dat je klíčovým krokem spojení dat z více kolekcí. Zvažte proto použití fáze $lookup ve vašem agregačním kanálu v MongoDB. Umožní vám lépe zpracovávat vaše data a získávat smysluplné informace ze surových dat uložených napříč různými kolekcemi.

Můžete také prozkoumat některé další příkazy a dotazy v MongoDB.