TL;DR
NULL di SQL adalah penanda bahwa nilai sebuah kolom nggak diketahui atau nggak ada, bukan nol dan bukan string kosong. Perbandingan apa pun dengan NULL menghasilkan UNKNOWN, bukan TRUE atau FALSE, makanya WHERE kolom = NULL selalu balik kosong. Buat ngecek NULL, pakai IS NULL atau IS NOT NULL.
NULL di SQL adalah penanda bahwa nilai sebuah kolom nggak diketahui. Bukan nol. Bukan string kosong. Bukan spasi.
Kedengeran sepele. Tapi salah paham soal NULL itu penyebab paling umum kenapa laporan kamu ngasih angka yang beda sama data mentah. Nggak ada satu pun pesan error muncul.
Query-nya jalan. Hasilnya keluar. Angkanya salah.
NULL adalah penanda khusus yang artinya "nilai di sini nggak ada atau nggak diketahui". Dia bukan tipe data dan bukan nilai. Dia justru ketiadaan nilai.
Bedanya sama yang mirip-mirip:
| Nilai | Artinya | Contoh kasus |
|---|---|---|
| NULL | Kita nggak tau | Pelanggan belum isi kolom email |
| 0 | Angka nol beneran | Pelanggan belanja Rp0 bulan ini |
| '' (string kosong) | Teks tanpa karakter | Kolom catatan sengaja dikosongin |
| ' ' (spasi) | Teks berisi 1 spasi | Data kotor hasil import |
Contoh di artikel ini pakai tabel pelanggan dari dataset Toko Berkah, 5 baris, beberapa kolomnya sengaja NULL.
CREATE TABLE pelanggan (
id INT PRIMARY KEY,
nama VARCHAR(50),
email VARCHAR(80),
telepon VARCHAR(20),
kota VARCHAR(30),
total_belanja INT
);
INSERT INTO pelanggan VALUES
(1, 'Siti Rahayu', 'siti@mail.com', '081234567890', 'Jakarta', 4500000),
(2, 'Budi Santoso', NULL, '081298765432', 'Bandung', 2300000),
(3, 'Dewi Lestari', 'dewi@mail.com', NULL, 'Jakarta', NULL),
(4, 'Agus Pratama', NULL, NULL, 'Surabaya', 890000),
(5, 'Rina Wulandari', 'rina@mail.com', '081377788899', NULL, 6200000);
Coba jalanin ini:
SELECT * FROM pelanggan WHERE email = NULL;
Hasilnya: 0 baris. Padahal jelas-jelas ada 2 pelanggan yang emailnya NULL.
Penyebabnya, SQL pakai logika tiga nilai: TRUE, FALSE, dan UNKNOWN. Perbandingan apa pun dengan NULL menghasilkan UNKNOWN, termasuk NULL = NULL.
Klausa WHERE cuma ngeloloskan baris yang hasilnya TRUE. UNKNOWN dibuang, sama kayak FALSE.
Cara yang benar:
SELECT nama, kota FROM pelanggan WHERE email IS NULL;
-- Budi Santoso, Agus Pratama
SELECT nama, email FROM pelanggan WHERE email IS NOT NULL;
-- Siti, Dewi, Rina
IS NULL dan IS NOT NULL. Itu satu-satunya cara ngecek NULL di SQL.
Ini jebakan yang paling sering bikin laporan nggak cocok.
SELECT
COUNT(*) AS semua_baris,
COUNT(email) AS punya_email,
COUNT(telepon) AS punya_telepon
FROM pelanggan;
Hasilnya: 5, 3, 3.
COUNT(*) ngitung baris. COUNT(email) ngitung nilai yang bukan NULL di kolom email.
Jadi kalau bos nanya "berapa pelanggan kita?", jawabannya 5. Kalau dia nanya "berapa yang bisa kita email?", jawabannya 3. Dua angka berbeda dari tabel yang sama.
Aku pernah lihat tim marketing salah hitung conversion rate gara-gara ini. Pembilangnya pakai COUNT(kolom), penyebutnya pakai COUNT(*). Angkanya kelihatan wajar, jadi nggak ada yang curiga selama 3 bulan.
Fungsi agregat ngelewatin NULL. Bukan nganggapnya nol. Dia bener-bener nggak ngitung barisnya.
SELECT
SUM(total_belanja) AS total,
COUNT(total_belanja) AS jumlah_isi,
AVG(total_belanja) AS rata_rata
FROM pelanggan;
Total belanja: Rp13.890.000. Baris yang keitung: 4, bukan 5. Jadi AVG-nya Rp3.472.500, hasil bagi 4.
Kalau kamu ngira NULL dianggap nol, kamu bakal ngira rata-ratanya Rp2.778.000 (dibagi 5). Selisihnya Rp694.500 per pelanggan. Di dataset 10.000 baris, selisih segini bisa jadi laporan yang salah total.
Kalau NULL memang harus dianggap nol, bilang ke database secara eksplisit:
SELECT AVG(COALESCE(total_belanja, 0)) FROM pelanggan;
Detail cara kerjanya ada di artikel COALESCE SQL.
SELECT nama, total_belanja + 100000 AS setelah_bonus FROM pelanggan;
Dewi Lestari punya total_belanja NULL. Hasil NULL + 100000 bukan 100000, tapi NULL.
Logikanya masuk akal kalau dipikir: kalau kita nggak tau berapa belanjanya, kita juga nggak tau berapa hasilnya setelah ditambah bonus.
Efeknya nyebar. Satu kolom NULL di tengah rumus panjang bikin seluruh hasilnya NULL. Ini yang bikin kolom perhitungan kamu tiba-tiba kosong padahal rumusnya bener.
Ini yang paling ganas.
SELECT nama FROM pelanggan
WHERE kota NOT IN (SELECT kota FROM pelanggan WHERE total_belanja > 5000000);
Subquery-nya narik kota Rina, yang nilainya NULL. Begitu ada NULL di dalam daftar NOT IN, seluruh kondisi jadi UNKNOWN dan hasilnya kosong total.
Nol baris. Bukan error. Cuma kosong.
Cara amannya, pakai NOT EXISTS:
SELECT p.nama FROM pelanggan p
WHERE NOT EXISTS (
SELECT 1 FROM pelanggan x
WHERE x.total_belanja > 5000000 AND x.kota = p.kota
);
Kalau kamu naruh kondisi kolom tabel kanan di WHERE (bukan di ON), baris NULL hasil LEFT JOIN bakal kebuang. LEFT JOIN kamu diam-diam berubah jadi INNER JOIN.
-- LEFT JOIN yang "rusak"
SELECT p.nama, t.id
FROM pelanggan p
LEFT JOIN transaksi t ON t.pelanggan_id = p.id
WHERE t.status = 'lunas'; -- baris NULL kebuang di sini
-- Yang benar: pindahin ke ON
SELECT p.nama, t.id
FROM pelanggan p
LEFT JOIN transaksi t ON t.pelanggan_id = p.id AND t.status = 'lunas';
GROUP BY nggabungin semua NULL jadi satu grup. Jadi kamu bakal lihat satu baris hasil dengan kolom grup kosong. Ini beda sama WHERE, yang justru buang NULL.
Di kebanyakan database, kolom dengan UNIQUE constraint tetap boleh punya banyak baris NULL. Soalnya dua NULL nggak dianggap sama. Jadi UNIQUE nggak nge-block duplikat NULL.
PostgreSQL naruh NULL di akhir waktu ORDER BY ASC. MySQL naruh di awal. Kalau kamu pindah database dan urutan laporannya berubah, ini penyebabnya. Pakai NULLS FIRST atau NULLS LAST biar eksplisit.
Aturan logika tiga nilai ini standar SQL, bukan kebiasaan satu database. Kamu bisa baca definisi resminya di dokumentasi comparison functions PostgreSQL.
NULL artinya nilainya nggak diketahui atau nggak ada. Nol (0) itu angka beneran yang nilainya nol. String kosong ('') itu teks beneran yang panjangnya nol karakter. Ketiganya beda di database. Pelanggan dengan total_belanja NULL artinya kita nggak tau berapa dia belanja. Yang total_belanja-nya 0 artinya kita tau dia belanja nol rupiah.
Karena perbandingan apa pun dengan NULL menghasilkan UNKNOWN, bukan TRUE. Klausa WHERE cuma ngeloloskan baris yang hasilnya TRUE, jadi baris NULL selalu dibuang, bahkan waktu kamu justru lagi nyari NULL. Solusinya pakai IS NULL.
COUNT(*) ngitung semua baris termasuk yang isinya NULL. COUNT(nama_kolom) cuma ngitung baris yang kolomnya bukan NULL. Di tabel 5 baris dengan 2 email kosong, COUNT(*) ngasih 5 dan COUNT(email) ngasih 3. Beda 2 angka ini yang sering bikin laporan nggak cocok sama data mentah.
Keduanya ngelewatin baris NULL, bukan nganggapnya nol. Kalau 5 pelanggan tapi 1 total_belanja-nya NULL, AVG bakal ngebagi total dengan 4, bukan 5. Rata-ratanya jadi lebih tinggi dari yang kamu kira. Kalau NULL memang harus dianggap nol, bungkus dulu pakai COALESCE(total_belanja, 0).
Kalau daftar di dalam NOT IN mengandung satu saja NULL, seluruh kondisi jadi UNKNOWN dan nggak ada baris yang lolos. Ini terjadi diam-diam waktu subquery kamu narik kolom yang boleh NULL. Cara amannya: tambahin WHERE kolom IS NOT NULL di subquery, atau ganti seluruhnya pakai NOT EXISTS.
Yang perlu kamu inget soal NULL:
Cara paling cepat ngerti NULL? Bikin tabel 5 baris kayak contoh di atas, kosongin beberapa sel, terus jalanin COUNT(*) dan COUNT(kolom) berdampingan. Lihat angkanya beda. Setelah itu kamu nggak akan lupa.
Lanjut baca soal cara ngeganti NULL dengan nilai default di COALESCE SQL, atau pelajari operator perbandingannya di halaman fungsi WHERE NgulikData.
Mau praktek langsung? Mulai latihan SQL gratis
Latihan interaktif, langsung di browser.
Analisa per kuartal, hari kerja, atau musim jadi ribet kalau tiap query ngitung ulang atribut tanggal. Tabel kalender nyimpen semua atribut itu sekali, biar tinggal di-JOIN.
Laporan penjualan harian sering bolong di hari tanpa transaksi. Ini cara bikin deret tanggal lengkap di SQL biar tiap hari muncul, walau nilainya nol.
Struktur organisasi tersimpan sebagai kolom id_atasan yang saling nunjuk. Ini cara narik rantai jabatan, hitung total bawahan, dan span of control cuma pakai SQL.