web-dev-qa-db-fra.com

Comment dois-je concevoir une table des relations pour l'amitié?

Si A est un ami de B, dois-je stocker les deux valeurs AB et BA, ou une seule suffit? Quels sont les avantages et les inconvénients des deux méthodes.

Voici mon observation:

  • Si je garde les deux, je dois les mettre à jour lorsque je reçois une demande d'un ami.
  • Si je ne garde pas les deux, j'ai eu du mal à faire plusieurs JOIN avec cette table.

Actuellement, je garde la relation dans un sens.

enter image description here

Alors, que dois-je faire dans ce cas? Aucun conseil?

34
Chan

Je stockerais AB et BA. Une amitié est vraiment une relation à double sens, chaque entité est liée à une autre. Même si nous considérons intuitivement "l'amitié" comme un lien entre deux personnes, d'un point de vue relationnel, cela ressemble plus à "A a un ami B" et "B a un ami A". Deux relations, deux enregistrements.

30
datagod

Si l'amitié est censée être symétrique (c'est-à-dire qu'il n'est pas possible pour A d'être ami avec B mais pas vice-versa), je stocke simplement la relation à sens unique avec une contrainte de vérification assurant que chaque relation ne peut être représentée que dans un sens.

Je voudrais également abandonner l'ID de substitution et avoir un PK composite à la place (et éventuellement un index unique composite également sur les colonnes inversées).

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)
  ) 

Vous ne dites pas les requêtes que cela rend difficiles, mais vous pouvez toujours créer une vue

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

En supposant qu'une "amitié" est toujours à double sens/mutuelle, je gérerais probablement quelque chose comme ça.

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)
)

Le résultat est que vous passez d'une jointure plusieurs à plusieurs de "personne" à "personne", à une jointure plusieurs à plusieurs de "personne" à "amitié". Cela simplifiera les jointures et les contraintes, mais aura pour effet secondaire de permettre à plus de deux personnes dans une seule "amitié" (même si la flexibilité supplémentaire serait un avantage potentiel).

7
db2

Vous devrez peut-être définir des index autour des amitiés au lieu de doubler le nombre de lignes:

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 cette façon, vous doublez le stockage pour les index mais pas pour les données de table. En conséquence, cela devrait représenter une économie de 25% sur l'espace disque. L'optimiseur de requêtes MySQL choisira d'effectuer des analyses de plage d'index uniquement, c'est pourquoi le concept de couverture des index fonctionne bien ici.

Voici quelques liens intéressants sur les indices de couverture:

CAVEAT

Si l'amitié n'est pas réciproque, vous avez la base d'un autre type de relation: SUIVEUR

Si friend_to n'est pas un ami de friend_of, vous pouvez simplement laisser cette relation hors de la table.

Si vous souhaitez définir des relations pour tous les types, qu'elles soient mutuelles ou non, vous pouvez probablement utiliser la présentation de tableau suivante:

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');

Dans le tableau des relations, vous pouvez organiser les relations pour inclure les éléments suivants:

  • Les amis devraient être mutuels
  • Les ennemis peuvent être mutuels ou non
  • Les abonnés pourraient être mutuels ou non
  • Les autres relations seraient sujettes à interprétation (par l'oubli ou l'abandon ou le destinataire de la vengeance (fixe))
  • Les relations possibles peuvent être étendues

Cela devrait être plus robuste pour toutes les relations, que la relation soit mutuelle ou non.

4
RolandoMySQLDBA

Si vous pouvez contrôler dans l'application que l'id de A est toujours inférieur à l'id de B (précommandez les identifiants des éléments A, B), vous pouvez tirer parti de demander sans OR (sélectionner où id_A = a AND id_B = b, au lieu de demander (id_A = a AND id_B = b) OR (id_A = b AND id_B = a)), et également conserver la moitié des enregistrements dont vous aurez besoin avec les approximations de l'autre. Ensuite, vous devez utiliser un autre champ pour maintenir l'état de la relation (are-friends, a-solicited-to-b, b-solicited-to-a, exfriends-a, exfriends-b), and you c'est fini.

C'est la façon dont j'ai géré mon système d'amitié, et cela simplifie le système et utilise la moitié des lignes dont vous aurez besoin avec d'autres systèmes, en disant seulement que A est égal à la valeur id inférieure dans le code.

1
appartisan