Come dovrei progettare una tabella delle relazioni per l'amicizia?


33

Se Aè un amico di B, allora dovrei memorizzare entrambi i valori ABe BA, o uno è abbastanza? Quali sono i vantaggi e gli svantaggi di entrambi i metodi.

Ecco la mia osservazione:

  • Se conservo entrambi, devo aggiornare entrambi quando ricevo una richiesta da un amico.
  • Se non li tengo entrambi, allora trovo difficile dover fare più JOINcon questa tabella.

Attualmente, mantengo la relazione in un modo.

inserisci qui la descrizione dell'immagine

Quindi cosa dovrei fare in questo caso? Qualche consiglio?


Sei impegnato in una piattaforma o è una domanda teorica?
Nick Chammas,

Che dire di un approccio ibrido: modello di amicizie obbligate e non corrisposte rispettivamente in tabelle separate, assicurati che un'amicizia sia inserita esattamente in una di quelle tabelle, non piacevole da ottenere utilizzando i prodotti SQL di oggi :(
onedaywhen

@onedaywhen - Sì, sembra più appropriato per un database grafico .
Nick Chammas,

@NickChammas: non è una domanda teorica. Sto lavorando su mysqlquale è archiviato nel cloud Amazon.
Chan,

1
@Chan - Ah, ciò significa che non è possibile utilizzare i vincoli di controllo per imporre che la relazione sia archiviata in un solo modo (MySQL non li applica)
Martin Smith

Risposte:


30

Conserverei AB e BA. Un'amicizia è davvero una relazione a doppio senso, ogni entità è collegata a un'altra. Anche se intuitivamente pensiamo all '"amicizia" come a un legame tra due persone, dal punto di vista relazionale è più simile a "A ha un amico B" e "B ha un amico A". Due relazioni, due record.


3
Grazie molto. Ho davvero bisogno di pensare attentamente alla tua idea! Il motivo per cui evito di conservare AB e BA è a causa dell'archiviazione, poiché ogni volta che ho un'amicizia, il mio tavolo memorizzerebbe il doppio.
Chan,

1
Hai ragione sulla memoria, ma ricorda che se memorizzata come numeri interi, ogni relazione amico-amico richiederebbe circa 30 byte (2 record x 3 colonne x 4 byte per intero = 24 byte più un po 'di riempimento). 1 milione di persone con 10 amici ciascuna sarebbe comunque solo circa 300 MB di dati.
datagod

1
datagod: esatto!
Chan

È così che ho disegnato anche i miei tavoli, AB e BA.
kabuto178,

2
Inoltre, nelle situazioni in cui esiste solo AB e non BA, ciò può rappresentare una "richiesta di amicizia in sospeso".
Greg,

13

Se l'amicizia vuole essere simmetrica (cioè non è possibile A essere amici Bma non viceversa), memorizzerei la relazione unidirezionale con un vincolo di verifica assicurando che ogni relazione possa essere rappresentata solo in un modo.

Inoltre vorrei abbandonare l'id surrogato e avere invece un PK composito (e possibilmente un indice univoco composito anche sulle colonne invertite).

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

Non dite le domande che ciò rende difficile ma è sempre possibile creare una vista

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

So che è piuttosto vecchio, quindi mi dispiace averlo scoperto. Non sarebbe meglio NON definire l' indice di amicizia inversaUNIQUE , al fine di non sovraccaricare inutilmente e ridondanti gli INSERTs? Dal momento che abbiamo PRIMARY KEY (a,b)e poiché un PK è UNIQUE, l'inversione KEY (b,a)è anche UNIQUEnon importa quale.
venerdì

1
@tf Indovina che dipende dall'ottimizzatore di query. Come indicato, è necessario solo effettuare un controllo circolare in modo che il piano di inserimento possa farlo comunque. La domanda è taggata MySQL - non ho idea di come si comporti.
Martin Smith,

So che questa è una vecchia risposta, ma voglio solo sottolineare a chiunque inciampare su questo che MySQL ignora completamente i vincoli CHECK (anche se li "analizzerà" con successo), quindi questo approccio probabilmente non è la strada da percorrere con quella tecnologia.
Michea

@Micah true. Non ne ero a conoscenza nel 2012. Continuerò a lavorare in altri DBMS ...
Martin Smith,

+1 per l'implementazione di una vista per quello. La memorizzazione di AB e BA comporta incoerenze (se la relazione non è bidirezionale) mentre questo metodo è un approccio migliore
imans77

7

Supponendo che una "amicizia" sia sempre bidirezionale / reciproca, probabilmente la gestirò in questo modo.

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

Il risultato è che lo si cambia da un'unione molti-a-molti da "persona" a "persona", a un'unione molti-a-molti da "persona" a "amicizia". Ciò semplifica le unioni e i vincoli, ma ha l'effetto collaterale di consentire a più di due persone in un'unica "amicizia" (anche se forse la flessibilità aggiuntiva sarebbe un potenziale vantaggio).


Questo è fondamentalmente un modello di gruppo / appartenenza. Idea interessante, però.
einSelbst,

4

Potrebbe essere necessario definire gli indici attorno alle amicizie invece di raddoppiare il numero di righe:

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

In questo modo, raddoppi la memoria per gli indici ma non per i dati della tabella. Di conseguenza, questo dovrebbe essere un risparmio del 25% sullo spazio su disco. MySQL Query Optimizer sceglierà di eseguire solo scansioni dell'intervallo di indici, motivo per cui il concetto di copertura degli indici funziona bene qui.

Ecco alcuni link utili sugli indici di copertura:

AVVERTIMENTO

Se l'amicizia non è reciproca, hai le basi per un altro tipo di relazione: FOLLOWER

Se friend_to non è amico di friend_of, puoi semplicemente lasciare quella relazione fuori dal tavolo.

Se si desidera definire relazioni per tutti i tipi, siano essi reciproci o meno, è possibile utilizzare il seguente layout di tabella:

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

Dalla tabella delle relazioni, è possibile organizzare le relazioni in modo da includere quanto segue:

  • Gli amici dovrebbero essere reciproci
  • I nemici potrebbero essere reciproci o no
  • I follower potrebbero essere reciproci o meno
  • Le altre relazioni sarebbero soggette a interpretazione (da parte dell'oblio o dell'oblio o dal destinatario della vendetta (fissa))
  • Le relazioni possibili possono essere ulteriormente estese

Questo dovrebbe essere più solido per tutte le relazioni, indipendentemente dal fatto che la relazione sia reciproca o meno.


ciao @rolandomysqldba, sono un grande fan delle tue risposte. è davvero utile per me (in questo caso il primo esempio). Ora ecco un avvertimento per me, voglio una relazione unica. (es. Se l'utente A è amico di B, allora B amico di A non è accettabile.) devo fare con il trigger? e le prestazioni? perché ho una tabella molto grande (circa 1 milione di record) e se cerco gli amici dell'utente A (A è memorizzato in entrambi i campi (friend_of, friend_to) e mysql usando solo un indice, allora sta funzionando molto lentamente. Devo memorizzare voci duplicate nella mia tabella (ad es. A-> B, B-> A)
.Qualche

1

Se è possibile controllare all'interno dell'applicazione che l'id di A sia sempre inferiore all'id di B (preordinare gli ID di elementi A, B) è possibile fare leva sul chiedere senza OR (selezionare dove id_A = a AND id_B = b, invece di chiedere (id_A = a AND id_B = b) OPPURE (id_A = b AND id_B = a)) e conserva anche metà dei record necessari con le approssimazioni dell'altro. Quindi dovresti usare un altro campo per mantenere lo stato della relazione (are-amici, a-sollecitato-a-b, b-sollecitato-a-a, ex-amici-a, ex-amici-b) e il gioco è fatto.

Questo è il modo in cui ho gestito il mio sistema di amicizia, e questo semplifica il sistema e utilizza metà delle righe necessarie con altri sistemi, dicendo solo che A è uguale al valore ID inferiore nel codice.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.