Bagaimana cara menerapkan hubungan banyak ke banyak di PostgreSQL?

102

Saya yakin judulnya sudah cukup jelas. Bagaimana Anda membuat struktur tabel di PostgreSQL untuk membuat hubungan banyak-ke-banyak.

Contoh saya:

Product(name, price);
Bill(name, date, Products);
Radu Gheorghiu
sumber
2
hapus produk dari tabel tagihan, buat tabel baru bernama "bill_products" dengan dua bidang: satu mengarah ke produk, satu lagi mengarah ke tagihan. jadikan kedua bidang tersebut sebagai kunci utama tabel baru.
Marc B
Jadi bill_products (bill, products); ? Dan keduanya PK?
Radu Gheorghiu
1
ya. Mereka secara individu akan menjadi FK yang menunjuk ke meja masing-masing, dan bersama-sama menjadi PK untuk meja baru tersebut.
Marc B
Jadi, bill_product (referensi produk product.name, referensi tagihan bill.name, (product, bill) primary key)?
Radu Gheorghiu
Mereka akan menunjukkan berapa bidang PK dari tabel Produk dan Tagihan.
Marc B

Jawaban:

307

Pernyataan SQL DDL (bahasa definisi data) akan terlihat seperti ini:

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

Saya membuat beberapa penyesuaian:

  • Hubungan n: m biasanya diimplementasikan oleh tabel terpisah - bill_productdalam kasus ini.

  • Saya menambahkan serialkolom sebagai kunci utama pengganti . Di Postgres 10 atau yang lebih baru, pertimbangkan IDENTITYkolom sebagai gantinya. Lihat:

    Saya sangat merekomendasikannya, karena nama suatu produk hampir tidak unik (bukan "kunci alami" yang baik). Juga, menegakkan keunikan dan mereferensikan kolom dalam kunci asing biasanya lebih murah dengan 4-byte integer(atau bahkan 8-byte bigint) dibandingkan dengan string yang disimpan sebagai textatau varchar.

  • Jangan gunakan nama tipe data dasar seperti datesebagai pengenal . Meskipun ini mungkin, itu adalah gaya yang buruk dan menyebabkan kesalahan yang membingungkan dan pesan kesalahan. Gunakan tanda kutip legal, huruf kecil, dan tidak dikutip . Jangan pernah menggunakan kata - kata khusus dan hindari pengenal huruf campuran yang dikutip ganda jika Anda bisa.

  • "nama" bukanlah nama yang baik. Saya mengganti nama kolom tabel productmenjadi product( product_nameatau serupa). Itu adalah konvensi penamaan yang lebih baik . Jika tidak, ketika Anda bergabung beberapa tabel dalam query - yang Anda lakukan banyak dalam database relasional - Anda berakhir dengan beberapa kolom bernama "nama" dan harus menggunakan alias kolom memilah kekacauan. Itu tidak membantu. Anti-pola lain yang tersebar luas hanya akan menjadi "id" sebagai nama kolom.
    Saya tidak yakin billakan menjadi apa nama itu. bill_idmungkin akan cukup dalam kasus ini.

  • priceadalah tipe data numeric untuk menyimpan bilangan pecahan persis seperti yang dimasukkan (tipe presisi arbitrer, bukan tipe floating point). Jika Anda berurusan dengan bilangan bulat secara eksklusif, buat itu integer. Misalnya, Anda bisa menghemat harga sebagai Cents .

  • The amount( "Products"dalam pertanyaan Anda) masuk ke tabel penghubung bill_productdan juga bertipe numeric. Sekali lagi, integerjika Anda berurusan dengan bilangan bulat secara eksklusif.

  • Anda melihat kunci asing masuk bill_product? Saya membuat keduanya untuk perubahan cascade: ON UPDATE CASCADE. Jika a product_idatau bill_idharus berubah, perubahan tersebut mengalir ke semua entri yang bergantung bill_productdan tidak ada yang rusak. Itu hanya referensi tanpa arti penting mereka sendiri.
    Saya juga digunakan ON DELETE CASCADEuntuk bill_id: Jika sebuah tagihan dihapus, detailnya ikut mati.
    Tidak demikian halnya untuk produk: Anda tidak ingin menghapus produk yang digunakan dalam tagihan. Postgres akan memberikan kesalahan jika Anda mencoba ini. Anda akan menambahkan kolom lain ke productuntuk menandai baris usang ("hapus lunak") sebagai gantinya.

  • Semua kolom dalam contoh dasar ini berakhir menjadi NOT NULL, jadi NULLnilai tidak diperbolehkan. (Ya, semua kolom - kolom kunci utama ditentukan UNIQUE NOT NULLsecara otomatis.) Itu karena NULLnilai tidak masuk akal di kolom mana pun. Itu membuat hidup seorang pemula lebih mudah. Tetapi Anda tidak akan lolos begitu saja, Anda harus memahami NULLpenanganannya . Kolom tambahan mungkin memungkinkan NULLnilai, fungsi dan gabungan dapat memperkenalkan NULLnilai dalam kueri, dll.

  • Baca bab CREATE TABLEdi manual .

  • Kunci utama diimplementasikan dengan indeks unik pada kolom kunci, yang membuat kueri dengan kondisi pada kolom PK menjadi cepat. Namun, urutan kolom kunci relevan dalam kunci multikolom. Karena PK pada bill_productpada (bill_id, product_id)dalam contoh saya, Anda mungkin ingin menambahkan indeks lain hanya pada product_idatau (product_id, bill_id)jika Anda memiliki pertanyaan mencari diberikan product_iddan tidak ada bill_id. Lihat:

  • Baca bab tentang indeks di manual .

Erwin Brandstetter
sumber
Bagaimana cara membuat indeks untuk tabel pemetaan bill_product? Biasanya itu harus terlihat seperti: CREATE INDEX idx_bill_product_id ON booked_rates(bill_id, product_id). Apakah ini benar?
codyLine
1
@codyLine: Indeks ini dibuat secara otomatis oleh PK.
Erwin Brandstetter
1
@ErwinBrandstetter: Bukankah seharusnya dibuat indeks pada bill_product untuk kolom product_id?
Christian
2
@ ChristianB.Almeida: Itu berguna dalam banyak kasus, ya. Saya menambahkan sedikit tentang pengindeksan.
Erwin Brandstetter
1
@Jakov: Hanya ada 1 baris untuk setiap tagihan di tabel bill. Kami membutuhkan jumlah per item yang ditambahkan bill_product.
Erwin Brandstetter