Episode ini membahas analitik SQL tingkat lanjut: CTE, subqueries, dan nested queries, user-defined functions dan aggregate functions, integrasi spatial dan graph processing, serta integrasi dengan Delta Lake dan Iceberg untuk lakehouse.

Kalian sudah menguasai SQL dasar: select, join, agregasi, dan window. Episode 15 ini menaikkan level: query yang kompleks, fungsi buatan sendiri, dan integrasi dengan sistem lakehouse modern. Ini adalah keterampilan yang memisahkan analis SQL biasa dari engineer analitik.
Di dunia nyata, query tidak pernah sesederhana buku latihan. Butuh CTE untuk membagi logika, subquery untuk filtering bertingkat, UDF untuk logika khusus domain, dan format tabel yang mendukung transaksi serta time travel. Semuanya ada di Spark.
Episode ini membahas empat topik: CTE dan nested queries, UDF dan UDAF, spatial dan graph analytics, serta integrasi dengan Delta Lake dan Iceberg.
CTE (Common Table Expression) membagi query besar menjadi blok bernama yang bisa dipakai ulang. Ini membuat logika rumit mudah dibaca dan di-debug:
WITH transaksi_valid AS (
SELECT * FROM transaksi WHERE status = 'selesai'
),
per_kota AS (
SELECT kota, SUM(jumlah) AS total
FROM transaksi_valid
GROUP BY kota
)
SELECT kota, total, RANK() OVER (ORDER BY total DESC) AS peringkat
FROM per_kota;WITH transaksi_valid AS (...) mendefinisikan CTE pertama yang dipakai CTE kedua. Spark mengevaluasi CTE secara efisien dan bisa menghindari pembacaan ulang jika hasilnya dipakai beberapa kali.
Spark SQL mendukung correlated subquery — subquery yang merujuk kolom dari query luar:
SELECT nama, jumlah
FROM transaksi t
WHERE jumlah > (
SELECT AVG(jumlah)
FROM transaksi
WHERE kota = t.kota
);Subquery di atas membandingkan setiap transaksi dengan rata-rata di kotanya sendiri. Spark menangani ini dengan window dan join secara otomatis — namun untuk data sangat besar, subquery terkorelasi bisa mahal dan sering lebih baik ditulis ulang sebagai window function atau self join.
Logika yang tidak tersedia di fungsi bawaan bisa dibuat sebagai UDF (User-Defined Function). Contoh sederhana dengan PySpark:
from pyspark.sql import functions as F
def kategori_stok(stok):
if stok == 0:
return "habis"
if stok < 10:
return "menipis"
return "cukup"
spark.udf.register("kategori_stok", kategori_stok)Lalu pakai di SQL: SELECT kategori_stok(stok) FROM produk. Catatan penting: UDF Python melewati Catalyst dan memaksa serialisasi, jadi untuk data besar pastikan UDF memang diperlukan — sering kali logika yang sama bisa ditulis dengan F.when.
UDAF (User-Defined Aggregate Function) menggabungkan banyak baris menjadi satu nilai. Di Scala dan Java, implementasikan Aggregator dengan buffer yang bisa digabungkan secara paralel:
zero → merge(b, input) → merge(b1, b2) → finish(b)UDAF bisa menggabungkan hasil antar partisi, sehingga agregasi tetap terdistribusi. Detail implementasinya dibahas lebih dalam di episode 17.
Analisis spasial (jarak, bounding box, join berbasis geometri) tidak ada di inti Spark. Ekosistem menyediakan Sedona (sebelumnya GeoSpark) sebagai library pihak ketiga yang menambahkan tipe geometri dan fungsi spasial:
ST_Distance, ST_Contains, ST_Intersects, ST_BufferUntuk kebutuhan ringan tanpa library, kombinasi F.atan2, F.sin, dan trigonometri lain bisa menghitung jarak haversine — namun untuk produksi, library spesialis jauh lebih andal.
GraphX adalah API graph Spark untuk analisis jaringan: mencari jalur terpendek, peringkat halaman (PageRank), dan komunitas. Cocok untuk rekomendasi sosial, deteksi fraud, dan analisis transportasi. Diperkenalkan di episode 21 sebagai bagian ekosistem library Spark.
Gabungkan window dengan logika kompleks:
from pyspark.sql.window import Window
from pyspark.sql import functions as F
w = Window.partitionBy("kota") \
.orderBy("tanggal") \
.rowsBetween(Window.unboundedPreceding, Window.currentRow)
hasil = df.withColumn("total_kumulatif", F.sum("jumlah").over(w))rowsBetween(Window.unboundedPreceding, Window.currentRow) menciptakan kumulatif running total per kota — pola dasar untuk metrik seperti revenue to-date.
Delta Lake menambahkan lapisan transaksi ACID di atas Parquet: schema enforcement, time travel, dan merge (upsert) — kemampuan yang tidak dimiliki Parquet polos:
MERGE INTO transaksi_delta AS target
USING pembaruan AS source
ON target.id = source.id
WHEN MATCHED THEN UPDATE SET jumlah = source.jumlah
WHEN NOT MATCHED THEN INSERT *;MERGE INTO ... USING ... melakukan upsert atomik — sangat berguna untuk pipeline CDC dan SCD (slowly changing dimensions). Time travel memungkinkan query ke versi data lama: SELECT * FROM t VERSION AS OF 10.
Iceberg adalah alternatif open table format dengan fokus pada konsistensi snapshot, hidden partitioning, dan kemampuan scale besar:
CREATE TABLE produk_iceberg (
id INT, nama STRING, harga DECIMAL(10,2)
) USING iceberg
PARTITIONED BY (nama);Perbedaan utama dengan Delta: Iceberg menyimpan daftar partisi secara eksplisit (hidden partitioning) sehingga query otomatis memakai pruning tanpa kalian menyebut kolom partisi. Baik Delta maupun Iceberg mengubah data lake menjadi lakehouse — data lake dengan sifat warehouse.
Tip
Pilih open table format lebih awal dalam arsitektur, bukan setelah data membesar. Migrasi Parquet polos ke Delta atau Iceberg membutuhkan rewrite data yang mahal. Keputusan ini akan dielaborasi di episode 21.
Episode 15 memperluas perangkat analitik kalian: CTE dan subquery merapikan query kompleks, UDF dan UDAF menambahkan logika kustom, library spasial dan graph menangani domain khusus, serta Delta Lake dan Iceberg membawa transaksi dan time travel ke data lake.
Inti yang harus dibawa pulang:
Di episode 16 selanjutnya kita akan membahas save and restore, checkpointing, dan fault tolerance — checkpointing di Structured Streaming, perilaku recovery saat kegagalan, state management untuk streaming jobs, serta best practices untuk aplikasi streaming jangka panjang.