LAG dan LEAD SQL: Bandingkan Baris Sekarang dengan Sebelumnya
Blog/Tutorial SQL/LAG dan LEAD SQL: Bandingkan Baris Sekarang dengan Sebelumnya

LAG dan LEAD SQL: Bandingkan Baris Sekarang dengan Sebelumnya

BimaBima
·12 April 2026·10 menit baca

Penulis

Bima

Bima

Founder & Data Professional

Bagikan

TL;DR

LAG dan LEAD adalah window function SQL yang narik nilai dari baris lain dalam hasil query yang sama: LAG ngambil dari baris sebelumnya, LEAD dari baris berikutnya. Sintaksnya LAG(kolom, offset, default) OVER (PARTITION BY grup ORDER BY urutan). Dipakai buat ngitung selisih dan pertumbuhan antar periode tanpa perlu self-join.

LAG dan LEAD adalah window function SQL yang narik nilai dari baris lain dalam hasil query yang sama — LAG dari baris sebelumnya, LEAD dari baris berikutnya.

Ini yang jawab pertanyaan paling sering ditanya bos: "omzet bulan ini naik berapa persen dari bulan lalu?"

Tanpa LAG, kamu harus self-join tabel ke dirinya sendiri, bikin kondisi join yang ribet, dan berdoa nggak ada bulan yang bolong. Dengan LAG, satu baris kode.

Apa itu LAG dan LEAD di SQL?

LAG dan LEAD adalah window function — fungsi yang ngeliat baris lain tanpa ngegabungin baris. Hasil query-mu tetap punya jumlah baris yang sama; cuma nambah kolom yang isinya nilai dari tetangga.

Sintaksnya:

LAG(kolom, offset, nilai_default) OVER (
  PARTITION BY grup
  ORDER BY urutan
)

Tiga argumen, dua di antaranya opsional:

  • kolom — nilai apa yang mau diambil dari baris tetangga.
  • offset — mundur/maju berapa baris. Default 1.
  • nilai_default — dipakai kalau baris tetangganya nggak ada. Default NULL.

Bagian OVER (...) wajib. Di dalamnya, ORDER BY itu yang nentuin siapa "sebelumnya". Tanpa ORDER BY, SQL nggak tau urutan barisnya, dan hasilnya nggak bisa diandalkan.

Gimana cara ngitung growth bulan ke bulan pakai LAG?

Tabel omzet_bulanan dari toko_berkah:

bulan       omzet
2026-01     42500000
2026-02     38200000
2026-03     51800000
2026-04     49100000
2026-05     58400000

Query-nya:

SELECT
  bulan,
  omzet,
  LAG(omzet) OVER (ORDER BY bulan) AS omzet_bulan_lalu,
  omzet - LAG(omzet) OVER (ORDER BY bulan) AS selisih,
  ROUND(
    100.0 * (omzet - LAG(omzet) OVER (ORDER BY bulan))
    / LAG(omzet) OVER (ORDER BY bulan), 1
  ) AS growth_persen
FROM omzet_bulanan
ORDER BY bulan;

Hasilnya:

bulanomzetomzet_bulan_laluselisihgrowth_persen
2026-0142.500.000NULLNULLNULL
2026-0238.200.00042.500.000−4.300.000−10,1
2026-0351.800.00038.200.000+13.600.000+35,6
2026-0449.100.00051.800.000−2.700.000−5,2
2026-0558.400.00049.100.000+9.300.000+18,9

Baris Januari NULL semua — wajar, nggak ada bulan sebelumnya buat dibandingin.

Lonjakan Maret +35,6% itu Ramadan. Turun lagi April setelah lebaran. Pola musiman kayak gini nggak keliatan kalau kamu cuma lihat angka omzet mentahnya.

Query-nya jadi berulang-ulang nulis LAG(omzet) OVER (ORDER BY bulan) tiga kali. Rapiin pakai CTE:

WITH dengan_lag AS (
  SELECT
    bulan,
    omzet,
    LAG(omzet) OVER (ORDER BY bulan) AS omzet_lalu
  FROM omzet_bulanan
)
SELECT
  bulan,
  omzet,
  omzet_lalu,
  omzet - omzet_lalu AS selisih,
  ROUND(100.0 * (omzet - omzet_lalu) / omzet_lalu, 1) AS growth_persen
FROM dengan_lag
ORDER BY bulan;

Lebih enak dibaca, dan kalau mau ganti logikanya cukup edit satu tempat. Kalau CTE masih asing, cek glossary CTE.

Kenapa harus pakai PARTITION BY?

Sekarang datanya per kota:

kota       bulan     omzet
Jakarta    2026-01   28000000
Jakarta    2026-02   31000000
Surabaya   2026-01   14500000
Surabaya   2026-02   17200000

Kalau kamu pakai LAG(omzet) OVER (ORDER BY bulan) doang, baris pertama Surabaya bakal ngambil nilai dari baris terakhir Jakarta. Growth-nya jadi ngawur total.

PARTITION BY yang benerin ini:

SELECT
  kota,
  bulan,
  omzet,
  LAG(omzet) OVER (PARTITION BY kota ORDER BY bulan) AS omzet_bulan_lalu
FROM omzet_kota
ORDER BY kota, bulan;

PARTITION BY bikin LAG mulai ulang dari nol tiap ganti kota. Baris pertama tiap kota balikin NULL — itu yang bener.

Aturan praktis: kalau datamu punya dimensi grup (kota, produk, pelanggan), hampir selalu butuh PARTITION BY. Lupa PARTITION BY adalah bug paling sering di window function, dan yang paling susah kedeteksi — soalnya query-nya jalan, angkanya keluar, tapi salah.

Kapan pakai LEAD?

LEAD narik dari baris berikutnya. Kepake buat ngitung jarak ke kejadian selanjutnya.

Contoh: berapa hari jeda antar belanja tiap pelanggan toko_berkah?

SELECT
  pelanggan_id,
  tanggal,
  LEAD(tanggal) OVER (
    PARTITION BY pelanggan_id
    ORDER BY tanggal
  ) AS transaksi_berikutnya,
  LEAD(tanggal) OVER (
    PARTITION BY pelanggan_id
    ORDER BY tanggal
  ) - tanggal AS jarak_hari
FROM transaksi
ORDER BY pelanggan_id, tanggal;

Hasilnya buat pelanggan P-0042:

tanggaltransaksi_berikutnyajarak_hari
2026-03-022026-03-097
2026-03-092026-03-178
2026-03-172026-04-2539
2026-04-25NULLNULL

Pola belanjanya mingguan, terus tiba-tiba bolong 39 hari. Itu sinyal pelanggan hampir hilang.

Baris terakhir NULL — belum ada transaksi berikutnya. Kalau kamu filter WHERE transaksi_berikutnya IS NULL (lewat subquery), kamu dapat daftar transaksi terakhir tiap pelanggan. Itu bahan mentah buat analisis churn.

Gimana cara pakai offset lebih dari 1?

Argumen kedua bikin kamu bisa loncat lebih jauh. Buat bandingin sama periode yang sama tahun lalu — 12 bulan ke belakang:

SELECT
  bulan,
  omzet,
  LAG(omzet, 12) OVER (ORDER BY bulan) AS omzet_tahun_lalu,
  ROUND(
    100.0 * (omzet - LAG(omzet, 12) OVER (ORDER BY bulan))
    / LAG(omzet, 12) OVER (ORDER BY bulan), 1
  ) AS yoy_persen
FROM omzet_bulanan
ORDER BY bulan;

Ini year-over-year growth. Lebih jujur dari month-over-month buat bisnis musiman, soalnya dia bandingin Ramadan sama Ramadan, bukan Ramadan sama bulan biasa.

Syaratnya: datanya harus lengkap tanpa bulan bolong. LAG ngitung mundur 12 baris, bukan 12 bulan. Kalau ada bulan yang nggak ada barisnya, offset-nya meleset diam-diam.

Argumen ketiga buat ganti NULL:

LAG(omzet, 1, 0) OVER (ORDER BY bulan)

Baris pertama keluar 0, bukan NULL. Tapi hati-hati — kalau kamu pakai nilai itu sebagai pembagi, kamu dapat division by zero. Kadang NULL justru lebih aman.

Contoh kasus: nemuin produk yang lagi meluncur turun

Di dataset toko_berkah, 82 produk, data omzet 6 bulan terakhir. Pertanyaannya: produk mana yang turun 2 bulan berturut-turut?

WITH bulanan AS (
  SELECT
    produk,
    bulan,
    SUM(total) AS omzet
  FROM transaksi
  GROUP BY produk, bulan
),
dengan_lag AS (
  SELECT
    produk,
    bulan,
    omzet,
    LAG(omzet, 1) OVER (PARTITION BY produk ORDER BY bulan) AS m1,
    LAG(omzet, 2) OVER (PARTITION BY produk ORDER BY bulan) AS m2
  FROM bulanan
)
SELECT
  produk,
  bulan,
  m2 AS dua_bulan_lalu,
  m1 AS bulan_lalu,
  omzet AS bulan_ini
FROM dengan_lag
WHERE omzet < m1
  AND m1 < m2
  AND bulan = '2026-05'
ORDER BY (m2 - omzet) DESC;

Hasilnya: 7 produk dari 82. Yang paling parah Minyak Goreng 2L — turun dari 8,4 juta ke 6,1 juta ke 4,2 juta. Anjlok 50% dalam dua bulan.

Pas dicek, ternyata kompetitor di ujung jalan lagi jual minyak lebih murah 3.000 per botol. Tanpa query ini, pemilik toko baru sadar 3 bulan kemudian pas stok numpuk.

Perhatiin: filter omzet < m1 ditaruh di query luar, bukan di WHERE query dalam. Itu bukan gaya-gayaan — window function nggak bisa dipakai di WHERE yang sama.

Kesalahan umum LAG dan LEAD

1. Lupa ORDER BY di dalam OVER. Tanpa ORDER BY, "baris sebelumnya" nggak punya arti. Beberapa database ngasih error; yang lain diam-diam ngasih hasil ngaco.

2. Lupa PARTITION BY. Baris pertama tiap grup nyolong nilai dari grup sebelumnya. Query jalan, angka salah.

3. Pakai LAG di WHERE. Nggak bisa. Window function dieksekusi setelah WHERE. Bungkus pakai CTE atau subquery, filter di luar.

4. Nganggap offset = periode. LAG(omzet, 12) mundur 12 baris, bukan 12 bulan. Kalau ada bulan bolong, hasilnya bergeser. Pastiin datanya lengkap atau bikin tabel kalender dulu.

5. Bagi dengan hasil LAG tanpa cek NULL atau nol. Growth persen di baris pertama pasti error atau NULL. Bungkus pakai NULLIF: / NULLIF(omzet_lalu, 0).

6. Pakai MySQL 5.7. Window function baru ada di MySQL 8.0. Cek versi kamu pakai SELECT VERSION(). Detail lengkapnya di dokumentasi window function PostgreSQL yang jadi acuan standar SQL.

FAQ

Apa beda LAG dan LEAD di SQL?

LAG narik nilai dari baris sebelumnya dalam urutan yang kamu tentuin di ORDER BY. LEAD narik dari baris berikutnya. Keduanya sintaksnya identik, cuma arahnya kebalik. LAG dipakai buat ngitung pertumbuhan dari periode lalu; LEAD dipakai buat ngitung jarak ke kejadian berikutnya, misalnya berapa hari sampai pelanggan belanja lagi.

Kenapa hasil LAG saya NULL di baris pertama?

Itu perilaku normal. Baris pertama nggak punya baris sebelumnya, jadi LAG balikin NULL. Kalau kamu mau ganti jadi angka lain, isi argumen ketiga: LAG(omzet, 1, 0) bikin baris pertama keluar 0, bukan NULL. Hati-hati kalau kamu bagi pakai nilai itu — pembagian dengan nol bikin error di beberapa database.

Apakah LAG dan LEAD ada di MySQL?

Ada, tapi mulai MySQL 8.0. Versi 5.7 ke bawah nggak punya window function sama sekali, jadi kamu harus pakai self-join atau variabel. PostgreSQL udah dukung sejak versi 8.4, SQL Server sejak 2012, BigQuery dan Snowflake dukung penuh. Cek versi MySQL kamu pakai SELECT VERSION().

Bisa nggak pakai LAG di klausa WHERE?

Nggak bisa langsung. Window function dieksekusi setelah WHERE, jadi WHERE belum tau hasil LAG-nya. Solusinya: bungkus query-nya jadi subquery atau CTE, lalu filter di query luar. Pola ini juga berlaku buat semua window function lain kayak ROW_NUMBER dan RANK.

Apa gunanya PARTITION BY di LAG?

PARTITION BY bikin LAG mulai ulang dari awal di tiap grup. Kalau kamu ngitung growth per kota dan nggak pakai PARTITION BY kota, baris pertama Surabaya bakal ngambil nilai dari baris terakhir Jakarta — angkanya jadi ngawur. Dengan PARTITION BY kota, tiap kota punya urutannya sendiri dan baris pertamanya balikin NULL.

Penutup

LAG buat ngitung perubahan dari masa lalu. LEAD buat ngukur jarak ke masa depan. Dua-duanya butuh ORDER BY di dalam OVER, dan hampir selalu butuh PARTITION BY kalau datamu punya grup.

Latihan yang paling cepet nempel: ambil tabel penjualan di kerjaanmu, bikin growth bulan ke bulan, terus lihat bulan mana yang paling anjlok. Sepuluh menit, dan kamu bakal nemu sesuatu.

Lanjut baca panduan window function SQL buat kenalan sama ROW_NUMBER dan RANK, atau cek fungsi SUM kalau kamu perlu ngerapiin agregatnya duluan.

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

7 SQL Client Gratis Terbaik buat Analis Data (2026)
Tutorial SQL
10 Juli 2026•9 menit baca

7 SQL Client Gratis Terbaik buat Analis Data (2026)

Nggak perlu bayar buat nulis query. Ini 7 SQL client gratis yang beneran dipakai analis, plus kelebihan dan kekurangannya masing-masing.

BimaBima
SQL Style Guide: Cara Nulis Query yang Enak Dibaca Tim
Tutorial SQL
7 Juli 2026•9 menit baca

SQL Style Guide: Cara Nulis Query yang Enak Dibaca Tim

Sepuluh aturan SQL style guide yang bikin query kamu kebaca sama tim — dan sama kamu sendiri 6 bulan lagi. Lengkap dengan contoh sebelum-sesudah.

BimaBima
Take-Home Test SQL Data Analyst: Contoh Soal + Cara Ngerjainnya
Tutorial SQL
4 Juli 2026•11 menit baca

Take-Home Test SQL Data Analyst: Contoh Soal + Cara Ngerjainnya

Contoh soal take-home test SQL yang beneran dipakai perusahaan, dikerjain step by step — dari baca soal, nulis query, sampai nyusun insight yang bikin recruiter nengok.

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.

Copyright © 2026 - All rights reserved

LINKS
SupportPricingDatasetBlogAffiliates
LEGAL
Terms of servicesPrivacy policy
Ngulik Data
DatasetLeaderboardBlogStore