Third Normal Form (3NF) adalah aturan normalisasi yang ketiga: kolom non-key gak boleh bergantung pada kolom non-key lain. Setiap kolom harus menjelaskan baris itu sendiri, bukan menjelaskan kolom tetangganya. Bayangin papan menu Kedai Senja yang menulis nama kategori di samping setiap menu: "Kopi Susu, kategori Kopi", "Kopi Susu Aren, kategori Kopi". Nama kategori itu sebenarnya milik kategorinya, bukan milik menunya. Mau ganti nama kategori "Kopi" berarti menghapus dan menulis ulang di samping setiap menu kopi.
🧪 Tebakan cepat: tabel
productsmenyimpancategory_iddancategory_name. Kamu mengganti nama kategori 1 di baris Kopi Susu Aren saja. Kalau ditanya "kategori 1 namanya apa?", jawabannya satu nama atau dua nama? Tebak dulu, jawabannya ada di bagian "Satu kategori, dua nama".
Tabel yang kita pakai
Setelah 2NF, Kedai Senja punya tabel products dengan primary key satu kolom, id. Partial dependency sudah gak mungkin. Tapi lihat dua kolom terakhirnya:
| id | name | price | category_id | category_name |
|---|---|---|---|---|
| 1 | Kopi Susu | 18000 | 1 | Kopi |
| 2 | Kopi Susu Aren | 22000 | 1 | Kopi |
| 3 | Teh Tarik | 15000 | 2 | Non-Kopi |
| 4 | Roti Bakar | 20000 | 3 | Makanan |
Rantai dependency: transitive dependency
Ikuti siapa menentukan siapa:
id -> category_id -> category_name
1 -> 1 -> Kopi
idmenentukancategory_id: produk 2 masuk kategori 1. Ini wajar, itu memang fakta tentang produknya.category_idmenentukancategory_name: kategori 1 pasti "Kopi", produk mana pun.- Jadi
category_namebergantung padaidcuma lewatcategory_id, yang bukan key.
Ketergantungan berantai seperti itu disebut transitive dependency: kolom non-key bergantung pada kolom non-key lain, dan baru lewat kolom itu sampai ke primary key. Syarat 3NF ada dua: tabelnya sudah memenuhi 2NF, dan gak ada transitive dependency. products di atas melanggar 3NF karena category_name.
Masalah nomor HP Budi di pelajaran pertama juga sama bentuknya: di tabel orders yang serba-ada, customer_phone bergantung pada pelanggannya, bukan pada id pesanan.
Satu kategori, dua nama
Pemilik kedai mengganti nama kategori dari halaman Kopi Susu Aren:
UPDATE products SET category_name = 'Kopi Nusantara' WHERE id = 2;
SELECT DISTINCT category_id, category_name
FROM products
ORDER BY category_id;
| category_id | category_name |
|---|---|
| 1 | Kopi |
| 1 | Kopi Nusantara |
| 2 | Non-Kopi |
| 3 | Makanan |
Kategori 1 sekarang punya dua nama, dan database gak protes. Itu jawaban tebakannya. Ini update anomaly yang sama seperti di 2NF, cuma penyebabnya beda. Saudaranya juga ikut:
- Delete anomaly: Roti Bakar dihapus dari menu (
DELETE FROM products WHERE id = 4;), dan kategori Makanan ikut lenyap. - Insert anomaly: kategori baru "Camilan" gak bisa dicatat sebelum ada menu di dalamnya.
Update yang "benar" (WHERE category_id = 1) memang mengubah semua baris, tapi itu 2 baris untuk satu fakta. Dengan 40 menu kopi, 40 baris.
Pindahkan ke tabel sendiri
Kolom yang bergantung pada category_id pindah ke tabel yang primary key-nya category_id itu:
CREATE TABLE categories (
id INTEGER PRIMARY KEY,
name TEXT NOT NULL
);
CREATE TABLE products (
id INTEGER PRIMARY KEY,
name TEXT NOT NULL,
price INTEGER NOT NULL,
category_id INTEGER REFERENCES categories(id) -- foreign key
);
INSERT INTO categories VALUES (1, 'Kopi'), (2, 'Non-Kopi'), (3, 'Makanan');
INSERT INTO products VALUES (1, 'Kopi Susu', 18000, 1), (2, 'Kopi Susu Aren', 22000, 1),
(3, 'Teh Tarik', 15000, 2), (4, 'Roti Bakar', 20000, 3);
categoriesmenyimpan nama tiap kategori sekali.productstetap menyimpancategory_id. Kolom penunjuk ini disebut foreign key: dia gak melanggar 3NF, karena "produk ini masuk kategori mana" memang fakta tentang produknya.category_namehilang dariproducts. Kalau butuh, ambil lewatJOIN.
UPDATE categories SET name = 'Kopi Nusantara' WHERE id = 1;
SELECT p.name, c.name AS category_name
FROM products p
JOIN categories c ON c.id = p.category_id
WHERE p.category_id = 1;
| name | category_name |
|---|---|
| Kopi Susu | Kopi Nusantara |
| Kopi Susu Aren | Kopi Nusantara |
Satu baris diubah, semua menu kopi ikut. Roti Bakar dihapus pun, Makanan tetap ada di categories, dan Camilan bisa ditambah kapan saja.
Pola yang sama berlaku di mana pun. Tabel employees (id, name, department_id, department_name): department_name bergantung pada department_id, bukan pada id. Solusinya tabel departments terpisah, dan employees cukup menyimpan department_id sebagai foreign key.
Ringkasan 1NF, 2NF, 3NF
| Masalahnya | Melanggar | Perbaikannya |
|---|---|---|
multi-value: satu sel berisi banyak nilai, atau array dalam kolom ("0812, 0813", ["a","b"]) |
1NF | satu nilai per sel, sisanya jadi baris di tabel lain |
| partial dependency: bergantung pada sebagian composite key | 2NF | pindah ke tabel milik bagian key itu |
| transitive dependency: non-key bergantung pada non-key | 3NF | pindah ke tabel milik kolom penentunya |
| gak ada ketiganya | sudah normal |
Normal form yang lebih tinggi (BCNF, 4NF, 5NF) ada, tapi kebanyakan aplikasi production cukup sampai 3NF.
⚠️ Jebakan yang sering ketemu
- Menghapus kolom tanpa membuat tabelnya. Membuang
category_namesaja bikin nama kategori hilang selamanya. Pindahkan, jangan buang. - Menganggap foreign key sebagai pelanggaran.
category_iddiproductsjustru yang benar. Yang melanggar adalah kolom yang menjelaskan foreign key itu (category_name). - Salinan yang sebenarnya fakta sendiri. Alamat kirim di sebuah pesanan adalah fakta pesanan itu, bukan salinan alamat pelanggan: kalau pelanggan pindah, pesanan lama tetap dikirim ke alamat lama.
- String di latihan. Fungsinya mengembalikan teks persis, misalnya
"melanggar 3NF"(huruf kecil, NF besar), dan"sudah normal"untuk masalah lain.
TL;DR: 3NF = sudah 2NF, dan gak ada transitive dependency: kolom non-key gak boleh bergantung pada kolom non-key lain. Di products (id, name, price, category_id, category_name), rantainya id -> category_id -> category_name, jadi mengganti nama kategori di satu baris bikin satu kategori punya dua nama. Perbaikannya: tabel categories sendiri, dan products menyimpan category_id sebagai foreign key (sama seperti employees dengan departments). Urutannya: multi-value atau array dalam kolom melanggar 1NF, partial dependency melanggar 2NF, transitive dependency melanggar 3NF.