Pertemuan 10 - SQLite Database

Pertemuan 10 - SQLite Database

Merancang database, membuat tabel beserta relasinya, dan menghubungkan Python dengan SQLite untuk aplikasi penjualan.

Tujuan Praktikum

Setelah menyelesaikan praktikum ini, mahasiswa mampu:

  1. Menjelaskan apa itu database dan mengapa data disimpan dalam tabel.
  2. Menjelaskan konsep Primary Key, Foreign Key, dan relasi antar tabel.
  3. Menghubungkan Python dengan SQLite menggunakan modul sqlite3.
  4. Membuat empat tabel aplikasi penjualan beserta relasinya.
  5. Mengisi data awal dan menampilkannya kembali, termasuk menggabungkan tabel dengan JOIN.

Persiapan

Kebutuhan praktikum:

text
- VS Code
- Project toko-elektronik (dari Pertemuan 09)
- Virtual environment aktif (venv)
- Modul sqlite3 (bawaan Python, tidak perlu diinstal)

Buka kembali project toko-elektronik di VS Code dan aktifkan venv:

bash
venv\Scripts\activate
macOS / Linux: gunakan source venv/bin/activate.

Buat file baru bernama setup_database.py di folder utama project.

Konsep Singkat

Mengapa Butuh Database?

Sampai Pertemuan 07, data kita tersimpan dalam file CSV. Cara itu cukup untuk menganalisis, tetapi menyulitkan bila data harus sering ditambah, diubah, dan dihapus oleh sebuah aplikasi. Database dirancang khusus untuk itu.

Kita memakai SQLite — database yang ringan dan tersimpan hanya sebagai satu file (toko.db), tanpa perlu memasang server. Python sudah menyediakan modul sqlite3 secara bawaan.

Bahasa untuk berbicara dengan database disebut SQL (Structured Query Language).

Tabel, Primary Key, dan Foreign Key

Data disimpan dalam tabel (mirip DataFrame: ada baris dan kolom).

  • Primary Key (PK) — kolom penanda unik tiap baris. Contoh: id_produk. Tidak boleh kembar.
  • Foreign Key (FK) — kolom yang menunjuk ke Primary Key di tabel lain. Inilah yang menghubungkan tabel.

Rancangan Database Aplikasi Penjualan

Aplikasi kita memakai empat tabel. Perhatikan relasinya:

Relasi tabel database

Membaca relasi di atas:

  • Satu pelanggan dapat melakukan banyak penjualan (1–N).
  • Satu penjualan (nota) memiliki banyak baris detail_penjualan (1–N). Pola ini disebut master–detail.
  • Satu produk dapat muncul di banyak detail_penjualan (1–N).
Mengapa dipisah penjualan dan detail_penjualan? Karena satu nota bisa berisi banyak barang. Tabel penjualan menyimpan informasi nota-nya (tanggal, pembeli, total), sedangkan detail_penjualan menyimpan daftar barang di nota itu. Rancangan inilah yang akan dipakai pada CRUD Transaksi (Pertemuan 13).

Tipe Data SQLite

Tipe Untuk
INTEGER Bilangan bulat (id, harga, stok, qty)
TEXT Teks (nama, kategori, tanggal)
REAL Bilangan desimal

Langkah 1 - Menghubungkan Python dengan SQLite

Buka setup_database.py, lalu tulis kode berikut. Tiga baris ini adalah pola dasar setiap kali bekerja dengan database.

python
import sqlite3

# 1. membuka koneksi (file dibuat otomatis jika belum ada)
conn = sqlite3.connect("database/toko.db")

# 2. membuat cursor sebagai alat menjalankan perintah SQL
cursor = conn.cursor()

print("Koneksi database berhasil.")

# 3. menutup koneksi
conn.close()

Jalankan:

bash
python setup_database.py

Output:

text
Koneksi database berhasil.

Periksa folder database/ — kini muncul file toko.db.

Istilah: conn (connection) adalah sambungan ke database; cursor adalah alat untuk menjalankan perintah SQL dan mengambil hasilnya.

Langkah 2 - Membuat Tabel

Perintah SQL untuk membuat tabel adalah CREATE TABLE. Tulis ulang isi setup_database.py menjadi:

python
import sqlite3

conn = sqlite3.connect("database/toko.db")
cursor = conn.cursor()

# aktifkan pengecekan relasi antar tabel
cursor.execute("PRAGMA foreign_keys = ON")

# --- Tabel produk ---
cursor.execute("""
CREATE TABLE IF NOT EXISTS produk (
    id_produk   INTEGER PRIMARY KEY AUTOINCREMENT,
    nama_produk TEXT    NOT NULL,
    kategori    TEXT    NOT NULL,
    harga       INTEGER NOT NULL,
    stok        INTEGER NOT NULL DEFAULT 0
)
""")

# --- Tabel pelanggan ---
cursor.execute("""
CREATE TABLE IF NOT EXISTS pelanggan (
    id_pelanggan INTEGER PRIMARY KEY AUTOINCREMENT,
    nama         TEXT NOT NULL,
    telepon      TEXT,
    alamat       TEXT
)
""")

# --- Tabel penjualan (master) ---
cursor.execute("""
CREATE TABLE IF NOT EXISTS penjualan (
    id_penjualan INTEGER PRIMARY KEY AUTOINCREMENT,
    tanggal      TEXT    NOT NULL,
    id_pelanggan INTEGER NOT NULL,
    total        INTEGER NOT NULL DEFAULT 0,
    FOREIGN KEY (id_pelanggan) REFERENCES pelanggan(id_pelanggan)
)
""")

# --- Tabel detail_penjualan (detail) ---
cursor.execute("""
CREATE TABLE IF NOT EXISTS detail_penjualan (
    id_detail    INTEGER PRIMARY KEY AUTOINCREMENT,
    id_penjualan INTEGER NOT NULL,
    id_produk    INTEGER NOT NULL,
    qty          INTEGER NOT NULL,
    subtotal     INTEGER NOT NULL,
    FOREIGN KEY (id_penjualan) REFERENCES penjualan(id_penjualan),
    FOREIGN KEY (id_produk)    REFERENCES produk(id_produk)
)
""")

conn.commit()
conn.close()
print("Empat tabel berhasil dibuat.")

Jalankan kembali. Output:

text
Empat tabel berhasil dibuat.

Penjelasan kata kunci SQL:

Kata kunci Arti
CREATE TABLE IF NOT EXISTS Buat tabel, lewati bila sudah ada
PRIMARY KEY AUTOINCREMENT Kunci utama, nomornya bertambah otomatis
NOT NULL Kolom wajib diisi
DEFAULT 0 Nilai bawaan bila tidak diisi
FOREIGN KEY ... REFERENCES Menghubungkan ke kunci utama tabel lain
Penting: conn.commit() wajib dipanggil agar perubahan benar-benar disimpan ke file database. Tanpa commit, perubahan hilang.

Langkah 3 - Mengisi Data Awal

Buat file baru isi_data.py untuk memasukkan data contoh. Perintah SQL-nya INSERT INTO.

python
import sqlite3

conn = sqlite3.connect("database/toko.db")
cursor = conn.cursor()

# --- data produk ---
daftar_produk = [
    ("Laptop", "Elektronik", 8500000, 10),
    ("Monitor", "Elektronik", 2000000, 15),
    ("Printer", "Elektronik", 1500000, 8),
    ("Mouse", "Aksesoris", 150000, 50),
    ("Keyboard", "Aksesoris", 250000, 40),
]
cursor.executemany(
    "INSERT INTO produk (nama_produk, kategori, harga, stok) VALUES (?, ?, ?, ?)",
    daftar_produk,
)

# --- data pelanggan ---
daftar_pelanggan = [
    ("Budi Santoso", "08123456789", "Jakarta"),
    ("Siti Aminah", "08234567890", "Tangerang"),
]
cursor.executemany(
    "INSERT INTO pelanggan (nama, telepon, alamat) VALUES (?, ?, ?)",
    daftar_pelanggan,
)

conn.commit()
conn.close()
print("Data awal berhasil dimasukkan.")
Perhatikan tanda tanya (?). Nilai tidak ditulis langsung ke dalam SQL, melainkan diganti tanda ? lalu dikirim terpisah. Ini cara aman yang mencegah kesalahan dan penyalahgunaan data. Biasakan sejak sekarang.

executemany() dipakai untuk memasukkan banyak baris sekaligus, sedangkan execute() untuk satu perintah.

Langkah 4 - Menampilkan Data

Perintah SQL untuk membaca data adalah SELECT. Buat file lihat_data.py:

python
import sqlite3

conn = sqlite3.connect("database/toko.db")
cursor = conn.cursor()

cursor.execute("SELECT * FROM produk")
hasil = cursor.fetchall()

print("Daftar Produk:")
for baris in hasil:
    print(baris)

conn.close()

Output:

text
Daftar Produk:
(1, 'Laptop', 'Elektronik', 8500000, 10)
(2, 'Monitor', 'Elektronik', 2000000, 15)
(3, 'Printer', 'Elektronik', 1500000, 8)
(4, 'Mouse', 'Aksesoris', 150000, 50)
(5, 'Keyboard', 'Aksesoris', 250000, 40)

Beberapa variasi SELECT:

python
# kolom tertentu saja
cursor.execute("SELECT nama_produk, harga FROM produk")

# dengan syarat (mirip filtering di Pandas)
cursor.execute("SELECT * FROM produk WHERE kategori = ?", ("Elektronik",))

# diurutkan
cursor.execute("SELECT * FROM produk ORDER BY harga DESC")

# hanya satu baris
cursor.execute("SELECT * FROM produk WHERE id_produk = ?", (1,))
satu = cursor.fetchone()
Perintah Fungsi
fetchall() Mengambil semua baris hasil
fetchone() Mengambil satu baris hasil

Langkah 5 - Membuktikan Relasi Bekerja

Sekarang kita uji relasi master–detail: mencatat satu transaksi berisi dua barang. Buat file uji_relasi.py.

python
import sqlite3

conn = sqlite3.connect("database/toko.db")
cursor = conn.cursor()

# 1. buat nota penjualan (master) untuk pelanggan id 1
cursor.execute(
    "INSERT INTO penjualan (tanggal, id_pelanggan, total) VALUES (?, ?, ?)",
    ("2025-03-01", 1, 0),
)
id_penjualan = cursor.lastrowid          # id nota yang baru dibuat

# 2. isi barang yang dibeli (detail): 1 Laptop + 2 Mouse
keranjang = [(1, 1), (4, 2)]             # (id_produk, qty)
total = 0

for id_produk, qty in keranjang:
    cursor.execute("SELECT harga FROM produk WHERE id_produk = ?", (id_produk,))
    harga = cursor.fetchone()[0]
    subtotal = harga * qty
    total += subtotal

    cursor.execute(
        "INSERT INTO detail_penjualan (id_penjualan, id_produk, qty, subtotal) VALUES (?, ?, ?, ?)",
        (id_penjualan, id_produk, qty, subtotal),
    )
    # kurangi stok produk
    cursor.execute("UPDATE produk SET stok = stok - ? WHERE id_produk = ?", (qty, id_produk))

# 3. simpan total ke nota
cursor.execute("UPDATE penjualan SET total = ? WHERE id_penjualan = ?", (total, id_penjualan))

conn.commit()
print("Total nota:", total)
conn.close()

Output:

text
Total nota: 8800000
cursor.lastrowid memberi id baris yang baru saja dimasukkan. Ini penting pada pola master–detail: kita perlu tahu id nota agar baris detail bisa menunjuk ke nota yang benar.

Menggabungkan Tabel dengan JOIN

JOIN menyatukan data dari beberapa tabel berdasarkan relasinya — inilah gunanya Foreign Key.

python
import sqlite3

conn = sqlite3.connect("database/toko.db")
cursor = conn.cursor()

query = """
SELECT p.id_penjualan, p.tanggal, c.nama, pr.nama_produk, d.qty, d.subtotal
FROM penjualan p
JOIN pelanggan c        ON p.id_pelanggan = c.id_pelanggan
JOIN detail_penjualan d ON p.id_penjualan = d.id_penjualan
JOIN produk pr          ON d.id_produk = pr.id_produk
"""

for baris in cursor.execute(query):
    print(baris)

conn.close()

Output:

text
(1, '2025-03-01', 'Budi Santoso', 'Laptop', 1, 8500000)
(1, '2025-03-01', 'Budi Santoso', 'Mouse', 2, 300000)

Perhatikan: satu nota (id_penjualan = 1) menghasilkan dua baris — persis isi keranjang belanja. Inilah bukti relasi master–detail bekerja.

Referensi Perintah Penting

Python + sqlite3

Perintah Fungsi
sqlite3.connect("file.db") Membuka/membuat database
conn.cursor() Membuat cursor
cursor.execute(sql, (nilai,)) Menjalankan satu perintah SQL
cursor.executemany(sql, daftar) Menjalankan untuk banyak baris
cursor.fetchall() / fetchone() Mengambil semua / satu baris hasil
cursor.lastrowid Id baris yang baru dimasukkan
conn.commit() Menyimpan perubahan
conn.close() Menutup koneksi

Perintah SQL Dasar

SQL Fungsi
CREATE TABLE ... Membuat tabel
INSERT INTO tabel (kolom) VALUES (?) Menambah data
SELECT * FROM tabel Membaca semua data
SELECT * FROM tabel WHERE kolom = ? Membaca dengan syarat
SELECT ... ORDER BY kolom DESC Mengurutkan
UPDATE tabel SET kolom = ? WHERE ... Mengubah data
DELETE FROM tabel WHERE ... Menghapus data
JOIN tabel2 ON ... Menggabungkan tabel
UPDATE dan DELETE akan dibahas tuntas pada Pertemuan 12 (CRUD).

Padanan dengan Pandas

Pandas (Colab) SQL (Database)
df.head() SELECT * FROM tabel
df[df["kategori"] == "Elektronik"] SELECT * FROM produk WHERE kategori = 'Elektronik'
df.sort_values("harga") SELECT * FROM produk ORDER BY harga

Bagaimana dengan MySQL?

Mahasiswa mungkin bertanya: mengapa memakai SQLite, bukan MySQL yang lebih sering terdengar?

Alasannya praktis: SQLite tidak perlu diinstal (sudah bawaan Python) dan databasenya cukup satu file, sehingga kita bisa langsung fokus pada Python tanpa repot memasang server database.

Kabar baiknya, perintah SQL yang dipelajari di sini hampir seluruhnya sama di MySQL. Yang berbeda pada dasarnya hanya cara menghubungkannya:

python
# SQLite (dipakai di mata kuliah ini)
import sqlite3
conn = sqlite3.connect("database/toko.db")

# MySQL (umum dipakai di dunia kerja)
import mysql.connector
conn = mysql.connector.connect(
    host="localhost", user="root", password="", database="toko"
)

Setelah baris koneksi tersebut, perintah cursor.execute("SELECT ..."), commit(), dan lainnya dipakai dengan cara yang sama. Artinya, ilmu yang dipelajari di sini langsung terpakai bila nanti berpindah ke MySQL.

Ingin mencoba memindahkan aplikasi ini ke MySQL? Tersedia materi tambahan Bonus Materi - Migrasi SQLite ke MySQL sebagai tugas pengayaan (opsional, bernilai tambahan).

Pengujian

Pastikan hasil praktikum sesuai berikut:

  1. File database/toko.db terbentuk.
  2. Terdapat empat tabel: produk, pelanggan, penjualan, detail_penjualan.
  3. Tabel produk berisi 5 baris, pelanggan berisi 2 baris.
  4. Setelah uji_relasi.py dijalankan, total nota = 8.800.000 dan stok Laptop berkurang dari 10 menjadi 9.
  5. Query JOIN menghasilkan 2 baris (Laptop dan Mouse pada nota yang sama).

Verifikasi cepat:

python
import sqlite3

conn = sqlite3.connect("database/toko.db")
cursor = conn.cursor()

cursor.execute("SELECT name FROM sqlite_master WHERE type='table'")
print("Tabel:", [t[0] for t in cursor.fetchall()])

cursor.execute("SELECT COUNT(*) FROM produk")
print("Jumlah produk:", cursor.fetchone()[0])

cursor.execute("SELECT stok FROM produk WHERE nama_produk = 'Laptop'")
print("Stok Laptop:", cursor.fetchone()[0])

conn.close()

Troubleshooting

Masalah Penyebab Solusi
unable to open database file Folder database/ belum ada Buat folder database di project
Data hilang setelah program ditutup Lupa conn.commit() Panggil conn.commit() sebelum close()
table produk already exists Tabel dibuat ulang Gunakan CREATE TABLE IF NOT EXISTS
Data ganda saat menjalankan isi_data.py berulang Script dijalankan lebih dari sekali Hapus file toko.db, jalankan ulang dari awal
no such column Nama kolom salah ketik Cocokkan dengan CREATE TABLE
database is locked Database dibuka di program lain Tutup aplikasi lain yang membuka toko.db
Tips melihat isi database: pasang ekstensi SQLite Viewer di VS Code, lalu klik file toko.db untuk melihat isinya secara visual.

Tugas Praktikum

Kembangkan database yang sudah dibuat:

  1. Tambahkan 3 produk baru ke tabel produk (mis. Speaker, Webcam, Headset) beserta harga dan stoknya.
  2. Tambahkan 1 pelanggan baru.
  3. Catat satu transaksi baru: pelanggan baru tersebut membeli 2 jenis barang berbeda. Pastikan detail_penjualan terisi, total pada nota benar, dan stok produk berkurang.
  4. Tampilkan seluruh isi nota tersebut menggunakan JOIN (tanggal, nama pelanggan, nama produk, qty, subtotal).
  5. Tampilkan daftar produk dengan stok di bawah 10, diurutkan dari stok terkecil.

Kumpulkan berupa file .py beserta tangkapan layar hasil menjalankannya (format PDF).

Kesimpulan

Pada pertemuan ini mahasiswa berpindah dari menyimpan data di file CSV menjadi menyimpannya di database SQLite. Mahasiswa mengenal konsep tabel, Primary Key, dan Foreign Key, lalu merancang empat tabel aplikasi penjualan beserta relasinya — termasuk pola master–detail antara penjualan dan detail_penjualan yang memungkinkan satu nota memuat banyak barang. Mahasiswa juga menghubungkan Python dengan database menggunakan sqlite3, mengisi data, dan menggabungkan tabel dengan JOIN. Struktur database inilah fondasi seluruh aplikasi hingga akhir semester. Pada pertemuan berikutnya, tabel-tabel ini akan diwakili dalam bentuk class Python (OOP).