Skojarzenie wielu do wielu MongoDB

143

Jak utworzyłbyś powiązanie wiele do wielu z MongoDB?

Na przykład; powiedzmy, że masz tabelę użytkowników i tabelę ról. Użytkownicy mają wiele ról, a role mają wielu użytkowników. W krainie SQL utworzyłbyś tabelę UserRoles.

Users:
    Id
    Name

Roles:
    Id
    Name

UserRoles:
    UserId
    RoleId

Jak jest obsługiwany ten sam rodzaj relacji w MongoDB?

Josh Close
źródło
Zobacz także odpowiedzi na to pytanie i to pytanie
Matthew Murdoch

Odpowiedzi:

96

W zależności od potrzeb zapytania możesz umieścić wszystko w dokumencie użytkownika:

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

Aby zdobyć wszystkich Inżynierów, użyj:

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

Jeśli chcesz zachować role w oddzielnych dokumentach, możesz dołączyć identyfikator _id dokumentu do tablicy ról zamiast nazwy:

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

i skonfiguruj role takie jak:

{_id:"6c6793300334001000000006"
,rolename:"Engineer"
}
dieerikh
źródło
7
To drugie byłoby lepsze, ponieważ muszę uzyskać listę wszystkich dostępnych ról. Jedyną wadą jest to, że muszę wtedy ustawić oba końce skojarzenia. Podczas wykonywania SQL, dodanie roli użytkownika spowoduje, że użytkownik będzie wiedział o roli, a rola o użytkowniku. W ten sposób będę musiał ustawić rolę użytkownika, a użytkownika rolę. Myślę, że to w porządku.
Josh Close
46
Tylko dlatego, że baza danych nie obsługuje sql, nie oznacza, że ​​odniesienia nie są użytecznymi narzędziami NoSQL! = NoReference Zobacz to wyjaśnienie: mongodb.org/display/DOCS/Schema+Design
Tom Gruner
8
To nie wydaje się być dobrym pomysłem. Jeśli masz tylko sześć ról, to jasne, ale co by było, gdybyś miał 20000 obiektów, które można by połączyć z 20000 innymi obiektami (w relacji wiele-wiele)? Nawet dokumentacja MongoDB podpowiada, że ​​należy unikać posiadania zmiennych, ogromnych tablic odniesień. docs.mongodb.org/manual/tutorial/…
CaptSaltyJack
Oczywiście w przypadku relacji wiele-do-wielu z wieloma obiektami chcesz użyć innego rozwiązania (na przykład przykład wydawcy / książki w dokumentach). W tym przypadku działa dobrze i tylko skomplikowałoby sprawę, gdybyś utworzył oddzielne dokumenty ról użytkownika.
dieerikh
1
Działa to w przypadku większości systemów, ponieważ role to zwykle mały zestaw i zazwyczaj bierzemy użytkownika, a następnie przyglądamy się jego rolom. Ale co, jeśli role są duże? a co, jeśli poproszę Cię o listę użytkowników, którzy mają role == "Inżynier"? Teraz musiałbyś zapytać całą kolekcję użytkowników (odwiedzając wszystkich użytkowników, którzy nie mają również roli Inżyniera) tylko po to, aby uzyskać 2 lub 3 użytkowników, którzy mogą pełnić tę rolę, na przykład spośród milionów takich użytkowników. Oddzielny stół lub kolekcja jest znacznie lepsza.
theprogrammer
32

Zamiast próbować modelować zgodnie z naszym wieloletnim doświadczeniem z systemami RDBMS, okazało się, że znacznie łatwiej jest modelować rozwiązania repozytoriów dokumentów przy użyciu MongoDB, Redis i innych magazynów danych NoSQL, optymalizując pod kątem przypadków użycia odczytu, jednocześnie biorąc pod uwagę atomowe operacje zapisu, które muszą być obsługiwane przez przypadki użycia zapisu.

Na przykład zastosowania domeny „Użytkownicy w rolach” są następujące:

  1. Rola - tworzenie, odczytywanie, aktualizowanie, usuwanie, wyświetlanie listy użytkowników, dodawanie użytkownika, usuwanie użytkownika, czyszczenie wszystkich użytkowników, indeks użytkownika lub podobne w celu obsługi „Czy użytkownik ma rolę” (operacje takie jak kontener + własne metadane).
  2. Użytkownik - tworzenie, odczytywanie, aktualizowanie, usuwanie (operacje CRUD jak samodzielna jednostka)

Można to modelować za pomocą następujących szablonów dokumentów:

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

W celu obsługi zastosowań o dużej częstotliwości, takich jak funkcje związane z rolami z jednostki użytkownika, użytkownik.Role są celowo zdenormalizowane, przechowywane zarówno w przypadku użytkownika, jak i roli. Użytkowników posiadających zduplikowaną pamięć.

Jeśli nie jest to wyraźnie widoczne w tekście, ale taki sposób myślenia jest zalecany podczas korzystania z repozytoriów dokumentów.

Mam nadzieję, że pomoże to wypełnić lukę w odniesieniu do czytelniczej strony operacji.

Jeśli chodzi o pisanie, zachęca się do modelowania zgodnie z pismami atomowymi. Na przykład, jeśli struktury dokumentów wymagają uzyskania blokady, zaktualizowania jednego dokumentu, następnie innego i prawdopodobnie większej liczby dokumentów, a następnie zwolnienia blokady, prawdopodobnie model zawiódł. To, że możemy budować rozproszone blokady, nie oznacza, że ​​mamy ich używać.

W przypadku modelu User in Roles operacjami, które rozciągają nasze unikanie blokad przed zapisem atomowym, jest dodanie lub usunięcie użytkownika z roli. W obu przypadkach pomyślna operacja skutkuje zaktualizowaniem zarówno jednego użytkownika, jak i jednego dokumentu roli. Jeśli coś się nie powiedzie, łatwo jest przeprowadzić czyszczenie. Jest to jeden z powodów, dla których wzorzec jednostki pracy pojawia się często, gdy używane są repozytoria dokumentów.

Operacją, która naprawdę rozciąga nasze unikanie blokad zapisu atomowego, jest wyczyszczenie roli, co spowodowałoby wiele aktualizacji użytkownika w celu usunięcia Role.name z tablicy User.roles. Ta operacja czyszczenia jest generalnie odradzana, ale w razie potrzeby można ją wykonać, zlecając operacje:

  1. Uzyskaj listę nazw użytkowników z Role.users.
  2. Iteruj nazwy użytkowników z kroku 1, usuń nazwę roli z User.roles.
  3. Wyczyść plik Role.users.

W przypadku problemu, który najprawdopodobniej wystąpi w kroku 2, wycofanie jest łatwe, ponieważ ten sam zestaw nazw użytkowników z kroku 1 może być użyty do odzyskania lub kontynuowania.

paegun
źródło
15

Właśnie natknąłem się na to pytanie i chociaż jest stare, pomyślałem, że warto dodać kilka możliwości nie wymienionych w udzielonych odpowiedziach. W ciągu ostatnich kilku lat sytuacja nieco się zmieniła, dlatego warto podkreślić, że SQL i NoSQL zbliżają się do siebie.

Jeden z komentatorów poruszył mądrą ostrożną postawę, że „jeśli dane są relacyjne, używaj relacyjności”. Jednak ten komentarz ma sens tylko w świecie relacji, gdzie schematy zawsze poprzedzają aplikację.

ŚWIAT RELACYJNY : Dane strukturalne> Napisz aplikację, aby ją otrzymać
NOSQL WORLD: Zaprojektuj aplikację> Odpowiednio skonstruuj dane

Nawet jeśli dane są relacyjne, NoSQL jest nadal opcją. Na przykład relacje jeden do wielu nie stanowią żadnego problemu i są szeroko omówione w dokumentach MongoDB

ROZWIĄZANIE PROBLEMU Z ROKU 2010

Odkąd opublikowano to pytanie, podjęto poważne próby zbliżenia noSQL do SQL. Zespół kierowany przez Yannisa Papakonstantinou z Uniwersytetu Kalifornijskiego (San Diego) pracował nad FORWARD , implementacją SQL ++, która wkrótce może być rozwiązaniem uporczywych problemów, takich jak ten opublikowany tutaj.

Na bardziej praktycznym poziomie, wydanie Couchbase 4.0 oznaczało, że po raz pierwszy możesz wykonywać natywne JOIN w NoSQL. Używają własnego N1QL. Oto przykład JOINz ich samouczków :

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 pozwala na większość, jeśli nie wszystkie, operacje SQL, w tym agregację, filtrowanie itp.

NIE-TAK NOWE ROZWIĄZANIE HYBRYDOWE

Jeśli MongoDB jest nadal jedyną opcją, to chciałbym wrócić do mojego punktu, że aplikacja powinna mieć pierwszeństwo przed strukturą danych. Żadna z odpowiedzi nie wspomina o osadzaniu hybrydowym, w którym większość danych, o które chodzi, jest osadzana w dokumencie / obiekcie, a odniesienia są zachowywane dla mniejszości przypadków.

Przykład: czy informacje (inne niż nazwa roli) mogą czekać? czy ładowanie aplikacji może być szybsze, jeśli nie żąda się niczego, czego użytkownik jeszcze nie potrzebuje?

Może tak być w przypadku, gdy użytkownik loguje się i musi zobaczyć wszystkie opcje dla wszystkich ról, do których należy. Jednak użytkownik jest „Inżynierem” i opcje tej roli są rzadko używane. Oznacza to, że aplikacja musi tylko wyświetlać opcje dla inżyniera na wypadek, gdyby chciał je kliknąć.

Można to osiągnąć za pomocą dokumentu, który informuje aplikację na początku (1), do jakich ról należy użytkownik i (2) gdzie uzyskać informacje o zdarzeniu związanym z określoną rolą.

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

Lub, jeszcze lepiej, zindeksuj pole role.name w kolekcji ról i może nie być konieczne osadzanie ObjectID ().

Inny przykład: czy informacje o WSZYSTKICH żądanych rolach są CAŁKOWITE?

Może się również zdarzyć, że użytkownik loguje się do dashboardu i 90% czasu wykonuje zadania związane z rolą „Inżyniera”. Osadzanie hybrydowe można wykonać dla tej konkretnej roli w całości i zachować odniesienia tylko do reszty.

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

Brak schematów jest nie tylko cechą NoSQL, ale w tym przypadku może być zaletą. Zagnieżdżanie różnych typów obiektów we właściwości „Role” obiektu użytkownika jest całkowicie prawidłowe.

cortopy
źródło
5

w przypadku, gdy pracownik i firma są podmiotem-obiektem, spróbuj zastosować następujący schemat:

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

company{
   //and duplicate it in company
   contracts:{ item1, item2, item3,...}
}
Andrei Andrushkevich
źródło