Basis Data dan Parquet

Membaca tabel basis data dengan read_sql dan bolak-balik Parquet untuk data besar.

Daftar isi

Basis data relasional dan format kolom Parquet menutup kebutuhan penyimpanan yang tidak terlayani CSV, yaitu kueri terstruktur di satu sisi dan pelestarian tipe data pada volume besar di sisi lain. Topik ini membahas penulisan dan pembacaan tabel SQLite melalui to_sql dan read_sql tanpa library tambahan, pemetaan konsep SQL ke operasi pandas, serta bolak-balik Parquet dengan to_parquet dan read_parquet. Dataset yang dipakai tetap log sensor tiga ruangan, dan kini dibangkitkan dari generator deterministik sehingga seluruh keluaran dapat direproduksi.

Metode to_sql menulis isi DataFrame menjadi tabel basis data, dan SQLite dipakai di sini karena modulnya ikut terpasang bersama Python tanpa instalasi tambahan. Blok berikut membangkitkan dataset log sensor, menulisnya menjadi tabel, lalu membaca lima baris pertamanya kembali.

python Contoh: menulis tabel log_sensor ke SQLite
import sqlite3

import numpy as np
import pandas as pd

rng = np.random.default_rng(42)
n = 96
df = pd.DataFrame({
    "waktu": pd.date_range("2026-01-05 07:00", periods=n, freq="15min"),
    "ruang": rng.choice(["Lab A", "Lab B", "Ruang Arsip"], n, p=[0.4, 0.35, 0.25]),
    "suhu_c": np.round(rng.normal(24, 1.5, n), 1),
    "lembap_pct": np.round(rng.normal(60, 5, n), 1),
    "status_sensor": rng.choice(["normal", "kalibrasi", "rusak"], n, p=[0.85, 0.10, 0.05]),
})
kolom = ["ruang", "suhu_c", "lembap_pct", "status_sensor"]
con = sqlite3.connect("sensor.db")
df[kolom].to_sql("log_sensor", con, if_exists="replace", index=False)
tabel = pd.read_sql("SELECT * FROM log_sensor LIMIT 5", con)
print(tabel)
print(tabel.dtypes)
con.close()
text Keluaran
         ruang  suhu_c  lembap_pct status_sensor
0  Ruang Arsip    21.5        68.6        normal
1        Lab B    21.8        59.1        normal
2  Ruang Arsip    22.0        58.1        normal
3        Lab B    22.5        67.3        normal
4        Lab A    24.6        54.5        normal
ruang                str
suhu_c           float64
lembap_pct       float64
status_sensor        str
dtype: object

Koneksi dibuka dengan sqlite3.connect("sensor.db"), dan berkas basis data yang belum ada dibuat otomatis olehnya. Parameter if_exists="replace" mengganti tabel lama dengan yang baru sehingga blok ini aman dijalankan berulang, sedangkan index=False mencegah index DataFrame ikut menjadi kolom tabel. Kolom waktu sengaja tidak ikut karena SQLite tidak memiliki tipe tanggal bawaan, sehingga tabel berfokus pada kolom pengukuran. Hasil bacaan menunjukkan tipe terjaga dengan baik, yaitu teks bertipe str dan angka bertipe float64.

Query dengan parameter#

Metode read_sql menjalankan pernyataan SELECT dan mengembalikan hasilnya sebagai DataFrame, dan parameter params mengisi tanda tanya pada query dengan nilai yang diberikan. Blok berikut menyaring baris terpanas yang berstatus normal, lalu merangkum tabel per ruangan dengan agregasi SQL.

python Contoh: query dengan params dan agregasi
import sqlite3

import pandas as pd

con = sqlite3.connect("sensor.db")
query = """
SELECT ruang, suhu_c, lembap_pct
FROM log_sensor
WHERE suhu_c > ? AND status_sensor = ?
ORDER BY suhu_c DESC
"""
hasil = pd.read_sql(query, con, params=(24.0, "normal"))
print(hasil.head(5))
ringkas = pd.read_sql(
    "SELECT ruang, COUNT(*) AS jumlah, ROUND(AVG(suhu_c), 2) AS rata_suhu_c "
    "FROM log_sensor GROUP BY ruang", con,
)
print(ringkas)
con.close()
text Keluaran
         ruang  suhu_c  lembap_pct
0  Ruang Arsip    28.4        56.7
1        Lab A    27.2        59.3
2  Ruang Arsip    27.0        56.4
3        Lab B    26.7        61.7
4        Lab B    26.5        57.6
         ruang  jumlah  rata_suhu_c
0        Lab A      36        23.73
1        Lab B      38        23.81
2  Ruang Arsip      22        24.26

Tanda tanya pada query adalah penanda tempat nilai, dan pasangan nilainya diberikan lewat params dalam bentuk tuple. Nilai tidak pernah ditempelkan langsung ke string query, sehingga pembacaan aman dari penyuntikan SQL (SQL injection) dan terbebas dari masalah tanda kutip ketika nilainya mengandung karakter khusus. Query kedua memperlihatkan agregasi di sisi basis data, yaitu menghitung jumlah baris dan rata-rata suhu per ruangan sebelum data menyentuh pandas. Koneksi ditutup dengan con.close() setelah seluruh pembacaan selesai.

Peta konsep SQL dan pandas#

Operasi SELECT pada SQL memiliki padanan langsung pada operasi pandas, dan pemetaan ini mempermudah pembaca yang sudah terbiasa dengan basis data maupun yang sebaliknya. Tabel berikut menyusun padanan operasi yang paling sering dipakai pada dataset log sensor ini.

SQLpandas
SELECT ruang, suhu_cdf[["ruang", "suhu_c"]]
WHERE suhu_c > 24df[df["suhu_c"] > 24]
ORDER BY suhu_c DESCdf.sort_values("suhu_c", ascending=False)
GROUP BY ruang dengan AVGdf.groupby("ruang")["suhu_c"].mean()
LIMIT 5df.head(5)
JOIN antartabelpd.merge pada modul Penggabungan Data

Padanan WHERE adalah indeks boolean yang dibahas pada topik Indeks Boolean, sedangkan padanan GROUP BY beserta agregasinya dibahas pada topik Agregasi Dasar. Kedua pendekatan menghasilkan data yang sama, tetapi batas kerjanya berbeda. Agregasi berat sebaiknya ditumpangkan ke basis data lewat query agar data yang berpindah ke memori sudah ringkas, sedangkan penyaringan dan pengolahan lanjutan lebih lentur dilakukan pada pandas. Pengetahuan kunci join dari SQL juga tetap terpakai karena modul Penggabungan Data membahas merge dengan istilah yang sama, yaitu inner, left, right, dan outer.

Parquet mempertahankan dtype#

Format Parquet menyimpan tipe data setiap kolom bersama nilainya, sehingga bolak-balik to_parquet dan read_parquet tidak mengubah dtype sama sekali. Blok berikut membuktikannya pada dataset log sensor yang memuat kolom datetime, teks, dan angka sekaligus.

python Contoh: bolak-balik Parquet dengan dtype utuh
import numpy as np
import pandas as pd

rng = np.random.default_rng(42)
n = 96
df = pd.DataFrame({
    "waktu": pd.date_range("2026-01-05 07:00", periods=n, freq="15min"),
    "ruang": rng.choice(["Lab A", "Lab B", "Ruang Arsip"], n, p=[0.4, 0.35, 0.25]),
    "suhu_c": np.round(rng.normal(24, 1.5, n), 1),
    "lembap_pct": np.round(rng.normal(60, 5, n), 1),
    "status_sensor": rng.choice(["normal", "kalibrasi", "rusak"], n, p=[0.85, 0.10, 0.05]),
})
df.to_parquet("log_sensor.parquet")
df_baca = pd.read_parquet("log_sensor.parquet")
print(df.dtypes)
print(df_baca.dtypes)
print(df.equals(df_baca))
text Keluaran
waktu            datetime64[us]
ruang                       str
suhu_c                  float64
lembap_pct              float64
status_sensor               str
dtype: object
waktu            datetime64[us]
ruang                       str
suhu_c                  float64
lembap_pct              float64
status_sensor               str
dtype: object
True

Dua dtypes pada keluaran identik dan equals mengembalikan True, yang berarti isi tabel sama persis sebelum dan sesudah disimpan. Kolom waktu tetap datetime64[us] tanpa konversi apa pun, sedangkan teks tetap str. Bandingkan dengan CSV yang menulis tanggal sebagai teks dan menuntut parse_dates saat dibaca ulang seperti pada topik read_csv Secara Mendalam. Sifat inilah yang membuat Parquet cocok menjadi format kerja, yaitu wadah data antartahap pipeline yang tidak menuntut perbaikan tipe setiap kali dibuka.

Ukuran berkas CSV versus Parquet#

Untuk data numerik besar, ukuran berkas Parquet jauh lebih kecil daripada CSV yang isinya setara. Blok berikut membangkitkan seratus ribu baris pengukuran sensor yang dibulatkan satu desimal, lalu membandingkan ukuran kedua berkasnya.

python Contoh: perbandingan ukuran CSV dan Parquet
import os

import numpy as np
import pandas as pd

rng = np.random.default_rng(7)
besar = pd.DataFrame({
    "suhu_c": np.round(rng.normal(24, 1.5, 100_000), 1),
    "lembap_pct": np.round(rng.normal(60, 5, 100_000), 1),
    "tekanan_hpa": np.round(rng.normal(1013, 5, 100_000), 1),
})
besar.to_csv("besar.csv", index=False)
besar.to_parquet("besar.parquet")
ukuran_csv = os.path.getsize("besar.csv")
ukuran_parquet = os.path.getsize("besar.parquet")
print(f"CSV     : {ukuran_csv:,} byte")
print(f"Parquet : {ukuran_parquet:,} byte")
print(f"rasio   : {ukuran_csv / ukuran_parquet:.1f} kali")
text Keluaran
CSV     : 1,699,590 byte
Parquet : 320,439 byte
rasio   : 5.3 kali

Parquet berukuran sekitar seperlima CSV untuk data yang sama, dan keuntungannya bergerak mengikuti pola pada data. Dua hal menjelaskannya. CSV menyimpan setiap baris sebagai teks yang dipisahkan koma, sedangkan Parquet menyimpan nilai per kolom dalam bentuk biner yang dipadatkan secara otomatis dengan kompresi snappy sebagai nilai bawaannya. Pembulatan satu desimal juga membantu kompresi karena banyak nilai yang berulang dan mudah diringkas, dan pembacaan yang sama tanpa pembulatan hanya memberi rasio sekitar dua kali. Dengan selisih seperti ini, menyimpan data kerja dalam CSV berarti memboroskan ruang penyimpanan sekaligus kecepatan baca, kecuali berkasnya memang ditujukan untuk pertukaran antaralat.

Ringkasan#

  • to_sql menulis DataFrame menjadi tabel SQLite dengan koneksi sqlite3 tanpa SQLAlchemy, dan read_sql membacanya kembali sebagai DataFrame bertipe terjaga.
  • Parameter params mengisi tanda tanya pada query sehingga nilainya tidak ditempelkan ke string dan aman dari penyuntikan SQL.
  • to_parquet dan read_parquet mempertahankan seluruh dtype, termasuk datetime dan teks, tanpa konversi apa pun.
  • Untuk data numerik besar, Parquet berukuran beberapa kali lebih kecil daripada CSV karena menyimpan kolom biner yang dipadatkan.

Referensi#