Association plusieurs-à-plusieurs MongoDB

143

Comment feriez-vous une association plusieurs-à-plusieurs avec MongoDB?

Par exemple; disons que vous avez une table Users et une table Roles. Les utilisateurs ont de nombreux rôles et les rôles ont de nombreux utilisateurs. Dans SQL land, vous créeriez une table UserRoles.

Users:
    Id
    Name

Roles:
    Id
    Name

UserRoles:
    UserId
    RoleId

Comment le même type de relation est-il géré dans MongoDB?

Josh Fermer
la source
Voir aussi les réponses à cette question et à cette question
Matthew Murdoch

Réponses:

96

En fonction de vos besoins de requête, vous pouvez tout mettre dans le document utilisateur:

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

Pour obtenir tous les ingénieurs, utilisez:

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

Si vous souhaitez conserver les rôles dans des documents séparés, vous pouvez inclure le _id du document dans le tableau des rôles au lieu du nom:

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

et configurez les rôles comme:

{_id:"6c6793300334001000000006"
,rolename:"Engineer"
}
Dieerikh
la source
7
Ce dernier serait mieux car j'ai besoin d'obtenir une liste de tous les rôles disponibles. Le seul problème est que je dois configurer les deux extrémités de l'association. Lorsque vous faites de la manière SQL, l'ajout d'un UserRole permettra à l'utilisateur de connaître le rôle et le rôle de connaître l'utilisateur. De cette façon, je devrai définir le rôle sur l'utilisateur et l'utilisateur sur le rôle. Je suppose que c'est bien cependant.
Josh Fermer le
46
Ce n'est pas parce qu'une base de données ne prend pas en charge sql que les références ne sont pas des outils utiles NoSQL! = NoReference voir cette explication: mongodb.org/display/DOCS/Schema+Design
Tom Gruner
8
Cela ne semble pas être une bonne idée. Si vous n'avez que six rôles, bien sûr, mais que se passe-t-il si vous aviez 20000 objets pouvant être liés à 20000 objets supplémentaires (dans une relation plusieurs-plusieurs)? Même les documents MongoDB suggèrent que vous devriez éviter d'avoir d'énormes tableaux de références mutables. docs.mongodb.org/manual/tutorial/…
CaptSaltyJack
De toute évidence, pour les relations plusieurs à plusieurs avec de nombreux objets, vous souhaitez utiliser une solution différente (comme l'exemple éditeur / livre dans la documentation). Dans ce cas, cela fonctionne bien et ne compliquerait les choses que si vous créez des documents de rôle d'utilisateur distincts.
dieerikh
1
Cela fonctionne pour la plupart des systèmes car les rôles sont généralement un petit ensemble et nous prenons généralement un utilisateur et examinons ensuite ses rôles. Mais que faire si les rôles sont importants? ou que faire si je vous demande de me donner une liste d'utilisateurs qui ont le rôle == "Ingénieur"? Maintenant, vous devriez interroger l'ensemble de votre collection d'utilisateurs (en visitant tous les utilisateurs qui n'ont pas le rôle Ingénieur également) uniquement pour obtenir 2 ou 3 utilisateurs qui peuvent avoir ce rôle parmi des millions d'utilisateurs par exemple. Une table ou une collection séparée est bien meilleure.
theprogrammer le
32

Au lieu d'essayer de modéliser en fonction de nos années d'expérience avec les SGBDR, j'ai trouvé beaucoup plus facile de modéliser des solutions de référentiel de documents à l'aide de MongoDB, Redis et d'autres magasins de données NoSQL en optimisant pour les cas d'utilisation de lecture, tout en tenant compte de l'atome les opérations d'écriture qui doivent être prises en charge par les cas d'utilisation d'écriture.

Par exemple, les utilisations d'un domaine «Utilisateurs dans des rôles» sont les suivantes:

  1. Rôle - Créer, lire, mettre à jour, supprimer, répertorier les utilisateurs, ajouter un utilisateur, supprimer un utilisateur, effacer tous les utilisateurs, index de l'utilisateur ou similaire pour prendre en charge «est l'utilisateur dans le rôle» (opérations comme un conteneur + ses propres métadonnées).
  2. Utilisateur - Créer, lire, mettre à jour, supprimer (opérations CRUD comme une entité autonome)

Cela peut être modélisé comme les modèles de document suivants:

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

Pour prendre en charge les utilisations à haute fréquence, telles que les fonctionnalités liées au rôle de l'entité utilisateur, User.Roles est intentionnellement dénormalisé, stocké sur l'utilisateur ainsi que sur Role.Users ayant un stockage en double.

Si ce n'est pas évident dans le texte, mais c'est le type de réflexion qui est encouragé lors de l'utilisation de référentiels de documents.

J'espère que cela aidera à combler le fossé en ce qui concerne le côté lecture des opérations.

Pour le côté écriture, ce qui est encouragé est de modéliser selon les écritures atomiques. Par exemple, si les structures de document nécessitent l'acquisition d'un verrou, la mise à jour d'un document, puis un autre et éventuellement plus de documents, puis la libération du verrou, il est probable que le modèle a échoué. Ce n'est pas parce que nous pouvons créer des verrous distribués que nous sommes censés les utiliser.

Dans le cas du modèle User in Roles, les opérations qui étendent notre évitement des verrous en écriture atomique consistent à ajouter ou à supprimer un utilisateur d'un rôle. Dans les deux cas, une opération réussie entraîne la mise à jour d'un seul utilisateur et d'un seul document de rôle. Si quelque chose échoue, il est facile d'effectuer un nettoyage. C'est la seule raison pour laquelle le modèle d'unité de travail est souvent utilisé lorsque les référentiels de documents sont utilisés.

L'opération qui étend vraiment notre évitement des verrous en écriture atomique consiste à effacer un rôle, ce qui entraînerait de nombreuses mises à jour d'utilisateur pour supprimer le Role.name du tableau User.roles. Cette opération d'effacement est alors généralement déconseillée, mais si nécessaire peut être mise en œuvre en ordonnant les opérations:

  1. Obtenez la liste des noms d'utilisateur de Role.users.
  2. Itérez les noms d'utilisateur de l'étape 1, supprimez le nom de rôle de User.roles.
  3. Effacez Role.users.

Dans le cas d'un problème, qui est le plus susceptible de se produire à l'étape 2, une restauration est facile car le même ensemble de noms d'utilisateur de l'étape 1 peut être utilisé pour récupérer ou continuer.

paegun
la source
15

Je viens de tomber sur cette question et, bien qu'elle soit ancienne, j'ai pensé qu'il serait utile d'ajouter quelques possibilités non mentionnées dans les réponses données. De plus, les choses ont un peu évolué ces dernières années, il convient donc de souligner que SQL et NoSQL se rapprochent l'un de l'autre.

Un des commentateurs a évoqué la sage attitude de prudence selon laquelle «si les données sont relationnelles, utilisez relationnelles». Cependant, ce commentaire n'a de sens que dans le monde relationnel, où les schémas viennent toujours avant l'application.

MONDE RELATIONNEL: Structurer les données> Ecrire une application pour l'obtenir
NOSQL WORLD: Concevoir une application> Structurer les données en conséquence

Même si les données sont relationnelles, NoSQL reste une option. Par exemple, les relations un-à-plusieurs ne posent aucun problème et sont largement couvertes dans la documentation MongoDB

UNE SOLUTION 2015 À UN PROBLÈME DE 2010

Depuis que cette question a été publiée, il y a eu de sérieuses tentatives pour rapprocher noSQL de SQL. L'équipe dirigée par Yannis Papakonstantinou à l'Université de Californie (San Diego) a travaillé sur FORWARD , une implémentation de SQL ++ qui pourrait bientôt être la solution à des problèmes persistants comme celui posté ici.

À un niveau plus pratique, la sortie de Couchbase 4.0 a signifié que, pour la première fois, vous pouvez faire des JOIN natifs dans NoSQL. Ils utilisent leur propre N1QL. Voici un exemple d'un JOINde leurs tutoriels :

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 permet la plupart sinon toutes les opérations SQL, y compris l'agrégation, le filtrage, etc.

LA SOLUTION HYBRIDE PAS SI NOUVELLE

Si MongoDB est toujours la seule option, alors j'aimerais revenir sur mon point selon lequel l'application doit avoir priorité sur la structure des données. Aucune des réponses ne mentionne l'incorporation hybride, dans laquelle la plupart des données interrogées sont incorporées dans le document / objet et les références sont conservées pour une minorité de cas.

Exemple: des informations (autres que le nom du rôle) peuvent-elles attendre? Le démarrage de l'application pourrait-il être plus rapide en ne demandant rien dont l'utilisateur n'a pas encore besoin?

Cela pourrait être le cas si l'utilisateur se connecte et qu'il / elle a besoin de voir toutes les options pour tous les rôles auxquels il appartient. Cependant, l'utilisateur est un «ingénieur» et les options pour ce rôle sont rarement utilisées. Cela signifie que l'application n'a besoin que d'afficher les options pour un ingénieur au cas où il / elle voudrait cliquer dessus.

Ceci peut être réalisé avec un document qui indique à l'application au début (1) à quels rôles l'utilisateur appartient et (2) où obtenir des informations sur un événement lié à un rôle particulier.

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

Ou, mieux encore, indexez le champ role.name dans la collection de rôles, et vous n'aurez peut-être pas besoin d'incorporer ObjectID () non plus.

Autre exemple: des informations sur TOUS les rôles sont-elles demandées TOUT le temps?

Il peut également arriver que l'utilisateur se connecte au tableau de bord et exécute 90% du temps des tâches liées au rôle «Ingénieur». L'intégration hybride pourrait être effectuée pour ce rôle particulier dans son intégralité et ne conserver que les références pour le reste.

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

Être sans schéma n'est pas seulement une caractéristique de NoSQL, cela pourrait être un avantage dans ce cas. Il est parfaitement valable d'imbriquer différents types d'objets dans la propriété «Roles» d'un objet utilisateur.

cortopie
la source
5

dans le cas où l'employé et la société sont entité-objet, essayez d'utiliser le schéma suivant:

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

company{
   //and duplicate it in company
   contracts:{ item1, item2, item3,...}
}
Andrei Andrushkevich
la source