Jak zaprojektować tabelę relacji dla przyjaźni?

33

Jeśli Ajesteś przyjacielem B, to czy powinienem przechowywać obie wartości ABi BAczy jedna wystarczy? Jakie są zalety i wady obu metod.

Oto moja obserwacja:

  • Jeśli zachowam oba, muszę je zaktualizować po otrzymaniu prośby od znajomego.
  • Jeśli nie zachowam obu, wtedy trudno mi było zrobić wiele JOINprzy tej tabeli.

Obecnie utrzymuję związek w jedną stronę.

wprowadź opis zdjęcia tutaj

Więc co powinienem zrobić w tym przypadku? Jakakolwiek rada?

Chan
źródło
Czy jesteś zaangażowany w platformę, czy jest to pytanie teoretyczne?
Nick Chammas,
Co z podejściem hybrydowym: model wymaga i nieodwzajemnione przyjaźnie odpowiednio w osobnych tabelach, upewnij się, że przyjaźń jest wstawiona do dokładnie jednej z tych tabel, co nie jest przyjemne przy użyciu dzisiejszych produktów SQL :(
onedaywhen
@onedaywhen - Tak, brzmi bardziej odpowiedni dla bazy danych grafów .
Nick Chammas,
@NickChammas: To nie jest pytanie teoretyczne. Pracuję nad tym, mysqlktóre są przechowywane w chmurze Amazon.
Chan,
1
@Chan - Ach to oznacza, że nie można używać do egzekwowania ograniczeń wyboru relację są przechowywane tylko jeden sposób, wówczas (MySQL nie wymusza tych)
Martin Smith

Odpowiedzi:

30

Przechowałbym AB i BA. Przyjaźń jest tak naprawdę dwustronnym związkiem, każdy byt jest powiązany z innym. Chociaż intuicyjnie myślimy o „przyjaźni” jako o połączeniu między dwojgiem ludzi, z relacyjnego punktu widzenia bardziej przypomina „A ma przyjaciela B” i „B ma przyjaciela A”. Dwie relacje, dwie rekordy.

datagod
źródło
3
Wielkie dzięki. Naprawdę muszę dokładnie przemyśleć twój pomysł! Powodem, dla którego unikam przechowywania AB i BA, jest przechowywanie, ponieważ za każdym razem, gdy mam przyjaźń, mój stół będzie przechowywać dwa razy więcej.
Chan
1
Masz rację co do przechowywania, ale pamiętaj, że jeśli przechowywane jako liczby całkowite, każda relacja przyjaciela przyjmie około 30 bajtów (2 rekordy x 3 kolumny x 4 bajty na liczbę całkowitą = 24 bajty plus trochę dopełnienia). 1 milion osób z 10 znajomymi nadal zawierałby tylko około 300 MB danych.
datagod
1
datagod: właśnie tak!
Chan
Tak też zaprojektowałem swoje stoły, AB i BA.
kabuto178
2
Ponadto w sytuacjach, w których występuje tylko AB, a nie BA, może to oznaczać „oczekujące zaproszenie do znajomych”.
Greg
13

Jeśli przyjaźń ma być symetryczna (tj. Nie jest możliwe, A aby być przyjaciółmi, Bale nie odwrotnie), zapisałbym tylko relację jednokierunkową z ograniczeniem sprawdzania, aby każda relacja mogła być reprezentowana tylko w jeden sposób.

Również porzuciłbym identyfikator zastępczy i zamiast tego miałbym złożony PK (i prawdopodobnie złożony unikalny indeks również na odwróconych kolumnach).

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

Nie podajesz zapytań, które to utrudniają, ale zawsze możesz utworzyć Widok

CREATE VIEW Foo
AS
SELECT UserID1,UserID2 
FROM Friends
UNION ALL
SELECT UserID2,UserID1 
FROM Friends
Martin Smith
źródło
Wiem, że to dość stare, więc przepraszam za wykopanie tego. Czy nie byłoby lepiej NIE definiować wskaźnika odwrotnej przyjaźni UNIQUE, aby nie nakładać zbędnego i zbędnego dodatkowego obciążenia na INSERTs? Ponieważ mamy, PRIMARY KEY (a,b)a ponieważ PK jest UNIQUE, odwrotność KEY (b,a)również UNIQUEnie ma znaczenia.
tfrommen
1
@tf Zgadnij, który zależy od optymalizatora zapytania. Jak zauważyłeś, konieczne jest sprawdzenie tylko w jedną stronę, aby plan wstawiania i tak mógł to zrobić. Pytanie jest oznaczone MySQL - nie mam pojęcia, jak się to zachowuje.
Martin Smith
Wiem, że to stara odpowiedź, ale chcę tylko zwrócić uwagę na każdego, kto się na nią natknie, że MySQL całkowicie ignoruje ograniczenia CHECK (chociaż „pomyślnie je parsuje”), więc to podejście prawdopodobnie nie jest dobrym sposobem na zastosowanie tej technologii.
Micheasza
@ Micheasz prawda. Nie wiedziałem o tym w 2012 roku. Nadal będzie działać w innych systemach DBMS ...
Martin Smith
+1 za wdrożenie w tym celu Widoku. Przechowywanie AB i BA powoduje niespójność (jeśli związek nie jest dwukierunkowy), podczas gdy ta metoda jest lepszym podejściem
imans77
7

Zakładając, że „przyjaźń” jest zawsze dwustronna / wzajemna, prawdopodobnie poradziłbym sobie z czymś takim.

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

W rezultacie zmieniasz to z łączenia wielu w wielu z „osoby” w „osobę”, w łączenia wielu w wielu z „osoby” w „przyjaźń”. Uprości to połączenia i ograniczenia, ale efektem ubocznym jest umożliwienie więcej niż dwóm osobom w jednej „przyjaźni” (choć może dodatkowa elastyczność byłaby potencjalną zaletą).

db2
źródło
Jest to w zasadzie wzór grupy / członkostwa. Ciekawy pomysł.
einSelbst,
4

Może być konieczne zdefiniowanie indeksów wokół przyjaźni zamiast podwojenia liczby wierszy:

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

W ten sposób podwajasz pamięć dla indeksów, ale nie dla danych tabeli. W rezultacie powinno to stanowić 25% oszczędności miejsca na dysku. Optymalizator zapytań MySQL wybierze tylko skanowanie zakresu indeksów, dlatego koncepcja pokrywania indeksów działa tutaj dobrze.

Oto kilka ciekawych linków na temat indeksów obejmujących:

CAVEAT

Jeśli przyjaźń nie jest wzajemna, masz podstawę do innego rodzaju relacji: OBSERWUJ

Jeśli friend_to nie jest przyjacielem friend_of, możesz po prostu pozostawić ten związek poza tabelą.

Jeśli chcesz zdefiniować relacje dla wszystkich typów, niezależnie od tego, czy są one wzajemne, czy nie, prawdopodobnie możesz użyć następującego układu tabeli:

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

W tabeli relacji można ułożyć relacje w następujący sposób:

  • Przyjaciele powinni być wzajemni
  • Przeciwnicy mogą być wzajemni lub nie
  • Obserwujący mogą być wzajemni lub nie
  • Inne relacje podlegałyby interpretacji (przez zapomnianego, porzuconego lub odbiorcę zemsty (ustalony))
  • Relacje z Possibie można dalej rozszerzać

Powinno to być bardziej niezawodne dla wszystkich relacji, niezależnie od tego, czy związek jest wzajemny, czy nie.

RolandoMySQLDBA
źródło
cześć @rolandomysqldba, jestem wielkim fanem twoich odpowiedzi. jest to dla mnie naprawdę pomocne (w tym przypadku pierwszy przykład). Teraz jest jedno zastrzeżenie dla mnie, chcę wyjątkowego związku. (np. jeśli użytkownik A zaprzyjaźnia się z B, to przyjaciel B z A jest niedopuszczalny.) czy powinienem zrobić z wyzwalaczem? a co z wydajnością? ponieważ mam bardzo dużą tabelę (około 1 miliona rekordów) i jeśli szukam znajomych użytkownika A (A jest przechowywany w obu polach (friend_of, friend_to) i mysql za pomocą tylko jednego indeksu, to działa bardzo wolno. Muszę przechowywać zduplikowane wpisy w mojej tabeli (np. A-> B, B-> A.) Czy jest
jakaś
1

Jeśli możesz kontrolować w aplikacji, że identyfikator A jest zawsze niższy niż identyfikator B (zamów w przedsprzedaży identyfikatory elementów A, B), możesz skorzystać z pytania bez OR (wybierz gdzie id_A = AND AND id_B = b, zamiast pytać (id_A = a AND id_B = b) OR (id_A = b AND id_B = a)), a także zachowaj połowę rekordów, których potrzebujesz z przybliżeniami drugiej osoby. Następnie powinieneś użyć innego pola, aby utrzymać stan relacji (są-przyjaciółmi, a-prosi-do-b, b-prosi-do-a, exfriends-a, exfriends-b) i gotowe.

W ten sposób zarządzałem moim systemem przyjaźni, a to upraszcza system i wykorzystuje połowę wierszy, których potrzebujesz w innych systemach, mówiąc tylko, że A jest równe niższej wartości id w kodzie.

mieszkaniec
źródło