Wie implementiere ich eine Viele-zu-Viele-Beziehung in PostgreSQL?

94

Ich glaube, der Titel ist selbsterklärend. Wie erstellen Sie die Tabellenstruktur in PostgreSQL, um eine Viele-zu-Viele-Beziehung herzustellen?

Mein Beispiel:

Product(name, price);
Bill(name, date, Products);
Radu Gheorghiu
quelle
2
Entfernen Sie Produkte aus der Rechnungstabelle, erstellen Sie eine neue Tabelle mit dem Namen "bill_products" mit zwei Feldern: eines zeigt auf Produkte, eines zeigt auf Rechnung. Machen Sie diese beiden Felder zum Primärschlüssel dieser neuen Tabelle.
Marc B
Also bill_products (Rechnung, Produkte); ? Und beide PK?
Radu Gheorghiu
1
Ja. Sie wären einzeln ein FK, der auf ihre jeweiligen Tabellen zeigt, und zusammen wären sie die PK für die neue Tabelle.
Marc B
Also, bill_product (Produktreferenzen Produktname, Rechnungsreferenzen Rechnungsname, (Produkt, Rechnung) Primärschlüssel)?
Radu Gheorghiu
Sie würden darauf hinweisen, wie die PK-Felder der Produkt- und Rechnungstabellen aussehen würden.
Marc B

Antworten:

295

Die SQL DDL-Anweisungen (Data Definition Language) könnten folgendermaßen aussehen:

CREATE TABLE product (
  product_id serial PRIMARY KEY  -- implicit primary key constraint
, product    text NOT NULL
, price      numeric NOT NULL DEFAULT 0
);

CREATE TABLE bill (
  bill_id  serial PRIMARY KEY
, bill     text NOT NULL
, billdate date NOT NULL DEFAULT CURRENT_DATE
);

CREATE TABLE bill_product (
  bill_id    int REFERENCES bill (bill_id) ON UPDATE CASCADE ON DELETE CASCADE
, product_id int REFERENCES product (product_id) ON UPDATE CASCADE
, amount     numeric NOT NULL DEFAULT 1
, CONSTRAINT bill_product_pkey PRIMARY KEY (bill_id, product_id)  -- explicit pk
);

Ich habe einige Anpassungen vorgenommen:

  • Die n: m-Beziehung wird normalerweise durch eine separate Tabelle implementiert - bill_productin diesem Fall.

  • Ich habe serialSpalten als Ersatzprimärschlüssel hinzugefügt . Betrachten Sie in Postgres 10 oder höher stattdessen eine IDENTITYSpalte . Sehen:

    Ich kann das nur empfehlen, da der Name eines Produkts kaum eindeutig ist (kein guter "natürlicher Schlüssel"). Das Erzwingen der Eindeutigkeit und das Verweisen auf die Spalte in Fremdschlüsseln ist in der Regel mit einem 4-Byte integer(oder sogar einem 8-Byte bigint) billiger als mit einer als textoder gespeicherten Zeichenfolge varchar.

  • Sie keine Namen von Basisdatentypen wie verwenden dateals Bezeichner . Dies ist zwar möglich, hat aber einen schlechten Stil und führt zu verwirrenden Fehlern und Fehlermeldungen. Verwenden Sie legale, nicht zitierte Bezeichner in Kleinbuchstaben . Verwenden Sie niemals reservierte Wörter und vermeiden Sie doppelte Anführungszeichen für gemischte Groß- und Kleinschreibung, wenn Sie können.

  • "Name" ist kein guter Name. Ich habe die Spalte der Tabelle productin product( product_nameoder ähnlich) umbenannt. Das ist eine bessere Namenskonvention . Andernfalls , wenn Sie ein paar Tabellen in einer Join - Abfrage - etwas Sie tun eine Menge in einer relationalen Datenbank - Sie mehrere Spalten mit dem Namen „name“ am Ende mit und müssen Spaltenaliasnamen verwenden , um das Chaos in Ordnung bringen. Das ist nicht hilfreich. Ein weiteres weit verbreitetes Anti-Pattern wäre nur "id" als Spaltenname.
    Ich bin mir nicht sicher, wie der Name eines billlauten würde. bill_idwird in diesem Fall wahrscheinlich ausreichen.

  • priceist vom Datentyp numeric , um Bruchzahlen genau wie eingegeben zu speichern (beliebiger Genauigkeitstyp anstelle des Gleitkommatyps). Wenn Sie sich ausschließlich mit ganzen Zahlen beschäftigen, machen Sie das integer. Sie können beispielsweise Preise als Cent speichern .

  • Das amount( "Products"in Ihrer Frage) geht in die Verknüpfungstabelle bill_productund ist ebenfalls vom Typ numeric. Auch hier gilt, integerwenn Sie sich ausschließlich mit ganzen Zahlen befassen.

  • Sie sehen die Fremdschlüssel in bill_product? Ich habe beide erstellt, um Änderungen zu kaskadieren : ON UPDATE CASCADE. Wenn sich a product_idoder bill_idändern sollte, wird die Änderung auf alle abhängigen Einträge in kaskadiert bill_productund nichts wird unterbrochen. Dies sind nur Referenzen ohne eigene Bedeutung.
    Ich habe auch verwendet ON DELETE CASCADEfür bill_id: Wenn eine Rechnung gelöscht wird, sterben ihre Details damit.
    Nicht so bei Produkten: Sie möchten kein Produkt löschen, das in einer Rechnung verwendet wird. Postgres gibt einen Fehler aus, wenn Sie dies versuchen. Sie würden productstattdessen eine weitere Spalte hinzufügen , um veraltete Zeilen zu markieren ("soft-delete").

  • Alle Spalten in diesem Basisbeispiel sind am Ende so NOT NULL, dass NULLWerte nicht zulässig sind. (Ja, alle Spalten - Primärschlüsselspalten werden UNIQUE NOT NULLautomatisch definiert .) Dies liegt daran, dass NULLWerte in keiner der Spalten sinnvoll sind. Es erleichtert das Leben eines Anfängers. Aber Sie werden nicht so leicht davonkommen, Sie müssen sowieso die NULLHandhabung verstehen . Zusätzliche Spalten können NULLWerte zulassen , Funktionen und Verknüpfungen können NULLWerte in Abfragen usw. einführen .

  • Lesen Sie das Kapitel CREATE TABLEim Handbuch .

  • Primärschlüssel werden mit einem eindeutigen Index für die Schlüsselspalten implementiert , wodurch Abfragen mit Bedingungen für die PK-Spalte (n) schnell durchgeführt werden. Die Reihenfolge der Schlüsselspalten ist jedoch bei mehrspaltigen Schlüsseln relevant. Da in meinem Beispiel die PK aktiviert bill_productist (bill_id, product_id), möchten Sie möglicherweise einen weiteren Index für just hinzufügen, product_idoder (product_id, bill_id)wenn Sie Abfragen haben, die nach einem bestimmten product_idund einem bestimmten suchen bill_id. Sehen:

  • Lesen Sie das Kapitel über Indizes im Handbuch .

Erwin Brandstetter
quelle
Wie kann ich einen Index für die Zuordnungstabelle erstellen bill_product? Normalerweise sollte es so aussehen : CREATE INDEX idx_bill_product_id ON booked_rates(bill_id, product_id). Ist das richtig?
CodyLine
1
@codyLine: Dieser Index wird automatisch von der PK erstellt.
Erwin Brandstetter
1
@ErwinBrandstetter: Sollte kein Index für bill_product für die Spalte product_id erstellt werden?
Christian
2
@ ChristianB.Almeida: Das ist in vielen Fällen nützlich, ja. Ich habe ein bisschen über die Indizierung hinzugefügt.
Erwin Brandstetter
1
@ Jakov: Es gibt nur 1 Zeile für jede Rechnung in der Tabelle bill. Wir benötigen den Betrag pro hinzugefügtem Artikel in bill_product.
Erwin Brandstetter