Як використовувати $lookup у MongoDB

MongoDB є популярною базою даних NoSQL, яка організовує інформацію в колекціях. Кожна колекція MongoDB містить один або декілька документів, де зберігаються фактичні дані у форматі JSON. Документи можна розглядати як аналоги рядків у традиційних реляційних базах даних SQL, а колекції – як таблиці.

Однією з ключових функцій будь-якої бази даних є можливість запитувати збережені дані. Запити дозволяють витягувати конкретну інформацію, аналізувати дані, створювати звіти та інтегрувати дані з різних джерел.

Для ефективного використання бази даних важливо мати можливість об’єднувати дані з різних таблиць (у випадку SQL) або колекцій (у випадку NoSQL) в один результуючий набір.

У MongoDB оператор $lookup дозволяє користувачам об’єднувати інформацію з двох колекцій під час виконання запиту. Він виконує функцію, аналогічну лівому зовнішньому з’єднанню в базі даних SQL.

Застосування та мета $lookup

Важлива функція баз даних – це обробка даних для вилучення корисної інформації. Наприклад, якщо ви керуєте рестораном, ви можете аналізувати дані, щоб з’ясувати, скільки страв готується щодня, які страви є популярними у вихідні, або навіть скільки кави продається протягом кожної години дня.

Для таких цілей простих запитів до бази даних недостатньо. Вам потрібно виконувати розширені запити. Для задоволення таких потреб MongoDB надає функцію конвеєра агрегації.

Конвеєр агрегації – це система, що складається з операцій, які називаються етапами. Ці етапи використовуються для обробки даних з метою отримання кінцевого результату. Серед прикладів етапів конвеєра агрегації можна назвати $sort, $match, $group, $merge, $count і $lookup.

Ці етапи можна застосовувати в будь-якому порядку. Кожен етап конвеєра виконує свою операцію над даними, які проходять через конвеєр.

Таким чином, $lookup є етапом конвеєра агрегації MongoDB. Він використовується для виконання лівого зовнішнього з’єднання між двома колекціями в базі даних MongoDB. Ліве зовнішнє з’єднання поєднує всі документи з лівої колекції з відповідними документами з правої колекції.

Розглянемо дві колекції, представлені у форматі таблиці для кращого розуміння:

збір_замовлень:

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

customer_collection:

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

Якщо ми виконаємо ліве зовнішнє з’єднання цих колекцій за полем customer_id в order_collection (ліва колекція) та customer_collection (права колекція), то результат включатиме всі документи з order_collection і документи з customer_collection, які мають customer_num, що відповідає customer_id будь-якого з записів у order_collection.

Ось як виглядатиме результат операції лівого зовнішнього з’єднання, якщо його подати у табличному форматі:

Зверніть увагу, що для клієнта з customer_id 101 у колекції замовлень, який не мав відповідного значення customer_num в колекції клієнтів, відповідні значення з таблиці клієнтів заповнені нулями.

Оператор $lookup виконує точне порівняння на рівність між полями та вилучає весь документ, а не лише поля, які збігаються.

Синтаксис $lookup

Синтаксис $lookup виглядає так:

{
   $lookup:
     {
       from: <колекція для з'єднання>,
       localField: <поле з вхідних документів>,
       foreignField: <поле з документів колекції "from">,
       as: <поле вихідного масиву>
     }
}

$lookup приймає чотири параметри:

  • from – представляє колекцію, з якої ми хочемо отримувати документи. У попередньому прикладі, де використовувалися orders_collection та customers_collection, це буде customer_collection.
  • localField – це поле в робочій або первинній колекції, яке ми використовуємо для порівняння з полями в колекції from (customers_collection в нашому випадку). У прикладі localField буде customer_id з orders_collection.
  • foreignField – це поле, з яким ми хочемо порівняти в колекції from. У нашому випадку це буде customer_num з customer_collection.
  • as – це нове ім’я поля, яке ми задаємо для представлення поля, яке буде відображатися в нашому документі. Це поле містить масив документів, отриманих в результаті збігів між localField та foreignField. Якщо збігів немає, це поле буде містити порожній масив.

З нашими двома попередніми колекціями ми використали б наступний код для виконання операції $lookup між двома колекціями, де orders_collection є нашою робочою або основною колекцією:

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

Зауважте, що поле as може мати будь-яке рядкове значення. Однак якщо ви дасте йому ім’я, яке вже існує в робочому документі, це поле буде перезаписане.

Об’єднання даних з кількох колекцій

Оператор $lookup в MongoDB є важливим етапом у конвеєрі агрегації. Хоча конвеєр агрегації не обов’язково повинен містити етап $lookup, цей етап є вирішальним для виконання складних запитів, які потребують об’єднання даних з різних колекцій.

Етап $lookup виконує ліве зовнішнє з’єднання двох колекцій, створюючи нове поле або перезаписуючи значення існуючого поля масивом, що містить документи з іншої колекції.

Ці документи вибираються на основі того, чи мають вони значення, які відповідають значенням поля, з яким вони порівнюються. Кінцевим результатом є поле, що містить масив документів (якщо збіги знайдені), або порожній масив (якщо збігів не знайдено).

Розглянемо колекції співробітників та проектів, показані нижче:

Ми можемо використати наступний код для об’єднання цих двох колекцій:

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

Результатом цієї операції є об’єднання двох колекцій. Результатом є проекти та всі співробітники, призначені для кожного проекту. Співробітники представлені у вигляді масиву.

Етапи конвеєра, які можна використовувати разом з $lookup

Як вже згадувалося, $lookup є етапом конвеєра агрегації MongoDB, і його можна використовувати разом з іншими етапами. Щоб продемонструвати, як ці етапи можна використовувати разом із $lookup, ми використаємо наступні дві колекції:

У MongoDB вони зберігаються у форматі JSON. Ось так виглядають наведені вище колекції в MongoDB:

Деякі приклади етапів конвеєра агрегації, які можна використовувати разом із $lookup, включають:

$match

$match – це етап конвеєра агрегації, який використовується для фільтрації потоку документів. Він дозволяє лише тим документам, які відповідають заданій умові, перейти до наступного етапу конвеєра. Цей етап найкраще використовувати на початку конвеєра, щоб видалити непотрібні документи і оптимізувати конвеєр агрегації.

Використовуючи попередні дві колекції, можна поєднати $match та $lookup так:

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

$match використовується для фільтрації користувачів із США. Результат $match потім об’єднується з $lookup для отримання деталей замовлень користувачів із США. Результат цієї операції показаний нижче:

$project

$project – це етап, що використовується для зміни структури документів. Він дозволяє визначити, які поля слід включити, виключити або додати до документів. Наприклад, якщо ви обробляєте документи з десятьма полями, але лише чотири поля містять дані, необхідні для обробки, ви можете використати $project, щоб відфільтрувати непотрібні поля.

Це дозволяє уникнути надсилання непотрібних даних на наступний етап конвеєра агрегації.

Ми можемо об’єднати $lookup і $project таким чином:

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

Наведений вище код об’єднує колекції користувачів та замовлень за допомогою $lookup. Потім $project використовується для відображення імені кожного користувача та загальної суми, витраченої кожним користувачем. $project також використовується для видалення поля _id з результатів. Результат цієї операції показаний нижче:

$unwind

$unwind – це етап агрегації, який використовується для деконструкції або розгортання масиву, створюючи нові документи для кожного елемента в масиві. Це корисно, якщо ви хочете виконати агрегацію значень полів масиву.

Наприклад, у прикладі нижче, якщо ви хочете запустити агрегацію в полі “hobbies”, ви не можете цього зробити, оскільки це масив. Однак ви можете використати $unwind для розгортання масиву, а потім виконати агрегацію отриманих документів.

Використовуючи колекції користувачів та замовлень, ми можемо використати $lookup та $unwind разом таким чином:

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

У наведеному вище коді $lookup повертає масив під назвою “orders”. Потім $unwind використовується для розгортання цього масиву. Результат цієї операції показаний нижче: Зверніть увагу, що Аліса з’являється двічі, тому що вона має два замовлення.

Приклади використання $lookup

$lookup є корисним інструментом під час обробки даних. Наприклад, у вас можуть бути дві колекції, які ви хочете об’єднати на основі полів, що містять подібні дані. Для цього можна використати $lookup, додаючи нове поле до основних колекцій, яке міститиме документи з іншої колекції.

Розглянемо колекції користувачів та замовлень, показані нижче:

Дві колекції можна об’єднати за допомогою $lookup для отримання результату, показаного нижче:

$lookup також можна використовувати для виконання складніших об’єднань. Він не обмежується лише двома колекціями. Ви можете використовувати кілька етапів $lookup для об’єднання більш ніж двох колекцій. Розглянемо три колекції:

Ми можемо використати наступний код для виконання складнішого об’єднання трьох колекцій, щоб отримати всі зроблені замовлення, а також деталі замовлених продуктів:

Наступний код дозволяє нам це зробити:

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

Результат цієї операції показано нижче:

Висновок

Під час обробки даних із залученням кількох колекцій, $lookup є корисним інструментом. Він дозволяє об’єднувати дані та робити висновки на основі інформації, що зберігається в різних колекціях. Обробка даних рідко обмежується однією колекцією.

Об’єднання даних з кількох колекцій є важливим кроком для вилучення значущої інформації. Тому, подумайте про використання етапу $lookup у конвеєрі агрегації MongoDB, щоб краще обробляти дані та отримувати корисну інформацію з необроблених даних, що зберігаються в колекціях.

Ви також можете дослідити інші команди та запити MongoDB.