DuckDB: Database Analitik In-Process untuk Data Science
DuckDB adalah database analitik in-process yang dirancang khusus untuk workload OLAP (Online Analytical Processing). Berbeda dengan database tradisional seperti PostgreSQL atau MySQL yang memerlukan server terpisah, DuckDB berjalan langsung di dalam proses aplikasi Anda, mirip seperti SQLite tetapi dioptimalkan untuk analitik. Dalam tutorial ini, kita akan mempelajari cara menggunakan DuckDB untuk workflow data science yang efisien dan powerful.
Mengapa DuckDB?
Sebelum membahas teknis, mari pahami mengapa DuckDB menjadi pilihan populer di kalangan data scientist:
- Tanpa server: Tidak perlu instalasi database server, konfigurasi, atau manajemen
- Columnar storage: Data disimpan per kolom, optimal untuk query analitik
- Vectorized execution: Memproses data dalam batch untuk performa maksimal
- Query file langsung: Bisa query CSV, Parquet, dan JSON tanpa import terlebih dahulu
- Integrasi Python: API Python yang seamless dengan Pandas dan Polars
- SQL standar: Mendukung SQL yang kaya fitur termasuk window functions dan CTE
Instalasi
Instalasi DuckDB sangat mudah. Cukup gunakan pip:
pip install duckdb
Untuk verifikasi instalasi:
import duckdb
print(duckdb.version)
Jika Anda ingin menggunakan CLI DuckDB, bisa download binary dari website resmi atau gunakan:
pip install duckdb[cli]
Memulai dengan DuckDB
In-Memory Database
Cara paling sederhana untuk memulai adalah dengan in-memory database:
import duckdb
Membuat koneksi in-memory
con = duckdb.connect()
Atau secara eksplisit
con = duckdb.connect(database=':memory:')
Menjalankan query sederhana
result = con.sql("SELECT 42 AS jawaban, 'Halo DuckDB' AS pesan")
print(result.fetchall())
[(42, 'Halo DuckDB')]
Persistent Database
Untuk menyimpan data secara permanen, berikan nama file:
import duckdb
Membuat atau membuka database persisten
con = duckdb.connect('analitik.duckdb')
Membuat tabel
con.sql("""
CREATE TABLE IF NOT EXISTS penjualan (
id INTEGER PRIMARY KEY,
produk VARCHAR,
kategori VARCHAR,
jumlah INTEGER,
harga DECIMAL(10, 2),
tanggal DATE
)
""")
Insert data
con.sql("""
INSERT INTO penjualan VALUES
(1, 'Laptop Pro', 'Elektronik', 5, 15000000, '2026-01-15'),
(2, 'Mouse Wireless', 'Aksesoris', 50, 250000, '2026-01-16'),
(3, 'Keyboard Mekanik', 'Aksesoris', 30, 750000, '2026-01-16'),
(4, 'Monitor 27 inch', 'Elektronik', 10, 4500000, '2026-01-17'),
(5, 'Headset Gaming', 'Aksesoris', 25, 500000, '2026-01-18')
""")
Query data
result = con.sql("SELECT FROM penjualan WHERE kategori = 'Elektronik'")
result.show()
Query File Langsung Tanpa Import
Salah satu fitur paling powerful dari DuckDB adalah kemampuan query file langsung.
Query CSV
import duckdb
Query CSV langsung
result = duckdb.sql("""
SELECT
FROM readcsvauto('data/transaksi.csv')
WHERE total > 1000000
ORDER BY tanggal DESC
LIMIT 10
""")
result.show()
Dengan opsi spesifik
result = duckdb.sql("""
SELECT
FROM readcsv('data/transaksi.csv',
delim=',',
header=true,
dateformat='%Y-%m-%d'
)
""")
Query Parquet
Parquet adalah format file kolumnar yang sangat efisien. DuckDB membacanya secara native:
import duckdb
Query file Parquet
result = duckdb.sql("""
SELECT
kategori,
COUNT() AS jumlahtransaksi,
SUM(total) AS totalpendapatan,
AVG(total) AS ratarata
FROM readparquet('data/transaksi2026.parquet')
GROUP BY kategori
ORDER BY totalpendapatan DESC
""")
result.show()
Query multiple file Parquet dengan glob pattern
result = duckdb.sql("""
SELECT
FROM readparquet('data/transaksi.parquet')
""")
Query Parquet dari URL (S3, HTTP)
result = duckdb.sql("""
SELECT
FROM readparquet('https://example.com/data/sample.parquet')
LIMIT 100
""")
Query JSON
import duckdb
Query file JSON
result = duckdb.sql("""
SELECT
FROM readjsonauto('data/logaktivitas.json')
WHERE eventtype = 'purchase'
""")
result.show()
JSON Lines format
result = duckdb.sql("""
SELECT
FROM readjsonauto('data/events.jsonl', format='newlinedelimited')
""")
Python API dan Integrasi
Integrasi dengan Pandas
DuckDB terintegrasi sangat baik dengan Pandas DataFrame:
import duckdb
import pandas as pd
Membuat DataFrame Pandas
dfpenjualan = pd.DataFrame({
'produk': ['Laptop', 'Mouse', 'Keyboard', 'Monitor', 'Headset'],
'kategori': ['Elektronik', 'Aksesoris', 'Aksesoris', 'Elektronik', 'Aksesoris'],
'harga': [15000000, 250000, 750000, 4500000, 500000],
'stok': [10, 100, 50, 20, 75]
})
Query DataFrame langsung dengan SQL
result = duckdb.sql("""
SELECT
kategori,
COUNT() AS jumlahproduk,
AVG(harga) AS ratarataharga,
SUM(stok) AS totalstok
FROM dfpenjualan
GROUP BY kategori
""")
Konversi hasil ke DataFrame
dfresult = result.df()
print(dfresult)
Gabungkan DataFrame dan file
dfpelanggan = pd.DataFrame({
'id': [1, 2, 3],
'nama': ['Andi', 'Budi', 'Citra'],
'kota': ['Jakarta', 'Surabaya', 'Bandung']
})
result = duckdb.sql("""
SELECT
p.nama,
p.kota,
t.
FROM dfpelanggan p
JOIN readcsvauto('data/transaksi.csv') t
ON p.id = t.pelangganid
ORDER BY t.total DESC
""")
Integrasi dengan Polars
DuckDB juga mendukung Polars, library DataFrame modern yang ditulis dalam Rust:
import duckdb
import polars as pl
Membuat Polars DataFrame
df = pl.DataFrame({
'nama': ['Produk A', 'Produk B', 'Produk C', 'Produk D'],
'penjualanq1': [1500, 2300, 800, 3100],
'penjualanq2': [1800, 2100, 1200, 2900],
'penjualanq3': [2000, 2500, 1500, 3300],
'penjualanq4': [2200, 2800, 1800, 3500]
})
Query Polars DataFrame dengan DuckDB
result = duckdb.sql("""
SELECT
nama,
penjualanq1 + penjualanq2 + penjualanq3 + penjualanq4 AS totaltahunan,
(penjualanq4 - penjualanq1)::FLOAT / penjualanq1 100 AS pertumbuhanpersen
FROM df
ORDER BY totaltahunan DESC
""")
Konversi ke Polars DataFrame
plresult = result.pl()
print(plresult)
Window Functions dan Aggregasi Lanjutan
DuckDB mendukung window functions yang powerful untuk analisis data:
import duckdb
con = duckdb.connect()
Membuat data sampel
con.sql("""
CREATE TABLE transaksi AS
SELECT FROM (VALUES
('2026-01-01', 'Elektronik', 5000000),
('2026-01-02', 'Pakaian', 1500000),
('2026-01-03', 'Elektronik', 3000000),
('2026-01-04', 'Makanan', 500000),
('2026-01-05', 'Pakaian', 2000000),
('2026-01-06', 'Elektronik', 7000000),
('2026-01-07', 'Makanan', 800000),
('2026-01-08', 'Pakaian', 1800000),
('2026-01-09', 'Elektronik', 4500000),
('2026-01-10', 'Makanan', 600000)
) AS t(tanggal, kategori, total)
""")
Running total per kategori
result = con.sql("""
SELECT
tanggal,
kategori,
total,
SUM(total) OVER (
PARTITION BY kategori
ORDER BY tanggal
ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
) AS runningtotal,
ROWNUMBER() OVER (
PARTITION BY kategori
ORDER BY total DESC
) AS ranking,
LAG(total) OVER (
PARTITION BY kategori
ORDER BY tanggal
) AS totalsebelumnya,
total - COALESCE(LAG(total) OVER (
PARTITION BY kategori
ORDER BY tanggal
), 0) AS selisih
FROM transaksi
ORDER BY kategori, tanggal
""")
result.show()
Moving average
result = con.sql("""
SELECT
tanggal,
total,
AVG(total) OVER (
ORDER BY tanggal
ROWS BETWEEN 2 PRECEDING AND CURRENT ROW
) AS movingavg3hari,
NTILE(4) OVER (ORDER BY total) AS kuartil
FROM transaksi
""")
result.show()
Aggregasi dengan GROUPING SETS, ROLLUP, dan CUBE
# ROLLUP untuk subtotal bertingkat
result = con.sql("""
SELECT
kategori,
EXTRACT(MONTH FROM tanggal::DATE) AS bulan,
SUM(total) AS totalpenjualan,
COUNT() AS jumlahtransaksi
FROM transaksi
GROUP BY ROLLUP (kategori, EXTRACT(MONTH FROM tanggal::DATE))
ORDER BY kategori NULLS LAST, bulan NULLS LAST
""")
result.show()
Extensions DuckDB
DuckDB memiliki sistem extension yang memperluas fungsionalitasnya:
import duckdb
con = duckdb.connect()
Melihat extension yang tersedia
con.sql("SELECT FROM duckdbextensions()").show()
Install dan load extension
con.sql("INSTALL httpfs")
con.sql("LOAD httpfs")
Sekarang bisa query file dari HTTP/S3
result = con.sql("""
SELECT
FROM readparquet('https://example.com/data/sample.parquet')
LIMIT 10
""")
Extension untuk format spatial
con.sql("INSTALL spatial")
con.sql("LOAD spatial")
Extension JSON
con.sql("INSTALL json")
con.sql("LOAD json")
Extension full-text search
con.sql("INSTALL fts")
con.sql("LOAD fts")
Perbandingan Performa: DuckDB vs SQLite vs Pandas
Berikut perbandingan karakteristik ketiga tool populer ini:
import duckdb
import pandas as pd
import sqlite3
import time
Membuat dataset besar untuk benchmark
nrows = 1000000
duckdb.sql(f"""
CREATE TABLE benchmark AS
SELECT
i AS id,
'Produk' || (i % 1000) AS produk,
'Kategori' || (i % 50) AS kategori,
(random() 1000000)::INTEGER AS total,
'2026-01-01'::DATE + (i % 365) AS tanggal
FROM generateseries(1, {nrows}) AS t(i)
""")
Benchmark DuckDB
start = time.time()
resultduck = duckdb.sql("""
SELECT
kategori,
COUNT() AS cnt,
SUM(total) AS sumtotal,
AVG(total) AS avgtotal
FROM benchmark
GROUP BY kategori
ORDER BY sumtotal DESC
""").df()
timeduck = time.time() - start
print(f"DuckDB: {timeduck:.4f} detik")
Benchmark Pandas (equivalen)
df = duckdb.sql("SELECT FROM benchmark").df()
start = time.time()
resultpandas = (
df.groupby('kategori')
.agg(cnt=('id', 'count'), sumtotal=('total', 'sum'), avgtotal=('total', 'mean'))
.sortvalues('sumtotal', ascending=False)
.resetindex()
)
timepandas = time.time() - start
print(f"Pandas: {timepandas:.4f} detik")
Secara umum, keunggulan masing-masing:
| Aspek | DuckDB | SQLite | Pandas |
|-------|--------|--------|--------|
| Tipe workload | OLAP (Analitik) | OLTP (Transaksional) | Analitik |
| Storage | Columnar | Row-based | In-memory |
| Dataset besar | Sangat baik | Kurang optimal | Terbatas RAM |
| SQL support | Sangat kaya | Dasar | Tidak native |
| Memory usage | Efisien (streaming) | Efisien | Tinggi |
| Kecepatan aggregasi | Sangat cepat | Lambat | Cepat (kecil) |
Contoh Praktis: Analisis Dataset Besar Tanpa Load ke Memory
Berikut contoh nyata menganalisis dataset besar tanpa harus memuatnya sepenuhnya ke memori:
import duckdb
con = duckdb.connect()
Simulasi: buat dataset besar dan simpan sebagai Parquet
con.sql("""
COPY (
SELECT
i AS transactionid,
'CUST-' || LPAD((i % 10000)::VARCHAR, 5, '0') AS customerid,
CASE (i % 5)
WHEN 0 THEN 'Elektronik'
WHEN 1 THEN 'Pakaian'
WHEN 2 THEN 'Makanan'
WHEN 3 THEN 'Olahraga'
WHEN 4 THEN 'Buku'
END AS kategori,
CASE (i % 3)
WHEN 0 THEN 'Jakarta'
WHEN 1 THEN 'Surabaya'
WHEN 2 THEN 'Bandung'
END AS kota,
(random() 5000000 + 50000)::INTEGER AS total,
'2025-01-01'::DATE + (i % 365) AS tanggal
FROM generateseries(1, 5000000) AS t(i)
) TO 'datatransaksibesar.parquet' (FORMAT PARQUET)
""")
print("Dataset 5 juta baris berhasil dibuat!")
Analisis 1: Tren penjualan bulanan per kategori
result = con.sql("""
SELECT
DATETRUNC('month', tanggal) AS bulan,
kategori,
COUNT() AS jumlahtransaksi,
SUM(total) AS totalpenjualan,
AVG(total)::INTEGER AS rataratatransaksi
FROM readparquet('datatransaksibesar.parquet')
GROUP BY DATETRUNC('month', tanggal), kategori
ORDER BY bulan, totalpenjualan DESC
""")
print("\n=== Tren Penjualan Bulanan ===")
result.show()
Analisis 2: Top customer per kota
result = con.sql("""
WITH customerspending AS (
SELECT
customerid,
kota,
SUM(total) AS totalbelanja,
COUNT() AS frekuensi,
AVG(total)::INTEGER AS avgbelanja
FROM readparquet('datatransaksibesar.parquet')
GROUP BY customerid, kota
),
ranked AS (
SELECT
,
ROWNUMBER() OVER (
PARTITION BY kota
ORDER BY totalbelanja DESC
) AS ranking
FROM customerspending
)
SELECT kota, customerid, totalbelanja, frekuensi, avgbelanja
FROM ranked
WHERE ranking <= 5
ORDER BY kota, ranking
""")
print("\n=== Top 5 Customer per Kota ===")
result.show()
Analisis 3: Distribusi penjualan dengan percentile
result = con.sql("""
SELECT
kategori,
MIN(total) AS mintotal,
PERCENTILECONT(0.25) WITHIN GROUP (ORDER BY total)::INTEGER AS p25,
PERCENTILECONT(0.50) WITHIN GROUP (ORDER BY total)::INTEGER AS median,
PERCENTILECONT(0.75) WITHIN GROUP (ORDER BY total)::INTEGER AS p75,
MAX(total) AS maxtotal,
STDDEV(total)::INTEGER AS stddev
FROM readparquet('datatransaksibesar.parquet')
GROUP BY kategori
ORDER BY median DESC
""")
print("\n=== Distribusi Penjualan per Kategori ===")
result.show()
Analisis 4: Cohort analysis - retensi pelanggan
result = con.sql("""
WITH firstpurchase AS (
SELECT
customerid,
MIN(tanggal) AS tanggalpertama
FROM readparquet('datatransaksibesar.parquet')
GROUP BY customerid
),
cohortdata AS (
SELECT
DATETRUNC('month', fp.tanggalpertama) AS cohortbulan,
DATEDIFF('month', fp.tanggalpertama, t.tanggal) AS bulanke,
COUNT(DISTINCT t.customerid) AS jumlahcustomer
FROM readparquet('datatransaksibesar.parquet') t
JOIN firstpurchase fp ON t.customerid = fp.customerid
GROUP BY cohortbulan, bulanke
)
SELECT
cohortbulan,
bulanke,
jumlahcustomer
FROM cohortdata
WHERE bulanke <= 6
ORDER BY cohortbulan, bulanke
LIMIT 30
""")
print("\n=== Cohort Analysis ===")
result.show()
Export hasil analisis
con.sql("""
COPY (
SELECT
kota,
kategori,
DATETRUNC('month', tanggal) AS bulan,
SUM(total) AS totalpenjualan,
COUNT() AS jumlahtransaksi
FROM readparquet('datatransaksibesar.parquet')
GROUP BY kota, kategori, DATETRUNC('month', tanggal)
) TO 'ringkasanpenjualan.csv' (HEADER, DELIMITER ',')
""")
print("\nHasil analisis berhasil diekspor ke CSV!")
Tips dan Best Practices
1. Gunakan Parquet untuk Dataset Besar
# Konversi CSV ke Parquet untuk performa lebih baik
duckdb.sql("""
COPY (SELECT FROM readcsvauto('databesar.csv'))
TO 'databesar.parquet' (FORMAT PARQUET, COMPRESSION ZSTD)
""")
2. Manfaatkan Lazy Evaluation
# Gunakan relation untuk lazy evaluation
rel = duckdb.sql("SELECT FROM readparquet('data.parquet')")
filtered = rel.filter("total > 1000000")
grouped = filtered.aggregate("kategori, SUM(total) AS total")
Query baru dieksekusi saat .show() atau .df() dipanggil
grouped.show()
3. Konfigurasi Memory dan Thread
import duckdb
con = duckdb.connect()
con.sql("SET memorylimit = '4GB'")
con.sql("SET threads = 4")
con.sql("SET tempdirectory = '/tmp/duckdbtemp'")
4. Gunakan EXPLAIN untuk Optimasi Query
con.sql("""
EXPLAIN ANALYZE
SELECT kategori, SUM(total)
FROM read_parquet('data.parquet')
GROUP BY kategori
""").show()
Kesimpulan
DuckDB adalah tool yang sangat powerful untuk data scientist. Kemampuannya untuk query file langsung, integrasi seamless dengan Python ecosystem, dan performa yang luar biasa untuk workload analitik menjadikannya pilihan ideal untuk:
- Eksplorasi data awal (EDA) dengan SQL
- Analisis dataset besar tanpa infrastruktur database
- Prototyping pipeline data
- Pengganti Pandas untuk dataset yang tidak muat di memori
- ETL sederhana tanpa setup yang rumit
Dengan DuckDB, Anda mendapatkan kekuatan SQL analitik yang setara dengan data warehouse, langsung di laptop Anda tanpa perlu setup server apapun.