Jak zaimplementować relację wiele do wielu w PostgreSQL?

101

Myślę, że tytuł jest oczywisty. Jak utworzyć strukturę tabeli w PostgreSQL, aby utworzyć relację wiele do wielu.

Mój przykład:

Product(name, price);
Bill(name, date, Products);
Radu Gheorghiu
źródło
2
usuń produkty z tabeli rachunków, utwórz nową tabelę o nazwie „produkty_faktowe” z dwoma polami: jednym wskazującym na produkty, drugim wskazującym na rachunek. uczyń te dwa pola kluczem podstawowym nowej tabeli.
Marc B
Więc bill_products (rachunek, produkty); ? A oboje PK?
Radu Gheorghiu
1
Tak. indywidualnie byliby FK wskazującymi na ich stoliki i razem byliby PK przy nowym stole.
Marc B
Więc, bill_product (referencje produktu nazwa.produktu, referencje rachunku nazwa.rachunku, (produkt, rachunek) klucz podstawowy)?
Radu Gheorghiu
Wskazywaliby, jakie będą pola PK w tabelach produktu i rachunku.
Marc B

Odpowiedzi:

306

Instrukcje SQL DDL (język definicji danych) mogą wyglądać następująco:

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

Dokonałem kilku poprawek:

  • N: m związek jest zwykle realizowane przez oddzielne stole - bill_productw tym przypadku.

  • Dodałem serialkolumny jako zastępcze klucze podstawowe . W Postgres 10 lub nowszym rozważmy zamiast tego IDENTITYkolumnę . Widzieć:

    Bardzo to polecam, ponieważ nazwa produktu nie jest unikalna (nie jest dobrym „naturalnym kluczem”). Ponadto wymuszanie niepowtarzalności i odwoływanie się do kolumny w kluczach obcych jest zwykle tańsze w przypadku 4-bajtowego integer(lub nawet 8-bajtowego bigint) niż w przypadku ciągu przechowywanego jako textlub varchar.

  • Nie używaj nazw podstawowych typów danych, takich datejak identyfikatory . Chociaż jest to możliwe, jest to zły styl i prowadzi do mylących błędów i komunikatów o błędach. Użyj prawidłowych, małych liter, niecytowanych identyfikatorów . Nigdy nie używaj słów zastrzeżonych i unikaj podwójnych cudzysłowów mieszanych identyfikatorów wielkości liter, jeśli możesz.

  • „imię” nie jest dobrym imieniem. Zmieniłem nazwę kolumny tabeli productna product( product_namelub podobną). To jest lepsza konwencja nazewnictwa . W przeciwnym razie, kiedy łączysz kilka tabel w zapytaniu - co robisz dużo w relacyjnej bazie danych - kończysz z wieloma kolumnami o nazwie „nazwa” i musisz użyć aliasów kolumn, aby uporządkować bałagan. To nie jest pomocne. Innym szeroko rozpowszechnionym anty-wzorcem byłby po prostu „id” jako nazwa kolumny.
    Nie jestem pewien, jak billmiałoby się nazywać . bill_idw tym przypadku prawdopodobnie wystarczy.

  • pricejest typu danych numeric do przechowywania liczb ułamkowych dokładnie tak, jak zostały wprowadzone (typ o dowolnej precyzji zamiast typu zmiennoprzecinkowego). Jeśli zajmujesz się wyłącznie liczbami całkowitymi, zrób to integer. Na przykład możesz zapisać ceny w centach .

  • amount( "Products"W pytaniu) przechodzi w tabeli łączącej bill_producti jest typu numeric, jak również. Ponownie, integerjeśli masz do czynienia wyłącznie z liczbami całkowitymi.

  • Widać kluczy obcych w bill_product? I stworzył zarówno do zmian kaskadowych: ON UPDATE CASCADE. Jeśli a product_idlub bill_idpowinno się zmienić, zmiana jest kaskadowana do wszystkich zależnych wpisów w bill_producti nic się nie psuje. To tylko odniesienia bez własnego znaczenia.
    Użyłem również ON DELETE CASCADEdo bill_id: Jeśli rachunek zostanie usunięty, jego szczegóły umrą wraz z nim.
    Nie dotyczy to produktów: nie chcesz usuwać produktu, który jest używany na rachunku. Postgres zgłosi błąd, jeśli spróbujesz tego. Zamiast tego można dodać kolejną kolumnę do, productaby zaznaczyć przestarzałe wiersze („usuwanie nietrwałe”).

  • Wszystkie kolumny w tym podstawowym przykładzie kończą się wartościami NOT NULL, więc NULLwartości nie są dozwolone. (Tak, wszystkie kolumny - kolumny klucza podstawowego są definiowane UNIQUE NOT NULLautomatycznie). Dzieje się tak, ponieważ NULLwartości nie miałyby sensu w żadnej z kolumn. Ułatwia życie początkującym. Ale nie uciekniesz tak łatwo, i tak musisz zrozumieć NULLobsługę . Dodatkowe kolumny mogą zezwalać na NULLwartości, funkcje i łączenia mogą wprowadzać NULLwartości w zapytaniach itp.

  • Przeczytaj rozdział CREATE TABLEw instrukcji .

  • Klucze podstawowe są implementowane z unikalnym indeksem w kolumnach kluczy, co sprawia, że ​​zapytania z warunkami w kolumnach PK są szybkie. Jednak sekwencja kolumn kluczy jest odpowiednia w przypadku kluczy wielokolumnowych. Ponieważ PK bill_productjest włączony (bill_id, product_id)w moim przykładzie, możesz chcieć dodać kolejny indeks tylko product_idlub (product_id, bill_id)jeśli masz zapytania szukające podanego product_idi nie bill_id. Widzieć:

  • Przeczytaj rozdział dotyczący indeksów w instrukcji .

Erwin Brandstetter
źródło
Jak mogę utworzyć indeks dla tabeli mapowania bill_product? Normalnie powinno to wyglądać tak: CREATE INDEX idx_bill_product_id ON booked_rates(bill_id, product_id). Czy to jest poprawne?
codyLine
1
@codyLine: ten indeks jest tworzony automatycznie przez PK.
Erwin Brandstetter
1
@ErwinBrandstetter: Czy nie należy tworzyć indeksu na bill_product dla kolumny product_id?
Christian,
2
@ ChristianB.Almeida: To przydatne w wielu przypadkach, tak. Dodałem trochę o indeksowaniu.
Erwin Brandstetter
1
@Jakov: Dla każdego rachunku w tabeli jest tylko 1 wiersz bill. Potrzebujemy kwoty za dodany przedmiot w bill_product.
Erwin Brandstetter