Program yang kita tulis sejauh ini menyimpan data di memori: list, dictionary, dan object semuanya hilang begitu program berhenti. Aplikasi nyata seperti kasir toko tidak boleh kehilangan daftar produk setiap kali komputer dimatikan. Database relasional menyelesaikan masalah ini dengan menyimpan data secara permanen di disk, dalam struktur yang bisa dicari, divalidasi, dan dihubungkan antar satu dengan yang lain.

Tabel, Baris, dan Kolom #

Database relasional menyimpan data dalam tabel: struktur seperti spreadsheet yang punya nama. Setiap kolom menyimpan satu jenis informasi dengan tipe data tetap, dan setiap baris adalah satu entri data. Untuk domain inventaris toko, tabel produk kita berisi empat kolom:

KolomIsiTipe umum
ididentitas unik tiap produkINTEGER
namanama produkTEXT
hargaharga satuan dalam rupiahREAL
stokjumlah unit di gudangINTEGER

Satu baris pada tabel tersebut misalnya (1, 'Kopi Hitam 200g', 18000, 12). Berbeda dengan spreadsheet, database menolak baris yang melanggar aturan: kolom bertipe angka tidak bisa diisi teks, dan kolom yang ditandai NOT NULL tidak boleh dibiarkan kosong.

Primary Key dan Foreign Key #

Primary key adalah kolom (atau gabungan kolom) yang menjadi identitas unik setiap baris. Kolom id pada tabel produk adalah primary key klasik: dua produk boleh bernama sama, tetapi tidak boleh berbagi id. Mesin database aktif menjaga aturan ini, bukan sekadar konvensi. Mari kita buktikan dengan SQLite bawaan Python:

python
import sqlite3

conn = sqlite3.connect(":memory:")
conn.execute("""CREATE TABLE produk (
    id INTEGER PRIMARY KEY,
    nama TEXT NOT NULL,
    harga REAL NOT NULL,
    stok INTEGER NOT NULL DEFAULT 0
)""")
conn.execute("INSERT INTO produk VALUES (1, 'Gula Pasir 1kg', 15500, 25)")
conn.commit()

try:
    conn.execute("INSERT INTO produk VALUES (1, 'Kopi Hitam 200g', 18000, 12)")
except sqlite3.IntegrityError as e:
    print("Galat:", e)

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

Output yang Dihasilkan:

text
Galat: UNIQUE constraint failed: produk.id
Jumlah produk: 1

Mari kita telaah baris-baris pentingnya:

  1. Baris 3 (conn = sqlite3.connect(":memory:")): membuat koneksi ke database sementara di memori. Pola lengkapnya kita bahas tuntas pada Chapter SQLite dengan Python.
  2. Baris 4-9 (CREATE TABLE ...): mendefinisikan tabel produk beserta tipe tiap kolom; INTEGER PRIMARY KEY pada id memicu pemeriksaan keunikan otomatis.
  3. Baris 10 (INSERT ... VALUES (1, ...)): menyisipkan baris pertama dengan id = 1.
  4. Baris 13 (conn.execute("INSERT INTO produk VALUES (1, ...")): mencoba menyisipkan baris kedua dengan id yang sama; mesin SQLite menolaknya dengan sqlite3.IntegrityError.
  5. Baris 17 (print("Jumlah produk:", ...)): membuktikan tabel tetap berisi satu baris — percobaan duplikat tidak pernah tersimpan.

Foreign key adalah kolom yang merujuk primary key di tabel lain. Tabel penjualan menyimpan produk_id yang harus cocok dengan salah satu id di tabel produk, sehingga tidak mungkin ada transaksi untuk produk yang tidak pernah ada:

python
import sqlite3

conn = sqlite3.connect(":memory:")
conn.execute("PRAGMA foreign_keys = ON")
conn.execute("CREATE TABLE produk (id INTEGER PRIMARY KEY, nama TEXT NOT NULL)")
conn.execute("""CREATE TABLE penjualan (
    id INTEGER PRIMARY KEY,
    produk_id INTEGER NOT NULL REFERENCES produk(id),
    jumlah INTEGER NOT NULL
)""")
conn.execute("INSERT INTO produk VALUES (1, 'Gula Pasir 1kg')")
conn.commit()

try:
    conn.execute("INSERT INTO penjualan VALUES (1, 99, 3)")
except sqlite3.IntegrityError as e:
    print("Galat:", e)

print("Jumlah penjualan:", conn.execute("SELECT COUNT(*) FROM penjualan").fetchone()[0])
conn.close()

Output yang Dihasilkan:

text
Galat: FOREIGN KEY constraint failed
Jumlah penjualan: 0

Penjualan dengan produk_id = 99 ditolak karena tabel produk tidak memiliki baris dengan id = 99.

Tiga Bentuk Relasi #

Cara tabel saling terhubung disebut relasi, dan ada tiga bentuk dasarnya:

  • Satu-ke-satu (1-1): satu baris tabel A berpasangan dengan tepat satu baris tabel B. Contoh: satu toko memiliki satu profil pajak. Bentuk ini jarang dipakai karena biasanya lebih sederhana menggabungkannya ke satu tabel.
  • Satu-ke-banyak (1-N): satu penjualan memuat banyak baris detail penjualan. Ini bentuk relasi paling umum di dunia nyata.
  • Banyak-ke-banyak (N-N): satu produk bisa masuk banyak kategori, dan satu kategori menampung banyak produk. Bentuk ini membutuhkan junction table (produk_kategori) yang berisi pasangan produk_id dan kategori_id.

Skema inventaris toko kita hingga chapter ini tergambar sebagai berikut:

Notasi ||--o{ berarti “tepat satu” ke “nol atau banyak”: satu penjualan memuat satu atau banyak detail, sementara satu produk boleh saja belum pernah tercatat di penjualan mana pun.

ACID dan Peta RDBMS #

Database relasional menjanjikan ACID — empat jaminan yang membuat data bisa dipercaya:

SifatJaminan
Atomicityrangkaian perubahan terjadi seluruhnya atau tidak sama sekali
Consistencyaturan tabel (tipe, primary key, foreign key) tidak pernah dilanggar
Isolationtransaksi yang berjalan bersamaan tidak saling merusak
Durabilityperubahan yang sudah disimpan bertahan meski aplikasi mati

Kita akan merasakan langsung jaminan atomicity pada Chapter Transactions. Terakhir, kenali medannya — SQL yang kita pelajari di sini berlaku di semuanya:

RDBMSBentukKegunaan umum
SQLitesatu file, tanpa serveraplikasi desktop dan mobile, prototipe, analisis data
PostgreSQLserver, open sourceaplikasi web serius, data spasial, pencarian teks
MySQL / MariaDBserver, open sourceaplikasi web, CMS seperti WordPress
SQL Serverserver, komersial (Microsoft)ekosistem enterprise Windows dan .NET
Oracle Databaseserver, komersialsistem perbankan dan enterprise berskala besar

Dengan konsep tabel, primary key, foreign key, dan relasi di tangan, kita siap menulis SQL pertama dari Python pada Chapter SQL Dasar untuk Python.