Associazione Many-to-Many di MongoDB


143

Come faresti un'associazione molti-a-molti con MongoDB?

Per esempio; diciamo che hai una tabella Users e una tabella Ruoli. Gli utenti hanno molti ruoli e ruoli hanno molti utenti. In SQL Land è necessario creare una tabella UserRoles.

Users:
    Id
    Name

Roles:
    Id
    Name

UserRoles:
    UserId
    RoleId

Come viene gestito lo stesso tipo di relazione in MongoDB?


Vedi anche le risposte a questa domanda e questa domanda
Matthew Murdoch,

Risposte:


96

A seconda delle esigenze della query, puoi inserire tutto nel documento utente:

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

Per ottenere tutti gli ingegneri, utilizzare:

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

Se si desidera mantenere i ruoli in documenti separati, è possibile includere _id del documento nella matrice dei ruoli anziché il nome:

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

e impostare i ruoli come:

{_id:"6c6793300334001000000006"
,rolename:"Engineer"
}

7
Quest'ultimo sarebbe meglio poiché ho bisogno di ottenere un elenco di tutti i ruoli disponibili. L'unica parte negativa è che devo impostare entrambe le estremità dell'associazione. Quando si esegue il metodo SQL, l'aggiunta di un ruolo utente farà conoscere all'utente il ruolo e il ruolo conoscere l'utente. In questo modo significa che dovrò impostare il ruolo sull'utente e l'utente sul ruolo. Immagino che vada bene però.
Josh Close,

46
Solo perché un database non supporta sql non significa che i riferimenti non siano strumenti utili NoSQL! = NoReference vedi questa spiegazione: mongodb.org/display/DOCS/Schema+Design
Tom Gruner

8
Questa non sembra una buona idea. Se hai solo sei ruoli, certo, ma se avessi 20000 oggetti che potrebbero essere collegati a 20000 più oggetti (in una relazione molte-molte)? Perfino i documenti di MongoDB suggeriscono che dovresti evitare di avere matrici di riferimenti mutevoli ed enormi. docs.mongodb.org/manual/tutorial/…
CaptSaltyJack

Ovviamente per relazioni molti-a-molti con molti oggetti vuoi usare una soluzione diversa (come l'esempio di editore / libro nei documenti). In questo caso funziona bene e complicherebbe le cose solo se si creano documenti di ruolo utente separati.
diederikh,

1
Questo funziona per la maggior parte dei sistemi in quanto i ruoli sono di solito un piccolo set e di solito prendiamo un utente e quindi guardiamo i suoi ruoli. E se i ruoli fossero grandi? o se ti chiedessi di darmi un elenco di utenti con ruolo == "Ingegnere"? Ora dovresti interrogare la tua intera raccolta di utenti (visitando tutti gli utenti che non hanno anche il ruolo di Tecnico) solo per ottenere 2 o 3 utenti che potrebbero avere questo ruolo tra milioni di tali utenti, ad esempio. Una tabella o una raccolta separata è molto meglio.
programmatore il

32

Invece di provare a modellare secondo i nostri anni di esperienza con RDBMS, ho trovato molto più facile modellare le soluzioni di repository di documenti usando MongoDB, Redis e altri archivi di dati NoSQL ottimizzando per i casi d'uso in lettura, pur considerando l'atomico operazioni di scrittura che devono essere supportate dai casi d'uso di scrittura.

Ad esempio, seguono gli usi di un dominio "Utenti nei ruoli":

  1. Ruolo: crea, leggi, aggiorna, elimina, elenca utenti, aggiungi utente, rimuovi utente, cancella tutti gli utenti, indice dell'utente o simili per supportare "È l'utente nel ruolo" (operazioni come un contenitore + i suoi metadati).
  2. Utente: crea, leggi, aggiorna, elimina (operazioni CRUD come un'entità indipendente)

Questo può essere modellato come i seguenti modelli di documento:

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

Per supportare gli usi ad alta frequenza, come le funzioni correlate al ruolo dell'entità Utente, User.Roles viene intenzionalmente denormalizzato, archiviato sull'Utente e su Role.Users con memoria duplicata.

Se non è prontamente evidente nel testo, ma questo è il tipo di pensiero che viene incoraggiato quando si utilizzano repository di documenti.

Spero che ciò aiuti a colmare il divario per quanto riguarda il lato letto delle operazioni.

Per quanto riguarda la scrittura, ciò che è incoraggiato è modellare secondo le scritture atomiche. Ad esempio, se le strutture del documento richiedono l'acquisizione di un blocco, l'aggiornamento di un documento, quindi di un altro, e forse di più documenti, quindi il rilascio del blocco, probabilmente il modello non è riuscito. Solo perché possiamo costruire blocchi distribuiti non significa che dovremmo usarli.

Nel caso del modello Utente in ruoli, le operazioni che allungano la nostra prevenzione atomica della scrittura dei blocchi sono l'aggiunta o la rimozione di un utente da un ruolo. In entrambi i casi, un'operazione riuscita comporta l'aggiornamento di un singolo utente e di un singolo documento di ruolo. Se qualcosa non riesce, è facile eseguire la pulizia. Questo è uno dei motivi per cui il modello di Unità di lavoro emerge parecchio in cui vengono utilizzati i repository di documenti.

L'operazione che allunga davvero la nostra prevenzione atomica della scrittura dei blocchi sta cancellando un ruolo, il che comporterebbe molti aggiornamenti dell'utente per rimuovere il Role.name dall'array User.roles. Questa operazione di clear quindi è generalmente sconsigliata, ma se necessario può essere implementata ordinando le operazioni:

  1. Ottieni l'elenco dei nomi utente da Role.users.
  2. Scorrere i nomi utente dal passaggio 1, rimuovere il nome ruolo da User.roles.
  3. Cancella il ruolo.user.

Nel caso di un problema, che è più probabile che si verifichi nel passaggio 2, un rollback è semplice in quanto lo stesso set di nomi utente dal passaggio 1 può essere utilizzato per ripristinare o continuare.


15

Mi sono appena imbattuto in questa domanda e, sebbene sia vecchia, ho pensato che sarebbe stato utile aggiungere un paio di possibilità non menzionate nelle risposte fornite. Inoltre, le cose sono andate un po 'avanti negli ultimi anni, quindi vale la pena sottolineare che SQL e NoSQL si stanno avvicinando.

Uno dei commentatori ha sollevato il saggio atteggiamento cautelativo secondo cui "se i dati sono relazionali, usa la relazione". Tuttavia, quel commento ha senso solo nel mondo relazionale, dove gli schemi vengono sempre prima dell'applicazione.

MONDO RELATIVO: Dati struttura> Scrivi applicazione per ottenerlo
MONDO NOSQL: Progetta applicazione> Dati struttura di conseguenza

Anche se i dati sono relazionali, NoSQL è ancora un'opzione. Ad esempio, le relazioni uno-a-molti non sono affatto un problema e sono ampiamente trattate nei documenti MongoDB

UNA SOLUZIONE 2015 A UN PROBLEMA 2010

Da quando questa domanda è stata pubblicata, ci sono stati seri tentativi di avvicinare noSQL a SQL. Il team guidato da Yannis Papakonstantinou dell'Università della California (San Diego) ha lavorato su FORWARD , un'implementazione di SQL ++ che potrebbe presto essere la soluzione a problemi persistenti come quello pubblicato qui.

A un livello più pratico, il rilascio di Couchbase 4.0 ha significato che, per la prima volta, è possibile eseguire JOIN nativi in ​​NoSQL. Usano il proprio N1QL. Questo è un esempio di a JOINdai loro tutorial :

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 consente la maggior parte, se non tutte, le operazioni SQL, inclusi aggregrazione, filtro, ecc.

LA NON NUOVA SOLUZIONE IBRIDA

Se MongoDB è ancora l'unica opzione, vorrei tornare al punto in cui l'applicazione dovrebbe avere la precedenza sulla struttura dei dati. Nessuna delle risposte menziona l'incorporamento ibrido, per cui la maggior parte dei dati interrogati è incorporata nel documento / oggetto e i riferimenti sono conservati per una minoranza di casi.

Esempio: le informazioni (diverse dal nome del ruolo) possono attendere? il bootstrap dell'applicazione potrebbe essere più veloce non richiedendo nulla di cui l'utente non abbia ancora bisogno?

Questo potrebbe accadere se l'utente accede e deve vedere tutte le opzioni per tutti i ruoli a cui appartiene. Tuttavia, l'utente è un "ingegnere" e le opzioni per questo ruolo vengono utilizzate raramente. Ciò significa che l'applicazione deve solo mostrare le opzioni per un ingegnere nel caso in cui voglia fare clic su di esse.

Ciò può essere ottenuto con un documento che indica all'applicazione all'inizio (1) a quali ruoli appartiene l'utente e (2) dove ottenere informazioni su un evento collegato a un ruolo particolare.

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

O, ancora meglio, indicizzare il campo role.name nella raccolta ruoli e potrebbe non essere necessario incorporare ObjectID ().

Un altro esempio: le informazioni su TUTTI i ruoli sono richieste TUTTO il tempo?

È possibile che l'utente acceda alla dashboard e che il 90% delle volte esegua attività collegate al ruolo di "Ingegnere". L'incorporamento ibrido potrebbe essere completo per quel particolare ruolo e mantenere i riferimenti solo per il resto.

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

Essere schematici non è solo una caratteristica di NoSQL, potrebbe essere un vantaggio in questo caso. È perfettamente valido per nidificare diversi tipi di oggetti nella proprietà "Ruoli" di un oggetto utente.


5

nel caso in cui dipendente e azienda siano entità-oggetto provare a utilizzare il seguente schema:

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

company{
   //and duplicate it in company
   contracts:{ item1, item2, item3,...}
}
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.