Membaca seluruh isi tabel itu seperti membuka seluruh isi gudang: pada lima baris pelanggan masih terasa wajar, pada lima juta baris transaksi jadi mustahil. WHERE adalah klausa yang memilih baris mana saja yang benar-benar diproses — inti dari hampir setiap query yang berguna. Chapter ini membedah operator perbandingannya, cara menggabungkan kondisi, jangkauan nilai dengan BETWEEN dan IN, lalu dua jebakan klasik SQLite: perbandingan dengan NULL dan perbandingan antar tipe.

Sesi latihan memakai data toko yang sama dengan Chapter sebelumnya. Jika memulai dari titik ini, jalankan dulu skrip persiapan berikut:

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 UNIQUE,
    harga INTEGER NOT NULL CHECK (harga >= 0),
    stok INTEGER DEFAULT 0
);

INSERT INTO pelanggan (nama, kota) VALUES
    ('Budi', 'Jakarta'),
    ('Siti', 'Bandung'),
    ('Ani', 'Surabaya'),
    ('Joko', 'Jakarta'),
    ('Rina', NULL);

INSERT INTO produk (nama, harga, stok) VALUES
    ('Pensil', 3000, 120),
    ('Buku Tulis', 5500, 45),
    ('Penghapus', 2500, 80),
    ('Tas Sekolah', 85000, 12),
    ('Botol Air', 25000, 30),
    ('Rautan Pensil', 8000, NULL);

INSERT INTO produk (nama, harga) VALUES ('Papan Tulis Kecil', 15000);

Operator Perbandingan #

Enam operator dasar bekerja seperti di matematika: =, <> (tidak sama), <, <=, >, dan >=:

text
sqlite> SELECT nama, harga FROM produk WHERE harga >= 25000;
Tas Sekolah|85000
Botol Air|25000
sqlite>

Baris dengan harga >= 25000 lolos, sisanya tidak pernah sampai ke layar. Perlu dicatat bahwa <> adalah penulisan “tidak sama dengan” pada SQL standar; SQLite juga menerima != dengan makna yang sama.

Menggabungkan Kondisi #

Satu kondisi jarang cukup. AND menggabungkan syarat yang harus dipenuhi bersamaan, OR memilih salah satu, dan NOT membalik sebuah kondisi:

text
sqlite> SELECT nama, harga, stok FROM produk WHERE harga < 10000 AND stok > 50;
Pensil|3000|120
Penghapus|2500|80
sqlite> SELECT nama, harga, stok FROM produk WHERE harga > 80000 OR stok >= 100;
Pensil|3000|120
Tas Sekolah|85000|12
sqlite>

Hasil pertama menarik untuk dicermati: Rautan Pensil berharga 8000 — lolos syarat harga — tetapi stok-nya NULL, dan NULL > 50 tidak bernilai benar. Baris dengan NULL tersaring diam-diam pada semua perbandingan seperti ini.

Ketika AND dan OR bercampur, gunakan tanda kurung untuk menegaskan urutan evaluasi — jangan mengandalkan prioritas operator:

text
sqlite> SELECT nama, kota FROM pelanggan
   ...> WHERE (kota = 'Jakarta' OR kota = 'Bandung') AND nama <> 'Budi';
Siti|Bandung
Joko|Jakarta
sqlite>

Tanpa tanda kurung, statement tersebut berarti kota = 'Jakarta' saja, atau kota = 'Bandung' AND nama <> 'Budi' — hasil yang jauh berbeda.

NOT menyimpan satu kejutan yang berkaitan dengan NULL:

text
sqlite> SELECT nama, kota FROM pelanggan WHERE NOT (kota = 'Jakarta');
Siti|Bandung
Ani|Surabaya
sqlite>

Hasilnya hanya dua baris — Rina hilang. Evaluasi untuk Rina berjalan begini: NULL = 'Jakarta' menghasilkan NULL (bukan salah), lalu NOT NULL tetap NULL, dan WHERE hanya menerima nilai benar. Logika tiga nilai NULL ini dibedah penuh di bagian bawah chapter.

BETWEEN dan IN #

BETWEEN menyatakan jangkauan nilai, IN menyatakan daftar nilai yang diterima:

text
sqlite> SELECT nama, harga FROM produk WHERE harga BETWEEN 3000 AND 8000;
Pensil|3000
Buku Tulis|5500
Rautan Pensil|8000
sqlite> SELECT nama, kota FROM pelanggan WHERE kota IN ('Jakarta', 'Surabaya');
Budi|Jakarta
Ani|Surabaya
Joko|Jakarta
sqlite>

BETWEEN bersifat inklusif di kedua ujungnya — produk berharga tepat 3000 dan tepat 8000 ikut lolos. IN setara dengan rangkaian OR, tetapi jauh lebih ringkas ketika daftar nilainya panjang. Keduanya punya bentuk negatif — NOT BETWEEN dan NOT IN:

text
sqlite> SELECT nama, harga FROM produk WHERE harga NOT IN (2500, 3000, 5500);
Tas Sekolah|85000
Botol Air|25000
Rautan Pensil|8000
Papan Tulis Kecil|15000
sqlite>

IS NULL dan Jebakan = NULL #

Nilai NULL tidak bisa ditangkap dengan = — hanya IS NULL dan IS NOT NULL yang bekerja:

text
sqlite> SELECT nama, stok FROM produk WHERE stok IS NULL;
Rautan Pensil|
sqlite> SELECT nama FROM pelanggan WHERE kota IS NULL;
Rina
sqlite> SELECT nama FROM produk WHERE stok = NULL;
sqlite> SELECT 1 = 1, NULL = NULL;
1|
sqlite>

Query ketiga mengembalikan kosong — bukan karena tidak ada baris berstok NULL, melainkan karena stok = NULL dievaluasi menjadi NULL untuk setiap baris. Query keempat memperlihatkan akarnya: NULL = NULL tidak bernilai benar, tetapi NULL itu sendiri — tercetak sebagai kolom kosong di samping angka 1 dari 1 = 1.

Perbandingan Antar Tipe #

Bagaimana SQLite membandingkan angka dengan teks? Untuk dua literal, tidak ada konversi — integer dan teks adalah dua storage class yang berbeda:

text
sqlite> SELECT 1 = '1';
0
sqlite> SELECT 1 < 'abc';
1
sqlite>

Bilangan 1 tidak sama dengan teks '1', dan dalam urutan bawaan SQLite, angka selalu dianggap lebih kecil daripada teks — makanya 1 < 'abc' bernilai benar. Namun begitu salah satu sisi adalah kolom, type affinity kolom tersebut mengubah keadaan:

text
sqlite> SELECT nama FROM produk WHERE harga = '85000';
Tas Sekolah
sqlite>

Kolom harga beraffinity INTEGER, sehingga engine mengonversi teks '85000' menjadi integer 85000 sebelum membandingkan — dan Tas Sekolah ditemukan. Perilaku ini nyaman bagi pemakai biasa, tetapi bisa mengejutkan ketika perbandingan antar kolom dengan affinity berbeda tidak berperilaku seperti dugaan; aturan lengkapnya dibahas pada Chapter Tipe Data dan Type Affinity.

Dengan WHERE di tangan, kita sudah bisa membaca data secara presisi. Sayangnya data yang tersimpan tidak selalu benar lagi besoknya — harga naik, stok berkurang — dan Chapter Mengubah dan Menghapus Data membahas UPDATE serta DELETE untuk mengikutinya.