Kolom yang mengizinkan NULL memberi keleluasaan, tetapi keleluasaan itu punya harga: setiap query yang memakai kolom tersebut harus waspada terhadap nilai yang tidak ada. NULL tidak sama dengan nol, tidak sama dengan string kosong, dan bahkan tidak bisa dibandingkan dengan apapun menggunakan =. Constraint NOT NULL memotong masalah itu dari sumbernya — kolom wajib diisi sebelum baris diterima. Pada chapter ini kita akan menguji penolakan NULL secara langsung, menyaksikan galat saat kolom wajib terlewat dari INSERT, lalu membedah perbedaan NULL dan string kosong '' yang sering dicampuradukkan pemula.
Menandai Kolom Wajib #
Desain tabel yang sehat memilah kolom menjadi dua kubu: kolom wajib yang bisnis tidak berjalan tanpanya, dan kolom opsional yang memang sah kosong. Tabel pelanggan berikut memakai pemilahan itu:
CREATE DATABASE belajar_wajib;
USE belajar_wajib;Output yang Dihasilkan:
Query OK, 1 row affected (0.00 sec)
Database changedCREATE TABLE pelanggan (
id INT PRIMARY KEY,
nama VARCHAR(100) NOT NULL,
email VARCHAR(100) NOT NULL,
telepon VARCHAR(20),
kota VARCHAR(50)
);Output yang Dihasilkan:
Query OK, 0 rows affected (0.00 sec)Kolom nama dan email wajib terisi — pelanggan tanpa nama atau kontak email tidak bisa dilayani. Kolom telepon dan kota dibiarkan opsional: pelanggan baru mungkin belum menyerahkan nomor teleponnya, dan kota tinggalnya bisa saja belum dikonfirmasi. Keputusan wajib atau opsional selalu kembali ke kebutuhan bisnis, bukan kebiasaan menulis NOT NULL di semua kolom.
INSERT Bernilai NULL Ditolak #
Ujian pertama: statement yang sengaja mengirim NULL ke kolom wajib nama:
-- @error
INSERT INTO pelanggan (id, nama, email, telepon, kota) VALUES (1, NULL, '[email protected]', '081211112222', 'Jakarta');Output yang Dihasilkan:
Query OK, 0 rows affected (0.00 sec)
ERROR 1048 (23000): Column 'nama' cannot be null
ByeServer menolaknya dengan galat ERROR 1048 (23000): Column 'nama' cannot be null — kolom yang jadi masalah disebut eksplisit, sehingga perbaikannya langsung terarah. Statement gagal seluruhnya, artinya email dan telepon pada baris itu juga tidak tersimpan; tabel tetap kosong seperti sebelum statement dijalankan.
Kolom Wajib yang Terlewat #
Bentuk pelanggaran kedua lebih sering muncul di aplikasi nyata: kolom wajib bukan dikirim NULL, melainkan tidak disebut sama sekali dalam daftar kolom INSERT. Statement berikut hanya mengirim id dan telepon:
-- @error
INSERT INTO pelanggan (id, telepon) VALUES (2, '0813');Output yang Dihasilkan:
Query OK, 0 rows affected (0.00 sec)
ERROR 1364 (HY000): Field 'nama' doesn't have a default value
ByeGalat yang muncul adalah ERROR 1364 (HY000): Field 'nama' doesn't have a default value — server mencari nilai pengganti untuk kolom nama yang tidak dikirim, tidak menemukan nilai bawaan, lalu menolak barisnya. Perilaku ini adalah efek gabungan NOT NULL dengan mode ketat STRICT_TRANS_TABLES yang aktif secara bawaan di MySQL 8.4; tanpa mode ketat, server justru diam-diam mengisi nilai kosong tiruan. Nilai bawaan itu sendiri adalah topik chapter DEFAULT Value — di sini cukup dicatat bahwa kolom wajib tanpa nilai bawaan wajib disebut eksplisit.
NULL Bukan String Kosong #
Sekarang bagian yang paling penting: NULL dan '' adalah dua kondisi berbeda yang sering disamakan. Kita isi tabel dengan tiga pelanggan yang status teleponnya berbeda-beda:
INSERT INTO pelanggan (id, nama, email, telepon, kota) VALUES
(1, 'Budi', '[email protected]', '081211112222', 'Jakarta'),
(2, 'Siti', '[email protected]', '', 'Bandung'),
(3, 'Ani', '[email protected]', NULL, 'Jakarta');
SELECT id, nama, telepon FROM pelanggan ORDER BY id;Output yang Dihasilkan:
Query OK, 3 rows affected (0.00 sec)
Records: 3 Duplicates: 0 Warnings: 0
+----+------+--------------+
| id | nama | telepon |
+----+------+--------------+
| 1 | Budi | 081211112222 |
| 2 | Siti | |
| 3 | Ani | NULL |
+----+------+--------------+
3 rows in set (0.00 sec)Pada hasil SELECT, telepon Siti tampil sebagai sel kosong sedangkan telepon Ani tampil sebagai NULL — di layar keduanya mirip, tetapi maknanya jauh berbeda. Query berikut membedah perbedaannya dengan dua uji terpisah:
SELECT
id,
telepon,
telepon IS NULL AS telepon_null,
telepon = '' AS telepon_kosong
FROM pelanggan
ORDER BY id;Output yang Dihasilkan:
+----+--------------+--------------+----------------+
| id | telepon | telepon_null | telepon_kosong |
+----+--------------+--------------+----------------+
| 1 | 081211112222 | 0 | 0 |
| 2 | | 0 | 1 |
| 3 | NULL | 1 | NULL |
+----+--------------+--------------+----------------+
3 rows in set (0.00 sec)Mari kita baca hasilnya baris demi baris:
- Budi:
telepon_null = 0dantelepon_kosong = 0— nilainya ada dan bukan string kosong. - Siti:
telepon_null = 0dantelepon_kosong = 1— nilainya ada, diketahui, dan memang string kosong.IS NULLmengembalikan0karena nilai ini bukanNULL. - Ani:
telepon_null = 1dan kolomtelepon_kosongmenampilkanNULL— perhatikan! BandingkanNULLdengan string kosong lewat=tidak menghasilkan1maupun0, melainkanNULL, karena jawaban perbandingan atas nilai yang tidak diketahui juga tidak diketahui.
Poin ketiga itulah sumber galat klasik pada query penyaringan. Statement berikut mencari pelanggan tanpa telepon dengan cara yang salah:
SELECT id, nama FROM pelanggan WHERE telepon = NULL;Output yang Dihasilkan:
Empty set (0.00 sec)Hasilnya kosong — Empty set — padahal Ani jelas tidak punya telepon. Perbandingan telepon = NULL menghasilkan NULL (bukan benar), sehingga tidak ada baris yang lolos. Cara yang benar memakai operator khusus IS NULL:
SELECT id, nama FROM pelanggan WHERE telepon IS NULL;Output yang Dihasilkan:
+----+------+
| id | nama |
+----+------+
| 3 | Ani |
+----+------+
1 row in set (0.00 sec)Baris Ani kini ditemukan. Aturan pendeknya: NULL hanya bisa diuji dengan IS NULL dan IS NOT NULL, tidak pernah dengan = atau <>.
COUNT Bintang vs COUNT Kolom #
Perbedaan NULL dan nilai terisi juga terasa di fungsi agregat. COUNT(*) menghitung baris, sedangkan COUNT(kolom) hanya menghitung baris yang kolomnya tidak NULL:
SELECT COUNT(*) AS total_pelanggan, COUNT(telepon) AS telepon_terisi
FROM pelanggan;Output yang Dihasilkan:
+-----------------+----------------+
| total_pelanggan | telepon_terisi |
+-----------------+----------------+
| 3 | 2 |
+-----------------+----------------+
1 row in set (0.00 sec)Tabel berisi tiga pelanggan, tetapi hanya dua yang tercatat punya telepon — nilai NULL pada baris Ani tidak dihitung oleh COUNT(telepon). Perilaku ini bukan keanehan melainkan konsekuensi logis: agregat seperti COUNT, SUM, dan AVG mengabaikan NULL karena nilai yang tidak diketahui tidak bisa ikut dihitung. Saat memilih bentuk COUNT, kita sebenarnya sedang memilih pertanyaan: “ada berapa baris?” memakai COUNT(*), “ada berapa baris yang nilainya terisi?” memakai COUNT(kolom).
Referensi #
- CREATE TABLE Statement — MySQL 8.4 Reference Manual
- Constraints — MySQL 8.4 Reference Manual
- Data Type Default Values — MySQL 8.4 Reference Manual
Kolom wajib tabel pelanggan kini benar-benar terjaga, dan kita punya pemahaman tegas tentang makna NULL. Pertanyaan berikutnya: darimana nilai bawaan kolom berasal ketika INSERT tidak mengirimnya? Chapter DEFAULT Value menjawabnya dengan nilai literal, klausa DEFAULT, sampai CURRENT_TIMESTAMP. Sebelum itu, kita bereskan database latihan:
DROP DATABASE IF EXISTS belajar_wajib;Output yang Dihasilkan:
Query OK, 1 row affected (0.00 sec)Satu tabel pelanggan beserta data latihannya ikut terbuang.