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

Как выразить классические реляционные связи средствами документов и массивов.

В документной модели связи выражают тремя способами: встраиванием, массивом ссылок и ссылкой «со стороны многих».

Один-ко-многим: встроить массив

У статьи есть комментарии — это связь «один-ко-многим». Если комментариев немного и они читаются только вместе со статьёй, их встраивают массивом:

{
  "_id": "a1",
  "title": "Привет, Mongo",
  "comments": [
    { "author": "Анна", "text": "Полезно!" },
    { "author": "Борис", "text": "Спасибо" }
  ]
}

Одним запросом получаете статью со всеми комментариями. Минус — если комментариев могут быть тысячи, документ распухнет (а у документа есть предел размера — 16 МБ). Тогда переходят к ссылкам.

Один-ко-многим: ссылка со стороны «многих»

Когда «многих» много (заказы пользователя, посты автора), удобнее хранить ссылку в дочерних документах. Каждый заказ знает своего пользователя:

{ "_id": "o1", "userId": "u1", "total": 1800 }
{ "_id": "o2", "userId": "u1", "total": 500 }

Все заказы пользователя находятся обычным запросом — и их число ничем не ограничено:

db.orders.find({ userId: "u1" })

Многие-ко-многим: массив ссылок

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

{
  "_id": "s1",
  "name": "Анна",
  "courseIds": ["c1", "c2", "c5"]
}

Найти всех студентов курса c2 — поиск по массиву (MongoDB сама проверяет вхождение значения в массив):

db.students.find({ courseIds: "c2" })

Какую сторону держать «главной» (массив у студента или у курса) — зависит от того, какие запросы чаще. Если обычно спрашивают «курсы студента» — массив у студента; если «студенты курса» — у курса. Хранить массивы с обеих сторон можно, но тогда их надо синхронизировать, а это источник ошибок.

Поиск по элементу массива — это просто

Важный момент для пришедших из SQL: фильтр { courseIds: "c2" } срабатывает, если c2 есть среди элементов массива. Никакого специального синтаксиса не нужно — MongoDB понимает массивы прозрачно. Это делает массив ссылок очень удобным инструментом.

Итог

  • Один-ко-многим: встраивать массив (если «многих» немного) или ссылаться со стороны дочерних документов (если много).
  • Многие-ко-многим: массив _id на одной из сторон вместо связующей таблицы.
  • Поиск по значению внутри массива не требует особого синтаксиса — обычный фильтр по полю-массиву.
Проверьте себя
1. Как обычно моделируют один-ко-многим, когда дочерних записей очень много?
AВстраивают все дочерние записи в родителя
BХранят ссылку на родителя в каждом дочернем документе
CСоздают отдельную базу данных
DИспользуют только _id родителя как имя коллекции
2. Как в MongoDB чаще выражают связь многие-ко-многим?
AСвязующей таблицей, как в SQL
BМассивом идентификаторов на одной из сторон
CДублированием всей базы
DЭто невозможно в документной модели
3. Сработает ли фильтр { courseIds: "c2" }, если courseIds — массив?
AНет, для массивов нужен особый оператор
BДа, он истинен, когда c2 есть среди элементов массива
CТолько если массив из одного элемента
DТолько после создания индекса
Поддержать проект