Database yang hanya bisa mencatat transaksi baru setengah jadi; nilai sejatinya muncul saat data ditanya kembali. Pada bagian penutup proyek ini kita mengisi perpustakaan_db dengan dua bulan riwayat peminjaman, lalu menyusun tiga laporan yang diminta setiap petugas perpustakaan: judul mana yang paling diminati, anggota mana yang paling rajin meminjam, dan peminjaman mana yang sudah melewati batas waktu. Seluruh query laporan disimpan sebagai berkas siap pakai di folder queries/, dan proyek ditutup dengan satu berkas pemeriksaan yang menguji ulang seluruh fitur secara otomatis.
Menyiapkan Riwayat Dua Bulan #
Kita mulai dari keadaan bersih seperti biasa: dua blok pertama menjalankan pernyataan yang sama dengan database/skema.sql dan database/data-awal.sql (berkas aslinya menyertakan kepala komentar berisi tujuan dan cara pakai):
DROP DATABASE IF EXISTS perpustakaan_db;
CREATE DATABASE perpustakaan_db;
USE perpustakaan_db;
CREATE TABLE buku (
kode_buku VARCHAR(15) PRIMARY KEY,
judul VARCHAR(100) NOT NULL,
penulis VARCHAR(80) NOT NULL,
tahun YEAR NOT NULL,
stok INT NOT NULL DEFAULT 0,
CONSTRAINT chk_buku_stok CHECK (stok >= 0)
);
CREATE TABLE anggota (
id INT AUTO_INCREMENT PRIMARY KEY,
nama VARCHAR(60) NOT NULL,
kelas VARCHAR(10) NOT NULL,
telepon VARCHAR(15) NULL
);
CREATE TABLE peminjaman (
id INT AUTO_INCREMENT PRIMARY KEY,
anggota_id INT NOT NULL,
tanggal_pinjam DATE NOT NULL,
tanggal_harus_kembali DATE NOT NULL,
tanggal_kembali DATE NULL,
CONSTRAINT fk_peminjaman_anggota
FOREIGN KEY (anggota_id) REFERENCES anggota(id)
);
CREATE TABLE detail_peminjaman (
peminjaman_id INT NOT NULL,
buku_kode VARCHAR(15) NOT NULL,
jumlah INT NOT NULL,
PRIMARY KEY (peminjaman_id, buku_kode),
CONSTRAINT fk_detail_peminjaman
FOREIGN KEY (peminjaman_id) REFERENCES peminjaman(id),
CONSTRAINT fk_detail_buku
FOREIGN KEY (buku_kode) REFERENCES buku(kode_buku)
);Output yang Dihasilkan:
Query OK, 0 rows affected, 1 warning (0.00 sec)
Query OK, 1 row affected (0.00 sec)
Database changed
Query OK, 0 rows affected (0.00 sec)
Query OK, 0 rows affected (0.00 sec)
Query OK, 0 rows affected (0.00 sec)
Query OK, 0 rows affected (0.00 sec)INSERT INTO buku (kode_buku, judul, penulis, tahun, stok) VALUES
('BK-001', 'Laskar Pelangi', 'Andrea Hirata', 2005, 3),
('BK-002', 'Bumi Manusia', 'Pramoedya Ananta Toer', 1980, 2),
('BK-003', 'Hujan Bulan Juni', 'Sapardi Djoko Damono', 1994, 2),
('BK-004', 'Dilan 1990', 'Pidi Baiq', 2014, 4),
('BK-005', 'Atomic Habits', 'James Clear', 2018, 2),
('BK-006', 'Matematika untuk SMA Kelas X', 'Tim Pusat Kurikulum', 2019, 6),
('BK-007', 'Kamus Besar Bahasa Indonesia', 'Badan Pengembangan Bahasa', 2016, 2),
('BK-008', 'Fisika Terapan untuk SMK', 'Ratna Wijaya', 2021, 3);
INSERT INTO anggota (nama, kelas, telepon) VALUES
('Budi Santoso', 'X-IPA-1', '081234560001'),
('Siti Rahmawati', 'X-IPA-2', '081234560002'),
('Andi Wijaya', 'XI-IPA-1', '081234560003'),
('Dewi Lestari', 'XI-IPS-1', '081234560004'),
('Rina Marlina', 'XII-IPA-1', '081234560005'),
('Tono Sutrisno', 'XII-IPS-1', NULL),
('Sari Puspita', 'X-IPS-1', '081234560007'),
('Joko Prasetyo', 'XI-IPS-2', NULL);Output yang Dihasilkan:
Query OK, 8 rows affected (0.00 sec)
Records: 8 Duplicates: 0 Warnings: 0
Query OK, 8 rows affected (0.00 sec)
Records: 8 Duplicates: 0 Warnings: 0Di atas titik awal itu kita tambahkan riwayat delapan peminjaman sepanjang Januari–Februari 2026. Nomor id ditulis eksplisit supaya laporan mudah dirujuk; dua peminjaman (4 dan 7) sengaja dibiarkan belum kembali:
INSERT INTO peminjaman (id, anggota_id, tanggal_pinjam, tanggal_harus_kembali, tanggal_kembali) VALUES
(1, 1, '2026-01-05', '2026-01-12', '2026-01-10'),
(2, 2, '2026-01-08', '2026-01-15', '2026-01-14'),
(3, 3, '2026-01-10', '2026-01-17', '2026-01-16'),
(4, 4, '2026-01-15', '2026-01-22', NULL),
(5, 5, '2026-01-20', '2026-01-27', '2026-01-24'),
(6, 1, '2026-02-02', '2026-02-09', '2026-02-05'),
(7, 6, '2026-02-10', '2026-02-17', NULL),
(8, 7, '2026-02-14', '2026-02-21', '2026-02-25');
INSERT INTO detail_peminjaman (peminjaman_id, buku_kode, jumlah) VALUES
(1, 'BK-004', 2), (1, 'BK-001', 1),
(2, 'BK-001', 2),
(3, 'BK-005', 1),
(4, 'BK-003', 1), (4, 'BK-007', 1),
(5, 'BK-002', 1),
(6, 'BK-005', 1),
(7, 'BK-001', 1), (7, 'BK-002', 1),
(8, 'BK-004', 2);Output yang Dihasilkan:
Query OK, 8 rows affected (0.00 sec)
Records: 8 Duplicates: 0 Warnings: 0
Query OK, 11 rows affected (0.00 sec)
Records: 11 Duplicates: 0 Warnings: 0Riwayat tersebut merefleksikan alur Bagian 3: peminjaman yang belum kembali berarti eksemplarnya masih di tangan anggota, sehingga stok rak harus dikurangi. Daripada menulis UPDATE satu per satu, kita sinkronkan sekaligus lewat agregasi:
UPDATE buku b
JOIN (
SELECT dp.buku_kode, SUM(dp.jumlah) AS sedang_dipinjam
FROM detail_peminjaman dp
JOIN peminjaman p ON p.id = dp.peminjaman_id
WHERE p.tanggal_kembali IS NULL
GROUP BY dp.buku_kode
) pinjaman ON pinjaman.buku_kode = b.kode_buku
SET b.stok = b.stok - pinjaman.sedang_dipinjam;Output yang Dihasilkan:
Query OK, 4 rows affected (0.00 sec)
Rows matched: 4 Changed: 4 Warnings: 0Pernyataan gabungan UPDATE ... JOIN ini menghitung total eksemplar yang sedang dipinjam per judul dari derived table pinjaman, lalu mengurangkan stok hanya pada judul yang tercatat. Hasilnya bisa dibaca sekali jalan lewat berkas queries/stok-tersedia.sql:
SELECT b.kode_buku, b.judul, b.stok AS stok_tersedia,
COALESCE(dipinjam.sedang, 0) AS sedang_dipinjam
FROM buku b
LEFT JOIN (
SELECT dp.buku_kode, SUM(dp.jumlah) AS sedang
FROM detail_peminjaman dp
JOIN peminjaman p ON p.id = dp.peminjaman_id
WHERE p.tanggal_kembali IS NULL
GROUP BY dp.buku_kode
) dipinjam ON dipinjam.buku_kode = b.kode_buku
ORDER BY b.kode_buku;Output yang Dihasilkan:
+-----------+------------------------------+---------------+-----------------+
| kode_buku | judul | stok_tersedia | sedang_dipinjam |
+-----------+------------------------------+---------------+-----------------+
| BK-001 | Laskar Pelangi | 2 | 1 |
| BK-002 | Bumi Manusia | 1 | 1 |
| BK-003 | Hujan Bulan Juni | 1 | 1 |
| BK-004 | Dilan 1990 | 4 | 0 |
| BK-005 | Atomic Habits | 2 | 0 |
| BK-006 | Matematika untuk SMA Kelas X | 6 | 0 |
| BK-007 | Kamus Besar Bahasa Indonesia | 1 | 1 |
| BK-008 | Fisika Terapan untuk SMK | 3 | 0 |
+-----------+------------------------------+---------------+-----------------+
8 rows in set (0.00 sec)Empat judul tercatat sedang dipinjam satu eksemplar — Laskar Pelangi, Bumi Manusia, Hujan Bulan Juni, dan KBBI — dan kolom stok_tersedia tiap judul sudah berkurang sesuai. Matematikanya terlihat pada BK-001: 3 eksemplar awal dikurangi 1 yang dipinjam Tono menyisakan 2 di rak.
Laporan Buku Terpopuler #
Pertanyaan pertama petugas: judul mana yang paling sering dipinjam? Jawabannya mengagregasi detail_peminjaman per judul, lalu memeringkat hasilnya dengan window function RANK(). Query berikut adalah query yang sama dengan queries/laporan-buku-terpopuler.sql:
SELECT b.kode_buku, b.judul,
COALESCE(SUM(dp.jumlah), 0) AS total_dipinjam,
RANK() OVER (ORDER BY COALESCE(SUM(dp.jumlah), 0) DESC) AS peringkat
FROM buku b
LEFT JOIN detail_peminjaman dp ON dp.buku_kode = b.kode_buku
GROUP BY b.kode_buku, b.judul
ORDER BY total_dipinjam DESC, b.judul;Output yang Dihasilkan:
+-----------+------------------------------+----------------+-----------+
| kode_buku | judul | total_dipinjam | peringkat |
+-----------+------------------------------+----------------+-----------+
| BK-004 | Dilan 1990 | 4 | 1 |
| BK-001 | Laskar Pelangi | 4 | 1 |
| BK-005 | Atomic Habits | 2 | 3 |
| BK-002 | Bumi Manusia | 2 | 3 |
| BK-003 | Hujan Bulan Juni | 1 | 5 |
| BK-007 | Kamus Besar Bahasa Indonesia | 1 | 5 |
| BK-008 | Fisika Terapan untuk SMK | 0 | 7 |
| BK-006 | Matematika untuk SMA Kelas X | 0 | 7 |
+-----------+------------------------------+----------------+-----------+
8 rows in set (0.00 sec)Tiga keputusan pada query ini menentukan mutu laporannya:
LEFT JOINmenjaga buku tanpa riwayat (Matematika dan Fisika) tetap tampil dengan angka nol — buku sepi justru informasi penting bagi pembelian berikutnya.COALESCE(SUM(dp.jumlah), 0)mengubahSUMyang menghasilkanNULL(buku tanpa baris detail) menjadi0, sehinggaORDER BYdanRANK()membandingkan angka, bukanNULL.RANK() OVER (ORDER BY ... DESC)memberi peringkat tanpa memutus seri: Dilan 1990 dan Laskar Pelangi sama-sama 4 eksemplar dan keduanya menjadi peringkat 1, lalu peringkat berikutnya melompat ke 3. Urutan tampil ditata terpisah lewatORDER BY total_dipinjam DESC, b.judulsehingga hasil selalu deterministik.
Pembacaan hasilnya lugas: dua judul remaja menyumbang sirkulasi terbesar, sementara buku pelajaran justru nyaris tak bergerak — petugas bisa menimbang memindahkan beberapa eksemplar Matematika ke rak yang lebih strategis.
Laporan Anggota Paling Aktif #
Pertanyaan kedua: anggota siapa yang paling sering meminjam? Kali ini kita menggabungkan tiga tabel dan menghitung dua metrik berbeda — banyaknya transaksi (COUNT) dan banyaknya buku (SUM rincian). Isi berkas queries/laporan-anggota-aktif.sql:
SELECT a.id, a.nama, a.kelas,
COUNT(DISTINCT p.id) AS jumlah_peminjaman,
COALESCE(SUM(dp.jumlah), 0) AS total_buku
FROM anggota a
LEFT JOIN peminjaman p ON p.anggota_id = a.id
LEFT JOIN detail_peminjaman dp ON dp.peminjaman_id = p.id
GROUP BY a.id, a.nama, a.kelas
ORDER BY jumlah_peminjaman DESC, a.nama;Output yang Dihasilkan:
+----+----------------+-----------+-------------------+------------+
| id | nama | kelas | jumlah_peminjaman | total_buku |
+----+----------------+-----------+-------------------+------------+
| 1 | Budi Santoso | X-IPA-1 | 2 | 4 |
| 3 | Andi Wijaya | XI-IPA-1 | 1 | 1 |
| 4 | Dewi Lestari | XI-IPS-1 | 1 | 2 |
| 5 | Rina Marlina | XII-IPA-1 | 1 | 1 |
| 7 | Sari Puspita | X-IPS-1 | 1 | 2 |
| 2 | Siti Rahmawati | X-IPA-2 | 1 | 2 |
| 6 | Tono Sutrisno | XII-IPS-1 | 1 | 2 |
| 8 | Joko Prasetyo | XI-IPS-2 | 0 | 0 |
+----+----------------+-----------+-------------------+------------+
8 rows in set (0.00 sec)Dua LEFT JOIN berantai menjaga Joko Prasetyo tetap tampil walau belum pernah meminjam sama sekali — COALESCE menjaga SUM kosong menjadi nol. Perhatikan pula query di atas memakai COUNT(DISTINCT p.id), bukan COUNT(p.id): join kedua menggandakan baris anggota yang meminjam beberapa judul sekaligus (Budi dengan peminjaman dua judul muncul sebagai tiga baris), sehingga COUNT(p.id) tanpa DISTINCT akan menghitung baris rincian, bukan jumlah transaksi. Budi Santoso unggul dengan dua transaksi berisi empat buku; sisanya terbagi rata satu transaksi. Laporan semacam ini biasa dipakai untuk penghargaan pembaca rajin tiap akhir semester.
Laporan Peminjaman Terlambat #
Pertanyaan ketiga — dan paling menekan — siapa yang melewati batas waktu? Deteksinya sederhana: tanggal_kembali masih NULL sementara tanggal_harus_kembali sudah terlewat. Keterlambatan dihitung dengan DATEDIFF terhadap tanggal acuan laporan, sesuai isi queries/laporan-terlambat.sql:
SELECT p.id, a.nama, a.kelas, p.tanggal_pinjam, p.tanggal_harus_kembali,
DATEDIFF('2026-02-28', p.tanggal_harus_kembali) AS hari_terlambat
FROM peminjaman p
JOIN anggota a ON a.id = p.anggota_id
WHERE p.tanggal_kembali IS NULL
AND p.tanggal_harus_kembali < '2026-02-28'
ORDER BY hari_terlambat DESC;Output yang Dihasilkan:
+----+---------------+-----------+----------------+-----------------------+----------------+
| id | nama | kelas | tanggal_pinjam | tanggal_harus_kembali | hari_terlambat |
+----+---------------+-----------+----------------+-----------------------+----------------+
| 4 | Dewi Lestari | XI-IPS-1 | 2026-01-15 | 2026-01-22 | 37 |
| 7 | Tono Sutrisno | XII-IPS-1 | 2026-02-10 | 2026-02-17 | 11 |
+----+---------------+-----------+----------------+-----------------------+----------------+
2 rows in set (0.00 sec)Dua baris hasilnya adalah dua peminjaman yang masih terbuka: peminjaman Dewi (id 4) telah melewati 37 hari dari batasnya, dan peminjaman Tono (id 7) terlambat 11 hari. Di sinilah nomor telepon pada tabel anggota memperoleh perannya — petugas cukup menggabungkan laporan ini dengan kolom kontak untuk menagih pengembalian.
Pemeriksaan Akhir Proyek #
Seluruh fitur proyek — skema, peminjaman, pengembalian, dan laporan — kini terkumpul dalam satu berkas uji: tests/pemeriksaan.sql. Berkas ini mandiri: berkembang dari nol, membangun skenario uji kecilnya sendiri, memverifikasi tiap fitur lewat query berlabel ekspektasi, lalu membersihkan database latihan. Kita jalankan dari direktori akar proyek:
-- @test-bash
mysql -h 127.0.0.1 -P 3306 -u root < content/mysql/proyek/database-manajemen-perpustakaan/tests/pemeriksaan.sqlOutput yang Dihasilkan:
kode_buku stok
BK-001 1
kode_buku stok
BK-001 3
id tanggal_kembali
1 2026-01-10
kode_buku total_dipinjam
BK-001 4
BK-002 1
BK-003 0
id hari_terlambat
3 20
kode_buku stok
BK-001 2
BK-002 2
BK-003 2Lima hasil di output tersebut adalah lima pemeriksaan: stok berkurang tepat setelah peminjaman (1), pulih setelah pengembalian (2), laporan terpopuler menjumlahkan tiga peminjaman dengan benar (3), peminjaman terlambat terdeteksi tepat satu baris (4), dan stok akhir cocok dengan seluruh mutasi (5). Karena berkas ini membangun skenarionya sendiri, pemeriksaan bisa dijalankan kapan pun — bahkan setelah database utama dibersihkan.
Membersihkan Database Latihan #
Proyek selesai dan database latihan tidak lagi diperlukan. Blok terakhir proyek ini membuangnya dengan sopan:
DROP DATABASE IF EXISTS perpustakaan_db;Output yang Dihasilkan:
Query OK, 4 rows affected (0.00 sec)IF EXISTS membuat pernyataan ini aman dijalankan ulang — bila database sudah tidak ada, MySQL hanya mengeluarkan peringatan, bukan error.
Ringkasan Proyek #
Empat bagian perjalanan kita menghasilkan sistem yang utuh dan teruji:
- Bagian 1 — kebutuhan dianalisis menjadi empat tabel:
buku,anggota,peminjaman, dandetail_peminjamansebagai jembatan banyak-ke-banyak, dengan foreign key bernama danCHECKstok non-negatif. - Bagian 2 — data master terisi dari
database/data-awal.sql; operasi CRUD harian berjalan, dan constraint terbukti menolak kode buku ganda (error 1062) serta peminjaman anggota fiktif (error 1452). - Bagian 3 — alur pinjam-kembali berjalan atomik dalam
START TRANSACTION ... COMMIT; stok berkurang dan pulih secara konsisten,ROLLBACKmembatalkan kesalahan, danCHECKmenahan peminjaman melebihi stok (error 3819). - Bagian 4 — tiga laporan siap pakai di
queries/memakaiJOIN, agregat, danRANK(), plus berkas pemeriksaan mandiri ditests/pemeriksaan.sqlyang membuktikan seluruh fitur berjalan.
Pola yang kita tempuh — rancang, isi, operasikan, laporkan, uji — adalah pola yang berlaku untuk proyek database apa pun. Proyek berikutnya, Database Inventaris Barang, mengulang pola yang sama pada domain gudang: stok barang bergerak dua arah lewat transaksi masuk dan keluar, lengkap dengan konsep stok minimum dan kartu stok.
Referensi #
Berkas Sumber
Berkas pendukung halaman ini dapat diunduh dan dijalankan langsung sesuai penjelasan di atas.
database/data-awal.sql
Unduh berkas-- data-awal.sql — Proyek Database Manajemen Perpustakaan
-- Tujuan: mengisi data contoh deterministik (buku dan anggota fiktif)
-- sebagai titik awal konsisten untuk seluruh bagian proyek.
-- Cara pakai: jalankan setelah skema.sql:
-- mysql -u root -p perpustakaan_db < database/data-awal.sql
INSERT INTO buku (kode_buku, judul, penulis, tahun, stok) VALUES
('BK-001', 'Laskar Pelangi', 'Andrea Hirata', 2005, 3),
('BK-002', 'Bumi Manusia', 'Pramoedya Ananta Toer', 1980, 2),
('BK-003', 'Hujan Bulan Juni', 'Sapardi Djoko Damono', 1994, 2),
('BK-004', 'Dilan 1990', 'Pidi Baiq', 2014, 4),
('BK-005', 'Atomic Habits', 'James Clear', 2018, 2),
('BK-006', 'Matematika untuk SMA Kelas X', 'Tim Pusat Kurikulum', 2019, 6),
('BK-007', 'Kamus Besar Bahasa Indonesia', 'Badan Pengembangan Bahasa', 2016, 2),
('BK-008', 'Fisika Terapan untuk SMK', 'Ratna Wijaya', 2021, 3);
INSERT INTO anggota (nama, kelas, telepon) VALUES
('Budi Santoso', 'X-IPA-1', '081234560001'),
('Siti Rahmawati', 'X-IPA-2', '081234560002'),
('Andi Wijaya', 'XI-IPA-1', '081234560003'),
('Dewi Lestari', 'XI-IPS-1', '081234560004'),
('Rina Marlina', 'XII-IPA-1', '081234560005'),
('Tono Sutrisno', 'XII-IPS-1', NULL),
('Sari Puspita', 'X-IPS-1', '081234560007'),
('Joko Prasetyo', 'XI-IPS-2', NULL);
database/skema.sql
Unduh berkas-- skema.sql — Proyek Database Manajemen Perpustakaan
-- Tujuan: membuat database perpustakaan_db beserta keempat tabelnya
-- (buku, anggota, peminjaman, detail_peminjaman) lengkap dengan
-- primary key, foreign key, dan check constraint.
-- Cara pakai: mysql -u root -p < database/skema.sql
DROP DATABASE IF EXISTS perpustakaan_db;
CREATE DATABASE perpustakaan_db;
USE perpustakaan_db;
-- Katalog buku; kolom stok menyimpan jumlah eksemplar yang tersedia di rak.
CREATE TABLE buku (
kode_buku VARCHAR(15) PRIMARY KEY,
judul VARCHAR(100) NOT NULL,
penulis VARCHAR(80) NOT NULL,
tahun YEAR NOT NULL,
stok INT NOT NULL DEFAULT 0,
CONSTRAINT chk_buku_stok CHECK (stok >= 0)
);
-- Anggota perpustakaan (siswa); telepon boleh kosong (NULL).
CREATE TABLE anggota (
id INT AUTO_INCREMENT PRIMARY KEY,
nama VARCHAR(60) NOT NULL,
kelas VARCHAR(10) NOT NULL,
telepon VARCHAR(15) NULL
);
-- Satu baris per transaksi peminjaman; tanggal_kembali NULL berarti masih dipinjam.
CREATE TABLE peminjaman (
id INT AUTO_INCREMENT PRIMARY KEY,
anggota_id INT NOT NULL,
tanggal_pinjam DATE NOT NULL,
tanggal_harus_kembali DATE NOT NULL,
tanggal_kembali DATE NULL,
CONSTRAINT fk_peminjaman_anggota
FOREIGN KEY (anggota_id) REFERENCES anggota(id)
);
-- Rincian buku per peminjaman; jembatan banyak-ke-banyak antara buku dan peminjaman.
CREATE TABLE detail_peminjaman (
peminjaman_id INT NOT NULL,
buku_kode VARCHAR(15) NOT NULL,
jumlah INT NOT NULL,
PRIMARY KEY (peminjaman_id, buku_kode),
CONSTRAINT fk_detail_peminjaman
FOREIGN KEY (peminjaman_id) REFERENCES peminjaman(id),
CONSTRAINT fk_detail_buku
FOREIGN KEY (buku_kode) REFERENCES buku(kode_buku)
);
queries/laporan-anggota-aktif.sql
Unduh berkas-- laporan-anggota-aktif.sql — Proyek Database Manajemen Perpustakaan
-- Tujuan: menampilkan frekuensi peminjaman dan total buku yang dipinjam
-- tiap anggota, termasuk anggota yang belum pernah meminjam.
-- Cara pakai: mysql -u root -p perpustakaan_db < queries/laporan-anggota-aktif.sql
SELECT a.id, a.nama, a.kelas,
COUNT(DISTINCT p.id) AS jumlah_peminjaman,
COALESCE(SUM(dp.jumlah), 0) AS total_buku
FROM anggota a
LEFT JOIN peminjaman p ON p.anggota_id = a.id
LEFT JOIN detail_peminjaman dp ON dp.peminjaman_id = p.id
GROUP BY a.id, a.nama, a.kelas
ORDER BY jumlah_peminjaman DESC, a.nama;
queries/laporan-buku-terpopuler.sql
Unduh berkas-- laporan-buku-terpopuler.sql — Proyek Database Manajemen Perpustakaan
-- Tujuan: memeringkat buku berdasarkan total eksemplar yang dipinjam,
-- termasuk buku yang belum pernah dipinjam, memakai window function RANK().
-- Cara pakai: mysql -u root -p perpustakaan_db < queries/laporan-buku-terpopuler.sql
SELECT b.kode_buku, b.judul,
COALESCE(SUM(dp.jumlah), 0) AS total_dipinjam,
RANK() OVER (ORDER BY COALESCE(SUM(dp.jumlah), 0) DESC) AS peringkat
FROM buku b
LEFT JOIN detail_peminjaman dp ON dp.buku_kode = b.kode_buku
GROUP BY b.kode_buku, b.judul
ORDER BY total_dipinjam DESC, b.judul;
queries/laporan-terlambat.sql
Unduh berkas-- laporan-terlambat.sql — Proyek Database Manajemen Perpustakaan
-- Tujuan: menemukan peminjaman yang melewati batas tanggal_harus_kembali
-- dan belum dikembalikan, dihitung terhadap tanggal acuan literal.
-- Cara pakai: mysql -u root -p perpustakaan_db < queries/laporan-terlambat.sql
-- Ganti tanggal acuan '2026-02-28' sesuai hari laporan dijalankan.
SELECT p.id, a.nama, a.kelas, p.tanggal_pinjam, p.tanggal_harus_kembali,
DATEDIFF('2026-02-28', p.tanggal_harus_kembali) AS hari_terlambat
FROM peminjaman p
JOIN anggota a ON a.id = p.anggota_id
WHERE p.tanggal_kembali IS NULL
AND p.tanggal_harus_kembali < '2026-02-28'
ORDER BY hari_terlambat DESC;
queries/stok-tersedia.sql
Unduh berkas-- stok-tersedia.sql — Proyek Database Manajemen Perpustakaan
-- Tujuan: menampilkan stok tiap judul berdampingan dengan jumlah eksemplar
-- yang sedang dipinjam, sehingga kecukupan koleksi terlihat sekali baca.
-- Cara pakai: mysql -u root -p perpustakaan_db < queries/stok-tersedia.sql
SELECT b.kode_buku, b.judul, b.stok AS stok_tersedia,
COALESCE(dipinjam.sedang, 0) AS sedang_dipinjam
FROM buku b
LEFT JOIN (
SELECT dp.buku_kode, SUM(dp.jumlah) AS sedang
FROM detail_peminjaman dp
JOIN peminjaman p ON p.id = dp.peminjaman_id
WHERE p.tanggal_kembali IS NULL
GROUP BY dp.buku_kode
) dipinjam ON dipinjam.buku_kode = b.kode_buku
ORDER BY b.kode_buku;
tests/pemeriksaan.sql
Unduh berkas-- pemeriksaan.sql — Proyek Database Manajemen Perpustakaan
-- Tujuan: pemeriksaan akhir seluruh fitur proyek (skema, peminjaman,
-- pengembalian, laporan terpopuler, dan deteksi keterlambatan).
-- Berkas ini MANDIRI: membangun skenario uji kecilnya sendiri,
-- lalu membersihkan database setelah selesai.
-- Cara pakai: mysql -u root -p < tests/pemeriksaan.sql
-- Ekspektasi tiap hasil ditulis sebagai komentar di atas query terkait.
DROP DATABASE IF EXISTS perpustakaan_db;
CREATE DATABASE perpustakaan_db;
USE perpustakaan_db;
CREATE TABLE buku (
kode_buku VARCHAR(15) PRIMARY KEY,
judul VARCHAR(100) NOT NULL,
penulis VARCHAR(80) NOT NULL,
tahun YEAR NOT NULL,
stok INT NOT NULL DEFAULT 0,
CONSTRAINT chk_buku_stok CHECK (stok >= 0)
);
CREATE TABLE anggota (
id INT AUTO_INCREMENT PRIMARY KEY,
nama VARCHAR(60) NOT NULL,
kelas VARCHAR(10) NOT NULL,
telepon VARCHAR(15) NULL
);
CREATE TABLE peminjaman (
id INT AUTO_INCREMENT PRIMARY KEY,
anggota_id INT NOT NULL,
tanggal_pinjam DATE NOT NULL,
tanggal_harus_kembali DATE NOT NULL,
tanggal_kembali DATE NULL,
CONSTRAINT fk_peminjaman_anggota
FOREIGN KEY (anggota_id) REFERENCES anggota(id)
);
CREATE TABLE detail_peminjaman (
peminjaman_id INT NOT NULL,
buku_kode VARCHAR(15) NOT NULL,
jumlah INT NOT NULL,
PRIMARY KEY (peminjaman_id, buku_kode),
CONSTRAINT fk_detail_peminjaman
FOREIGN KEY (peminjaman_id) REFERENCES peminjaman(id),
CONSTRAINT fk_detail_buku
FOREIGN KEY (buku_kode) REFERENCES buku(kode_buku)
);
INSERT INTO buku (kode_buku, judul, penulis, tahun, stok) VALUES
('BK-001', 'Laskar Pelangi', 'Andrea Hirata', 2005, 3),
('BK-002', 'Bumi Manusia', 'Pramoedya Ananta Toer', 1980, 2),
('BK-003', 'Hujan Bulan Juni', 'Sapardi Djoko Damono', 1994, 2);
INSERT INTO anggota (nama, kelas, telepon) VALUES
('Budi Santoso', 'X-IPA-1', '081234560001'),
('Siti Rahmawati', 'X-IPA-2', '081234560002');
-- Pemeriksaan 1: peminjaman mengurangi stok secara atomik.
START TRANSACTION;
INSERT INTO peminjaman (anggota_id, tanggal_pinjam, tanggal_harus_kembali)
VALUES (1, '2026-01-05', '2026-01-12');
INSERT INTO detail_peminjaman VALUES (1, 'BK-001', 2);
UPDATE buku SET stok = stok - 2 WHERE kode_buku = 'BK-001';
COMMIT;
-- Ekspektasi: BK-001 stok 1
SELECT kode_buku, stok FROM buku WHERE kode_buku = 'BK-001';
-- Pemeriksaan 2: pengembalian mengembalikan stok dan menutup pinjaman.
UPDATE peminjaman SET tanggal_kembali = '2026-01-10' WHERE id = 1;
UPDATE buku SET stok = stok + 2 WHERE kode_buku = 'BK-001';
-- Ekspektasi: BK-001 stok 3
SELECT kode_buku, stok FROM buku WHERE kode_buku = 'BK-001';
-- Ekspektasi: peminjaman 1 sudah memiliki tanggal_kembali
SELECT id, tanggal_kembali FROM peminjaman WHERE id = 1;
-- Pemeriksaan 3: peminjaman kedua (id 2) selesai, peminjaman ketiga (id 3) terbuka.
START TRANSACTION;
INSERT INTO peminjaman (anggota_id, tanggal_pinjam, tanggal_harus_kembali)
VALUES (2, '2026-01-15', '2026-01-22');
INSERT INTO detail_peminjaman VALUES (2, 'BK-002', 1), (2, 'BK-001', 1);
UPDATE buku SET stok = stok - 1 WHERE kode_buku = 'BK-002';
UPDATE buku SET stok = stok - 1 WHERE kode_buku = 'BK-001';
UPDATE peminjaman SET tanggal_kembali = '2026-01-20' WHERE id = 2;
UPDATE buku SET stok = stok + 1 WHERE kode_buku = 'BK-002';
UPDATE buku SET stok = stok + 1 WHERE kode_buku = 'BK-001';
COMMIT;
START TRANSACTION;
INSERT INTO peminjaman (anggota_id, tanggal_pinjam, tanggal_harus_kembali)
VALUES (2, '2026-02-01', '2026-02-08');
INSERT INTO detail_peminjaman VALUES (3, 'BK-001', 1);
UPDATE buku SET stok = stok - 1 WHERE kode_buku = 'BK-001';
COMMIT;
-- Ekspektasi: BK-001 total 4, BK-002 total 1, BK-003 total 0
SELECT b.kode_buku,
COALESCE(SUM(dp.jumlah), 0) AS total_dipinjam
FROM buku b
LEFT JOIN detail_peminjaman dp ON dp.buku_kode = b.kode_buku
GROUP BY b.kode_buku
ORDER BY total_dipinjam DESC, b.kode_buku;
-- Pemeriksaan 4: hanya peminjaman 3 (harus kembali 2026-02-08) yang terlambat.
-- Ekspektasi: 1 baris - peminjaman 3, 20 hari terlambat
SELECT p.id, DATEDIFF('2026-02-28', p.tanggal_harus_kembali) AS hari_terlambat
FROM peminjaman p
WHERE p.tanggal_kembali IS NULL
AND p.tanggal_harus_kembali < '2026-02-28'
ORDER BY p.id;
-- Pemeriksaan 5: stok akhir konsisten dengan seluruh mutasi.
-- Ekspektasi: BK-001 stok 2, BK-002 stok 2, BK-003 stok 2
SELECT kode_buku, stok FROM buku ORDER BY kode_buku;
DROP DATABASE perpustakaan_db;