to_sql() dan read_sql() tidak bisa bekerja sendirian: keduanya butuh koneksi ke database, dan pandas tidak menyertakan driver database apa pun. Di sinilah SQLAlchemy masuk — library ini menyediakan objek engine yang tahu cara berbicara dengan masing-masing database (SQLite, PostgreSQL, MySQL, dan lainnya), sementara pandas menyerahkan pembacaan dan penulisan tabel ke engine tersebut. Pada Chapter ini kita membangun engine SQLite, menulis DataFrame menjadi tabel, membaca hasil query, dan mengamankan query dengan parameter.
Peran SQLAlchemy sebagai Jembatan Database #
Objek utama yang kita butuhkan hanyalah engine dari create_engine(). Argumennya berupa connection string berpola dialect+driver://lokasi; untuk SQLite cukup sqlite:///nama_berkas.db. pandas kemudian menerima engine itu pada empat fungsi I/O:
| Fungsi pandas | Arah | Kegunaan |
|---|---|---|
df.to_sql(nama, engine) | DataFrame → tabel | menulis isi DataFrame menjadi tabel database |
pd.read_sql(sql, engine) | SQL → DataFrame | menjalankan query apa pun dan menampung hasilnya |
pd.read_sql_table(nama, engine) | tabel → DataFrame | membaca satu tabel utuh tanpa menulis SQL |
pd.read_sql_query(sql, engine) | SQL → DataFrame | padanan read_sql khusus query |
Perlu dibedakan: read_sql_table dan read_sql_query hanya berlaku dengan SQLAlchemy; read_sql cerdas dan menerima keduanya. Engine juga dikelola sebagai sumber daya — setelah selesai, panggil engine.dispose() agar koneksi tertutup rapi.
Menulis dan Membaca Tabel SQLite #
Contoh berikut menulis tabel penjualan sembako ke database SQLite sementara, lalu membacanya kembali dengan tiga gaya: query SQL biasa, query berparameter, dan pembacaan tabel utuh.
import os
import tempfile
import pandas as pd
from sqlalchemy import create_engine, text
penjualan = pd.DataFrame({
"kota": ["Jakarta", "Jakarta", "Jakarta", "Jakarta",
"Bandung", "Bandung", "Bandung", "Bandung",
"Surabaya", "Surabaya", "Surabaya", "Surabaya"],
"produk": ["Beras", "Minyak", "Gula", "Kopi",
"Beras", "Minyak", "Gula", "Kopi",
"Beras", "Minyak", "Gula", "Kopi"],
"jumlah": [40, 25, 30, 20, 32, 22, 27, 18, 36, 19, 25, 21],
"harga": [68000, 35000, 18000, 26000, 67500, 34500, 17500, 25500, 67000, 34000, 17200, 25000],
})
with tempfile.TemporaryDirectory() as folder:
db_path = os.path.join(folder, "penjualan.db")
engine = create_engine(f"sqlite:///{db_path}")
# 1. Menulis DataFrame menjadi tabel database
penjualan.to_sql("penjualan", engine, index=False, if_exists="replace")
print("tabel penjualan dibuat")
print()
# 2. Membaca hasil query SQL menjadi DataFrame
top = pd.read_sql(
"SELECT kota, produk, jumlah * harga AS total "
"FROM penjualan ORDER BY total DESC LIMIT 3",
engine,
)
print(top)
print()
# 3. Query berparameter untuk menghindari SQL injection
jakarta = pd.read_sql(
"SELECT produk, jumlah FROM penjualan "
"WHERE kota = :kota ORDER BY jumlah DESC",
engine,
params={"kota": "Jakarta"},
)
print(jakarta)
print()
# 4. Menulis ulang dan membaca seluruh tabel
with engine.begin() as conn:
conn.execute(
text("UPDATE penjualan SET jumlah = jumlah + 5 WHERE kota = 'Bandung'")
)
cek = pd.read_sql("SELECT COUNT(*) AS jumlah_baris FROM penjualan", engine)
print("jumlah baris:", cek["jumlah_baris"].tolist())
seluruh = pd.read_sql_table("penjualan", engine)
print("kolom:", seluruh.columns.tolist())
print("dtype kota:", seluruh["kota"].dtype)
engine.dispose()
print("database sementara terhapus bersama folder temp")Output yang Dihasilkan:
tabel penjualan dibuat
kota produk total
0 Jakarta Beras 2720000
1 Surabaya Beras 2412000
2 Bandung Beras 2160000
produk jumlah
0 Beras 40
1 Gula 30
2 Minyak 25
3 Kopi 20
jumlah baris: [12]
kolom: ['kota', 'produk', 'jumlah', 'harga']
dtype kota: str
database sementara terhapus bersama folder tempMari kita telaah baris-baris pentingnya:
- Baris 18-20 (
TemporaryDirectory,create_engine(...)): database dibuat di folder sementara, dancreate_engine("sqlite:///...")menghasilkan engine; pembuatan koneksi sebenarnya ditunda hingga query pertama. - Baris 23 (
penjualan.to_sql(...)):index=Falsemencegah index 0-11 ikut tersimpan sebagai kolom database, danif_exists="replace"menimpa tabel bila sudah ada; pandas menyusun perintahCREATE TABLEbeserta tipe kolomnya secara otomatis. - Baris 28-32 (
pd.read_sql(...)): string SQL dijalankan oleh engine; kolom hasiltotalberasal dari ekspresijumlah * harga, danORDER BY ... DESC LIMIT 3mengembalikan tiga transaksi terbesar. - Baris 37-42 (
params={"kota": "Jakarta"}): penanda:kotapada SQL dipasangkan dengan dictionary diparams; nilai dikirim terpisah dari teks SQL sehingga isiparamstidak pernah dieksekusi sebagai perintah. - Baris 47-50 (
engine.begin(),conn.execute(text(...))): perintah perubahan data (UPDATE,INSERT,DELETE) dijalankan lewat koneksi SQLAlchemy langsung; blokengine.begin()otomatis commit bila sukses dan membatalkan bila gagal. Perintah SQL dibungkustext()sesuai gaya SQLAlchemy 2.x. - Baris 51-56 (
read_sql,read_sql_table):COUNT(*)dikembalikan sebagai DataFrame satu sel, danread_sql_table("penjualan", engine)membaca seluruh tabel — kolom teksnya kembali sebagai dtypestrbawaan pandas 3.0.
Perhatikan bahwa hasil query selalu menjadi DataFrame lengkap dengan index baru dan dtype yang ditebak pandas dari tipe kolom database; kebutuhan penyempurnaan dtype cukup dilakukan setelahnya dengan astype().
Dengan database, data kini mengalir dua arah antara pandas dan penyimpanan relasional. Chapter berikutnya kembali ke meja kerja harian yang paling sering kita gunakan: Jupyter Notebook, tempat hasil read_sql biasanya langsung dieksplorasi.
Referensi resmi untuk pendalaman: dokumentasi SQLAlchemy , pandas.DataFrame.to_sql , dan pandas.read_sql .