First Normal Form (1NF) adalah aturan pertama normalisasi: setiap sel di tabel berisi satu nilai saja, bukan daftar. Normalisasi sendiri adalah cara merapikan tabel selangkah demi selangkah (1NF, 2NF, 3NF) supaya gak ada data dobel dan gak ada data yang saling bertentangan. Bayangin rak bumbu: satu toples untuk satu bumbu. Kalau lada, garam dan gula dicampur di satu toples berlabel "lada, garam, gula", kamu gak bisa mengambil garamnya saja, dan mengganti gula berarti mengorek-ngorek isinya. Sel yang berisi daftar persis seperti toples campuran itu.
🧪 Tebakan cepat: tabel
orderspunya kolomitemsberisi'Kopi Susu, Roti Bakar'. Kamu mencari pesanan yang berisi Kopi Susu denganWHERE items LIKE '%Kopi Susu%'. Pesanan #2 berisi'Kopi Susu Aren'. Ikut terambil atau gak? Tebak dulu, jawabannya ada di bagian "Kenapa daftar di satu sel merusak query".
Aturan 1NF
Sebuah tabel memenuhi 1NF kalau:
- Setiap kolom hanya menyimpan satu nilai di tiap baris. Nilai yang gak dipecah lagi ini disebut atomic.
- Gak ada daftar di satu sel: gak ada teks dipisah koma, gak ada array JSON seperti
["a","b"], gak ada objek seperti{"key":"val"}. - Gak ada kolom berulang seperti
phone1,phone2,phone3untuk data yang sejenis. - Setiap baris bisa dibedakan lewat primary key.
Tabel yang melanggar 1NF
Bayangin Kedai Senja belum mengenal order_items, dan seseorang menulis isi pesanan langsung di tabel orders:
| id | customer | items |
|---|---|---|
| 1 | Budi | Kopi Susu, Roti Bakar |
| 2 | Budi | Kopi Susu Aren |
| 3 | Siti | Teh Tarik, Kopi Susu |
Kelihatannya rapi dan hemat tabel. Masalahnya muncul begitu tabel ini ditanya.
Kenapa daftar di satu sel merusak query
Pertanyaannya sederhana: pesanan mana yang berisi Kopi Susu? Jawaban yang benar: #1 dan #3.
SELECT id FROM orders WHERE items = 'Kopi Susu';
-- 0 baris
SELECT id FROM orders WHERE items LIKE '%Kopi Susu%';
-- 1, 2, 3
=mencocokkan isi sel secara utuh. Gak ada sel yang isinya persis'Kopi Susu', jadi hasilnya kosong.LIKE '%Kopi Susu%'mencari potongan teks di mana saja (%artinya "teks apa pun").'Kopi Susu Aren'juga mengandung potongan itu, jadi pesanan #2 ikut terambil, padahal isinya bukan Kopi Susu. Itu jawaban tebakannya.
Mengubah data juga jadi berbahaya. Kedai Senja mengganti nama Kopi Susu jadi Kopi Susu Klasik:
UPDATE orders SET items = REPLACE(items, 'Kopi Susu', 'Kopi Susu Klasik');
Pesanan #2 berubah jadi 'Kopi Susu Klasik Aren': nama produk yang gak pernah ada. Dan pertanyaan seperti "berapa pesanan per produk?" gak bisa dijawab GROUP BY, karena tiap sel berisi gabungan yang beda-beda.
Memperbaiki: satu baris per nilai
Daftarnya dipecah ke tabel sendiri, satu baris untuk satu produk di satu pesanan:
CREATE TABLE orders (
id INTEGER PRIMARY KEY,
customer TEXT NOT NULL
);
CREATE TABLE order_items (
order_id INTEGER NOT NULL REFERENCES orders(id),
product TEXT NOT NULL,
PRIMARY KEY (order_id, product)
);
| order_id | product |
|---|---|
| 1 | Kopi Susu |
| 1 | Roti Bakar |
| 2 | Kopi Susu Aren |
| 3 | Teh Tarik |
| 3 | Kopi Susu |
SELECT order_id FROM order_items WHERE product = 'Kopi Susu';menjawab 1 dan 3, tepat. Gak perluLIKE.UPDATE order_items SET product = 'Kopi Susu Klasik' WHERE product = 'Kopi Susu';mengubah 2 baris, dan Kopi Susu Aren gak tersentuh.PRIMARY KEY (order_id, product)membuat tiap baris unik. Iniorder_itemsyang sama dengan pelajaran Kardinalitas. Kolomproductdi sini masih teks nama; pelajaran Second Normal Form menggantinya denganproduct_idyang menunjuk tabelproducts.
Nomor HP: kasus yang paling sering
Kolom phones berisi '0812-1111-2222, 0857-3333-4444' di tabel customers melanggar 1NF dengan cara yang sama, begitu juga kolom hobi berisi 'membaca, coding, gaming'. Perbaikannya juga sama: tabel terpisah dengan foreign key.
CREATE TABLE phone_numbers (
id INTEGER PRIMARY KEY,
customer_id INTEGER NOT NULL REFERENCES customers(id),
number TEXT NOT NULL
);
Budi punya dua baris di phone_numbers, Siti satu. Tiga "perbaikan" yang sering dicoba, dan kenapa gak menolong:
- Kolom
phone2danphone3. Nomor keempat gak punya tempat, kebanyakan selnya kosong, dan mencari satu nomor harus memeriksa tiga kolom. - Array JSON di kolom yang sama. Isinya tetap daftar di satu sel.
- Dipisah koma, lalu dipecah di aplikasi. Database tetap gak bisa mencari, menghitung atau menjaga nomornya.
📌 Koma gak selalu berarti daftar
Cara cepat mengecek 1NF (dan yang dipakai latihan): sel yang mengandung koma, [ atau { dicurigai berisi daftar. Tapi pertanyaan sebenarnya adalah "apakah aplikasimu perlu mencari, menghitung atau mengubah bagiannya satu per satu?". Alamat 'Jl. Melati 5, Bandung' mengandung koma, tapi kalau selalu dipakai utuh, dia satu nilai. Kalau kamu perlu mencari pelanggan per kota, pecah kotanya ke kolom sendiri.
⚠️ Jebakan yang sering ketemu
phone1,phone2,phone3. Kolom berulang tetap melanggar 1NF, cuma bentuknya melebar ke samping.LIKE '%...%'sebagai tambalan. Dia menemukan potongan teks, bukan nilai, jadi Kopi Susu Aren ikut terhitung sebagai Kopi Susu.- Mengira JSON membuatnya rapi.
["0812-1111-2222","0857-3333-4444"]tetap satu sel berisi dua nilai. - Salah tulis jawaban di latihan. Kembalikan
"melanggar 1NF"atau"memenuhi 1NF"persis, denganNFhuruf besar.
TL;DR: 1NF artinya setiap sel berisi satu nilai (atomic): gak ada teks dipisah koma, array, objek, atau kolom berulang seperti phone1, phone2. Daftar di satu sel merusak query: = gak menemukan apa-apa, LIKE ikut mengambil Kopi Susu Aren, dan REPLACE menciptakan nama produk palsu. Perbaikannya: pindahkan daftar ke tabel sendiri, satu baris per nilai, dengan foreign key ke pemiliknya (order_items untuk isi pesanan, phone_numbers untuk nomor HP).