MongoDB Ассоциация «многие ко многим» - PullRequest
122 голосов
/ 25 февраля 2010

Как бы вы связали многих с многими MongoDB?

Например;допустим, у вас есть таблица пользователей и таблица ролей.У пользователей много ролей, а у ролей много пользователей.В земле SQL вы бы создали таблицу UserRoles.

Users:
    Id
    Name

Roles:
    Id
    Name

UserRoles:
    UserId
    RoleId

Как обрабатываются такие же отношения в MongoDB?

Ответы [ 4 ]

82 голосов
/ 25 февраля 2010

В зависимости от ваших запросов вы можете поместить все в пользовательский документ:

{name:"Joe"
,roles:["Admin","User","Engineer"]
}

Чтобы получить всех инженеров, используйте:

db.things.find( { roles : "Engineer" } );

Если вы хотите сохранить роли в отдельных документах, вы можете включить _id документа в массиве ролей вместо имени:

{name:"Joe"
,roles:["4b5783300334000000000aa9","5783300334000000000aa943","6c6793300334001000000006"]
}

и установите такие роли, как:

{_id:"6c6793300334001000000006"
,rolename:"Engineer"
}
28 голосов
/ 11 сентября 2013

Вместо того, чтобы пытаться моделировать в соответствии с нашим многолетним опытом работы с RDBMS, я обнаружил, что гораздо проще моделировать решения для хранилищ документов с использованием MongoDB, Redis и других хранилищ данных NoSQL, оптимизируя их для случаев использования при чтении, и при этом внимательно атомарных операций записи, которые должны поддерживаться сценариями использования записи.

Например, использование домена «Пользователи в ролях»:

  1. Роль - создание, чтение, обновление, удаление, создание списка пользователей, добавление пользователя, удаление пользователя, удаление всех пользователей, индекс пользователя или аналогичная поддержка «Is User In Role» (такие операции, как контейнер + его собственные метаданные).
  2. Пользователь - создание, чтение, обновление, удаление (операции CRUD, такие как автономная сущность)

Это может быть смоделировано как следующие шаблоны документов:

User: { _id: UniqueId, name: string, roles: string[] }
    Indexes: unique: [ name ]
Role: { _id: UniqueId, name: string, users: string[] }
    Indexes: unique: [ name ]

Для поддержки высокочастотного использования, такого как функции, связанные с ролями, из сущности User, User.Roles преднамеренно денормализованы, хранятся как для пользователя, так и для пользователей Role.Users, имеющих дублированное хранилище.

Если это не совсем очевидно в тексте, но это тот тип мышления, который поощряется при использовании хранилищ документов.

Я надеюсь, что это поможет преодолеть разрыв в части операций чтения.

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

В случае модели «Пользователь в ролях» операции, которые растягивают нашу блокировку атомарной записи во избежание блокировок, - это добавление или удаление пользователя из роли. В любом случае успешная операция приводит к обновлению как отдельного пользователя, так и одного документа роли. Если что-то не получается, легко выполнить очистку. Это одна из причин, по которой шаблон единиц работы часто используется в хранилищах документов.

Операция, которая действительно растягивает наше предотвращение блокировок на атомарную запись, очищает Роль, что приведет к многочисленным обновлениям Пользователя для удаления Role.name из массива User.roles. Эта операция очистки обычно не рекомендуется, но при необходимости ее можно реализовать, упорядочив операции:

  1. Получить список имен пользователей из Role.users.
  2. Выполните итерации имен пользователей с шага 1, удалите имя роли из User.roles.
  3. Очистить пользователей ролей.

В случае возникновения проблемы, которая, вероятнее всего, возникнет на шаге 2, откат выполняется легко, поскольку для восстановления или продолжения можно использовать тот же набор имен пользователей, что и на шаге 1.

13 голосов
/ 05 декабря 2015

Я только что наткнулся на этот вопрос и, хотя он старый, я подумал, что было бы полезно добавить пару возможностей, не упомянутых в данных ответах. Кроме того, за последние несколько лет ситуация немного изменилась, поэтому стоит подчеркнуть, что SQL и NoSQL становятся ближе друг к другу.

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

ОТНОШЕННЫЙ МИР: Структурные данные> Напишите заявку, чтобы получить ее
NOSQL WORLD: Дизайн приложения> Структура данных соответственно

Даже если данные являются реляционными, NoSQL все еще является опцией. Например, отношения «один ко многим» вообще не являются проблемой и широко рассматриваются в документах MongoDB

Решение 2015 года к проблеме 2010 года

Поскольку этот вопрос был опубликован, предпринимались серьезные попытки приблизить noSQL к SQL. Команда под руководством Янниса Папаконстантину из Калифорнийского университета (Сан-Диего) работает над FORWARD , реализацией SQL ++, которая вскоре может стать решением для постоянных проблем, подобных той, что была опубликована здесь.

На более практическом уровне выпуск Couchbase 4.0 означает, что вы впервые можете выполнять собственные соединения в NoSQL. Они используют свой собственный N1QL. Это пример JOIN из их учебников :

SELECT usr.personal_details, orders 
        FROM users_with_orders usr 
            USE KEYS "Elinor_33313792" 
                JOIN orders_with_users orders 
                    ON KEYS ARRAY s.order_id FOR s IN usr.shipped_order_history END

N1QL допускает большинство, если не все операции SQL, включая агрегирование, фильтрацию и т. Д.

НЕЧТО НОВОЕ ГИБРИДНОЕ РЕШЕНИЕ

Если MongoDB по-прежнему является единственным вариантом, я хотел бы вернуться к моей точке зрения, что приложение должно иметь приоритет над структурой данных. Ни в одном из ответов не упоминается гибридное встраивание, при котором большинство запрашиваемых данных внедряется в документ / объект, а ссылки сохраняются для меньшего числа случаев.

Пример: может ли информация (кроме имени роли) ждать? Может ли начальная загрузка приложения быть быстрее, не запрашивая ничего, что пользователю пока не нужно?

Это может быть в том случае, если пользователь входит в систему и ему / ей необходимо просмотреть все параметры для всех ролей, которым он / она принадлежит. Тем не менее, пользователь является «инженером», и опции для этой роли используются редко. Это означает, что приложение должно показывать опции для инженера только в том случае, если он / она хочет нажать на них.

Это может быть достигнуто с помощью документа, который сообщает приложению при запуске (1), к каким ролям принадлежит пользователь и (2) где получить информацию о событии, связанном с определенной ролью.

   {_id: ObjectID(),
    roles: [[“Engineer”, “ObjectId()”],
            [“Administrator”, “ObjectId()”]]
   }

Или, что еще лучше, индексируйте поле role.name в коллекции ролей, и вам, возможно, не потребуется также встраивать ObjectID ().

Другой пример: информация обо ВСЕХ ролях запрашивается ВСЕ время?

Это также может быть случай, когда пользователь входит в панель управления, и 90% времени выполняет задачи, связанные с ролью «Инженер». Гибридное встраивание может быть выполнено для этой конкретной роли полностью и сохранить ссылки только для остальных.

{_id: ObjectID(),
  roles: [{name: “Engineer”, 
           property1: value1,
           property2: value2
          },   
          [“Administrator”, “ObjectId()”]
         ]
}

Отсутствие схемы - это не просто характеристика NoSQL, это может быть преимуществом в этом случае. Вполне допустимо вложить объекты разных типов в свойство «Роли» пользовательского объекта.

4 голосов
/ 17 марта 2011

в случае, когда сотрудник и компания объект-объект попробуйте использовать следующую схему:

employee{
   //put your contract to employee
   contracts:{ item1, item2, item3,...}
}

company{
   //and duplicate it in company
   contracts:{ item1, item2, item3,...}
}
...