Asociación de muchos a muchos de MongoDB

143

¿Cómo haría una asociación de muchos a muchos con MongoDB?

Por ejemplo; supongamos que tiene una tabla de usuarios y una tabla de roles. Los usuarios tienen muchos roles y los roles tienen muchos usuarios. En tierra SQL, crearía una tabla UserRoles.

Users:
    Id
    Name

Roles:
    Id
    Name

UserRoles:
    UserId
    RoleId

¿Cómo se maneja el mismo tipo de relación en MongoDB?

Josh Close
fuente
Ver también las respuestas a esta pregunta y esta pregunta
Matthew Murdoch

Respuestas:

96

Dependiendo de sus necesidades de consulta, puede poner todo en el documento del usuario:

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

Para obtener todos los ingenieros, use:

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

Si desea mantener los roles en documentos separados, puede incluir el _id del documento en la matriz de roles en lugar del nombre:

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

y configurar los roles como:

{_id:"6c6793300334001000000006"
,rolename:"Engineer"
}
diederikh
fuente
77
Lo último sería mejor ya que necesito obtener una lista de todos los roles disponibles. La única parte mala es que necesito configurar ambos extremos de la asociación. Al hacer la forma SQL, agregar un UserRole hará que el Usuario sepa sobre el Rol y el Rol sepa sobre el Usuario. De esta manera, tendré que establecer el Rol en el Usuario y el Usuario en el Rol. Aunque supongo que está bien.
Josh Close
46
El hecho de que una base de datos no sea compatible con sql no significa que las referencias no sean herramientas útiles NoSQL! = NoReference vea esta explicación: mongodb.org/display/DOCS/Schema+Design
Tom Gruner
8
Esto no parece una buena idea. Si solo tiene seis roles, claro, pero ¿y si tuviera 20000 objetos que podrían vincularse a 20000 objetos más (en una relación de muchos-muchos)? Incluso los documentos de MongoDB sugieren que debes evitar tener grandes matrices de referencias mutables. docs.mongodb.org/manual/tutorial/…
CaptSaltyJack 01 de
Obviamente, para las relaciones de muchos a muchos con muchos objetos, desea utilizar una solución diferente (como el ejemplo de editor / libro en los documentos). En este caso, funciona bien y solo complicaría las cosas si crea documentos separados de roles de usuario.
diederikh 01 de
1
Esto funciona para la mayoría de los sistemas porque los roles suelen ser un conjunto pequeño y generalmente tomamos un usuario y luego observamos sus roles. Pero, ¿y si los roles son grandes? ¿O si le pido que me proporcione una lista de usuarios que tienen el rol == "Ingeniero"? Ahora tendría que consultar toda su colección de usuarios (visitando a todos los usuarios que no tienen también el rol de Ingeniero) solo para obtener 2 o 3 usuarios que puedan tener este rol entre millones de usuarios, por ejemplo. Una mesa o colección separada es mucho mejor.
theprogrammer
32

En lugar de tratar de modelar de acuerdo con nuestros años de experiencia con RDBMS, he encontrado que es mucho más fácil modelar soluciones de repositorio de documentos utilizando MongoDB, Redis y otros almacenes de datos NoSQL optimizando los casos de uso de lectura, al tiempo que considero el atómico operaciones de escritura que deben ser compatibles con los casos de uso de escritura.

Por ejemplo, los usos de un dominio "Usuarios en roles" son los siguientes:

  1. Rol: Crear, Leer, Actualizar, Eliminar, Listar usuarios, Agregar usuario, Eliminar usuario, Borrar todos los usuarios, Índice de usuario o similar para admitir "Es usuario en rol" (operaciones como un contenedor + sus propios metadatos).
  2. Usuario: crear, leer, actualizar, eliminar (operaciones CRUD como una entidad independiente)

Esto se puede modelar como las siguientes plantillas de documentos:

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

Para admitir los usos de alta frecuencia, como las funciones relacionadas con el rol de la entidad Usuario, User.Roles se desnormaliza intencionalmente, se almacena en el Usuario, así como Role.Users que tienen almacenamiento duplicado.

Si no es evidente en el texto, pero este es el tipo de pensamiento que se fomenta cuando se utilizan repositorios de documentos.

Espero que esto ayude a cerrar la brecha con respecto al lado de lectura de las operaciones.

Para el lado de la escritura, lo que se recomienda es modelar según las escrituras atómicas. Por ejemplo, si las estructuras de documentos requieren adquirir un bloqueo, actualizar un documento, luego otro y posiblemente más documentos, luego liberar el bloqueo, es probable que el modelo haya fallado. El hecho de que podamos construir bloqueos distribuidos no significa que se supone que debemos usarlos.

Para el caso del modelo Usuario en roles, las operaciones que amplían nuestra capacidad de evitar bloqueos de escritura atómica es agregar o eliminar un Usuario de un Rol. En cualquier caso, una operación exitosa resulta en la actualización de un solo Usuario y un solo documento de Rol. Si algo falla, es fácil realizar la limpieza. Esta es la única razón por la que el patrón de la Unidad de trabajo surge bastante cuando se utilizan repositorios de documentos.

La operación que realmente estira nuestra evitación de bloqueos de escritura atómica es borrar un Rol, lo que daría como resultado muchas actualizaciones de Usuario para eliminar el Role.name de la matriz User.roles. Esta operación de clear entonces generalmente se desaconseja, pero si es necesario se puede implementar ordenando las operaciones:

  1. Obtenga la lista de nombres de usuario de Role.users.
  2. Itere los nombres de usuario del paso 1, elimine el nombre de rol de User.roles.
  3. Borra los Role.users.

En el caso de un problema, que es más probable que ocurra dentro del paso 2, una reversión es fácil ya que el mismo conjunto de nombres de usuario del paso 1 se puede usar para recuperar o continuar.

paegun
fuente
15

Acabo de encontrarme con esta pregunta y, aunque es antigua, pensé que sería útil agregar un par de posibilidades que no se mencionan en las respuestas dadas. Además, las cosas se han movido un poco en los últimos años, por lo que vale la pena enfatizar que SQL y NoSQL se están acercando entre sí.

Uno de los comentaristas planteó la sabia actitud de precaución de que "si los datos son relacionales, use relacionales". Sin embargo, ese comentario solo tiene sentido en el mundo relacional, donde los esquemas siempre vienen antes de la aplicación.

MUNDO RELACIONAL: Datos de estructura> Escribir aplicación para obtenerlo
MUNDO NOSQL: Aplicación de diseño> Estructurar datos en consecuencia

Incluso si los datos son relacionales, NoSQL sigue siendo una opción. Por ejemplo, las relaciones uno a muchos no son ningún problema y están ampliamente cubiertas en los documentos de MongoDB

UNA SOLUCIÓN DE 2015 A UN PROBLEMA DE 2010

Desde que se publicó esta pregunta, ha habido intentos serios de acercar noSQL a SQL. El equipo dirigido por Yannis Papakonstantinou de la Universidad de California (San Diego) ha estado trabajando en FORWARD , una implementación de SQL ++ que pronto podría ser la solución a problemas persistentes como el publicado aquí.

En un nivel más práctico, el lanzamiento de Couchbase 4.0 ha significado que, por primera vez, puedes hacer uniones nativas en NoSQL. Usan su propio N1QL. Este es un ejemplo de un JOINde sus tutoriales :

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 permite la mayoría de las operaciones SQL, si no todas, incluidas la agregación, el filtrado, etc.

LA SOLUCIÓN HÍBRIDA NO TAN NUEVA

Si MongoDB sigue siendo la única opción, me gustaría volver a mi punto de que la aplicación debe tener prioridad sobre la estructura de los datos. Ninguna de las respuestas menciona la incrustación híbrida, por lo que la mayoría de los datos consultados se incrustan en el documento / objeto, y las referencias se guardan para una minoría de los casos.

Ejemplo: ¿puede esperar información (que no sea el nombre del rol)? ¿Podría el arranque de la aplicación ser más rápido al no solicitar nada que el usuario aún no necesita?

Este podría ser el caso si el usuario inicia sesión y necesita ver todas las opciones para todos los roles a los que pertenece. Sin embargo, el usuario es un "ingeniero" y las opciones para este rol rara vez se usan. Esto significa que la aplicación solo necesita mostrar las opciones para un ingeniero en caso de que quiera hacer clic en ellas.

Esto se puede lograr con un documento que le dice a la aplicación al inicio (1) a qué roles pertenece el usuario y (2) dónde obtener información sobre un evento vinculado a un rol en particular.

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

O, mejor aún, indexe el campo role.name en la colección de roles, y es posible que tampoco necesite incrustar ObjectID ().

Otro ejemplo: ¿hay información sobre TODOS los roles solicitados TODO el tiempo?

También podría ser el caso de que el usuario inicie sesión en el tablero y el 90% del tiempo realiza tareas vinculadas a la función de "ingeniero". La incrustación híbrida se puede hacer para ese rol en particular en su totalidad y mantener referencias para el resto únicamente.

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

No tener esquemas no es solo una característica de NoSQL, podría ser una ventaja en este caso. Es perfectamente válido anidar diferentes tipos de objetos en la propiedad "Roles" de un objeto de usuario.

cortopia
fuente
5

en caso de que el empleado y la empresa sean objeto de entidad, intente utilizar el siguiente esquema:

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

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