Data operasional perusahaan umumnya tinggal di dalam database: transaksi penjualan di PostgreSQL, catatan pelanggan di MySQL, atau log aktivitas di SQL Server. Menyalin isi database ke CSV setiap kali analisis membutuhkan waktu dan menghasilkan salinan yang cepat kedaluwarsa. Fungsi pd.read_sql() menyelesaikan masalah ini dengan mengeksekusi query SQL lalu mengembalikan hasilnya sebagai DataFrame, sehingga filter dan agregasi lebih lanjut tetap bisa dilakukan di pandas. Chapter ini membahas peran SQLAlchemy sebagai jembatan, membangun database SQLite untuk latihan, dan mengulas ragam fungsi pembaca SQL yang disediakan pandas.

SQLAlchemy sebagai Jembatan Database #

read_sql() tidak berbicara langsung dengan setiap jenis database. Pandas menyerahkan urusan koneksi kepada SQLAlchemy, library yang menyediakan lapisan akses seragam untuk banyak database melalui konsep engine. Engine dibuat dengan create_engine() dan URL koneksi berformat <dialect>+<driver>://<pengguna>:<kata sandi>@<host>/<nama_db>. Beberapa contoh URL yang sering dipakai:

DatabaseContoh URL SQLAlchemy
SQLite (file)sqlite:///penjualan.db
SQLite (memori)sqlite:///:memory:
PostgreSQLpostgresql+psycopg://analisis:YOUR_DATABASE_PASSWORD@localhost:5432/gudang
MySQLmysql+pymysql://analisis:YOUR_DATABASE_PASSWORD@localhost:3306/gudang

Untuk SQLite, URL cukup berisi path file database sehingga tidak ada server yang perlu dijalankan. Untuk database server, bagian YOUR_DATABASE_PASSWORD sebaiknya dibaca dari environment variable atau file konfigurasi, bukan ditulis langsung di kode. Pandas juga masih menerima objek sqlite3.Connection bawaan Python, tetapi SQLAlchemy adalah pilihan yang lebih baik karena pola kodenya sama untuk semua database; dokumentasi SQLAlchemy menjelaskan konsep engine secara mendalam.

Menyiapkan Database SQLite untuk Latihan #

Lingkungan latihan Course ini tidak memiliki server PostgreSQL atau MySQL, sedangkan SQLite justru ideal untuk belajar: satu file, tanpa instalasi, dan didukung modul sqlite3 bawaan Python. Skrip berikut membuat tabel transaksi berisi empat baris, lalu membacanya kembali lewat read_sql() dengan tiga cara berbeda. File database dibuat saat runtime dan dihapus di akhir skrip agar bundle tetap bersih.

python
import sqlite3
from pathlib import Path

import pandas as pd
from sqlalchemy import create_engine

db_path = Path("penjualan.db")
koneksi = sqlite3.connect(db_path)
koneksi.execute(
    "CREATE TABLE transaksi (id INTEGER PRIMARY KEY, kasir TEXT, kota TEXT, total INTEGER)"
)
data = [
    (1, "Budi", "Bandung", 150000),
    (2, "Siti", "Jakarta", 230000),
    (3, "Dewi", "Surabaya", 98000),
    (4, "Rian", "Bandung", 175000),
]
koneksi.executemany("INSERT INTO transaksi VALUES (?, ?, ?, ?)", data)
koneksi.commit()
koneksi.close()

engine = create_engine(f"sqlite:///{db_path}")

df = pd.read_sql("SELECT kasir, kota, total FROM transaksi", engine)
print(df)
print()
print(df.dtypes)
print()

df_bandung = pd.read_sql(
    "SELECT kasir, total FROM transaksi WHERE kota = ?", engine, params=("Bandung",)
)
print(df_bandung)
print()

df_tabel = pd.read_sql_table("transaksi", engine)
print("Kolom read_sql_table:", list(df_tabel.columns))
print("Index read_sql_table:", list(df_tabel.index))

engine.dispose()
db_path.unlink()
print("File database sementara sudah dihapus:", not db_path.exists())

Output yang Dihasilkan:

text
  kasir      kota   total
0  Budi   Bandung  150000
1  Siti   Jakarta  230000
2  Dewi  Surabaya   98000
3  Rian   Bandung  175000

kasir      str
kota       str
total    int64
dtype: object

  kasir   total
0  Budi  150000
1  Rian  175000

Kolom read_sql_table: ['id', 'kasir', 'kota', 'total']
Index read_sql_table: [0, 1, 2, 3]
File database sementara sudah dihapus: True

Baris demi baris, inilah yang terjadi:

  1. Baris 7-20: modul sqlite3 membuka file penjualan.db, membuat tabel transaksi, mengisi empat baris dengan executemany(), lalu menutup koneksi.
  2. Baris 22 (engine = create_engine(...)): SQLAlchemy engine menunjuk file yang sama; engine baru membuka koneksi saat operasi pertama berjalan.
  3. Baris 24-27: read_sql() mengeksekusi string SELECT dan mengembalikan DataFrame; kolom kasir dan kota bertipe str, kolom total bertipe int64.
  4. Baris 30-33: query memakai tanda ? dengan params=("Bandung",); nilai parameter dikirim ke driver secara terpisah sehingga aman dari SQL injection.
  5. Baris 36-38: read_sql_table() membaca seluruh isi tabel tanpa menulis SQL, dan semua kolom termasuk id ikut terbawa.
  6. Baris 40-42: engine.dispose() menutup pool koneksi, file database latihan dihapus, dan keberhasilan pembersihan dikonfirmasi.

read_sql, read_sql_query, dan read_sql_table #

Pandas menyediakan tiga pintu masuk dengan peran yang saling melengkapi. Fungsi pandas.read_sql adalah pintu umum yang menerima string query maupun nama tabel lalu menyerahkannya ke fungsi yang sesuai. Fungsi read_sql_query() khusus untuk perintah SELECT yang hasilnya berbentuk tabel, sedangkan fungsi pandas.read_sql_table mengambil seluruh isi satu tabel beserta semua kolomnya. Untuk tabel berukuran besar, parameter chunksize=n membuat fungsi mengembalikan iterator yang menghasilkan DataFrame berisi n baris per panggilan, sehingga memori tidak habis oleh satu DataFrame raksasa.

Satu detail yang sering ditanyakan: kolom id di database tidak otomatis menjadi index DataFrame. Hasil read_sql() memakai RangeIndex biasa; bila kolom id memang ingin dijadikan index, tambahkan parameter index_col="id".

Membaca data dari database baru separuh dari pekerjaan; sering kali hasil analisis juga harus ditulis kembali ke database agar bisa dipakai aplikasi atau rekan tim. Chapter berikutnya membahas to_sql() beserta tiga mode penulisannya.