Come implementare una relazione molti-a-molti in PostgreSQL?


95

Credo che il titolo sia autoesplicativo. Come crei la struttura della tabella in PostgreSQL per creare una relazione molti-a-molti.

Il mio esempio:

Product(name, price);
Bill(name, date, Products);

2
rimuovere i prodotti dalla tabella delle fatture, creare una nuova tabella denominata "bill_products" con due campi: uno che punta ai prodotti, uno che punta alla fattura. rendi questi due campi la chiave primaria di questa nuova tabella.
Marc B

Quindi bill_products (bill, products); ? Ed entrambi PK?
Radu Gheorghiu

1
si. sarebbero individualmente un FK che punta ai rispettivi tavoli, e insieme sarebbero il PK per il nuovo tavolo.
Marc B

Quindi, bill_product (prodotto fa riferimento a product.name, bill fa riferimento a bill.name, (prodotto, fattura) chiave primaria)?
Radu Gheorghiu

Indicherebbero quali sarebbero i campi PK delle tabelle Prodotto e Fattura.
Marc B

Risposte:


298

Le istruzioni SQL DDL (linguaggio di definizione dei dati) potrebbero avere questo aspetto:

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

Ho apportato alcune modifiche:

  • La relazione n: m è normalmente implementata da una tabella separata, bill_productin questo caso.

  • Ho aggiunto serialcolonne come chiavi primarie surrogate . In Postgres 10 o versioni successive, considera invece una IDENTITYcolonna . Vedere:

    Lo consiglio vivamente, perché il nome di un prodotto è difficilmente univoco (non è una buona "chiave naturale"). Inoltre, l'applicazione dell'unicità e il riferimento alla colonna nelle chiavi esterne è in genere più conveniente con un 4 byte integer(o anche un 8 byte bigint) rispetto a una stringa archiviata come texto varchar.

  • Non utilizzare nomi di tipi di dati di base datecome identificatori . Sebbene ciò sia possibile, è di cattivo stile e porta a errori e messaggi di errore confusi. Utilizza identificatori legali, minuscoli e non quotati . Non usare mai parole riservate ed evita gli identificatori di maiuscole e minuscole tra virgolette doppie, se puoi.

  • "nome" non è un buon nome. Ho rinominato la colonna della tabella productin product(o product_nameo simile). Questa è una convenzione di denominazione migliore . Altrimenti, quando unisci un paio di tabelle in una query, cosa che fai molto in un database relazionale, ti ritroverai con più colonne chiamate "nome" e dovrai usare gli alias di colonna per risolvere il pasticcio. Non è d'aiuto. Un altro anti-pattern molto diffuso sarebbe semplicemente "id" come nome della colonna.
    Non sono sicuro di quale billsarebbe il nome di a . bill_idprobabilmente sarà sufficiente in questo caso.

  • priceè del tipo di datinumeric per memorizzare i numeri frazionari esattamente come inseriti (tipo di precisione arbitraria invece del tipo a virgola mobile). Se ti occupi esclusivamente di numeri interi, fallo integer. Ad esempio, puoi salvare i prezzi in centesimi .

  • Il amount( "Products"nella tua domanda) va nella tabella di collegamento bill_producted è anch'esso di tipo numeric. Di nuovo, integerse ti occupi esclusivamente di numeri interi.

  • Vedete le chiavi esterne in bill_product? Ho creato sia per le modifiche a cascata: ON UPDATE CASCADE. Se un product_ido bill_iddovesse cambiare, la modifica viene applicata a cascata a tutte le voci dipendenti in bill_producte nulla si interrompe. Questi sono solo riferimenti senza significato proprio.
    Ho anche usato ON DELETE CASCADEper bill_id: Se una fattura viene cancellata, i suoi dettagli muoiono con essa.
    Non è così per i prodotti: non si desidera eliminare un prodotto utilizzato in una fattura. Postgres genererà un errore se ci provi. Aggiungerebbe un'altra colonna a productper contrassegnare le righe obsolete ("eliminazione temporanea").

  • Tutte le colonne in questo esempio di base finiscono per essere NOT NULL, quindi i NULLvalori non sono consentiti. (Sì, tutte le colonne: le colonne della chiave primaria vengono definite UNIQUE NOT NULLautomaticamente.) Questo perché i NULLvalori non avrebbero senso in nessuna delle colonne. Rende più facile la vita di un principiante. Ma non sarà possibile ottenere via così facilmente, è necessario capire NULLla gestione in ogni caso. Colonne aggiuntive potrebbero consentire NULLvalori, funzioni e join possono introdurre NULLvalori nelle query, ecc.

  • Leggere il capitolo sul CREATE TABLEmanuale .

  • Le chiavi primarie sono implementate con un indice univoco sulle colonne chiave, che rende veloci le query con le condizioni sulle colonne PK. Tuttavia, la sequenza delle colonne chiave è rilevante nelle chiavi a più colonne. Poiché il PK on bill_productè attivo (bill_id, product_id)nel mio esempio, potresti voler aggiungere un altro indice su just product_ido (product_id, bill_id)se hai query alla ricerca di un dato product_ide no bill_id. Vedere:

  • Leggere il capitolo sugli indici nel manuale .


Come posso creare un indice per la tabella di mappatura bill_product? Normalmente dovrebbe assomiglia: CREATE INDEX idx_bill_product_id ON booked_rates(bill_id, product_id). È giusto?
codyLine

1
@codyLine: questo indice viene creato automaticamente dal PK.
Erwin Brandstetter

1
@ErwinBrandstetter: non dovrebbe essere creato un indice su bill_product per la colonna product_id?
Christian

2
@ ChristianB.Almeida: è utile in molti casi, sì. Ho aggiunto qualcosa sull'indicizzazione.
Erwin Brandstetter

1
@ Jakov: c'è solo 1 riga per ogni fattura nella tabella bill. Abbiamo bisogno della quantità per articolo aggiunto in bill_product.
Erwin Brandstetter
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.