Comment dois-je concevoir une table de relation pour amitié?


33

Si Aest un ami de B, alors devrais-je stocker les deux valeurs ABet BA, ou est-ce suffisant? 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, alors j'ai eu du mal à faire plusieurs JOINavec cette table.

Actuellement, je garde la relation dans un sens.

entrez la description de l'image ici

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


Êtes-vous attaché à une plateforme ou s'agit-il d'une question théorique?
Nick Chammas

Qu'en est- il une approche hybride: modèle et rétribué des amitiés sans contrepartie respectivement dans des tables séparées, d' assurer une amitié est insérée dans exactement une de ces tables, pas agréable à réaliser en utilisant les produits SQL d'aujourd'hui :(
onedaywhen

@onedaywhen - Oui, cela semble plus approprié pour une base de données de graphes .
Nick Chammas

@ NickChammas: Ce n'est pas une question théorique. Je travaille sur mysqlce qui est stocké dans le nuage Amazon.
Chan

1
@Chan - Ah, ça veut dire que vous ne pouvez pas utiliser les contraintes de vérification pour appliquer la relation, puis que celle-ci n'est stockée que dans un seul sens (MySQL ne les applique pas)
Martin Smith

Réponses:


30

Je voudrais stocker AB et BA. Une amitié est vraiment une relation à double sens, chaque entité est liée à une autre. Même si, intuitivement, nous pensons que "l'amitié" est 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.


3
Merci beaucoup. J'ai vraiment besoin de réfléchir à votre idée avec soin! La raison pour laquelle j'évite de stocker AB et BA est due au stockage, car chaque fois que j'ai une amitié, ma table en stocke deux fois plus.
Chan

1
Vous avez raison en ce qui concerne le stockage, mais souvenez-vous que s’il était stocké sous forme d’entiers, chaque relation ami-ami prendrait environ 30 octets (2 enregistrements x 3 colonnes x 4 octets par entier = 24 octets plus un remplissage). 1 million de personnes avec 10 amis chacune ne disposerait encore que de 300 Mo de données.
Date

1
datagod: c'est vrai!
Chan

C’est ainsi que j’ai conçu mes tables, AB & BA.
Kabuto178

2
De plus, dans les situations où il y a seulement AB et pas BA, cela peut représenter une "demande d'ami en attente".
Greg

13

Si l’amitié est censée être symétrique (c’est-à-dire qu’il n’est pas possible A d’être ami Bmais pas l’inverse), je stockerais simplement la relation unidirectionnelle avec une contrainte de vérification garantissant que chaque relation ne peut être représentée que dans un seul sens.

De plus, j’aurais abandonné l’identificateur de substitution et obtenu un PK composite (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 difficile mais vous pouvez toujours créer une vue

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

Je sais que c'est assez vieux, alors désolé de l'avoir découvert. Ne vaudrait-il pas mieux ne PAS définir l’ indice d’amitié inverséUNIQUE pour ne pas imposer une charge supplémentaire inutile et redondante à INSERTs? Depuis que nous avons PRIMARY KEY (a,b)et depuis qu'une PC est UNIQUE, l'inverse KEY (b,a)est aussi UNIQUEn'importe quoi.
tfrommen

1
@tf Suppose que cela dépend de l'optimiseur de requêtes. Comme vous le signalez, il est seulement nécessaire de vérifier un sens pour que le plan d'insertion puisse le faire de toute façon. La question est étiquetée MySQL - aucune idée de la façon dont cela se comporte.
Martin Smith

Je sais que c’est une vieille réponse, mais je tiens à signaler à tous ceux qui tombent sur l’opportunité que MySQL ignore complètement les contraintes CHECK (bien qu’il les "analysera" avec succès), cette approche n’est donc probablement pas la solution.
Micah

@ Micah vrai. Je n'étais pas au courant de cela en 2012. Cela fonctionnera toujours dans d'autres SGBD ...
Martin Smith

+1 pour implémenter une vue pour cela. Le fait de conserver AB & BA en mémoire entraîne des incohérences (si la relation n'est pas bidirectionnelle) alors que cette méthode constitue une meilleure approche
imans77

7

En supposant qu'une "amitié" soit toujours bidirectionnelle / mutuelle, je le prendrais probablement comme ceci.

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 le changez de plusieurs à plusieurs, de "personne" à "personne", à une liaison de plusieurs à plusieurs, de "personne" à "amitié". Cela simplifiera les jointures et les contraintes, mais aura pour effet secondaire de permettre à plus de deux personnes de former une seule "amitié" (bien que la flexibilité supplémentaire puisse constituer un avantage potentiel).


Ceci est fondamentalement un modèle de groupe / appartenance. Idée intéressante, cependant.
einSelbst

4

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 la table. En conséquence, cela devrait représenter une économie de 25% sur l’espace disque. L'optimiseur de requêtes MySQL choisit uniquement d'effectuer des analyses de plages d'index. C'est pourquoi le concept de couverture d'index fonctionne bien ici.

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

CAVEAT

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

Si friend_to n'est pas un ami de friend_of, vous pouvez simplement laisser cette relation en dehors 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 disposition 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');

À partir de la table de relations, vous pouvez organiser les relations de manière à inclure les éléments suivants:

  • Les amis devraient être mutuels
  • Les ennemis peuvent être mutuels ou non
  • Les adeptes peuvent ê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, qu'elles soient mutuelles ou non.


salut @rolandomysqldba, je suis un grand fan de vos réponses. sa vraiment utile pour moi (dans ce cas 1er exemple). Maintenant, voici une mise en garde pour moi, je veux une relation unique. (Par exemple, si l'utilisateur A est ami avec B, alors B avec A n'est pas acceptable.) dois-je faire avec trigger? et qu'en est-il de la performance? parce que j'ai une très grande table (environ 1 million d'enregistrements), et si je recherche les amis de l'utilisateur A (A est stocké dans les deux champs (friend_of, friend_to) et que mysql n'utilise qu'un seul index, cela fonctionne très lentement. Je dois obligatoirement stocker les entrées en double dans mon tableau (par exemple, A-> B, B-> A). Une meilleure option?
Manish Sapkal

1

Si vous pouvez contrôler dans l’application que l’id de A est toujours inférieur à celui de B (pré-commandez les identifiants des éléments A, B), vous pouvez utiliser le moyen de demander sans OR (sélectionnez 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 conservez également 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 (êtes-amis, a-sollicité-à-b, b-sollicité-à-a, exfriends-a, exfriends-b), et vous avez terminé.

C’est ainsi que j’ai géré mon système d’amitié, ce qui simplifie le système et utilise la moitié des rangées dont vous aurez besoin avec d’autres systèmes, en indiquant simplement que A est égal à la valeur inférieure de code dans le code.

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.