Kolom telepon pelanggan tidak selalu terisi: sebagian pelanggan memang belum meninggalkan nomor kontak. SQL punya penanda khusus untuk keadaan seperti itu — NULL, yang berarti “tidak ada nilai”. Masalahnya, NULL tidak berperilaku seperti nilai biasa: mencarinya dengan telepon = NULL justru mengembalikan hasil kosong, bukan pelanggan yang kita cari. Pada chapter ini kita akan menyelami perilaku NULL, memakai IS NULL dan IS NOT NULL sebagai alat penyaring yang benar, menguji fungsi ISNULL(), lalu melihat sekilas bagaimana COUNT memperlakukan baris bertanda NULL.
Menyiapkan Tabel Latihan #
Kita pakai pola data yang sama seperti chapter-chapter sebelumnya pada Section Filtering dan Sorting. Tabel pelanggan sengaja memiliki kolom telepon yang boleh NULL, dan tabel pesanan memiliki kolom catatan pengiriman yang juga terisi sebagian:
DROP DATABASE IF EXISTS belajar_filter;
CREATE DATABASE belajar_filter;
USE belajar_filter;Output yang Dihasilkan:
Query OK, 0 rows affected, 1 warning (0.00 sec)
Query OK, 1 row affected (0.00 sec)
Database changedCREATE TABLE pelanggan (
id INT AUTO_INCREMENT PRIMARY KEY,
nama VARCHAR(100) NOT NULL,
kota VARCHAR(50) NOT NULL,
telepon VARCHAR(20) NULL
);
INSERT INTO pelanggan (nama, kota, telepon) VALUES
('Budi Santoso', 'Bandung', '0812-5000-1001'),
('Siti Rahayu', 'Jakarta', '0813-5000-1002'),
('Andi Wijaya', 'Surabaya', NULL),
('Dewi Lestari', 'Bandung', '0815-5000-1004'),
('Rian Hidayat', 'Yogyakarta', NULL),
('Maya Putri', 'Medan', '0817-5000-1006'),
('Eko Prasetyo', 'Semarang', NULL),
('Fitri Handayani', 'Bandung', '0819-5000-1008');Output yang Dihasilkan:
Query OK, 0 rows affected (0.00 sec)
Query OK, 8 rows affected (0.00 sec)
Records: 8 Duplicates: 0 Warnings: 0CREATE TABLE pesanan (
id INT AUTO_INCREMENT PRIMARY KEY,
nama_pelanggan VARCHAR(100) NOT NULL,
nama_produk VARCHAR(100) NOT NULL,
jumlah INT NOT NULL,
total DECIMAL(10,2) NOT NULL,
tanggal DATE NOT NULL,
catatan VARCHAR(100) NULL
);
INSERT INTO pesanan (nama_pelanggan, nama_produk, jumlah, total, tanggal, catatan) VALUES
('Budi Santoso', 'Pensil 2B', 10, 30000.00, '2026-01-05', 'Kirim sebelum jam 5 sore'),
('Siti Rahayu', 'Kertas HVS A4', 2, 55000.00, '2026-01-18', NULL),
('Dewi Lestari', 'Spidol Permanen', 3, 30000.00, '2026-02-01', 'Bungkus sebagai kado'),
('Budi Santoso', 'Kertas Origami', 5, 60000.00, '2026-02-14', NULL),
('Maya Putri', 'Buku Tulis Sidu 38', 20, 90000.00, '2026-02-28', NULL),
('Eko Prasetyo', 'Pulpen Gel Hitam', 4, 20000.00, '2026-03-05', 'Titipkan ke resepsionis'),
('Fitri Handayani', 'Selotip Bening', 6, 48000.00, '2026-03-12', NULL),
('Budi Santoso', 'Penghapus Putih', 12, 30000.00, '2026-03-25', 'Bungkus sebagai kado'),
('Budi Santoso', 'Pulpen Gel Hitam', 2, 10000.00, '2026-03-25', NULL);Output yang Dihasilkan:
Query OK, 0 rows affected (0.00 sec)
Query OK, 9 rows affected (0.00 sec)
Records: 9 Duplicates: 0 Warnings: 0Definisi telepon VARCHAR(20) NULL dan catatan VARCHAR(100) NULL secara eksplisit mengizinkan nilai kosong-data; sebaliknya kolom seperti nama dideklarasikan NOT NULL sehingga selalu wajib terisi. Mari kita lihat dulu kondisi data yang akan kita hadapi sepanjang chapter:
SELECT * FROM pelanggan ORDER BY id;Output yang Dihasilkan:
+----+-----------------+------------+----------------+
| id | nama | kota | telepon |
+----+-----------------+------------+----------------+
| 1 | Budi Santoso | Bandung | 0812-5000-1001 |
| 2 | Siti Rahayu | Jakarta | 0813-5000-1002 |
| 3 | Andi Wijaya | Surabaya | NULL |
| 4 | Dewi Lestari | Bandung | 0815-5000-1004 |
| 5 | Rian Hidayat | Yogyakarta | NULL |
| 6 | Maya Putri | Medan | 0817-5000-1006 |
| 7 | Eko Prasetyo | Semarang | NULL |
| 8 | Fitri Handayani | Bandung | 0819-5000-1008 |
+----+-----------------+------------+----------------+
8 rows in set (0.00 sec)Tiga pelanggan — Andi, Rian, dan Eko — tampil dengan kolom telepon berisi NULL. Inilah bahan latihan kita: baris-baris dengan nilai yang “tidak ada”.
NULL Bukan Nol dan Bukan String Kosong #
Pemula sering menyamakan NULL dengan angka 0 atau string kosong ''. Ketiganya berbeda. Angka 0 adalah nilai numerik yang benar-benar ada; string '' adalah teks sepanjang nol karakter yang juga ada; NULL adalah penanda bahwa nilai tersebut sama sekali tidak tersedia. Kita bisa membuktikan perbedaan itu langsung:
SELECT 0 IS NULL AS nol_adalah_null, '' IS NULL AS kosong_adalah_null;Output yang Dihasilkan:
+-----------------+--------------------+
| nol_adalah_null | kosong_adalah_null |
+-----------------+--------------------+
| 0 | 0 |
+-----------------+--------------------+
1 row in set (0.00 sec)Kedua kolom hasil bernilai 0 (salah) — artinya 0 dan '' bukan NULL. Konsekuensi praktisnya penting: kolom stok bernilai 0 berarti “barang ada tapi habis”, sedangkan stok NULL berarti “informasi stok tidak diketahui”. Mengganti NULL dengan 0 atau '' saat mendesain tabel akan mengaburkan makna data dan membuat kedua keadaan itu tidak bisa dibedakan lagi.
Kenapa = NULL Tidak Mengembalikan Baris #
Insting pertama kita mungkin mencari pelanggan tanpa telepon dengan kondisi telepon = NULL. Mari kita coba sungguhan:
SELECT id, nama, telepon
FROM pelanggan
WHERE telepon = NULL;Output yang Dihasilkan:
Empty set (0.00 sec)Hasilnya kosong — padahal tiga pelanggan jelas-jelas memiliki telepon bernilai NULL. Query tidak error, tidak ada peringatan, hanya hasil kosong. Justru keheningan inilah yang membuatnya berbahaya: query tampak berhasil padahal gagal menemukan baris yang dicari.
Akar masalahnya ada pada logika tiga nilai (three-valued logic) SQL. Perbandingan biasa menghasilkan benar atau salah, tetapi setiap perbandingan yang melibatkan NULL menghasilkan NULL — bukan benar, bukan salah:
SELECT NULL = NULL AS sama, NULL <> NULL AS beda, '0812-5000-1001' <> NULL AS beda_dari_null;Output yang Dihasilkan:
+------+------+----------------+
| sama | beda | beda_dari_null |
+------+------+----------------+
| NULL | NULL | NULL |
+------+------+----------------+
1 row in set (0.00 sec)Ketiga hasilnya NULL. Bahkan NULL = NULL pun bernilai NULL, karena dua hal yang tidak diketahui nilainya tidak bisa dinyatakan sama maupun berbeda. Klausa WHERE hanya meloloskan baris yang kondisinya bernilai benar; baris dengan kondisi NULL atau salah ikut gugur. Karena telepon = NULL dinilai NULL untuk semua baris, tidak ada satu pun yang lolos.
Pembaliknya pun sama buntunya:
SELECT id, nama
FROM pelanggan
WHERE telepon <> NULL;Output yang Dihasilkan:
Empty set (0.00 sec)Lima pelanggan yang punya nomor telepon seharusnya lolos, tetapi hasil tetap kosong karena telepon <> NULL juga bernilai NULL untuk semua baris. Inilah jebakan yang sama dengan yang sudah kita buktikan pada chapter IN dan NOT IN: satu NULL di daftar NOT IN mengosongkan seluruh hasil. Sekarang kita tahu akar masalahnya — perbandingan apa pun terhadap NULL tidak pernah menghasilkan nilai benar.
IS NULL dan IS NOT NULL #
Predikat IS NULL menguji apakah sebuah ekspresi tidak memiliki nilai. Query berikut menemukan seluruh pelanggan yang belum meninggalkan nomor telepon:
SELECT id, nama, kota
FROM pelanggan
WHERE telepon IS NULL
ORDER BY id;Output yang Dihasilkan:
+----+--------------+------------+
| id | nama | kota |
+----+--------------+------------+
| 3 | Andi Wijaya | Surabaya |
| 5 | Rian Hidayat | Yogyakarta |
| 7 | Eko Prasetyo | Semarang |
+----+--------------+------------+
3 rows in set (0.00 sec)Tiga baris yang tadi hilang kini ditemukan: Andi, Rian, dan Eko. Kebalikannya, IS NOT NULL mengambil baris yang kolomnya benar-benar terisi:
SELECT id, nama, telepon
FROM pelanggan
WHERE telepon IS NOT NULL
ORDER BY id;Output yang Dihasilkan:
+----+-----------------+----------------+
| id | nama | telepon |
+----+-----------------+----------------+
| 1 | Budi Santoso | 0812-5000-1001 |
| 2 | Siti Rahayu | 0813-5000-1002 |
| 4 | Dewi Lestari | 0815-5000-1004 |
| 6 | Maya Putri | 0817-5000-1006 |
| 8 | Fitri Handayani | 0819-5000-1008 |
+----+-----------------+----------------+
5 rows in set (0.00 sec)Kedua predikat ini menghasilkan nilai benar atau salah — tidak pernah NULL — sehingga aman dipakai di WHERE. Contoh penerapan pada tabel kedua: pesanan yang belum diberi catatan pengiriman bisa dipisahkan dari yang sudah berinstruksi khusus:
SELECT id, nama_pelanggan, nama_produk
FROM pesanan
WHERE catatan IS NULL
ORDER BY id;Output yang Dihasilkan:
+----+-----------------+--------------------+
| id | nama_pelanggan | nama_produk |
+----+-----------------+--------------------+
| 2 | Siti Rahayu | Kertas HVS A4 |
| 4 | Budi Santoso | Kertas Origami |
| 5 | Maya Putri | Buku Tulis Sidu 38 |
| 7 | Fitri Handayani | Selotip Bening |
| 9 | Budi Santoso | Pulpen Gel Hitam |
+----+-----------------+--------------------+
5 rows in set (0.00 sec)Predikat IS NULL juga bisa digabung dengan kondisi lain sebagaimana operator perbandingan biasa. Query berikut mengambil pesanan yang sudah memiliki catatan pengiriman sejak Maret 2026:
SELECT id, nama_pelanggan, catatan
FROM pesanan
WHERE catatan IS NOT NULL AND tanggal >= '2026-03-01'
ORDER BY id;Output yang Dihasilkan:
+----+----------------+-------------------------+
| id | nama_pelanggan | catatan |
+----+----------------+-------------------------+
| 6 | Eko Prasetyo | Titipkan ke resepsionis |
| 8 | Budi Santoso | Bungkus sebagai kado |
+----+----------------+-------------------------+
2 rows in set (0.00 sec)Fungsi ISNULL() #
Selain predikat, MySQL menyediakan fungsi ISNULL(ekspresi) yang mengembalikan 1 bila argumennya NULL dan 0 bila tidak. Fungsi ini paling berguna di daftar SELECT karena hasilnya bisa langsung ditampilkan sebagai kolom penanda:
SELECT id, nama, ISNULL(telepon) AS tanpa_telepon
FROM pelanggan
ORDER BY id;Output yang Dihasilkan:
+----+-----------------+---------------+
| id | nama | tanpa_telepon |
+----+-----------------+---------------+
| 1 | Budi Santoso | 0 |
| 2 | Siti Rahayu | 0 |
| 3 | Andi Wijaya | 1 |
| 4 | Dewi Lestari | 0 |
| 5 | Rian Hidayat | 1 |
| 6 | Maya Putri | 0 |
| 7 | Eko Prasetyo | 1 |
| 8 | Fitri Handayani | 0 |
+----+-----------------+---------------+
8 rows in set (0.00 sec)Kolom tanpa_telepon bernilai 1 untuk tiga pelanggan tanpa telepon dan 0 untuk sisanya. Untuk menyaring baris, IS NULL tetap lebih idiomatik daripada ISNULL(telepon) = 1 — penulisan yang lebih singkat, lebih portabel antar-database, dan lebih mudah dioptimasi server saat kolom tersebut memiliki index.
COUNT dan NULL: Pengantar Singkat #
Fungsi agregat COUNT memberi perlakuan istimewa pada NULL. Bentuk COUNT(*) menghitung baris, sedangkan COUNT(nama_kolom) hanya menghitung baris yang kolomnya tidak NULL:
SELECT COUNT(*) AS total_pelanggan, COUNT(telepon) AS punya_telepon
FROM pelanggan;Output yang Dihasilkan:
+-----------------+---------------+
| total_pelanggan | punya_telepon |
+-----------------+---------------+
| 8 | 5 |
+-----------------+---------------+
1 row in set (0.00 sec)Dari delapan baris pelanggan, hanya lima yang terhitung pada COUNT(telepon) — tiga baris bertanda NULL dilewati tanpa mengurangi hasil COUNT(*). Perilaku ini akan sangat berpengaruh saat kita menghitung rata-rata dan total di Section Agregasi, karena fungsi agregat lain seperti AVG dan SUM juga mengabaikan NULL sepenuhnya.
Membersihkan Database Latihan #
Latihan tentang NULL selesai. Kita jatuhkan database latihan beserta kedua tabelnya:
DROP DATABASE belajar_filter;Output yang Dihasilkan:
Query OK, 2 rows affected (0.00 sec)Referensi #
- Comparison Functions and Operators — MySQL 8.4 Reference Manual
- Aggregate Functions — MySQL 8.4 Reference Manual
- SELECT Statement — MySQL 8.4 Reference Manual
Kita kini bisa menyaring baris tanpa nilai dan memahami kenapa NULL menolak dibandingkan. Penyaringan dengan WHERE beserta seluruh operatornya baru satu sisi dari pengolahan hasil; sisi lainnya adalah mengatur urutan tampilan. Chapter berikutnya membahas ORDER BY — klausa untuk mengurutkan baris hasil berdasarkan satu kolom, beberapa kolom, alias, hingga ekspresi seperti harga * stok.