Ассоциация MongoDB "многие ко многим"

145

Как бы вы установили связь "многие ко многим" с MongoDB?

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

Users:
    Id
    Name

Roles:
    Id
    Name

UserRoles:
    UserId
    RoleId

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

Джош Клоуз
источник
См. Также ответы на этот вопрос и этот вопрос
Мэтью Мердок

Ответы:

95

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

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

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

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

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

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

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

{_id:"6c6793300334001000000006"
,rolename:"Engineer"
}
диедерих
источник
7
Последнее было бы лучше, так как мне нужно получить список всех доступных ролей. Единственная плохая часть - тогда мне нужно настроить оба конца ассоциации. При использовании способа SQL добавление UserRole позволит пользователю узнать о роли, а роль - о пользователе. Таким образом, мне нужно будет установить роль для пользователя, а для пользователя - роль. Я думаю, это нормально.
Джош Клоуз
48
Тот факт, что база данных не поддерживает sql, не означает, что ссылки не являются полезными инструментами NoSQL! = NoReference см. Это объяснение: mongodb.org/display/DOCS/Schema+Design
Tom Gruner
8
Это не кажется хорошей идеей. Конечно, если у вас всего шесть ролей, но что, если бы у вас было 20000 объектов, которые можно было бы связать с еще 20000 объектами (в отношении «многие-многие»)? Даже документы MongoDB намекают, что вам следует избегать изменяемых огромных массивов ссылок. docs.mongodb.org/manual/tutorial/…
CaptSaltyJack
Очевидно, что для отношений «многие ко многим» с большим количеством объектов вы хотите использовать другое решение (например, пример «издатель / книга» в документации). В этом случае он работает нормально и только усложнит ситуацию, если вы создадите отдельные документы для пользовательских ролей.
диедерих
1
Это работает для большинства систем, потому что ролей обычно небольшой, и мы обычно берем пользователя, а затем смотрим на его / ее роли. Но что, если роли большие? или что, если я попрошу вас дать мне список пользователей с ролью == "инженер"? Теперь вам нужно будет запросить всю свою коллекцию пользователей (посетив всех пользователей, у которых также нет роли Engineer), только чтобы получить, например, 2 или 3 пользователя, которые могут иметь эту роль, среди миллиона таких пользователей. Гораздо лучше отдельный стол или сборник.
theprogrammer
33

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

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

  1. Роль - создание, чтение, обновление, удаление, список пользователей, добавление пользователя, удаление пользователя, очистка всех пользователей, индекс пользователя или аналогичные для поддержки «Пользователь в роли» (такие операции, как контейнер + собственные метаданные).
  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. Очистите Role.users.

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

Paegun
источник
16

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

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

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

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

РЕШЕНИЕ В 2015 ГОДУ ПРОБЛЕМЫ 2010 ГОДА

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

На более практическом уровне выпуск Couchbase 4.0 означал, что впервые вы можете выполнять собственные JOIN в 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, в этом случае это может быть преимуществом. Совершенно верно вкладывать различные типы объектов в свойство «Roles» пользовательского объекта.

Cortopy
источник
5

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

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

company{
   //and duplicate it in company
   contracts:{ item1, item2, item3,...}
}
Андрей Андрушкевич
источник
это будет иметь производительность чтения, но обновления должны быть атомарными, что требует некоторых блокировок или чего-то подобного, верно?
Люк Арон,