Episode ini membangun fondasi SQL untuk analis: SELECT, WHERE, ORDER BY, JOIN, GROUP BY, HAVING, dan subquery, diuji pada dataset penjualan bisnis di PostgreSQL, lengkap dengan contoh query eksploratif dan kesalahan umum yang sering menjegal pemula

Setelah di episode 2 kita menyiapkan kerangka kerja dan data literacy, kini saatnya memegang senjata utama seorang analis: SQL. Hampir semua analisis dimulai dari query — data warehouse, database operasional, sampai BI tool semuanya berbicara bahasa yang sama di balik layar. Kalian tidak bisa menjadi analis yang handal tanpa SQL yang solid.
Episode ini membangun fondasinya: dari SELECT paling sederhana hingga GROUP BY dengan HAVING dan subquery. Kita memakai dataset penjualan e-commerce kecil yang bisa langsung kalian praktikkan di PostgreSQL yang sudah disiapkan di episode 0. Konsep yang kalian kuasai di sini akan menjadi batu loncatan untuk episode 4 (window functions) dan episode 16 (SQL untuk warehouse).
Buat dua tabel sederhana: customers dan orders, lalu isi dengan data contoh.
CREATE TABLE customers (
id SERIAL PRIMARY KEY,
name TEXT NOT NULL,
city TEXT,
joined_at DATE
);CREATE TABLE orders (
id SERIAL PRIMARY KEY,
customer_id INTEGER REFERENCES customers(id),
product TEXT,
amount NUMERIC(10,2),
status TEXT,
ordered_at TIMESTAMP
);INSERT INTO customers (name, city, joined_at) VALUES
('Budi', 'Jakarta', '2024-01-10'),
('Sari', 'Bandung', '2024-02-20'),
('Andi', 'Jakarta', '2024-03-05'),
('Dewi', 'Surabaya', '2024-05-15');
INSERT INTO orders (customer_id, product, amount, status, ordered_at) VALUES
(1, 'Laptop', 15000000, 'completed', '2025-11-01 10:00:00'),
(2, 'Keyboard', 500000, 'completed', '2025-11-03 11:00:00'),
(3, 'Mouse', 200000, 'pending', '2025-11-04 09:00:00'),
(1, 'Monitor', 3000000, 'completed', '2025-11-10 14:00:00'),
(2, 'Mouse', 200000, 'completed', '2025-12-02 10:30:00'),
(4, 'Laptop', 16000000, 'completed', '2025-12-15 16:00:00');Note
Untuk mengikuti episode ini, jalankan skrip di atas satu per satu di psql atau client SQL favorit kalian. Dataset ini sengaja kecil — fokusnya memahami logika query, bukan performa.
SELECT menentukan kolom yang diambil, WHERE menyaring baris berdasarkan kondisi.
SELECT name, city
FROM customers
WHERE city = 'Jakarta';Operator yang sering dipakai analis: =, !=, > , < , >= , <= , IN , BETWEEN , LIKE , dan IS NULL. Contoh kombinasi:
SELECT product, amount
FROM orders
WHERE amount BETWEEN 200000 AND 5000000
AND status = 'completed';Perhatikan NULL: dalam SQL, NULL bukan nol. Kondisi amount = NULL tidak pernah benar — kalian harus memakai amount IS NULL. Ini salah satu sumber bug klasik di SQL.
SELECT product, amount
FROM orders
WHERE status = 'completed'
ORDER BY amount DESC;Tanpa ORDER BY, urutan hasil tidak dijamin — database bebas mengembalikan baris dalam urutan apa pun. Jika urutan penting, selalu nyatakan eksplisit.
Data bisnis tersebar di banyak tabel. JOIN menggabungkannya lewat kolom kunci. Jenis yang paling sering dipakai analis:
SELECT o.id, c.name, o.product, o.amount
FROM orders o
JOIN customers c ON c.id = o.customer_id
WHERE o.status = 'completed';| Jenis JOIN | Hasil |
|---|---|
INNER JOIN | Hanya baris yang cocok di kedua tabel |
LEFT JOIN | Semua baris kiri, data kanan yang tidak cocok jadi NULL |
RIGHT JOIN | Kebalikan LEFT JOIN |
FULL JOIN | Semua baris di kedua sisi |
LEFT JOIN adalah senjata utama analis untuk menemukan data yang "hilang" — misalnya customer yang belum pernah order:
SELECT c.name, COUNT(o.id) AS total_order
FROM customers c
LEFT JOIN orders o ON o.customer_id = c.id
GROUP BY c.name;GROUP BY mengelompokkan baris dan HAVING menyaring hasil agregasi (sedangkan WHERE menyaring baris mentah sebelum agregasi).
SELECT product, SUM(amount) AS total_amount, COUNT(*) AS jumlah
FROM orders
WHERE status = 'completed'
GROUP BY product;SELECT product, SUM(amount) AS total_amount
FROM orders
WHERE status = 'completed'
GROUP BY product
HAVING SUM(amount) > 1000000;Urutan logika eksekusi query yang wajib kalian hafal: FROM → WHERE → GROUP BY → HAVING → SELECT → ORDER BY. Itulah mengapa kalian tidak bisa memakai alias hasil agregasi di dalam WHERE, tetapi bisa di ORDER BY.
Subquery bisa ditaruh di WHERE, FROM, maupun SELECT. Contoh klasik: membandingkan setiap baris dengan nilai agregat.
SELECT id, product, amount
FROM orders
WHERE amount > (SELECT AVG(amount) FROM orders);Contoh kedua — subquery di FROM sebagai tabel virtual:
SELECT name, revenue
FROM (
SELECT c.name, SUM(o.amount) AS revenue
FROM customers c
JOIN orders o ON o.customer_id = c.id
WHERE o.status = 'completed'
GROUP BY c.name
) AS summary
ORDER BY revenue DESC;WHERE pada COUNT. COUNT(*) menghitung semua baris; untuk menghitung subset, filter dulu di WHERE.GROUP BY. Memilih name bersama SUM(amount) tanpa GROUP BY name akan error — atau lebih buruk, hasil tak menentu di database lain.WHERE dipakai untuk filter agregasi. Ingat urutan eksekusi: agregasi hanya bisa difilter dengan HAVING.NULL dengan =. Selalu pakai IS NULL / IS NOT NULL.JOIN tanpa ON menghasilkan cartesian product yang diam-diam menggandakan baris.Inti yang harus dibawa pulang:
SELECT + WHERE adalah gerbang utama mengambil dan menyaring data.JOIN menggabungkan tabel lewat kunci; LEFT JOIN sangat berguna untuk menemukan data yang hilang.GROUP BY + HAVING untuk agregasi; hafalkan urutan eksekusi logis query.NULL adalah jebakan: selalu bandingkan dengan IS NULL.Di episode 4 selanjutnya kita akan menaikkan level SQL kalian: window functions (ROW_NUMBER, RANK, LAG/LEAD), CTE, dan optimasi query — lengkap dengan analisis cohort & retention yang menjadi skill wajib analis. Pastikan kalian sudah nyaman dengan episode 3, karena semua akan kalian pakai di sana!