2010-05-21 17 views
6

Imagina que vives en un ejemplo de tierra muy simplificado, e imagina que tienes una mesa de gente en su base de datos MySQL:Tengo una tabla de personas, a la que quiero vincular, many-to-many, con enlaces bidireccionales

create table person (
    person_id int, 
    name text 
) 

select * from person; 

+-------------------------------+ 
| person_id |   name | 
+-------------------------------+ 
|   1 |   Alice | 
|   2 |    Bob | 
|   3 |   Carol | 
+-------------------------------+ 

y estas personas necesitan colaborar/trabajar juntos, por lo que tiene una mesa de enlace que une un registro persona a otra:

create table person__person (
    person__person_id int, 
    person_id int, 
    other_person_id int 
) 

Esta configuración significa que los vínculos entre las personas son unidireccionales, es decir, Alicia puede vincular a Bob, sin que Bob se vincule con Alicia e, incluso, w orse, Alice puede vincular a Bob y Bob puede vincular a Alice al mismo tiempo, en dos registros de enlace separados. Como estos enlaces representan relaciones de trabajo, en el mundo real son relaciones mutuas bidireccionales. Los siguientes son posibles en esta configuración:

select * from person__person; 

+---------------------+-----------+--------------------+ 
| person__person_id | person_id | other_person_id | 
+---------------------+-----------+--------------------+ 
|     1 |   1 |     2 | 
|     2 |   2 |     1 | 
|     3 |   2 |     2 | 
|     4 |   3 |     1 | 
+---------------------+-----------+--------------------+ 

Por ejemplo, con person__person_id = 4 anterior, cuando se ve el perfil de Carol (person_id = 3), debería ver una relación con Alice (person_id = 1) y cuando ves el perfil de Alicia, deberías ver una relación con Carol, a pesar de que el enlace va en dirección opuesta.

Me doy cuenta de que puedo hacer consultas unitarias y distintas y qué no para presentar las relaciones como mutuas en la interfaz de usuario, pero ¿hay una mejor manera? Tengo la sensación de que es una mejor manera, una donde este problema desaparecería limpiamente configurando la base de datos correctamente, pero no puedo verlo. Alguien tiene una mejor idea?

+3

¡Esta es la pregunta mejor formateada que he visto de un usuario de <50 repetidores! ¡Aclamaciones! –

+1

Cambié las etiquetas de 'mysql many-to-many' a 'data-modeling many-to-many' (+1 buena pregunta!) – lexu

+1

+1 pregunta bien planteada. nit-pick: person__person es un nombre de tabla horrible; solo diga que es para propósitos de iluminación;) – msw

Respuesta

3

No estoy seguro de si hay una mejor manera de configurar las tablas. Creo que la forma en que los tienes es adecuada y sería la forma en que la implementaría.

Dado que su tabla de relaciones puede indicar relaciones unidireccionales, sugiero tratarlas como tales. En otras palabras, para cada relación, agregaría dos filas. Si Alice está colaborando con Bob, la mesa debe ser de la siguiente manera:

select * from person__person; 
+---------------------+-----------+--------------------+ 
| person__person_id | person_id | other_person_id | 
+---------------------+-----------+--------------------+ 
|     1 |   1 |     2 | 
|     2 |   2 |     1 | 
+---------------------+-----------+--------------------+ 

La razón es porque en una gran cantidad de ActiveRecord (rieles) como los sistemas, el objeto de tabla de muchos a muchos les no ser lo suficientemente inteligente para consultar tanto person_id como other_person_id. Al mantener dos filas, los objetos similares a ActiveRecord funcionarán correctamente.

Lo que debe hacer es aplicar la integridad de sus datos en el nivel del código. Cada vez que se establece una relación entre dos usuarios, se deben insertar dos registros. Cuando se destruye una relación, ambos registros deben eliminarse. Los usuarios no deberían poder establecer relaciones consigo mismos.

+1

Esto también tiene el mérito de la lógica de negocio al decidir que no todas las asociaciones son bidireccionales sin necesidad de cambios en el esquema. – msw

+0

¡Hecho y hecho! Muchas gracias: voy con este método, por todas las razones que declararon user239098 y msw. –

2

No hay mejor idea. Las bases de datos relacionales no pueden aplicar lo que usted pide, por lo que debe escribir una consulta especial para recuperar datos y un activador para aplicar la restricción.

Para obtener las personas relacionadas para @person me inclinaría por:

SELECT CASE person_id WHEN @person 
      THEN other_person_id 
      ELSE person_id 
     END as related_person_id 
FROM person_person 
WHERE ( [email protected] 
     OR [email protected]) 
+0

Siempre ponga() alrededor de un 'o' en la cláusula where. ¡Cosas malas sucederán cuando te olvides antes de agregar más restricciones! (+1 para la respuesta) – lexu

2

No hay forma de que pueda ver el uso de conceptos relacionales simples. Deberá agregar el código "comercial" para hacer cumplir sus relaciones persona-persona.

  • Una forma podría ser la de hacer cumplir un segundo registro de relación utilizando un desencadenador de inserción
  • entonces declarar uno de los registros "primaria" (por ejemplo, aquel en el que el person_id es más pequeño que el otro _person_id)
  • entonces compila vistas y pega código para tus aplicaciones (seleccionar, actualizar, eliminar) que acceden a los datos con este conocimiento.
2

Debe encontrar este post útil:

http://discuss.joelonsoftware.com/default.asp?design.4.361252.31

Como @user publicadas son por lo general mejor crear dos registros por relación bidireccional (Alice a Bob y Bob a Alice en su ejemplo). Hace que consultar mucho sea más fácil y refleja con precisión las relaciones. Si tienes verdaderas relaciones unidireccionales, es la única forma de volar.

+0

Gracias, leí ese hilo; Básicamente confirmó la solución 'solo almacenarlo dos veces', seguido de una carga de gritos. Me alegro de que stackoverflow no sea así :) –

Cuestiones relacionadas