Sepanjang section ini kita memecah data tanpa benar-benar menamai ilmunya: identitas pelanggan dipisah dari pesanan, atribut relasi dipindah ke tabel perantara. Ilmu di balik semua pemecahan itu adalah normalisasi — rangkaian aturan yang menyusun struktur tabel supaya setiap fakta tersimpan di tepat satu tempat. Kenapa hal itu penting tidak bisa dijelaskan dengan teori saja; kita perlu melihat tabel yang belum normal mengeluarkan gejala nyata: data baru tak bisa masuk, satu perubahan menuntut banyak baris diubah, dan satu penghapusan membawa kabar data lain. Chapter ini membangun tabel buruk lalu memicu ketiga anomali itu secara nyata di database latihan belajar_rdb, sebelum menutup dengan peta jalan bentuk normal yang akan kita bedah satu per satu.
Tujuan Normalisasi: Satu Fakta Satu Tempat #
Prinsip dasar normalisasi bisa diringkas satu kalimat: setiap fakta tentang data disimpan tepat satu kali. Kota asal Siti adalah satu fakta — tabel yang normal menyimpannya di satu baris tabel pelanggan, bukan diulang di setiap baris pesanan. Prinsip sederhana ini punya efek berjenjang: tidak ada salinan berarti tidak ada salinan yang bisa saling bertentangan, tidak ada pembaruan yang harus diulang berkali-kali, dan tidak ada fakta yang ikut lenyap hanya karena data lain dihapus. Struktur yang menjamin semua itu disebut normal, dan penyimpangannya menampakkan diri sebagai tiga anomali klasik yang akan kita pancing satu per satu.
Tabel uji kita meniru desain pesanan yang mencampur data pelanggan ke dalam baris pesanan — bentuk yang sering muncul saat aplikasi dibuat terburu-buru:
DROP DATABASE IF EXISTS belajar_rdb;
CREATE DATABASE belajar_rdb;
USE belajar_rdb;
CREATE TABLE pesanan_kotor (
id INT AUTO_INCREMENT PRIMARY KEY,
nama_pelanggan VARCHAR(50) NOT NULL,
kota VARCHAR(30) NOT NULL,
tanggal DATE NOT NULL,
total DECIMAL(12,2) NOT NULL
);
INSERT INTO pesanan_kotor (nama_pelanggan, kota, tanggal, total) VALUES
('Budi Santoso', 'Jakarta', '2026-01-05', 170000.00),
('Siti Rahayu', 'Bandung', '2026-01-07', 50000.00),
('Budi Santoso', 'Jakarta', '2026-01-10', 62000.00),
('Budi Santoso', 'Jakarta', '2026-01-12', 15000.00),
('Siti Rahayu', 'Bandung', '2026-01-18', 85000.00),
('Andi Wijaya', 'Surabaya', '2026-01-15', 137000.00);Output yang Dihasilkan:
Query OK, 0 rows affected, 1 warning (0.00 sec)
Query OK, 1 row affected (0.00 sec)
Database changed
Query OK, 0 rows affected (0.00 sec)
Query OK, 6 rows affected (0.00 sec)
Records: 6 Duplicates: 0 Warnings: 0Anomali Insert: Data Baru Tak Bisa Masuk #
Calon pelanggan baru bernama Tono ingin terdaftar lebih dulu supaya kasir mengenalinya, belum memesan apa pun. Tabel di atas tidak sanggup memenuhi permintaan itu — kolom tanggal dan total wajib terisi (NOT NULL) padahal Tono memang belum punya transaksi:
-- @error
INSERT INTO pesanan_kotor (nama_pelanggan, kota) VALUES ('Tono Hartono', 'Medan');Output yang Dihasilkan:
Query OK, 0 rows affected (0.00 sec)
ERROR 1364 (HY000): Field 'tanggal' doesn't have a default value
ByeServer menolak baris tersebut: kolom tanggal tidak punya nilai. Inilah anomali insert — fakta tentang pelanggan (nama, kota) tidak bisa disimpan tanpa sekaligus memfabrikasi fakta pesanan. Desain yang normal memisahkan keduanya: tabel pelanggan menerima Tono kapan pun, dan tabel pesanan menyusul saat transaksi sungguhan terjadi.
Anomali Update: Satu Perubahan, Banyak Baris #
Siti pindah domisili dari Bandung ke Yogyakarta. Fakta kotanya berubah satu kali di dunia nyata, tetapi pada tabel campuran ini fakta itu tersimpan dua kali — di setiap baris pesanan Siti:
UPDATE pesanan_kotor SET kota = 'Yogyakarta' WHERE nama_pelanggan = 'Siti Rahayu';Output yang Dihasilkan:
Query OK, 2 rows affected (0.00 sec)
Rows matched: 2 Changed: 2 Warnings: 0SELECT id, nama_pelanggan, kota, tanggal, total
FROM pesanan_kotor
WHERE nama_pelanggan = 'Siti Rahayu'
ORDER BY id;Output yang Dihasilkan:
+----+----------------+------------+------------+----------+
| id | nama_pelanggan | kota | tanggal | total |
+----+----------------+------------+------------+----------+
| 2 | Siti Rahayu | Yogyakarta | 2026-01-07 | 50000.00 |
| 5 | Siti Rahayu | Yogyakarta | 2026-01-18 | 85000.00 |
+----+----------------+------------+------------+----------+
2 rows in set (0.00 sec)Perintah UPDATE tadi menyentuh dua baris, dan hasilnya memang konsisten. Bahayanya ada pada skenario yang tidak konsisten: dengan lima ratus pesanan Siti tersebar di tabel, satu baris yang terlewat dari WHERE — atau satu proses yang gagal di tengah jalan — meninggalkan dua kota yang saling bertentangan untuk orang yang sama. Inilah anomali update: perubahan satu fakta menuntut pembaruan banyak baris, dan setiap baris tambahan adalah satu peluang inkonsistensi baru.
Anomali Delete: Fakta Ikut Lenyap #
Andi meminta satu-satunya pesanannya dibatalkan. Kita hapus baris pesanan tersebut:
DELETE FROM pesanan_kotor WHERE nama_pelanggan = 'Andi Wijaya';Output yang Dihasilkan:
Query OK, 1 row affected (0.00 sec)SELECT id, nama_pelanggan, kota FROM pesanan_kotor ORDER BY id;Output yang Dihasilkan:
+----+----------------+------------+
| id | nama_pelanggan | kota |
+----+----------------+------------+
| 1 | Budi Santoso | Jakarta |
| 2 | Siti Rahayu | Yogyakarta |
| 3 | Budi Santoso | Jakarta |
| 4 | Budi Santoso | Jakarta |
| 5 | Siti Rahayu | Yogyakarta |
+----+----------------+------------+
5 rows in set (0.00 sec)Pesanannya memang hilang — tetapi jejak keberadaan Andi sebagai pelanggan ikut lenyap: nama dan kotanya hanya hidup di dalam baris pesanan. Besok Andi datang kembali dan kasir tidak menemukan pelanggan bernama Andi Wijaya. Inilah anomali delete: penghapusan satu fakta (pesanan) menghapus fakta lain yang seharusnya berdiri sendiri (pelanggan). Tiga anomali di atas bukan tiga penyakit berbeda; semuanya gejala dari satu akar — fakta pelanggan dan fakta pesanan hidup bercampur di satu tabel.
Functional Dependency Tanpa Matematika Berat #
Bentuk normal didefinisikan lewat istilah functional dependency (ketergantungan fungsional), dan versi ramahnya begini: kolom B bergantung pada kolom A bila setiap nilai A selalu berpasangan dengan tepat satu nilai B. Pada tabel uji kita: id menentukan semua kolom lain (satu nomor pesanan menunjuk satu baris penuh), dan nama_pelanggan menentukan kota (setiap nama selalu berpasangan kota yang sama). Ketergantungan kedua inilah yang berbau: kota adalah fakta pelanggan, bukan fakta pesanan, tetapi ia bergantung pada kolom non-kunci nama_pelanggan di tengah tabel pesanan. Ketiga bentuk normal yang akan kita bahas pada dasarnya adalah tiga aturan pembersihan ketergantungan: 1NF melarang satu kolom memuat banyak nilai, 2NF melarang kolom bergantung pada sebagian kunci komposit, dan 3NF melarang kolom non-kunci bergantung pada kolom non-kunci lain.
Trade-off Normalisasi #
Normalisasi bukan gratis. Semakin normal strukturnya, semakin banyak tabel kecil yang harus dirangkai JOIN untuk menyusun ulang informasi utuh; laporan “pesanan dengan nama dan kota pelanggan” pada desain normal membutuhkan dua tabel, dan aplikasi dengan laporan kompleks bisa membutuhkan lima atau lebih. Penyimpanan juga berpindah bentuk: baris pendek berulang diganti baris sedikit dengan kolom kunci. Praktik industri menjawabnya dengan urutan kerja: rancang normal dulu karena konsistensi data adalah fondasi, lalu ukur performa query nyata, dan baru pertimbangkan penyelewengan terukur — itulah topik denormalisasi di akhir section ini. Normalisasi yang diikuti sejak awal jauh lebih murah daripada perbaikan struktur setelah tabel berisi jutaan baris.
Peta Jalan: 1NF, 2NF, 3NF #
Ketiga bentuk normal yang paling dipakai membentuk tangga berjenjang — masing-masing menuntut bentuk di bawahnya lalu menambah satu aturan:
| Bentuk normal | Aturan tambahan | Gejala yang hilang |
|---|---|---|
| 1NF (First Normal Form) | setiap kolom memuat nilai atomik, tanpa kelompok berulang | kolom berisi daftar terpisah koma seperti "Obeng, Bor" |
| 2NF (Second Normal Form) | 1NF + tidak ada ketergantungan parsial pada kunci komposit | kolom nama_mahasiswa menempel pada tabel junction padahal hanya bergantung sebagian kunci |
| 3NF (Third Normal Form) | 2NF + tidak ada ketergantungan transitif antar kolom non-kunci | kolom kota tersimpan berulang karena mengikuti kode_pos |
Tabel pesanan_kotor kita melanggar ketiganya sekaligus dalam bentuk berbeda-beda, dan itulah sebabnya pembahasan bentuk normal harus satu per satu. Setiap anak tangga punya contoh pelanggarannya sendiri, cara perbaikannya sendiri, dan uji keberhasilannya sendiri — urutan itu yang kita susuri pada tiga chapter berikutnya.
Membersihkan Database Latihan #
DROP DATABASE belajar_rdb;Output yang Dihasilkan:
Query OK, 1 row affected (0.00 sec)Referensi #
- CREATE TABLE Statement — MySQL 8.4 Reference Manual
- UPDATE Statement — MySQL 8.4 Reference Manual
- DELETE Statement — MySQL 8.4 Reference Manual
Tiga anomali telah kita pancing dengan tangan sendiri: data pelanggan baru tak bisa masuk tanpa pesanan fiktif, satu pindahan kota menuntut banyak baris diubah, dan pembatalan pesanan terakhir ikut menghapus keberadaan pelanggan. Normalisasi menawarkan kerangka pembenahan yang berjenjang, dan langkah pertamanya menargetkan pelanggaran paling kasat mata: kolom yang memuat banyak nilai dalam satu sel. Kita bedah FIRST NORMAL FORM di chapter berikutnya.