Urutan Eksekusi Query SQL: Kenapa FROM Jalan Duluan
Blog/Tutorial SQL/Urutan Eksekusi Query SQL: Kenapa FROM Jalan Duluan

Urutan Eksekusi Query SQL: Kenapa FROM Jalan Duluan

BimaBima
·21 Agustus 2025·10 menit baca

Penulis

Bima

Bima

Founder & Data Professional

Bagikan

TL;DR

Urutan eksekusi logis query SQL adalah FROM, JOIN, WHERE, GROUP BY, HAVING, SELECT, DISTINCT, ORDER BY, lalu LIMIT. SELECT diproses hampir paling akhir, jadi alias yang kamu bikin di SELECT nggak bisa dipakai di WHERE tapi bisa dipakai di ORDER BY. Paham urutan ini langsung jelasin kenapa agregat harus difilter pakai HAVING dan kenapa LEFT JOIN bisa berubah jadi INNER JOIN gara-gara satu kondisi di WHERE.

Query SQL ditulis mulai dari SELECT, tapi dijalanin mulai dari FROM.

Satu kalimat itu jelasin sebagian besar error SQL yang bikin orang bingung di tahun pertama.

Kenapa alias error di WHERE tapi aman di ORDER BY. Kenapa SUM harus difilter pakai HAVING. Kenapa LEFT JOIN kamu diam-diam berubah jadi INNER JOIN.

Semuanya satu penyebab: urutan pemrosesan.

Apa urutan eksekusi query SQL?

Urutan logisnya FROM, lalu ON dan JOIN, WHERE, GROUP BY, HAVING, SELECT, DISTINCT, ORDER BY, dan terakhir LIMIT. Tiap tahap ngasilin tabel sementara yang jadi masukan tahap berikutnya. SELECT diproses di urutan keenam, jauh setelah WHERE, dan itu sumber sebagian besar kebingungan.

Urutan jalanKlausaYang dikerjain
1FROMAmbil tabel sumbernya
2ON dan JOINGabungin tabel sesuai kondisi
3WHEREBuang baris yang nggak lolos syarat
4GROUP BYKelompokkan baris yang tersisa
5HAVINGBuang kelompok yang nggak lolos syarat
6SELECTPilih kolom, hitung ekspresi, bikin alias
7DISTINCTBuang baris kembar
8ORDER BYUrutin hasilnya
9LIMIT atau OFFSETPotong jumlah baris

Satu catatan penting: ini urutan logis, bukan urutan fisik. Mesin database bebas nyusun ulang langkahnya demi kecepatan, asal hasil akhirnya sama persis dengan urutan logis di atas. Penjelasan resminya ada di dokumentasi PostgreSQL soal pemrosesan query.

Kenapa alias di SELECT nggak bisa dipakai di WHERE?

Karena WHERE jalan di langkah ketiga sedangkan alias baru dibikin di langkah keenam. Waktu database mengevaluasi WHERE, nama alias itu belum ada sama sekali. Ini bukan kelemahan SQL, tapi konsekuensi langsung dari urutan pemrosesannya.

-- Error: kolom subtotal nggak dikenal
SELECT harga * qty AS subtotal
FROM transaksi
WHERE subtotal > 100000;

-- Benar: ulang ekspresinya
SELECT harga * qty AS subtotal
FROM transaksi
WHERE harga * qty > 100000;

Kalau ekspresinya panjang dan kamu males ngulang, bungkus jadi subquery atau CTE lalu filter di lapisan luar.

WITH hitung AS (
  SELECT id, harga * qty AS subtotal
  FROM transaksi
)
SELECT *
FROM hitung
WHERE subtotal > 100000;

Anehnya, alias yang sama aman dipakai di ORDER BY. Wajar, soalnya ORDER BY jalan di langkah kedelapan, setelah SELECT selesai bikin aliasnya.

Perbandingan performa antara CTE dan subquery dibahas terpisah di CTE vs subquery.

Kapan pakai WHERE dan kapan pakai HAVING?

Pakai WHERE buat nyaring baris sebelum dikelompokkan, dan HAVING buat nyaring hasil setelah dikelompokkan. WHERE nggak bisa nyentuh fungsi agregat kayak SUM atau COUNT, soalnya angka itu baru dihitung di langkah GROUP BY. Salah taruh bikin query-nya error, atau lebih bahaya lagi, jalan tapi hasilnya beda dari yang kamu maksud.

SELECT
  pelanggan_id,
  SUM(total) AS total_belanja
FROM transaksi
WHERE tanggal >= DATE '2024-01-01'
GROUP BY pelanggan_id
HAVING SUM(total) > 1000000
ORDER BY total_belanja DESC;

Baca query itu ngikutin urutan jalannya, bukan urutan tulisannya.

  1. Ambil semua baris dari tabel transaksi.
  2. Buang transaksi sebelum 1 Januari 2024. Ini kerjaan WHERE.
  3. Kelompokkan sisa barisnya per pelanggan.
  4. Hitung SUM per kelompok, lalu buang kelompok yang totalnya di bawah satu juta. Ini kerjaan HAVING.
  5. Baru pilih dua kolom yang mau ditampilin, termasuk bikin alias total_belanja.
  6. Urutin dari yang paling besar, boleh pakai alias tadi.

Urutan itu juga ngasih petunjuk performa. WHERE mbuang baris lebih awal, jadi taruh sebanyak mungkin syarat di WHERE dan sisain HAVING cuma buat syarat yang beneran butuh hasil agregat. Konsep dasarnya ada di penjelasan aggregate function.

Kenapa LEFT JOIN bisa berubah jadi INNER JOIN?

Karena kondisi tabel kanan ditaruh di WHERE, bukan di ON. LEFT JOIN ngisi baris tanpa pasangan dengan NULL di langkah kedua, lalu WHERE jalan di langkah ketiga dan mbuang semua baris NULL itu. Hasil akhirnya identik sama INNER JOIN, dan nggak ada pesan error yang ngasih tau.

-- Salah: semua pelanggan tanpa transaksi 2024 ikut kebuang
SELECT p.nama, t.total
FROM pelanggan p
LEFT JOIN transaksi t ON t.pelanggan_id = p.id
WHERE t.tanggal >= DATE '2024-01-01';

-- Benar: pelanggan tanpa transaksi tetap muncul dengan NULL
SELECT p.nama, t.total
FROM pelanggan p
LEFT JOIN transaksi t
  ON t.pelanggan_id = p.id
 AND t.tanggal >= DATE '2024-01-01';

Bedanya cuma posisi satu baris kondisi. Tapi di dataset toko_berkah, query pertama ngasih 1.187 baris pelanggan dan query kedua ngasih 1.402 baris, karena 215 pelanggan yang terakhir belanja di 2023 tetap kebawa.

Kesalahan ini masuk daftar pola query bermasalah yang dibahas di SQL antipattern.

Kenapa window function nggak bisa difilter di WHERE?

Window function dihitung di tahap SELECT, tepatnya setelah GROUP BY dan HAVING selesai. Waktu WHERE jalan, nilai ROW_NUMBER atau RANK belum ada. Solusinya bungkus query-nya jadi subquery atau CTE, lalu filter hasilnya di lapisan luar.

SELECT *
FROM (
  SELECT
    pelanggan_id,
    tanggal,
    total,
    ROW_NUMBER() OVER (
      PARTITION BY pelanggan_id
      ORDER BY tanggal DESC
    ) AS urutan
  FROM transaksi
) x
WHERE urutan = 1;

Query di atas ngambil transaksi terakhir tiap pelanggan. Di dataset toko_berkah hasilnya 1.187 baris, satu per pelanggan unik.

Bedanya window function sama GROUP BY dibahas lengkap di window function vs GROUP BY.

Contoh kasus: nyusun ulang query laporan toko_berkah

Dataset toko_berkah punya 4.312 transaksi sepanjang 2024 dari 1.187 pelanggan unik. Misalnya kamu diminta bikin laporan pelanggan bernilai tinggi.

Permintaannya: nama pelanggan asal Bandung, total belanja 2024 di atas Rp 1.000.000, urut dari terbesar, ambil 10 teratas.

SELECT
  p.nama,
  p.kota,
  SUM(t.total) AS total_belanja,
  COUNT(*) AS jumlah_transaksi
FROM pelanggan p
JOIN transaksi t ON t.pelanggan_id = p.id
WHERE p.kota = 'Bandung'
  AND t.tanggal BETWEEN DATE '2024-01-01' AND DATE '2024-12-31'
GROUP BY p.nama, p.kota
HAVING SUM(t.total) > 1000000
ORDER BY total_belanja DESC
LIMIT 10;

Dua syarat di WHERE mbuang baris lebih dulu, jadi GROUP BY cuma ngolah sisa data yang jauh lebih kecil. Syarat total belanja nggak bisa pindah ke WHERE, soalnya SUM belum ada di tahap itu.

Dari dataset ini, query tersebut ngasih 87 pelanggan yang lolos ambang satu juta sebelum dipotong LIMIT. Artinya 7,3 persen dari total pelanggan nyumbang porsi belanja terbesar.

Satu hal soal LIMIT: dia jalan paling akhir, jadi database tetap ngitung 87 baris itu sebelum motong ke 10. LIMIT ngirit waktu pengiriman hasil, bukan waktu ngitungnya.

Kesalahan umum yang berakar dari urutan eksekusi

  • Naruh COUNT di WHERE. Database bakal nolak, soalnya agregat belum ada waktu WHERE jalan. Pindah ke HAVING.
  • Nyangka LIMIT bikin query cepat. Kalau ada ORDER BY tanpa index yang cocok, semua baris tetap diurutin dulu.
  • Naruh kondisi tabel kanan di WHERE waktu pakai LEFT JOIN. Hasilnya senyap berubah jadi INNER JOIN.
  • Ngarep DISTINCT jalan sebelum ORDER BY selesai. DISTINCT jalan duluan, jadi kolom yang nggak ikut SELECT nggak bisa dipakai di ORDER BY.
  • Nulis kolom yang nggak ada di GROUP BY. Tiap kolom non-agregat di SELECT wajib muncul di GROUP BY, karena SELECT jalan setelah pengelompokan.
  • Ngandelin alias di WHERE karena pernah jalan di database lain. Sebagian database ngasih kelonggaran di GROUP BY, tapi WHERE nggak pernah. Query-mu jadi nggak portabel.

FAQ

Apa urutan eksekusi query SQL yang bener?

Urutan logisnya FROM, lalu ON dan JOIN, WHERE, GROUP BY, HAVING, SELECT, DISTINCT, ORDER BY, dan terakhir LIMIT atau OFFSET. Urutan ini beda dari urutan penulisan yang mulai dari SELECT. Paham bedanya langsung jelasin kenapa alias dari SELECT nggak dikenali di WHERE, dan kenapa filter agregat harus ditaruh di HAVING.

Kenapa alias di SELECT error kalau dipakai di WHERE?

Karena WHERE diproses sebelum SELECT, jadi waktu database mengevaluasi WHERE, alias itu belum ada. Solusinya ulang ekspresinya di WHERE, atau bungkus query-nya jadi subquery lalu filter di lapisan luar. Alias tetap bisa dipakai di ORDER BY, soalnya ORDER BY jalan setelah SELECT. Beberapa database juga ngizinin alias di GROUP BY sebagai kelonggaran, tapi jangan diandalkan.

Apa bedanya WHERE sama HAVING?

WHERE nyaring baris sebelum pengelompokan, sedangkan HAVING nyaring hasil setelah GROUP BY jalan. Jadi WHERE nggak bisa pakai fungsi agregat kayak SUM atau COUNT, karena angka itu belum dihitung. Kalau kamu mau ambil transaksi 2024 lalu cuma nampilin pelanggan dengan total di atas satu juta, filter tanggalnya di WHERE dan filter totalnya di HAVING.

Kenapa LEFT JOIN bisa berubah jadi INNER JOIN?

Karena kamu naruh kondisi tabel kanan di WHERE, bukan di ON. LEFT JOIN ngisi baris yang nggak ketemu pasangan dengan NULL, lalu WHERE jalan setelahnya dan mbuang semua baris NULL itu. Hasilnya identik sama INNER JOIN. Kalau kamu mau tetap nyimpen baris tanpa pasangan, taruh kondisinya di klausa ON.

Apakah LIMIT bikin query jadi lebih cepat?

Nggak selalu. LIMIT diproses paling akhir, jadi database tetap harus baca, gabung, dan urutin datanya dulu sebelum motong hasilnya. Kalau ada ORDER BY tanpa index yang cocok, seluruh baris tetap diurutin dulu. LIMIT baru beneran ngirit kerja kalau ada index yang urutannya udah sesuai ORDER BY, jadi database bisa berhenti lebih awal.

Lanjut dari sini

Dua hal yang bikin urutan ini kepakai tiap hari: baca query dari FROM ke bawah waktu debugging, dan taruh filter sedini mungkin biar tahap berikutnya ngolah data lebih sedikit.

Coba ambil satu query panjang yang pernah bikin kamu bingung, terus tulis ulang urutannya di kertas dari FROM sampai LIMIT. Biasanya sumber masalahnya kelihatan di langkah ketiga atau kelima.

Kalau kamu lagi belajar bedain kapan pakai GROUP BY dan kapan pakai window function, lanjut ke window function vs GROUP BY.

Coba Langsung

Mau praktek langsung? Mulai latihan SQL gratis

Latihan interaktif, langsung di browser.

Buka NgulikSQL →
Bagikan:
Bima
Ditulis oleh

Bima

Founder & Data Professional

Founder Ngulik Data. Passionate about making data analysis accessible for everyone.

Artikel terkait

Cara Membuat Pivot Dinamis di SQL (Panduan 2026)
Tutorial SQL
19 Juli 2026•10 menit baca

Cara Membuat Pivot Dinamis di SQL (Panduan 2026)

Pivot dinamis di SQL ngubah baris jadi kolom tanpa kamu hardcode nama kolomnya. Ini cara bikinnya pakai CASE WHEN dan versi yang kolomnya ngikut data.

BimaBima
Menghitung YTD, QTD, dan MTD di SQL (Panduan 2026)
Tutorial SQL
17 Juli 2026•9 menit baca

Menghitung YTD, QTD, dan MTD di SQL (Panduan 2026)

YTD, QTD, dan MTD ngukur total dari awal tahun, kuartal, atau bulan sampai hari ini. Ini cara ngitungnya di SQL pakai DATE_TRUNC, plus satu query gabungan.

BimaBima
Gap and Island Analysis di SQL
Tutorial SQL
15 Juli 2026•10 menit baca

Gap and Island Analysis di SQL

Gap and island analysis di SQL nyari deret data yang berturut-turut (island) dan celah yang bolong (gap). Cocok buat hitung streak login atau cari tanggal transaksi yang hilang.

BimaBima
Kembali ke Blog
Ngulik Data logoNgulik Data

Platform edukasi data lengkap untuk professionals Indonesia. Belajar SQL, Data Analysis, dan lebih banyak lagi dengan praktek langsung dan feedback real-time.

© 2026 Ngulik Data. Semua hak dilindungi.

TAUTAN
BantuanHargaDatasetBlogAfiliasi
LEGAL
Syarat & KetentuanKebijakan Privasi
Ngulik Data
DatasetLeaderboardBlogStore