Раздел 36 · MongoDB с нуля

Связи: один-ко-многим и многие-ко-многим

junior~20 мин

без входаВойди, чтобы отмечать прогресс, вести заметки и решать задачи в редакторе. войти

В прошлом уроке мы выбирали между чемоданом и камерой хранения: вкладывать данные или ссылаться на них. Сегодня применим этот выбор к связям. В реляционной базе их рисуют на ER-диаграмме, как в уроке про ER-модель. В MongoDB к виду связи добавляется ещё один вопрос: сколько записей на другой стороне. Все примеры на учебной базе магазина.

Сколько на другой стороне

Представь три связи из жизни. У человека один паспорт. У мамы трое детей. У студента пять кружков, а в каждом кружке двадцать студентов. Это один к одному, один ко многим и многие ко многим.

Реляционной базе этой классификации хватает: таблицы и внешние ключи выглядят одинаково, будь детей трое или три миллиона. В MongoDB к вопросу «один или много» добавляется второй: «насколько много». Специалисты MongoDB делят «много» на три размера:

  • несколько: до десятков, например отзывы на книгу или адреса покупателя;
  • много: сотни и тысячи, например книги одного издательства;
  • очень много: миллионы и дальше, например просмотры книг или строки журнала.

Это кардинальность связи, и от неё зависит способ хранения.

Один к одному

У покупателя один адрес доставки. Читают его всегда вместе с покупателем, отдельно он не нужен. Встраиваем:

db.customers.updateOne(
  { _id: 1 },
  { $set: { address: { city: 'Москва', street: 'Тверская, 7', zip: '125009' } } }
)
db.customers.findOne({ _id: 1 })
{
  _id: 1,
  name: 'Ольга',
  city: 'Москва',
  registered_at: ISODate('2025-11-02T00:00:00.000Z'),
  address: { city: 'Москва', street: 'Тверская, 7', zip: '125009' }
}

В PostgreSQL для этого понадобилась бы отдельная таблица со ссылкой или пачка колонок address_city, address_street. В MongoDB вложенный документ и есть естественная форма такой связи.

Один ко многим: три способа

Несколько: массив внутри

Отзывы на книгу это связь «один к нескольким». Их показывают со страницей книги, и их немного. Поэтому они лежат массивом reviews прямо в книге. Мы видели это в прошлом уроке.

Много: ссылка в ребёнке

У Гоголя три книги, у плодовитого автора их может быть сотня. Книги нужны сами по себе: их ищут, сортируют, продают. Поэтому книга хранит ссылку на автора, а не автор хранит книги:

db.authors.findOne({ _id: 3 })
db.books.find({ 'author._id': 3 }, { title: 1 }).sort({ _id: 1 })
{ _id: 3, name: 'Гоголь', country: 'Россия', born: 1809 }
[
  { _id: 5, title: 'Шинель' },
  { _id: 6, title: 'Мёртвые души' },
  { _id: 9, title: 'Нос' }
]

Ссылка лежит в стороне «многих», как ссылка на родителя. Это ровно то, как устроен внешний ключ в PostgreSQL: books.author_id указывает на authors.id. Разница в том, что MongoDB не проверяет, существует ли автор с таким номером. Следить за этим будет приложение.

Есть и зеркальный вариант: хранить в авторе массив номеров его книг, book_ids: [5, 6, 9]. Так делают, когда список детей ограничен и его часто читают целиком вместе с родителем. Но каждую новую книгу придётся дописывать в два места.

Очень много: только ссылка в ребёнке

Заказы покупателя и тем более просмотры книг растут без предела. Здесь вариантов нет: никаких массивов в родителе, только ссылка в каждом дочернем документе. Так устроены заказы учебной базы: в каждом лежит customer_id.

урок из курса

Урок из курса «MongoDB с нуля». Курс покупается один раз и остаётся навсегда: 500 ₽.

Оплата пока не подключена: премиум и курсы открываются промокодом.