Filter dengan = dan >= sudah cukup untuk kasus sederhana, tetapi tiga kebutuhan nyata masih belum terjawab: menyaring terhadap sekumpulan nilai, terhadap sebuah rentang, dan terhadap baris yang kolomnya kosong (NULL). SQL menyediakan tiga operator khusus untuk ketiganya: IN, BETWEEN, dan IS NULL. Pada chapter ini kita membedah ketiganya satu per satu memakai domain toko yang sama, termasuk jebakan klasik NOT IN ketika data mengandung NULL.

Skema dan Data Contoh #

Seluruh contoh memakai empat tabel domain toko yang diperkenalkan pada Section SQL Dasar. Jika Anda baru bergabung di sini, salin blok berikut ke dalam database baru, misalnya toko.db:

sql
CREATE TABLE pelanggan (
  id INTEGER PRIMARY KEY,
  nama TEXT NOT NULL,
  kota TEXT
);
CREATE TABLE produk (
  id INTEGER PRIMARY KEY,
  nama TEXT NOT NULL,
  harga INTEGER NOT NULL,
  stok INTEGER
);
CREATE TABLE pesanan (
  id INTEGER PRIMARY KEY,
  pelanggan_id INTEGER REFERENCES pelanggan(id),
  tanggal TEXT NOT NULL
);
CREATE TABLE detail_pesanan (
  pesanan_id INTEGER REFERENCES pesanan(id),
  produk_id INTEGER REFERENCES produk(id),
  jumlah INTEGER NOT NULL,
  PRIMARY KEY (pesanan_id, produk_id)
);

INSERT INTO pelanggan (id, nama, kota) VALUES
  (1, 'Budi', 'Jakarta'), (2, 'Siti', 'Bandung'), (3, 'Ani', NULL),
  (4, 'Rina', 'Surabaya'), (5, 'Dewi', 'Jakarta');
INSERT INTO produk (id, nama, harga, stok) VALUES
  (1, 'Kopi Gayo', 45000, 20), (2, 'Teh Melati', 18000, 35),
  (3, 'Gula Aren', 25000, 15), (4, 'Sirup Vanila', 25000, NULL),
  (5, 'Kopi Toraja', 52000, 10), (6, 'Teh Hijau', 22000, 0),
  (7, 'Cangkir Keramik', 35000, 8);
INSERT INTO pesanan (id, pelanggan_id, tanggal) VALUES
  (1, 1, '2025-06-01'), (2, 2, '2025-06-03'), (3, 1, '2025-06-07'),
  (4, 3, '2025-06-10'), (5, 2, '2025-06-15'), (6, 1, '2025-07-02');
INSERT INTO detail_pesanan (pesanan_id, produk_id, jumlah) VALUES
  (1, 1, 2), (1, 3, 1), (2, 2, 3), (3, 1, 1),
  (3, 4, 2), (4, 5, 1), (5, 6, 2), (5, 3, 2), (6, 1, 1);

Dua kolom sengaja memuat NULL: kota milik pelanggan Ani dan stok produk Sirup Vanila — nilai kosong inilah bahan utama chapter ini. Jalankan contoh dengan membuka sqlite3 toko.db lalu mengetikkan query, atau menyimpannya ke berkas dan menjalankan sqlite3 toko.db < berkas.sql. Pada mode list bawaan shell, kolom dipisah tanda | dan NULL tampil sebagai field kosong.

Menyaring Daftar Nilai dengan IN #

Kondisi yang membandingkan satu kolom terhadap beberapa nilai akan memanjang bila ditulis dengan OR satu per satu. Operator IN memampatkan daftar tersebut ke dalam satu kondisi:

sql
SELECT nama, harga
FROM produk
WHERE harga IN (18000, 25000)
ORDER BY harga, nama;

Output yang Dihasilkan:

text
Teh Melati|18000
Gula Aren|25000
Sirup Vanila|25000

Engine SQLite membandingkan harga terhadap setiap anggota daftar; baris lolos begitu satu nilai cocok. Kebalikannya, NOT IN, mempertahankan baris yang tidak cocok dengan satu pun anggota daftar:

sql
SELECT nama, harga
FROM produk
WHERE harga NOT IN (18000, 25000)
ORDER BY harga;

Output yang Dihasilkan:

text
Teh Hijau|22000
Cangkir Keramik|35000
Kopi Gayo|45000
Kopi Toraja|52000

Daftar nilai tidak harus diketik manual. IN juga menerima hasil SELECT lain — query berikut mengambil produk yang pernah masuk ke tabel detail_pesanan:

sql
SELECT nama
FROM produk
WHERE id IN (SELECT produk_id FROM detail_pesanan)
ORDER BY nama;

Output yang Dihasilkan:

text
Gula Aren
Kopi Gayo
Kopi Toraja
Sirup Vanila
Teh Hijau
Teh Melati

Enam dari tujuh produk pernah dipesan; hanya Cangkir Keramik yang tidak pernah muncul. Teknik query-di-dalam-query ini dibahas tuntas pada Chapter Subquery.

Rentang dengan BETWEEN #

BETWEEN menyaring nilai pada rentang tertutup — kedua batas ikut dihitung. Kondisi harga BETWEEN 25000 AND 45000 setara dengan harga >= 25000 AND harga <= 45000:

sql
SELECT nama, harga
FROM produk
WHERE harga BETWEEN 25000 AND 45000
ORDER BY harga, nama;

Output yang Dihasilkan:

text
Gula Aren|25000
Sirup Vanila|25000
Cangkir Keramik|35000
Kopi Gayo|45000

Perhatikan Gula Aren dan Kopi Gayo: keduanya tepat berada di batas bawah dan batas atas, dan keduanya tetap tampil. Bentuk negasinya, NOT BETWEEN, mempertahankan baris di luar rentang:

sql
SELECT nama, harga
FROM produk
WHERE harga NOT BETWEEN 22000 AND 45000
ORDER BY harga;

Output yang Dihasilkan:

text
Teh Melati|18000
Kopi Toraja|52000

BETWEEN juga nyaman untuk tanggal. Kolom tanggal bertipe TEXT berformat ISO YYYY-MM-DD, dan format ini membuat perbandingan teks berperilaku sama dengan perbandingan tanggal:

sql
SELECT id, tanggal
FROM pesanan
WHERE tanggal BETWEEN '2025-06-01' AND '2025-06-07'
ORDER BY id;

Output yang Dihasilkan:

text
1|2025-06-01
2|2025-06-03
3|2025-06-07

Menguji NULL dengan IS NULL #

NULL bukan nilai, melainkan penanda “tidak diketahui”. Karena itu NULL tidak bisa diuji dengan = — hasil perbandingan apa pun terhadap NULL adalah NULL, bukan benar atau salah:

sql
SELECT 'Jakarta' = NULL, 'Jakarta' IS NULL, NULL IS NULL;

Output yang Dihasilkan:

text
|0|1

Kolom pertama kosong karena 'Jakarta' = NULL menghasilkan NULL. Satu-satunya cara menguji kekosongan adalah IS NULL dan kebalikannya IS NOT NULL:

sql
SELECT nama
FROM pelanggan
WHERE kota IS NULL;

Output yang Dihasilkan:

text
Ani

Kombinasi dengan kondisi lain juga sering diperlukan. Query berikut mencari produk yang stoknya tercatat dan menipis — tanpa IS NOT NULL, Sirup Vanila justru hilang diam-diam karena NULL <= 15 bernilai NULL:

sql
SELECT nama, stok
FROM produk
WHERE stok IS NOT NULL AND stok <= 15
ORDER BY nama;

Output yang Dihasilkan:

text
Cangkir Keramik|8
Gula Aren|15
Kopi Toraja|10
Teh Hijau|0
sql
SELECT nama
FROM pelanggan
WHERE kota NOT IN ('Jakarta', 'Surabaya');

Output yang Dihasilkan:

text
Siti

Gunakan IS NULL secara eksplisit saat bekerja dengan kolom yang bisa kosong, dan simak lanjutan jebakan ini — versi subquery yang lebih tersembunyi — pada Chapter Subquery.