Topik ini mengeksekusi rancangan dari tahap perencanaan menjadi basis data yang benar-benar beroperasi. Seluruh langkahnya memakai dua berkas yang sama, yaitu skema.sql dan data.sql, yang tersimpan di samping halaman ini dan dijalankan dari direktori kerjanya. Pembangunan tidak berhenti pada basis data yang berhasil tercipta, karena setiap constraint diuji dengan pernyataan yang benar-benar ditolak, empat query kebutuhan utama dijalankan dengan keluarannya, dan cadangan pertama menutup pengoperasian awal.
Membangun basis data dari berkas rancangan#
Pembangunan dimulai dari keadaan kosong agar titik awalnya pasti. Perintah rm -f menghapus berkas basis data lama bila ada, lalu dua berkas rancangan dikirim ke program sqlite3 melalui stdin. Perintahnya identik dengan pembuktian pada tahap perencanaan, sehingga hasil keduanya dapat dibandingkan satu sama lain. Bukti pembangunan yang paling ringkas adalah daftar tabel beserta jumlah baris pada dua tabel terpadat.
# membangun perpustakaan.db dari dua berkas rancangan
rm -f perpustakaan.db
sqlite3 perpustakaan.db < skema.sql
sqlite3 perpustakaan.db < data.sql
# mendaftar tabel dan menghitung isinya sebagai bukti pembangunan
sqlite3 perpustakaan.db "SELECT name FROM sqlite_schema WHERE type='table' AND name NOT LIKE 'sqlite_%' ORDER BY name;"
sqlite3 perpustakaan.db "SELECT count(*) FROM buku; SELECT count(*) FROM peminjaman;"anggota
buku
kategori
peminjaman
8
13Daftar di atas memuat empat tabel tanpa tabel internal bawaan SQLite, dan angka 8 serta 13 cocok dengan jumlah baris pada data.sql. Apabila pembangunan diulang tanpa rm terlebih dahulu, pernyataan CREATE TABLE akan ditolak karena tabelnya sudah ada. Perilaku itu wajar karena skema.sql ditulis untuk membangun struktur dari nol, bukan untuk mengubah struktur yang sudah berdiri.
Mengaktifkan foreign key dan mode WAL#
Dua pragma mengatur perilaku yang paling menentukan pada pengoperasian. PRAGMA foreign_keys=ON memerintahkan mesin menegakkan seluruh constraint kunci tamu, sedangkan PRAGMA journal_mode=WAL mengubah cara pencatatan penulisan agar pembaca dan penulis tidak saling memblokir. Keduanya berbeda sifat keberlangsungannya. Mode WAL tersimpan di dalam berkas basis data sehingga berlaku pula bagi koneksi berikutnya, sedangkan foreign_keys kembali berstatus mati pada setiap koneksi baru dan harus diaktifkan lagi setiap kali sesi dibuka.
# mengaktifkan mode WAL lalu membaca kembali nilainya dari koneksi baru
sqlite3 perpustakaan.db "PRAGMA journal_mode=WAL;"
sqlite3 perpustakaan.db "PRAGMA journal_mode;"wal
walBaris pertama menegaskan mode yang baru diaktifkan, sedangkan baris kedua dibaca oleh koneksi yang berbeda setelah koneksi pertama ditutup. Nilainya tetap wal karena mode WAL memang tersimpan pada kepala berkas basis data. Selama beroperasi, mode ini menuliskan perubahan ke berkas sampingan berakhiran -wal dan menyinkronkannya secara berkala, sehingga pembacaan yang berbarengan dengan penulisan tidak lagi menimbulkan error busy sekeras mode journal bawaan.
Menguji constraint#
Constraint yang belum pernah menolak apa pun adalah constraint yang belum teruji. Pengujian berikut sengaja mengeksekusi pernyataan yang salah, dan pesan error yang muncul justru merupakan keluaran yang diharapkan. Setiap percobaan dijalankan pada sesi tunggal, dan percobaan kunci tamu mengaktifkan PRAGMA foreign_keys=ON di dalam sesi yang sama seperti pola pengoperasian yang benar.
# percobaan peminjaman dengan anggota_id 99 yang tidak ada di tabel anggota
sqlite3 perpustakaan.db <<'SQL'
PRAGMA foreign_keys=ON;
INSERT INTO peminjaman (anggota_id, buku_id, tanggal_pinjam) VALUES (99, 1, '2026-11-01');
SQLError near line 2: FOREIGN KEY constraint failedPesan FOREIGN KEY constraint failed muncul karena anggota_id bernilai 99 tidak memiliki baris pasangan di tabel anggota. Tanpa PRAGMA foreign_keys=ON pada baris pertama, pencatatan yang sama akan diterima diam-diam, dan kegagalan semacam itulah yang paling sering luput dari perhatian. Pengujian ini membuktikan pragma tersebut benar-benar aktif pada koneksi yang menjalankannya.
# menarik tanggal kembali ke masa sebelum tanggal pinjamnya
sqlite3 perpustakaan.db <<'SQL'
UPDATE peminjaman SET tanggal_kembali = '2026-08-30' WHERE id = 1;
SQLError near line 1: CHECK constraint failed: cek_tanggal_kembaliPercobaan kedua menyasar constraint yang dinamai. Nilai 2026-08-30 mendahului tanggal_pinjam 2026-09-01 pada baris pertama, sehingga cek_tanggal_kembali menolak pembaruan itu. Nama constraint yang tampil pada pesan membuat sumber penolakan langsung terbaca tanpa membuka skema lagi, dan nama ini menjadi alasan constraint penting diberi nama sejak tahap perencanaan.
# Citra sudah memegang tiga pinjaman aktif, sehingga pencatatan keempat ditolak
sqlite3 perpustakaan.db <<'SQL'
INSERT INTO peminjaman (anggota_id, buku_id, tanggal_pinjam) VALUES (3, 8, '2026-11-01');
SQLError near line 1: batas peminjaman aktif untuk satu anggota adalah tigaPercobaan ketiga menguji aturan yang dituliskan sebagai trigger. Citra memang memegang tiga pinjaman aktif pada data contoh, sehingga trigger cek_batas_peminjaman mengeksekusi RAISE(ABORT) dan pesan aturan bisnisnya muncul sebagai error. Ketiga penolakan tidak meninggalkan jejak data karena seluruhnya digagalkan sebelum ditulis ke tabel.
Menguji query kebutuhan utama#
Pengujian fungsional memeriksa empat kebutuhan yang dirumuskan pada tahap perencanaan. Setiap kebutuhan ditulis sebagai satu query, dan keluarannya dibandingkan dengan perkiraan dari data contoh. Susunan ini meniru cara aplikasi memakai basis data sehari-hari, yaitu menghitung, mendaftar, lalu merangkum.
# kebutuhan laporan: anggota dengan peminjaman terbanyak
sqlite3 perpustakaan.db <<'SQL'
SELECT a.nama, COUNT(*) AS jumlah_pinjam
FROM peminjaman p JOIN anggota a ON a.id = p.anggota_id
GROUP BY a.id ORDER BY jumlah_pinjam DESC, a.nama LIMIT 3;
SQLCitra|4
Ali|2
Budi|2Kebutuhan pertama adalah anggota paling rajin. Penghitungan memakai COUNT per anggota lalu mengurutkannya menurun, dan nama dipakai sebagai pemecah seri agar urutannya pasti. Citra memimpin dengan empat peminjaman, sedangkan Ali dan Budi berbagi posisi berikutnya dengan dua peminjaman masing-masing.
# kebutuhan laporan: seluruh buku yang sedang dipinjam lewat view
sqlite3 perpustakaan.db <<'SQL'
SELECT anggota, judul, tanggal_pinjam
FROM peminjaman_aktif ORDER BY tanggal_pinjam;
SQLCitra|Pengantar Mekanika Klasik|2026-10-20
Dewi|Novel Kota Tua|2026-10-22
Eka|Kamus Angin Lokal|2026-10-25
Citra|Peta Langit Tropis|2026-10-28
Citra|Antologi Hujan Pagi|2026-10-30Kebutuhan kedua memakai view peminjaman_aktif yang sudah dirancang pada skema. Seluruh barisnya berstatus dipinjam, dan pengurutan menurut tanggal pinjam menampilkan lima buku yang sedang keluar. Tiga baris pertama milik Citra, keadaan yang sama dengan batas penuh yang diuji sebelumnya.
# kebutuhan laporan: total denda per bulan pengembalian
sqlite3 perpustakaan.db <<'SQL'
SELECT strftime('%Y-%m', tanggal_kembali) AS bulan, SUM(denda) AS total_denda
FROM peminjaman WHERE tanggal_kembali IS NOT NULL
GROUP BY bulan ORDER BY bulan;
SQL2026-09|5000
2026-10|12500Kebutuhan ketiga adalah laporan denda bulanan. Kolom denda dihitung otomatis oleh kolom yang dihasilkan, lalu strftime mengelompokkan pengembalian menurut bulannya. Total 5000 pada September dan 12500 pada Oktober dapat dihitung ulang dari data contoh satu per satu, sehingga angka laporan ini menjadi tolok ukur yang tetap.
# kebutuhan katalog: seluruh kategori berjenjang lewat CTE rekursif
sqlite3 perpustakaan.db <<'SQL'
WITH RECURSIVE pohon(id, nama, id_induk, kedalaman, jalur) AS (
SELECT id, nama, id_induk, 0, printf('%02d.', id)
FROM kategori WHERE id_induk IS NULL
UNION ALL
SELECT k.id, k.nama, k.id_induk, p.kedalaman + 1, p.jalur || printf('%02d.', k.id)
FROM kategori k JOIN pohon p ON k.id_induk = p.id
)
SELECT substr(' ', 1, kedalaman * 2) || nama AS pohon_kategori
FROM pohon ORDER BY jalur;
SQLSains
Fisika
Astronomi
Sastra
Puisi
Prosa
SejarahKebutuhan keempat menelusuri kategori bertingkat lewat CTE rekursif. Bagian penjangkar mengambil kategori tanpa induk, sedangkan bagian rekursif menempelkan anaknya berikut kedalaman dan jalur berkode angka. Pengurutan menurut jalur menghasilkan tampilan pohon yang terbaca dari atas ke bawah, lengkap dengan lekukan dua spasi untuk setiap tingkatnya.
Kesalahan umum#
Kesalahan pertama adalah menguji constraint kunci tamu tanpa PRAGMA foreign_keys=ON. Koneksi yang baru memang menolak menegakkan kunci tamu secara default, sehingga percobaan yang seharusnya ditolak justru masuk dan pengujian dianggap lolos padahal tidak. Perbaikannya adalah mengaktifkan pragma pada koneksi yang sama dengan pernyataan ujinya, seperti pada setiap blok pengujian di atas.
Kesalahan kedua adalah menaruh constraint tingkat tabel di tengah daftar kolom. SQLite hanya menerima constraint tingkat tabel setelah seluruh definisi kolom, sehingga kolom yang ditulis setelahnya memicu pesan syntax error yang letaknya terasa tidak nyambung. Perbaikannya adalah memindahkan seluruh CONSTRAINT tingkat tabel ke bagian akhir definisi CREATE TABLE, sebagaimana cek_tanggal_kembali pada skema.sql.
Kesalahan ketiga adalah menyalin basis data bermode WAL dengan cp saat sesi sedang aktif. Salinan itu dapat tidak sinkron dengan berkas sampingan -wal yang belum penuh tersinkron, sehingga berkasnya tampak utuh tetapi isinya tertinggal. Cadangan yang aman dibuat dengan VACUUM INTO atau .backup, sebagaimana diperlihatkan pada bagian berikutnya, bukan dengan penyalinan berkas mentah.
Cadangan pertama menutup pengoperasian awal#
Pengoperasian awal dianggap selesai ketika basis data dan salinan amannya sudah ada. VACUUM INTO menuliskan seluruh isi basis data pada saat itu ke berkas baru yang rapi, dan perintah ini menuntut nama berkas yang belum ada sehingga cadangan lama dihapus lebih dahulu. Kombinasi ini aman dijalankan walau mode WAL sedang aktif karena perintahnya membaca keadaan yang konsisten secara internal.
# mencadangkan basis data lalu memeriksa integritas salinannya
rm -f perpustakaan-cadangan.db
sqlite3 perpustakaan.db "VACUUM INTO 'perpustakaan-cadangan.db';"
sqlite3 perpustakaan-cadangan.db "PRAGMA integrity_check;"okBaris ok adalah jawaban PRAGMA integrity_check atas berkas cadangan, bukan atas berkas utamanya, sehingga salinannya benar-benar utuh dan dapat dibuka. Cadangan ini menjadi titik pemulihan pertama proyek. Kebiasaan yang sama diulang setiap kali data berubah besar, dan penamaan berkasnya diberi pembeda agar generasi cadangan mudah dikenali.
Ringkasan#
- Basis data dibangun ulang dari skema.sql dan data.sql, dan daftar objek beserta jumlah barisnya membuktikan pembangunan berhasil.
- PRAGMA journal_mode=WAL tersimpan dalam berkas, sedangkan PRAGMA foreign_keys=ON wajib diaktifkan pada setiap koneksi yang menulis.
- Constraint foreign key, CHECK, dan trigger diuji dengan pernyataan yang benar-benar ditolak, lengkap dengan pesan error aslinya.
- Empat query kebutuhan utama menghasilkan keluaran yang dapat direproduksi dari data contoh, dan VACUUM INTO menutup pengoperasian awal dengan cadangan yang lolos integrity_check.
Referensi#
- Foreign Key Support — SQLite — Cara penegakan foreign key dan kebutuhan PRAGMA foreign_keys pada setiap koneksi.
- PRAGMA Statements — SQLite — Dokumentasi pragma foreign_keys, journal_mode, dan integrity_check.
- Write-Ahead Logging — SQLite — Cara kerja mode WAL beserta sifatnya yang tersimpan dalam berkas basis data.