Banyak anggota tim data TokoKita menulis SQL lebih lancar daripada Python, dan sebagian besar alat analitik mereka berbicara SQL. Kabar baiknya: Spark membawa SQL ke data, bukan sebaliknya — query berjalan di executor yang sama yang memproses DataFrame, sehingga kita tidak perlu menarik data ke database lain hanya untuk menjalankan GROUP BY. Pada chapter ini kita mendaftarkan DataFrame pesanan sebagai temp view, menjalankan query SQL murni, lalu mencampur SQL dan DataFrame API dalam satu rantai kerja. Terakhir, kita membuat managed table dan memeriksa isinya lewat katalog Spark.
Temp View: Memberi Nama SQL untuk DataFrame #
createOrReplaceTempView("nama") mendaftarkan DataFrame sebagai view bernama di dalam sesi Spark. View tidak menyalin data — view hanyalah nama yang bisa dituju oleh spark.sql(), dan karena seluruh operasi tetap lazy, query SQL yang kita tulis pun menyusun rencana eksekusi yang sama seperti DataFrame API. Dua gaya penulisan tersebut pada akhirnya diterjemahkan ke optimizer yang sama, sehingga pilihan menjadi soal kenyamanan tim, bukan performa.
Skrip Lengkap dan Outputnya #
Skrip berikut memakai data pesanan TokoKita yang sama dengan chapter sebelumnya, kali ini dengan kolom tanggal:
"""Spark SQL: temp view, query SQL, campuran dengan DataFrame API, dan katalog."""
import shutil
from datetime import date
from pathlib import Path
from pyspark.sql import SparkSession
from pyspark.sql.functions import col
spark = (
SparkSession.builder
.master("local[1]")
.config("spark.ui.enabled", "false")
.getOrCreate()
)
spark.sparkContext.setLogLevel("ERROR")
# Data pesanan TokoKita: (id, tanggal, pelanggan, kota, produk, qty, harga)
pesanan = [
(1, date(2026, 1, 10), "Budi", "Bandung", "Kemeja Batik", 2, 240000),
(2, date(2026, 1, 10), "Siti", "Jakarta", "Kain Songket", 1, 850000),
(3, date(2026, 1, 11), "Dewi", "Surabaya", "Rok Tenun", 3, 310000),
(4, date(2026, 1, 11), "Budi", "Bandung", "Blouse Katun", 1, 120000),
(5, date(2026, 1, 12), "Rina", "Bandung", "Kemeja Batik", 2, 240000),
(6, date(2026, 1, 12), "Siti", "Jakarta", "Rok Tenun", 1, 310000),
]
df = spark.createDataFrame(
pesanan,
"id INT, tanggal DATE, pelanggan STRING, kota STRING, produk STRING, qty INT, harga LONG",
)
# Daftarkan DataFrame sebagai temp view bernama pesanan
df.createOrReplaceTempView("pesanan")
print("== 1. Query SQL murni ==")
spark.sql("""
SELECT kota, COUNT(*) AS jumlah_pesanan, SUM(qty * harga) AS omzet
FROM pesanan
GROUP BY kota
ORDER BY omzet DESC
""").show()
print("== 2. Hasil SQL dilanjutkan dengan DataFrame API ==")
omzet_kota = spark.sql("""
SELECT kota, SUM(qty * harga) AS omzet
FROM pesanan
GROUP BY kota
""")
omzet_kota.filter(col("omzet") > 1000000).show()
print("== 3. Hasil DataFrame API dikembalikan ke SQL ==")
df.filter(col("qty") >= 2).createOrReplaceTempView("pesanan_banyak")
spark.sql("""
SELECT id, pelanggan, produk, qty
FROM pesanan_banyak
WHERE kota = 'Bandung'
""").show()
print("== 4. Managed table dan katalog ==")
# Hapus warehouse eksekusi sebelumnya agar saveAsTable aman dijalankan ulang
if Path("spark-warehouse").exists():
shutil.rmtree("spark-warehouse")
spark.sql("""
SELECT kota, COUNT(*) AS jumlah, SUM(qty * harga) AS omzet
FROM pesanan
GROUP BY kota
""").write.mode("overwrite").saveAsTable("ringkasan_kota")
spark.sql("SHOW TABLES").show()
spark.sql("SELECT * FROM ringkasan_kota ORDER BY kota").show()Output yang Dihasilkan:
== 1. Query SQL murni ==
+--------+--------------+-------+
| kota|jumlah_pesanan| omzet|
+--------+--------------+-------+
| Jakarta| 2|1160000|
| Bandung| 3|1080000|
|Surabaya| 1| 930000|
+--------+--------------+-------+
== 2. Hasil SQL dilanjutkan dengan DataFrame API ==
+-------+-------+
| kota| omzet|
+-------+-------+
|Jakarta|1160000|
|Bandung|1080000|
+-------+-------+
== 3. Hasil DataFrame API dikembalikan ke SQL ==
+---+---------+------------+---+
| id|pelanggan| produk|qty|
+---+---------+------------+---+
| 1| Budi|Kemeja Batik| 2|
| 5| Rina|Kemeja Batik| 2|
+---+---------+------------+---+
== 4. Managed table dan katalog ==
+---------+--------------+-----------+
|namespace| tableName|isTemporary|
+---------+--------------+-----------+
| default|ringkasan_kota| false|
| | pesanan| true|
| |pesanan_banyak| true|
+---------+--------------+-----------+
+--------+------+-------+
| kota|jumlah| omzet|
+--------+------+-------+
| Bandung| 3|1080000|
| Jakarta| 2|1160000|
|Surabaya| 1| 930000|
+--------+------+-------+Mari kita telaah baris-baris kode di atas:
- Baris 26-29 (
df = spark.createDataFrame(...)): schema ditulis sebagai string DDL —id INT, tanggal DATE, ...— cara ringkas menentukan schema eksplisit tanpaStructTypeseperti chapter sebelumnya. - Baris 32 (
df.createOrReplaceTempView("pesanan")): DataFrame didaftarkan sebagai temp view bernamapesanan; mulai titik ini, nama tersebut bisa dituju oleh query SQL. - Baris 35-40 (
spark.sql(...)): query SQL murni denganCOUNT(*),SUM(qty * harga),GROUP BY, danORDER BY omzet DESC— seluruhnya SQL standar yang dikenal analis. - Baris 43-48 (
omzet_kota = ... .filter(...)): hasilspark.sql()adalah DataFrame biasa, sehingga kita bisa langsung menyambungkannya dengan.filter()milik DataFrame API — inilah campuran kedua gaya dalam satu rantai. - Baris 51 (
df.filter(...).createOrReplaceTempView(...)): arah sebaliknya — hasil rantai DataFrame API didaftarkan sebagai view baru bernamapesanan_banyak, lalu dikonsumsi oleh query SQL berikutnya. - Baris 62-66 (
...saveAsTable("ringkasan_kota")): hasil query ditulis sebagai managed table — Spark yang mengelola lokasi penyimpanannya di dalamspark-warehouse; baris 60-61 membersihkan warehouse eksekusi sebelumnya supaya skrip aman dijalankan ulang. - Baris 67 (
SHOW TABLES): output memisahkan tabel katalog (isTemporary=false) dari dua temp view (isTemporary=true), dan baris 68 membuktikan tabel tersebut bisa ditanyai seperti view.
Mencampur SQL dan DataFrame API #
Bagian 2 dan 3 output membuktikan bahwa SQL dan DataFrame API hanyalah dua pintu menuju mesin yang sama. Kapan memakai yang mana? SQL unggul saat logika sudah berbentuk query — porting langsung dari analitik lama berjalan cepat — sedangkan DataFrame API unggul saat logika butuh konstruksi programatik seperti percabangan kompleks atau transformasi bertahap yang bergantung nilai runtime. Karena hasil keduanya bisa saling dikonversi, tim TokoKita bebas memilih pintu per tahap tanpa kehilangan performa. Rujukan lengkap sintaks dan fungsi SQL Spark ada di SQL Programming Guide .
Managed Table dan Katalog #
saveAsTable() menyimpan data sekaligus mendaftarkan tabelnya ke katalog, berbeda dari temp view yang hanya memberi nama pada data yang sudah ada. Pada output bagian 4, SHOW TABLES memperlihatkan ketiganya: satu tabel katalog di namespace default dan dua temp view. Ada satu batasan penting pada sesi latihan ini: katalog bawaan Spark bersifat in-memory, sehingga definisi tabel hidup hanya selama sesi — berkas datanya tetap ada di spark-warehouse, tetapi namanya tidak terdaftar lagi di sesi berikutnya. Di produksi, TokoKita memakai metastore eksternal (misalnya Hive Metastore atau katalog lakehouse seperti yang dibahas pada section Data Lake dan Lakehouse) supaya definisi tabel bertahan lintas sesi dan bisa dipakai banyak orang.
Satu kebutuhan analitik masih belum terlihat: memadukan data pesanan dengan data pelanggan dalam satu hasil. Untuk itu kita masuk ke topik yang menentukan biaya komputasi paling besar di dunia Spark: join, pada chapter Spark Joins.