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:

sql
DROP DATABASE IF EXISTS belajar_filter;
CREATE DATABASE belajar_filter;
USE belajar_filter;

Output yang Dihasilkan:

text
Query OK, 0 rows affected, 1 warning (0.00 sec)

Query OK, 1 row affected (0.00 sec)

Database changed
sql
CREATE 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:

text
Query OK, 0 rows affected (0.00 sec)

Query OK, 8 rows affected (0.00 sec)
Records: 8  Duplicates: 0  Warnings: 0
sql
CREATE 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:

text
Query OK, 0 rows affected (0.00 sec)

Query OK, 9 rows affected (0.00 sec)
Records: 9  Duplicates: 0  Warnings: 0

Definisi 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:

sql
SELECT * FROM pelanggan ORDER BY id;

Output yang Dihasilkan:

text
+----+-----------------+------------+----------------+
| 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:

sql
SELECT 0 IS NULL AS nol_adalah_null, '' IS NULL AS kosong_adalah_null;

Output yang Dihasilkan:

text
+-----------------+--------------------+
| 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:

sql
SELECT id, nama, telepon
FROM pelanggan
WHERE telepon = NULL;

Output yang Dihasilkan:

text
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:

sql
SELECT NULL = NULL AS sama, NULL <> NULL AS beda, '0812-5000-1001' <> NULL AS beda_dari_null;

Output yang Dihasilkan:

text
+------+------+----------------+
| 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:

sql
SELECT id, nama
FROM pelanggan
WHERE telepon <> NULL;

Output yang Dihasilkan:

text
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:

sql
SELECT id, nama, kota
FROM pelanggan
WHERE telepon IS NULL
ORDER BY id;

Output yang Dihasilkan:

text
+----+--------------+------------+
| 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:

sql
SELECT id, nama, telepon
FROM pelanggan
WHERE telepon IS NOT NULL
ORDER BY id;

Output yang Dihasilkan:

text
+----+-----------------+----------------+
| 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:

sql
SELECT id, nama_pelanggan, nama_produk
FROM pesanan
WHERE catatan IS NULL
ORDER BY id;

Output yang Dihasilkan:

text
+----+-----------------+--------------------+
| 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:

sql
SELECT id, nama_pelanggan, catatan
FROM pesanan
WHERE catatan IS NOT NULL AND tanggal >= '2026-03-01'
ORDER BY id;

Output yang Dihasilkan:

text
+----+----------------+-------------------------+
| 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:

sql
SELECT id, nama, ISNULL(telepon) AS tanpa_telepon
FROM pelanggan
ORDER BY id;

Output yang Dihasilkan:

text
+----+-----------------+---------------+
| 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:

sql
SELECT COUNT(*) AS total_pelanggan, COUNT(telepon) AS punya_telepon
FROM pelanggan;

Output yang Dihasilkan:

text
+-----------------+---------------+
| 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:

sql
DROP DATABASE belajar_filter;

Output yang Dihasilkan:

text
Query OK, 2 rows affected (0.00 sec)

Referensi #

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.