Cum să utilizați $lookup în MongoDB

MongoDB, un sistem de gestiune a bazelor de date NoSQL foarte popular, organizează informațiile în colecții. Aceste colecții sunt alcătuite din documente individuale, fiecare conținând datele propriu-zise în format JSON. Documentele pot fi asemănate cu rândurile dintr-o bază de date relațională SQL, în timp ce colecțiile corespund tabelelor.

O funcționalitate cheie a oricărei baze de date este capacitatea de a interoga datele stocate. Interogarea datelor permite extragerea informațiilor specifice, analizarea datelor, generarea rapoartelor și integrarea datelor în diverse sisteme.

Pentru a interoga eficient o bază de date, este crucial să se poată combina date din mai multe tabele, în cazul bazelor de date SQL, sau din multiple colecții, în cazul bazelor de date NoSQL, într-un singur set de rezultate.

În MongoDB, operațiunea $lookup este folosită pentru a combina informații din două colecții în timpul interogării. Aceasta realizează un efect similar cu o îmbinare externă stângă într-o bază de date SQL.

Utilizarea și scopul operațiunii $lookup

Un aspect fundamental al bazelor de date este abilitatea de a procesa datele pentru a extrage informații valoroase din datele brute.

De exemplu, într-un context de afaceri, cum ar fi un restaurant, ar putea fi necesar să analizați datele pentru a determina veniturile zilnice, cele mai căutate feluri de mâncare din weekend, sau chiar numărul de cești de cafea vândute la fiecare oră a zilei.

Pentru astfel de necesități, interogările simple ale bazei de date sunt insuficiente. Este necesară efectuarea unor interogări avansate asupra datelor stocate. Pentru a satisface aceste cerințe, MongoDB oferă o caracteristică numită conductă de agregare.

O conductă de agregare este un sistem compus din operațiuni modulare, denumite etape, care procesează datele în vederea producerii unui rezultat agregat final. Exemple de etape ale conductei de agregare includ $sort, $match, $group, $merge, $count și $lookup, printre altele.

Aceste etape pot fi aplicate în orice ordine într-o conductă de agregare. În fiecare etapă, se efectuează operații specifice asupra datelor care sunt transmise prin conductă.

$lookup este, așadar, o etapă a conductei de agregare MongoDB, folosită pentru a efectua o îmbinare externă stângă între două colecții. O îmbinare externă stângă combină toate documentele sau intrările dintr-o colecție sursă cu documentele sau intrările corespunzătoare din colecția țintă.

De exemplu, să presupunem că avem următoarele două colecții, prezentate sub formă tabelară pentru o mai bună înțelegere:

colectare_comenzi:

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

colecție_clienți:

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

Dacă efectuăm o îmbinare externă stângă pe cele două colecții folosind câmpul customer_id din colectare_comenzi și customer_num din colecție_clienți, rezultatul va conține toate documentele din colectare_comenzi, împreună cu documentele din colecție_clienți care au un customer_num corespunzător cu customer_id din colectare_comenzi.

Rezultatul operațiunii de îmbinare externă stângă a colecțiilor de comenzi și clienți arată astfel, sub formă tabelară:

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 Doe [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

Observați că pentru clientul cu customer_id 101 în colecția de comenzi, care nu avea un customer_num corespunzător în colecția de clienți, valorile corespunzătoare lipsă din tabelul clienți au fost completate cu null.

Operațiunea $lookup realizează o comparație strictă de egalitate între câmpuri și returnează întregul document care se potrivește, nu doar câmpurile potrivite.

Sintaxa $lookup

Sintaxa pentru $lookup este următoarea:

  {
     $lookup:
       {
         from: <colecția de îmbinat>,
         localField: <câmpul din documentele de intrare>,
         foreignField: <câmpul din documentele colecției "from">,
         as: <câmpul matrice de ieșire>
       }
  }
  

$lookup are patru parametri:

  • from – reprezintă colecția din care se caută documente. În exemplul anterior, folosind colectare_comenzi și colecție_clienți, am specifica colecție_clienți ca fiind colecția from.
  • localField – reprezintă câmpul din colecția de lucru sau principală folosit pentru a compara cu câmpurile din colecția secundară (colecție_clienți în cazul nostru). În exemplul de mai sus, localField ar fi customer_id din colectare_comenzi.
  • foreignField – este câmpul cu care se compară în colecția specificată prin from. În exemplul nostru, acesta ar fi customer_num din colecție_clienți.
  • as – reprezintă un nou nume de câmp specificat pentru a include un câmp care conține documentele rezultate din potrivirile dintre localField și foreignField. Toate aceste potriviri sunt plasate într-o matrice în acest câmp. Dacă nu există potriviri, acest câmp va conține o matrice goală.

Din cele două colecții anterioare, am utiliza următorul cod pentru a efectua o operațiune $lookup, având colectare_comenzi ca și colecție principală:

  {
     $lookup: {
       from: "colecție_clienți",
       localField: "customer_id",
       foreignField: "customer_num",
       as: "customer_info"
     }
  }
  

Câmpul as poate avea orice valoare string. Totuși, dacă i se dă un nume care există deja în documentul de lucru, acel câmp va fi suprascris.

Combinarea datelor din mai multe colecții

$lookup este o etapă utilă într-o conductă de agregare MongoDB. Deși prezența acestei etape nu este obligatorie, ea devine crucială atunci când se efectuează interogări complexe care necesită combinarea datelor din mai multe colecții.

Etapa $lookup realizează o îmbinare externă stângă între două colecții, ceea ce duce la crearea unui câmp nou sau la suprascrierea unui câmp existent cu o matrice care conține documente dintr-o altă colecție.

Documentele respective sunt selectate în funcție de existența unor valori potrivite cu valorile câmpului cu care se compară. Rezultatul final este un câmp care conține o matrice de documente în cazul în care s-au găsit potriviri, sau o matrice goală în cazul în care nu au fost găsite potriviri.

Să luăm ca exemplu colecțiile de angajați și proiecte, prezentate mai jos:

Putem folosi următorul cod pentru a combina cele două colecții:

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

Rezultatul acestei operațiuni este o combinație a celor două colecții. Rezultatul prezintă proiectele și toți angajații alocați fiecărui proiect, angajații fiind reprezentați într-o matrice.

Etape ale conductei care pot fi utilizate împreună cu $lookup

După cum am menționat, $lookup este o etapă dintr-o conductă de agregare MongoDB și poate fi utilizată în conjuncție cu alte etape. Pentru a exemplifica modul în care aceste etape pot fi combinate cu $lookup, vom folosi următoarele două colecții:

În MongoDB, acestea sunt stocate în format JSON. Iată cum arată colecțiile de mai sus în MongoDB:

Câteva exemple de etape ale conductei de agregare care pot fi utilizate împreună cu $lookup includ:

$match

$match este o etapă folosită pentru a filtra fluxul de documente, permițând doar acelor documente care îndeplinesc o condiție specificată să treacă la etapa următoare a conductei. Această etapă este cel mai bine utilizată la începutul procesului, pentru a elimina documentele care nu vor fi necesare, optimizând astfel conducta de agregare.

Folosind cele două colecții anterioare, putem combina $match și $lookup astfel:

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

$match este folosit pentru a filtra utilizatorii din SUA. Rezultatul de la $match este apoi combinat cu $lookup pentru a obține detaliile comenzilor utilizatorilor din SUA. Rezultatul operațiunii de mai sus este prezentat mai jos:

$project

$project este o etapă folosită pentru a remodela documentele prin specificarea câmpurilor care trebuie incluse, excluse sau adăugate. De exemplu, dacă procesați documente cu zece câmpuri fiecare, dar doar patru câmpuri conțin datele necesare pentru prelucrare, puteți utiliza $project pentru a filtra câmpurile inutile.

Acest lucru permite evitarea trimiterii datelor inutile la etapa următoare a conductei de agregare.

Putem combina $lookup și $project astfel:

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

Codul de mai sus combină colecțiile utilizatorilor și comenzilor folosind $lookup, apoi $project este folosit pentru a afișa doar numele fiecărui utilizator și suma cheltuită de fiecare. $project este folosit și pentru a elimina câmpul _id din rezultate. Rezultatul operațiunii de mai sus este prezentat mai jos:

$unwind

$unwind este o etapă de agregare folosită pentru a deconstrui sau a derula un câmp de tip matrice, creând noi documente pentru fiecare element al matricei. Acest lucru este util în cazul în care se dorește rularea unei agregări pe valorile unui câmp matrice.

De exemplu, în exemplul de mai jos, în cazul în care doriți să rulați agregarea în câmpul hobbies, nu se poate face direct deoarece este o matrice. Totuși, puteți utiliza $unwind și apoi efectua agregări pe documentele rezultate.

Folosind colecțiile de utilizatori și comenzi, putem combina $lookup și $unwind astfel:

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

În codul de mai sus, $lookup returnează un câmp matrice numit orders. Apoi, $unwind este folosit pentru a derula câmpul matricei. Rezultatul acestei operațiuni este prezentat mai jos: observăm că Alice apare de două ori, deoarece a avut două comenzi.

Exemple de cazuri de utilizare a $lookup

Operațiunea $lookup este un instrument util pentru procesarea datelor. De exemplu, pot exista două colecții care trebuie combinate pe baza unor câmpuri care conțin date similare. O etapă simplă $lookup poate fi folosită în acest scop și pentru a adăuga un nou câmp colecțiilor primare, care conține documente obținute dintr-o altă colecție.

Să luăm în considerare colecțiile de utilizatori și comenzi de mai jos:

Cele două colecții pot fi combinate folosind $lookup pentru a da rezultatul prezentat mai jos:

$lookup poate fi folosit și pentru a efectua îmbinări mai complexe. Operațiunea nu se limitează doar la combinarea a două colecții. Se pot implementa mai multe etape $lookup pentru a efectua îmbinări pe mai mult de două colecții. Să luăm în considerare cele trei colecții prezentate mai jos:

Putem folosi următorul cod pentru a efectua o îmbinare complexă între cele trei colecții, pentru a obține toate comenzile efectuate și, de asemenea, detaliile despre produsele comandate.

Codul de mai jos ne permite să facem exact asta:

  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" }
        }
     }
  ])
  

Rezultatul operațiunii de mai sus este prezentat mai jos:

Concluzie

În procesarea datelor care implică mai multe colecții, $lookup poate fi util, deoarece permite asocierea datelor și extragerea concluziilor pe baza datelor stocate în mai multe colecții. Prelucrarea datelor rareori se bazează pe o singură colecție.

Pentru a extrage concluzii relevante din date, combinarea datelor din mai multe colecții este un pas cheie. Prin urmare, se recomandă utilizarea etapei $lookup în cadrul conductei de agregare MongoDB pentru a procesa mai eficient datele și a obține informații valoroase din datele brute stocate în colecții.

De asemenea, se recomandă să explorați și alte comenzi și interogări MongoDB.