Wie erzwinge ich die Eindeutigkeit eines zusammengesetzten Primärschlüssels, der aus zwei Fremdschlüsseln für dieselbe Tabelle besteht?


7

Ich habe zwei Tische: einen userTisch und einen friendshipTisch. Angenommen, die friendshipTabelle sieht folgendermaßen aus:

friendship
------------
user_one_id
user_two_id
other_fields

Ich brauche die Durchsetzung Einzigartigkeit der Kombination von Werten (user_one_id, user_two_id)und ignorieren Sie die Bestellung , so:

user_one_id | user_two_id
------------+------------
          1 |          2
          2 |          1  -- the DBMS should throw a unique constraint error when trying to insert a row like this one.

Ein weiterer wichtiger Punkt ist, dass user_one_ider den Initiator von friendshipund user_two_idden Empfänger darstellt , sodass ich nicht einfach die "kleinere" der beiden IDs machen kann user_one_id .

Die Frage

Gibt es eine Möglichkeit, dies mithilfe von Einschränkungen zu tun, oder sollte ich dies auf eine andere Weise implementieren?

Antworten:


8

In Anbetracht Ihres Kommentars,

Für meinen Anwendungsfall repräsentiert user_one_id den Initiator der Freundschaft und user_two_id den Empfänger der Freundschaft. Ich kann also nicht einfach den niedrigsten Wert als user_one_id verwenden.

Nun, du kannst es immer noch tun. Ihr Anwendungsfall schließt lediglich eine Zeilenbeschränkung aus, um dies sicherzustellen. Was Sie wollen, ist eine Tabelleneinschränkung, so etwas.

CREATE TABLE friendship (
  user_one_id int NOT NULL,
  user_two_id int NOT NULL,
  CHECK (user_one_id != user_two_id ),
  PRIMARY KEY (user_one_id, user_two_id)
);
-- you can do least first if you want. doesn't matter.
CREATE UNIQUE INDEX ON friendship (
  greatest(user_one_id, user_two_id),
  least(user_one_id, user_two_id)
);

Hier ist viel los. Wir sorgen dafür.

  1. Beide sind NOT NULL
  2. Beide sind einander nicht gleich
  3. Beide sind UNIQUE (user_one_id, user_two_id)

Damit bleibt ein verbleibendes Problem der kommutativen Eindeutigkeit, das wir mit einer benutzerdefinierten eindeutigen Tabelleneinschränkung lösen, die mit einem Index implementiert ist.

Beweis im Pudding

INSERT INTO friendship VALUES ( 1,2 );
INSERT 0 1
test=# INSERT INTO friendship VALUES ( 2,1 );
ERROR:  duplicate key value violates unique constraint friendship_greatest_least_idx"
DETAIL:  Key ((GREATEST(user_one_id, user_two_id)), (LEAST(user_one_id, user_two_id)))=(2, 1) already exists.

Als wichtige freundliche Anmerkung sind Ihre Namen alle Arten von albern. Die Beziehung ist in Ordnung. Bitte geben Sie ihnen in der Produktion bessere Namen.

friendship
----------
request_initiator
request_target
other_fields

Richtige Lösung, es gibt jedoch ein kleines Detail, das verbessert werden könnte: Ihre Lösung erstellt zwei Indizes im Hintergrund, von denen einer ausreichen würde. Erstellen Sie die Tabelle ohne Primärschlüssel, dann einen Index über (GREATEST (), LEAST ()) und führen Sie ALTER TABLE ADD PRIMARY KEY USING INDEX aus ...
Twinkles

2
Obwohl, jetzt wo ich darüber nachdenke, die meisten realen Datenbanken wahrscheinlich sowieso vom zweiten Index profitieren werden.
Twinkles

@ Twinkles Dies hat nicht funktioniert, einen Fehler ERROR: index "friendship_greatest_least_idx" contains expressions LINE 1: alter table friendship add primary key using index friendshi... ^ DETAIL: Cannot create a primary key or unique constraint using such an index
auslösen

0

Die Lösung ist sehr gut. Zunächst sollten wir jedoch sicherstellen, dass die beiden Spalten keine Überlappungszahlen enthalten. Das heißt, eine Zahl ist für eine Spalte eindeutig.

Ich würde die folgende einfache Lösung vorschlagen:

Erstellen Sie zwei Sequenzen:

1.

user_one_id_seq: mit geraden Zahlen

user_two_id_seq mit ungeraden Zahlen

2.

user_one_id_seq: Zahlen zwischen 1 und 10000

user_two_id_seq Zahlen zwischen 10001 und 20000


0

Das Design sollte normalisiert werden, indem eine Schnittpunkttabelle mit dem Namen user_friendship erstellt wird:

Tabelle erstellen user_friendship (Benutzer-ID int NICHT NULL, Freundschafts-ID int NICHT NULL, PRIMARY KEY (Benutzer-ID, Freundschafts-ID), AUSLÄNDISCHER SCHLÜSSEL Freundschafts-ID REFERENZEN Freundschaft (Freundschafts-ID), AUSLÄNDISCHER SCHLÜSSEL Benutzer-ID REFERENZEN Benutzer (Benutzer-ID)
);


-1

Wenn Sie die Logik so einstellen, dass der Wert von user_one_id niedriger als der Wert von user_two_id ist , treten keine Probleme auf .


2
user_one_idRepräsentiert für meinen Anwendungsfall den Initiator der Freundschaft und user_two_idden Empfänger der Freundschaft. Ich kann also nicht einfach den niedrigsten Wert als verwenden user_one_id.
Maniciam

1
@ Maniciam siehe meine Antwort.
Evan Carroll
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.