← К оглавлению занятия

💻 Примеры

📁 Блок: MongoDB / практическая отработка ⏱️ Время изучения: ~90 мин 🎯 Сложность: Средняя

⚡ Кратко: суть занятия

Первая часть практической отработки MongoDB строится вокруг aggregation pipeline.

$bucket распределяет документы по диапазонам: например цены жилья в Барселоне.

$unwind превращает каждый элемент массива reviews в отдельный документ.

$set и $unset помогают превратить вложенный объект отзыва в плоский документ.

$out записывает результат агрегации в новую коллекцию reviews.

$merge обновляет существующую коллекцию, когда нужно добавить вычисленные поля.

$map, $multiply и $sum позволяют посчитать total по массиву items в sales.

Топ-3 ошибки: забыть $match перед $bucket, использовать $out вместо $merge для обновления, неправильно обратиться к $$item внутри $map.

Рабочие примеры

Цены жилья в Барселоне

db.listingsAndReviews.aggregate([
  { $match: { "address.country_code": "ES", "address.market": "Barcelona" } },
  { $bucket: {
      groupBy: "$price",
      boundaries: [0, 50, 100, 1000],
      default: "EXPENSIVE",
      output: { count: { $sum: 1 }, property: { $push: "$name" } }
  } }
])

Источник фильтрует Испанию; добавление market Barcelona делает условие ближе к формулировке про Барселону.

Коллекция reviews

db.listingsAndReviews.aggregate([
  { $match: { reviews: { $ne: [] } } },
  { $project: { _id: 0, reviews: 1 } },
  { $unwind: { path: "$reviews" } },
  { $set: {
      date: "$reviews.date",
      listing_id: "$reviews.listing_id",
      reviewer_id: "$reviews.reviewer_id",
      reviewer_name: "$reviews.reviewer_name",
      comments: "$reviews.comments"
  } },
  { $unset: "reviews" },
  { $out: "reviews" }
])

Каждый элемент reviews становится отдельным документом.

Средние оценки ресторанов

db.restaurants.aggregate([
  { $match: { grades: { $exists: true, $ne: [] } } },
  { $project: { name: 1, score: { $avg: "$grades.score" } } },
  { $bucket: {
      groupBy: "$score",
      boundaries: [0, 20, 40, 60, 80, 100],
      default: "unknown",
      output: { count: { $sum: 1 }, restaurants: { $push: "$name" } }
  } }
])

$project вычисляет средний score, $bucket раскладывает рестораны по диапазонам.

total покупки

db.sales.aggregate([
  { $addFields: {
      total: { $sum: { $map: { input: "$items", as: "item", in: { $multiply: ["$$item.price", "$$item.quantity"] } } } }
  } },
  { $merge: { into: "sales", whenMatched: "merge" } }
])

Исправленная форма $merge: в источнике показана идея, но не нужно вкладывать $merge внутрь $merge.