Skema adalah denah database: ada tabel apa saja, kolomnya apa, dan bagaimana tabel-tabel itu terhubung. Desain skema yang baik menyimpan setiap fakta sekali saja. Mirip kontak di HP: nomor temanmu disimpan sekali di daftar kontak, dan grup WhatsApp cuma menunjuk ke kontak itu. Kalau temanmu ganti nomor, kamu ubah sekali, dan semua grup ikut benar.
🧪 Tebakan cepat: toko punya 3 produk berkategori Elektronik, dan nama kategorinya mau diganti jadi Gadget. Berapa baris yang harus diubah kalau semua data ada di satu tabel, dan berapa kalau kategori punya tabel sendiri? Tebak dulu, jawabannya ada di bagian "Masalah satu tabel untuk semua".
Masalah satu tabel untuk semua
Ini tabel products_flat: setiap produk menyimpan nama kategorinya sendiri.
| id | name | price | category |
|---|---|---|---|
| 1 | Laptop | 12000000 | Elektronik |
| 2 | Mouse | 150000 | Elektronik |
| 3 | Monitor | 3500000 | Elektronik |
| 4 | Pulpen | 5000 | Alat Tulis |
"Elektronik" tertulis tiga kali. Fakta yang sama disimpan berulang seperti ini disebut redundansi data, dan dia bikin tiga masalah yang bisa kamu coba di Lab:
| Yang mau dilakukan | Di satu tabel |
|---|---|
| Ganti nama Elektronik jadi Gadget | UPDATE harus mengubah 3 baris. Satu terlewat, datanya saling bertentangan. |
| Hapus produk Pulpen | Kategori Alat Tulis ikut lenyap, karena cuma tercatat di baris Pulpen. |
| Tambah kategori Buku yang belum punya produk | Gak bisa: NOT NULL constraint failed: products_flat.name, karena setiap baris harus berupa produk. |
Dua tabel: categories dan products
Solusinya: kategori dapat tabel sendiri, dan produk cuma menyimpan nomornya, persis seperti pelanggan dan pesanan di pelajaran Relasi Antar Tabel.
CREATE TABLE categories (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL
);
CREATE TABLE products (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
price REAL NOT NULL,
category_id INTEGER,
FOREIGN KEY (category_id) REFERENCES categories(id)
);
categoriesdibuat duluan, karena dia yang ditunjuk.NOT NULLdinamedanprice: produk wajib punya nama dan harga.INSERTtanpa harga ditolak denganNOT NULL constraint failed: products.price.category_id INTEGERtanpaNOT NULL: produk boleh belum berkategori.FOREIGN KEY (category_id) REFERENCES categories(id):category_idmenunjukcategories.id.
Isinya: categories berisi (1, Elektronik) dan (2, Alat Tulis). Laptop, Mouse dan Monitor punya category_id 1, Pulpen 2. Sekarang ganti nama cukup satu baris:
UPDATE categories SET name = 'Gadget' WHERE id = 1;
1 baris berubah, dan ketiga produk ikut jadi Gadget waktu di-JOIN. Satu tabel: 3 baris, dua tabel: 1 baris. Itu jawaban tebakannya. Menghapus Pulpen juga gak menghapus Alat Tulis, dan Buku bisa masuk ke categories tanpa produk. Memecah tabel supaya setiap fakta tersimpan sekali seperti ini disebut normalisasi.
Langkah mendesain skema
- Daftar benda yang disimpan (entitas): pelanggan, produk, kategori, pesanan.
- Satu tabel per benda, nama jamak (
customers,products), denganidsebagai primary key. - Tentukan relasinya. One-to-many: foreign key di sisi banyak (
category_iddiproducts). Many-to-many: tabel pivot (bagian berikut). - Tandai kolom wajib dengan
NOT NULL. - Foreign key diberi nama tabel bentuk tunggal +
_id:category_id,customer_id.
Many-to-many: tabel pivot
Di pelajaran JOIN, orders punya kolom product berisi teks, jadi satu pesanan cuma bisa satu barang. Padahal satu pesanan bisa berisi banyak produk, dan satu produk dibeli di banyak pesanan: many-to-many. Foreign key gak bisa ditaruh di salah satu sisi, jadi dibuat tabel perantara, disebut tabel pivot (atau junction table):
CREATE TABLE order_items (
order_id INTEGER NOT NULL REFERENCES orders(id),
product_id INTEGER NOT NULL REFERENCES products(id),
qty INTEGER NOT NULL,
PRIMARY KEY (order_id, product_id)
);
Satu baris = satu produk di satu pesanan. PRIMARY KEY (order_id, product_id) adalah primary key gabungan: pasangan yang sama gak boleh muncul dua kali.
📌 Bentuk normal: 1NF, 2NF, 3NF
Aturan normalisasi biasanya ditulis bertingkat:
- 1NF: satu sel, satu nilai. Kolom
hobiberisi "coding, gaming" melanggar; pindahkan ke tabel sendiri, satu hobi per baris. - 2NF: sudah 1NF, dan di tabel dengan primary key gabungan, setiap kolom bergantung pada seluruh key. Di
order_items, nama produk cuma bergantung padaproduct_id, jadi tempatnya diproducts, bukan diorder_items. - 3NF: sudah 2NF, dan kolom biasa gak bergantung pada kolom biasa lain.
provincedi tabelusersbergantung padacity, jadi kota dan provinsinya masuk tabelcities.
Gak semua yang mirip itu duplikasi. Harga di order_items (harga saat dibeli) adalah fakta lain dari harga di products (harga hari ini), jadi boleh disimpan dua-duanya.
⚠️ Jebakan yang sering ketemu
- Menyimpan nama, bukan id. Nama kota atau kategori di setiap baris pesanan bikin redundansi: ganti nama berarti ganti banyak baris, dan satu salah ketik bikin data bertentangan.
- Lupa
NOT NULLdi kolom wajib. Latihan mengeceknamedanpricediproducts, dannamedicategories. - Cuma bikin kolom
category_idtanpaFOREIGN KEY. Angkanya tersimpan, tapi gak terhubung kecategories. Latihan mengecek hubungan ini. - Membuat tabel anak duluan. MySQL menolak foreign key ke tabel yang belum ada. SQLite membolehkannya, tapi biasakan membuat tabel induk duluan.
TL;DR: Desain skema yang baik menyimpan setiap fakta sekali. Nama kategori yang diulang di setiap produk (redundansi) bikin ganti nama harus mengubah banyak baris (3 baris, bukan 1), menghapus produk terakhir menghapus kategorinya, dan kategori baru gak bisa masuk tanpa produk. Normalisasi memecahnya jadi categories dan products yang dihubungkan category_id + FOREIGN KEY, dengan NOT NULL di kolom wajib. Relasi many-to-many butuh tabel pivot dengan dua foreign key. 1NF: satu sel satu nilai, 2NF: bergantung pada seluruh key, 3NF: gak bergantung pada kolom biasa lain.