Асоціація багатьох до багатьох MongoDB


143

Як би ви зробили асоціацію з багатьма на багато з MongoDB?

Наприклад; скажімо, у вас є таблиця користувачів та таблиця ролей. Користувачі мають багато ролей, а ролі мають багато користувачів. У землі SQL ви створили б таблицю UserRoles.

Users:
    Id
    Name

Roles:
    Id
    Name

UserRoles:
    UserId
    RoleId

Як подібні відносини обробляються в MongoDB?


Дивіться також відповіді на це запитання та це запитання
Меттью Мердок

Відповіді:


96

Залежно від потреб у запиті ви можете помістити все в документ користувача:

{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 дозволить користувачеві знати про роль, а роль - про користувача. Це означає, що мені доведеться встановити роль користувача, а користувача - роль. Я думаю, це добре, хоча.
Josh Close

46
Просто тому , що база даних не підтримує SQL не означає , що посилання не є корисними інструментами NoSQL = NoReference см це пояснення: mongodb.org/display/DOCS/Schema+Design
Том Gruner

8
Це не здається гарною ідеєю. Якщо у вас є лише шість ролей, звичайно, але що робити, якщо у вас було 20000 об'єктів, які можна було б пов’язати з ще 20000 об'єктами (у відносинах багато-багато)? Навіть документи MongoDB натякають, що вам слід уникати змінних, величезних масивів посилань. docs.mongodb.org/manual/tutorial/…
CaptSaltyJack

Очевидно, що для взаємовідносин багатьох до багатьох об’єктів ви хочете використовувати інше рішення (наприклад, приклад видавця / книги в документах). У цьому випадку це працює чудово і ускладнюватиме лише речі, якщо ви створюєте окремі документи з рольовими користувачами.
dieerikh

1
Це працює для більшості систем, оскільки ролі зазвичай є невеликим набором, і ми зазвичай беремо користувача, а потім переглядаємо його / її ролі. Але що робити, якщо ролі великі? або що робити, якщо я попрошу надати мені список користувачів, які мають роль == "Інженер"? Тепер вам доведеться запитувати всю вашу колекцію користувачів (відвідуючи всіх користувачів, які також не мають ролі Інженера), лише щоб отримати 2 або 3 користувачів, які можуть мати цю роль серед мільйонів таких користувачів, наприклад. Окремий стіл або колекція набагато краще.
theprogrammer

32

Замість того, щоб намагатися моделювати відповідно до нашого багаторічного досвіду роботи з RDBMS, мені було набагато простіше моделювати рішення для репозиторіїв документів, використовуючи 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.Roles навмисно денормалізується, зберігається у Користувача, а також Role.Users, які мають дублікат сховища.

Якщо це легко не видно в тексті, але це тип мислення, який рекомендується використовувати у сховищах документів.

Я сподіваюсь, що це допоможе подолати розрив щодо читаної сторони операцій.

Що стосується запису, те, що рекомендується, - це моделювати відповідно до атомних записів. Наприклад, якщо структури документів вимагають придбання блокування, оновлення одного документа, потім іншого та, можливо, більше документів, то звільнення блокування, ймовірно, модель не вдалася. Тільки тому, що ми можемо будувати розподілені замки, не означає, що ми повинні їх використовувати.

Що стосується моделі "Користувач у ролях", то операції, які розтягують наше уникнення блокування атомного запису, - це додавання або видалення користувача з ролі. У будь-якому випадку успішна операція призводить до оновлення як одного користувача, так і одного документа ролі. Якщо щось не вдається, зробити очищення легко. Це є однією з причин, коли модель «Одиниця роботи» з'являється досить багато, де використовуються сховища документів.

Операція, яка дійсно розтягує наше уникнення блокування атомного запису, - це очищення ролі, що призведе до багатьох оновлень Користувача для видалення імені Role.name з масиву User.roles. Ця операція ясного тоді, як правило, не рекомендується, але при необхідності може бути реалізована, замовивши операції:

  1. Отримайте список імен користувачів від Role.users.
  2. Ітератуйте імена користувачів з кроку 1, видаліть ім’я ролі з User.roles.
  3. Очистіть Role.users.

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


15

Я щойно натрапив на це питання, і, хоча це давнє, я подумав, що було б корисно додати пару можливостей, не зазначених у відповідях. Крім того, за останні кілька років справи трохи змінилися, тому варто підкреслити, що 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, це може бути перевагою в цьому випадку. Цілком справедливо вкладати різні типи об'єктів у властивості “Ролі” об’єкта користувача.


Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.