В прошлом уроке закончился кластер про CRUD: ты умеешь вставлять, искать, менять и удалять документы. Но мы ни разу не спросили, почему документы учебной базы магазина устроены так. Почему автор лежит прямо внутри книги, а покупатель в заказе записан одним номером? С этого урока начинается моделирование данных, и первый его вопрос главный: вкладывать или ссылаться.
Чемодан и камера хранения
Ты едешь на неделю в другой город. Самое нужное кладёшь в чемодан: зарядку, документы, одежду. Это всегда при тебе, достаёшь одним движением. Лыжи с собой не потащишь: сдашь их в камеру хранения и возьмёшь квитанцию. Лыжи пригодятся раз в поездке, и за ними придётся сходить отдельно.
У документа в MongoDB те же два варианта:
- Встроить. Положить связанные данные прямо внутрь документа, как вещи в чемодан. Это встраивание.
- Сослаться. Хранить в документе только номер другого документа, как квитанцию. Это ссылка.
В PostgreSQL выбора нет: связанные данные живут в разных таблицах, а собирает их внешний ключ и JOIN. Реляционная теория требует нормализации: каждый факт хранится один раз. MongoDB разрешает обе стратегии и оставляет выбор тебе.
Как это устроено в учебной базе
Посмотрим на книгу «Грокаем алгоритмы»:
db.books.findOne({ _id: 7 }, { title: 1, author: 1, reviews: 1 })
{
_id: 7,
title: 'Грокаем алгоритмы',
author: { _id: 4, name: 'Бхаргава', country: 'США' },
reviews: [
{ user: 'Павел', rating: 5, text: 'Понятно даже новичку' },
{ user: 'Светлана', rating: 3, text: 'Слишком просто' }
]
}
Здесь встроено два куска:
- Отзывы лежат массивом внутри книги. Отдельно от книги отзыв никому не нужен, а на странице книги его показывают всегда.
- Сводка об авторе: имя и страна. Полная карточка автора живёт в коллекции
authors, а в книге лежит копия того, что нужно для витрины. Об этом приёме подробно в уроке про денормализацию.
Одним запросом мы получили всё, что нужно для страницы книги. В PostgreSQL здесь были бы три таблицы и два соединения.
А вот заказы устроены иначе. Покупатель в заказе записан одним полем customer_id, это ссылка. Чтобы показать Ольге историю заказов, нужно два шага:
const olga = db.customers.findOne({ name: 'Ольга' })
db.orders.find({ customer_id: olga._id }, { status: 1, created_at: 1 }).sort({ created_at: 1, _id: 1 })
[
{
_id: 1,
status: 'delivered',
created_at: ISODate('2026-09-01T00:00:00.000Z')
},
{
_id: 2,
status: 'delivered',
created_at: ISODate('2026-09-14T00:00:00.000Z')
}
]
Первый запрос находит покупателя, второй ищет его заказы по ссылке. Так это выглядит и в коде приложения: два обращения к базе. Собрать то же одним запросом умеет этап $lookup, его разберём в уроке про конвейер агрегации.
Почему заказы не встроены в покупателя? Их число растёт всю жизнь клиента, а читать их нужно отдельно: «все заказы за сентябрь» не должно перебирать каждого покупателя.
урок из курса
Урок из курса «MongoDB с нуля». Курс покупается один раз и остаётся навсегда: 500 ₽.
Оплата пока не подключена: премиум и курсы открываются промокодом.