Wie soll ich eine Beziehungstabelle für Freundschaft entwerfen?

33

Wenn Aein Freund von ist B, sollte ich dann beide Werte speichern ABund BA, oder einer ist genug? Was sind die Vor- und Nachteile beider Methoden.

Hier ist meine Beobachtung:

  • Wenn ich beide behalte, muss ich beide aktualisieren, wenn ich eine Anfrage von einem Freund erhalte.
  • Wenn ich nicht beide behalte, fand ich es schwierig, JOINmit dieser Tabelle mehrere zu machen .

Derzeit halte ich die Beziehung in eine Richtung.

Bildbeschreibung hier eingeben

Was soll ich in diesem Fall tun? Irgendein Rat?

Chan
quelle
Sind Sie einer Plattform verpflichtet oder ist dies eine theoretische Frage?
Nick Chammas
Was ist ein Hybrid - Ansatz: Modell vergolten bzw. unerwiderte Freundschaften in separaten Tabellen, eine Freundschaft sicherzustellen , dass in genau eine dieser Tabellen eingefügt, mit dem heutigen SQL Produkten :( nicht schön zu erreichen
onedaywhen
@onedaywhen - Ja, klingt besser geeignet für eine Graph - Datenbank .
Nick Chammas
@ NickChammas: Es ist keine theoretische Frage. Ich arbeite daran, mysqlwas in der Amazon Cloud gespeichert ist.
Chan
1
@Chan - Ah, das heißt, Sie können Check-Einschränkungen nicht verwenden, um die Beziehung zu erzwingen, wird nur in einer Richtung gespeichert (MySQL erzwingt diese nicht)
Martin Smith

Antworten:

30

Ich würde AB und BA speichern. Eine Freundschaft ist wirklich eine wechselseitige Beziehung, jede Entität ist mit einer anderen verbunden. Auch wenn wir die "Freundschaft" intuitiv als ein Bindeglied zwischen zwei Personen betrachten, ähnelt sie aus relationaler Sicht eher "A hat einen Freund B" und "B hat einen Freund A". Zwei Beziehungen, zwei Aufzeichnungen.

datagod
quelle
3
Danke vielmals. Ich muss wirklich sorgfältig über Ihre Idee nachdenken! Der Grund, warum ich es vermeide, AB und BA zu speichern, ist der Speicher, da mein Tisch jedes Mal, wenn ich eine Freundschaft habe, doppelt so viel speichert.
Chan
1
Sie haben Recht mit der Speicherung, aber denken Sie daran, dass bei der Speicherung als Ganzzahl jede Freund-Freund-Beziehung etwa 30 Bytes benötigt (2 Datensätze x 3 Spalten x 4 Bytes pro Ganzzahl = 24 Bytes plus etwas Abstand). 1 Million Menschen mit jeweils 10 Freunden wären immer noch nur etwa 300 MB Daten.
Datum
1
datagod: das stimmt!
Chan
So habe ich auch meine Tische entworfen, AB & BA.
Kabuto178
2
Außerdem kann dies in Situationen, in denen nur AB und nicht BA vorhanden sind, eine ausstehende Freundschaftsanfrage darstellen.
Greg
13

Wenn Freundschaft symmetrisch sein soll (dh es ist nicht möglich A , Freunde zu sein, Baber nicht umgekehrt), würde ich die Einwegbeziehung nur mit einer Check-Einschränkung speichern, um sicherzustellen, dass jede Beziehung nur in eine Richtung dargestellt werden kann.

Außerdem würde ich die Ersatz-ID weglassen und stattdessen eine zusammengesetzte PK haben (und möglicherweise einen zusammengesetzten eindeutigen Index auch für die umgekehrten Spalten).

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

Sie sagen nicht die Abfragen, die dies schwierig macht, aber Sie können immer eine Ansicht erstellen

CREATE VIEW Foo
AS
SELECT UserID1,UserID2 
FROM Friends
UNION ALL
SELECT UserID2,UserID1 
FROM Friends
Martin Smith
quelle
Ich weiß, dass dies ziemlich alt ist. Wäre es nicht besser, den umgekehrten Freundschaftsindex NICHT zu definieren UNIQUE, um INSERTs nicht unnötig und überflüssig zu belasten ? Da wir haben PRIMARY KEY (a,b)und da ein PK ist UNIQUE, ist das Umkehren KEY (b,a)auch UNIQUEegal was.
17.
1
@tf Das hängt vom Queroptimierer ab. Wie Sie bereits erwähnt haben, ist es nur erforderlich, eine Richtung zu überprüfen, damit der Einfügeplan dies trotzdem tun kann. Die Frage ist mit MySQL verschlagwortet - keine Ahnung, wie sich das verhält.
Martin Smith
Ich weiß, dass dies eine alte Antwort ist, aber ich möchte nur jeden darauf hinweisen, der darüber stolpert, dass MySQL CHECK-Einschränkungen vollständig ignoriert (obwohl es sie erfolgreich "parsen" wird), sodass dieser Ansatz wahrscheinlich nicht der richtige für diese Technologie ist.
Micah
@ Micah wahr. 2012 war mir das nicht bewusst. Funktioniert noch in anderen DBMSs ...
Martin Smith
+1 für die Implementierung einer View dafür. Das Speichern von AB & BA führt zu Inkonsistenzen (wenn die Beziehung nicht bidirektional ist), wohingegen diese Methode einen besseren Ansatz darstellt
imans77
7

Unter der Annahme, dass eine "Freundschaft" immer wechselseitig ist, würde ich wahrscheinlich damit umgehen.

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

Das Ergebnis ist, dass Sie es von einer Viele-zu-Viele-Verbindung von "Person" zu "Person", zu einer Viele-zu-Viele-Verbindung von "Person" zu "Freundschaft" ändern. Dies vereinfacht Verknüpfungen und Einschränkungen, hat jedoch den Nebeneffekt, dass mehr als zwei Personen eine einzige "Freundschaft" eingehen können (obwohl die zusätzliche Flexibilität möglicherweise ein potenzieller Vorteil wäre).

db2
quelle
Dies ist im Grunde ein Gruppen- / Mitgliedschaftsmuster. Eine interessante Idee.
einSelbst
4

Möglicherweise müssen Sie Indizes für Freundschaften definieren, anstatt die Anzahl der Zeilen zu verdoppeln:

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

Auf diese Weise verdoppeln Sie den Speicher für Indizes, jedoch nicht für die Tabellendaten. Infolgedessen sollte dies eine Einsparung von 25% des Speicherplatzes bedeuten. Der MySQL Query Optimizer wählt nur die Ausführung von Indexbereichs-Scans aus, weshalb das Konzept der Indexabdeckung hier gut funktioniert.

Hier sind ein paar nette Links zu den Titelindizes:

VORBEHALT

Wenn Freundschaft nicht auf Gegenseitigkeit beruht, haben Sie die Grundlage für eine andere Art von Beziehung: FOLLOWER

Wenn friend_to kein Freund von friend_of ist, können Sie diese Beziehung einfach aus der Tabelle streichen.

Wenn Sie Beziehungen für alle Typen definieren möchten, unabhängig davon, ob sie wechselseitig sind oder nicht, können Sie möglicherweise das folgende Tabellenlayout verwenden:

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

In der Beziehungstabelle können Sie die Beziehungen so anordnen, dass sie Folgendes enthalten:

  • Freunde sollten gegenseitig sein
  • Feinde können gegenseitig sein oder nicht
  • Anhänger können gegenseitig sein oder nicht
  • Die anderen Beziehungen würden interpretiert (durch den Vergessenen oder Verlassenen oder den Empfänger von Rache (fixiert))
  • Possibie-Beziehungen können weiter ausgebaut werden

Dies sollte für alle Beziehungen robuster sein, unabhängig davon, ob die Beziehung wechselseitig ist oder nicht.

RolandoMySQLDBA
quelle
hi @rolandomysqldba, ich bin ein großer Fan von deinen Antworten. Es ist wirklich hilfreich für mich (in diesem Fall 1. Beispiel). Hier ist eine Einschränkung für mich, ich möchte eine einzigartige Beziehung. (zB Wenn Benutzer A mit B befreundet ist, dann ist B mit A nicht akzeptabel.) Soll ich mit Trigger machen? und was ist mit der Leistung? weil ich eine sehr große Tabelle habe (ungefähr 1 Million Datensätze) und wenn ich nach Freunden von Benutzer A suche (A wird in beiden Feldern (friend_of, friend_to) und mysql mit nur einem Index gespeichert), dann ist die Leistung sehr langsam Ich muss doppelte Einträge in meiner Tabelle speichern müssen (zB A-> B, B-> A). Bessere Option?
Manish Sapkal
1

Wenn Sie in der Anwendung steuern können, dass die ID von A immer niedriger als die ID von B ist (vorbestellen der IDs der A- und B-Elemente), können Sie das Fragen ohne ODER nutzen (wählen Sie, wo id_A = a UND id_B = b, anstatt zu fragen (id_A = a UND id_B = b) ODER (id_A = b UND id_B = a)) und behalten Sie auch die Hälfte der Datensätze bei, die Sie mit den Annäherungen des anderen benötigen. Dann sollten Sie ein anderes Feld verwenden, um den Status der Beziehung beizubehalten (sind Freunde, a-angefragt-an-b, b-angefragt-an-a, Exfreunde-a, Exfreunde-b), und fertig.

Auf diese Weise habe ich mein Freundschaftssystem verwaltet. Dies vereinfacht das System und belegt die Hälfte der Zeilen, die Sie für andere Systeme benötigen. Nur A entspricht dem niedrigeren ID-Wert im Code.

appartisan
quelle