¿Cómo debo diseñar una tabla de relaciones para la amistad?


33

Si Aes amigo de B, ¿debería almacenar ambos valores ABy BAuno es suficiente? ¿Cuáles son las ventajas y desventajas de ambos métodos?

Aquí está mi observación:

  • Si mantengo ambos, entonces tengo que actualizar ambos cuando recibo una solicitud de un amigo.
  • Si no guardo ambos, entonces me resulta difícil tener que hacer múltiples JOINcon esta tabla.

Actualmente, mantengo la relación de una manera.

ingrese la descripción de la imagen aquí

Entonces, ¿qué debo hacer en este caso? ¿Algún consejo?


¿Estás comprometido con una plataforma o es una pregunta teórica?
Nick Chammas

¿Qué pasa con un enfoque híbrido ?: amistades modelo y no correspondido, respectivamente, en tablas separadas, asegúrese de que una amistad se inserte exactamente en una de esas tablas, lo que no es agradable de lograr con los productos SQL de hoy :(
día

@onedaywhen - Sí, suena más apropiado para una base de datos gráfica .
Nick Chammas

@NickChammas: No es una pregunta teórica. Estoy trabajando en lo mysqlque está almacenado en la nube de Amazon.
Chan

1
@Chan - Ah, eso significa que no puedes usar restricciones de verificación para hacer cumplir la relación, solo se almacena de una manera (MySQL no hace cumplir esto)
Martin Smith

Respuestas:


30

Almacenaría AB y BA. Una amistad es realmente una relación bidireccional, cada entidad está vinculada a otra. Aunque intuitivamente pensamos en la "amistad" como un vínculo entre dos personas, desde un punto de vista relacional es más como "A tiene un amigo B" y "B tiene un amigo A". Dos relaciones, dos registros.


3
Muchas gracias. ¡Realmente necesito pensar en tu idea cuidadosamente! La razón por la que evito almacenar AB y BA es por el almacenamiento, ya que cada vez que tengo una amistad, mi mesa se almacena el doble.
Chan

1
Tiene razón sobre el almacenamiento, pero recuerde que si se almacena como números enteros, cada relación amigo-amigo tomaría alrededor de 30 byes (2 registros x 3 columnas x 4 bytes por entero = 24 bytes más algo de relleno). Un millón de personas con 10 amigos cada una solo tendría unos 300 MB de datos
datagod el

1
datagod: eso es correcto!
Chan

Así es como también diseñé mis mesas, AB & BA.
kabuto178

2
Además, en situaciones donde solo hay AB y no BA, esto puede representar una 'solicitud de amistad pendiente'.
Greg

13

Si la amistad tiene la intención de ser simétrica (es decir, no es posible A ser amigo Bpero no al revés), entonces simplemente almacenaría la relación unidireccional con una restricción de verificación para garantizar que cada relación solo se pueda representar de una manera.

También abandonaría la identificación sustituta y tendría un PK compuesto en su lugar (y posiblemente un índice único compuesto también en las columnas invertidas).

CREATE TABLE Friends
  (
     UserID1 INT NOT NULL REFERENCES Users(UserID),
     UserID2 INT NOT NULL REFERENCES Users(UserID),
     CONSTRAINT CheckOneWay CHECK (UserID1 < UserID2),
     CONSTRAINT PK_Friends_UserID1_UserID2 PRIMARY KEY (UserID1, UserID2),
     CONSTRAINT UQ_Friends_UserID2_UserID1 UNIQUE (UserID2, UserID1)
  ) 

No dice las consultas que esto dificulta, pero siempre puede crear una Vista

CREATE VIEW Foo
AS
SELECT UserID1,UserID2 
FROM Friends
UNION ALL
SELECT UserID2,UserID1 
FROM Friends

Sé que esto es bastante viejo, lo siento por desenterrar esto. ¿No sería mejor NO definir el índice de amistad inversaUNIQUE para no poner una carga adicional innecesaria y redundante en INSERTs? Como tenemos PRIMARY KEY (a,b)y como es un PK UNIQUE, lo inverso KEY (b,a)también es UNIQUElo que sea.
tfrommen

1
@tf Supongo que depende del optimizador querŷ. Como señala, solo es necesario marcar una vuelta para que el plan de inserción pueda hacer esto de todos modos. La pregunta está etiquetada como MySQL; no tengo idea de cómo se comporta eso.
Martin Smith

Sé que esta es una respuesta anterior, pero solo quiero señalar a cualquiera que se encuentre con esto que MySQL ignora por completo las restricciones CHECK (aunque las "analizará" con éxito), por lo que este enfoque probablemente no sea el camino a seguir con esa tecnología.
Micah

@Micah cierto. No estaba al tanto de eso en 2012. Todavía funcionará en otros DBMS ...
Martin Smith

+1 para implementar una Vista para eso. Tener AB & BA almacenado trae inconsistencia (si la relación no es bidireccional) mientras que este método es un mejor enfoque
imans77

7

Suponiendo que una "amistad" siempre sea bidireccional / mutua, probablemente lo manejaría de esta manera.

CREATE TABLE person (
    person_id int IDENTITY(1,1) PRIMARY KEY,
    ...other columns...
)

CREATE TABLE friendship (
    friendship_id int IDENTITY(1,1) PRIMARY KEY,
    ...other columns, if any...
)

CREATE TABLE person_friendship (
    person_id int NOT NULL,
    friendship_id int NOT NULL
    PRIMARY KEY (person_id, friendship_id)
)

El resultado es que lo cambia de una unión de muchos a muchos de "persona" a "persona", a una unión de muchos a muchos de "persona" a "amistad". Esto simplificará las uniones y restricciones, pero tiene el efecto secundario de permitir que más de dos personas en una sola "amistad" (aunque tal vez la flexibilidad adicional sería una ventaja potencial).


Esto es básicamente un patrón de grupo / membresía. Una idea interesante, sin embargo.
einSelbst

4

Es posible que deba definir índices en torno a las amistades en lugar de duplicar el número de filas:

CREATE TABLE person
(
    person_id INT NOT NULL AUTO_INCREMENT,
    ...
    PRIMARY KEY (person_id)
);
CREATE TABLE friendship
(
    friend_of INT NOT NULL,
    friend_to INT NOT NULL,
    PRIMARY KEY (friend_of,friend_to),
    UNIQUE KEY friend_to (friend_to,friend_of)
);

De esta manera, duplica el almacenamiento para los índices pero no para los datos de la tabla. Como resultado, esto debería ser un ahorro del 25% en el espacio en disco. MySQL Query Optimizer elegirá realizar escaneos de rango de índice solamente, por eso el concepto de cubrir índices funciona bien aquí.

Aquí hay algunos enlaces agradables sobre los índices de cobertura:

ADVERTENCIA

Si la amistad no es mutua, tienes la base para otro tipo de relación: SEGUIR

Si friend_to no es amigo de friend_of, simplemente puede dejar esa relación fuera de la mesa.

Si desea definir relaciones para todos los tipos, sean mutuos o no, probablemente podría usar el siguiente diseño de tabla:

CREATE TABLE person
(
    person_id INT NOT NULL AUTO_INCREMENT,
    ...
    PRIMARY KEY (person_id)
);
CREATE TABLE relationship
(
    rel_id INT NOT NULL AUTO_INCREMENT,
    person_id1 INT NOT NULL,
    person_id2 INT NOT NULL,
    reltype_id TINYINT,
    PRIMARY KEY (rel_id),
    UNIQUE KEY outer_affinity (reltype_id,person_id1,person_id2),
    UNIQUE KEY inner_affinity (reltype_id,person_id2,person_id1),
    KEY has_relationship_to (person1_id,reltype_id),
    KEY has_relationship_by (person2_id,reltype_id)
);
CREATE TABLE relation
(
    reltype_id TINYINT NOT NULL AUTO_INCREMENT,
    rel_name VARCHAR(20),
    PRIMARY KEY (reltype_id),
    UNIQUE KEY (rel_name)
);
INSERT INTO relation (relation_name) VALUES
('friend'),('follower'),('foe'),
('forgotabout'),('forsaken'),('fixed');

Desde la tabla de relaciones, puede organizar las relaciones para incluir lo siguiente:

  • Los amigos deben ser mutuos
  • Los enemigos pueden ser mutuos o no
  • Los seguidores pueden ser mutuos o no
  • Las otras relaciones estarían sujetas a interpretación (por parte del olvidado o abandonado o el receptor de la venganza (fijo))
  • Posibles relaciones pueden extenderse aún más

Esto debería ser más robusto para todas las relaciones, ya sea que la relación sea mutua o no.


hola @rolandomysqldba, soy un gran admirador de tus respuestas. es realmente útil para mí (en este caso, primer ejemplo). Ahora aquí hay una advertencia para mí, quiero una relación única. (por ejemplo, si el usuario A es amigo de B, entonces B amigo con A no es aceptable). ¿Debería hacerlo con el disparador? ¿Y qué hay del rendimiento? porque tengo una tabla muy grande (aproximadamente 1 millón de registros), y si busco amigos del usuario A (A se almacena en ambos campos (friend_of, friend_to) y mysql usando solo un índice, entonces está funcionando muy lentamente. Debo tener que almacenar entradas duplicadas en mi tabla (por ejemplo, A-> B, B-> A). ¿Alguna opción mejor?
Manish Sapkal

1

Si puede controlar dentro de la aplicación que la identificación de A siempre es menor que la identificación de B (ordene previamente los identificadores de los elementos A, B), puede aprovechar preguntar sin un OR (seleccione dónde id_A = a AND id_B = b, en lugar de preguntar (id_A = a AND id_B = b) OR (id_A = b AND id_B = a)), y también mantenga la mitad de los registros que necesitará con las aproximaciones del otro. Luego debe usar otro campo para mantener el estado de la relación (are-friends, a-solicited-to-b, b-solicited-to-a, exfriends-a, exfriends-b), y ya está.

Esta es la forma en que he administrado mi sistema de amistad, y esto simplifica el sistema y usa la mitad de las filas que necesitará con otros sistemas, solo diciendo que A es igual al valor de identificación más bajo en el código.

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.