Keputusan termahal pada sebuah database tidak diambil saat menulis query, melainkan saat merancang tabelnya. Salah menilai relasi barang–lokasi sebagai satu kolom sederhana, dan gudang yang sama akan kesulitan ketika satu barang harus tersimpan di dua rak sekaligus. Pada bagian ini kita merancang seluruh struktur database inventaris gudang: lima tabel, constraint bisnis yang menolak data mustahil, dan index untuk jalur query yang paling sering dipakai — lalu membuktikan bahwa setiap aturan benar-benar ditegakkan oleh PostgreSQL, bukan hanya oleh niat baik aplikasi.
Lima Tabel Inti #
Gudang pada proyek ini milik CV Maju Jaya, distributor barang kebutuhan harian dengan dua gedung: Gudang Utama dan Gudang Cadangan. Kebutuhan datanya menuntut lima tabel. Tabel barang dan lokasi adalah data master: apa yang disimpan dan di mana. Tabel stok_lokasi menjadi jembatan keduanya — pola junction table — karena satu barang dapat berada di banyak lokasi dan satu lokasi menampung banyak barang. Riwayat pergerakan dicatat pada pasangan mutasi (kepala dokumen: tipe, tanggal, petugas) dan mutasi_item (baris detail: barang, jumlah, dari lokasi mana ke lokasi mana).
-- buat-tabel.sql — struktur database inventaris gudang.
-- Lima tabel: barang, lokasi, stok_lokasi, mutasi, dan mutasi_item.
CREATE TABLE barang (
id integer GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
kode text NOT NULL UNIQUE,
nama text NOT NULL,
kategori text NOT NULL,
satuan text NOT NULL,
stok_minimum integer NOT NULL CHECK (stok_minimum >= 0),
harga_beli numeric(12, 2) NOT NULL CHECK (harga_beli > 0)
);
CREATE TABLE lokasi (
id integer GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
kode text NOT NULL UNIQUE,
gudang text NOT NULL,
rak text NOT NULL,
UNIQUE (gudang, rak)
);
CREATE TABLE stok_lokasi (
barang_id integer NOT NULL REFERENCES barang (id) ON DELETE CASCADE,
lokasi_id integer NOT NULL REFERENCES lokasi (id),
qty integer NOT NULL CONSTRAINT stok_qty_nonneg CHECK (qty >= 0),
PRIMARY KEY (barang_id, lokasi_id)
);
CREATE TABLE mutasi (
id integer GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
kode text NOT NULL UNIQUE,
tipe text NOT NULL CHECK (tipe IN ('masuk', 'keluar', 'pindah')),
tanggal date NOT NULL,
petugas text NOT NULL,
keterangan text
);
CREATE TABLE mutasi_item (
id integer GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
mutasi_id integer NOT NULL REFERENCES mutasi (id) ON DELETE CASCADE,
barang_id integer NOT NULL REFERENCES barang (id),
qty integer NOT NULL CHECK (qty > 0),
lokasi_asal_id integer REFERENCES lokasi (id),
lokasi_tujuan_id integer REFERENCES lokasi (id),
CONSTRAINT arah_lokasi_terisi
CHECK (num_nonnulls(lokasi_asal_id, lokasi_tujuan_id) >= 1)
);
CREATE INDEX idx_mutasi_tanggal ON mutasi (tanggal);
CREATE INDEX idx_mutasi_item_barang ON mutasi_item (barang_id);
CREATE INDEX idx_mutasi_item_mutasi ON mutasi_item (mutasi_id);Output yang Dihasilkan:
CREATE TABLE
CREATE TABLE
CREATE TABLE
CREATE TABLE
CREATE TABLE
CREATE INDEX
CREATE INDEX
CREATE INDEXMari kita bedah keputusan-keputusan pentingnya:
GENERATED ALWAYS AS IDENTITY PRIMARY KEY: id diisi otomatis oleh PostgreSQL dan dijamin unik. Berbeda denganserial, identity column mengikuti standar SQL dan tidak dapat diisi sembarangan tanpa perintahOVERRIDING SYSTEM VALUE— perlindungan kecil yang menyelamatkan konsistensi id.kode text NOT NULL UNIQUE: kode barang sepertiBRG-007adalah identitas yang dibaca manusia di label fisik dan surat jalan. Kolom ini dijamin unik sehingga dua barang berbeda tidak akan pernah tertukar labelnya.stok_minimumdanharga_beli: dua angka bisnis yang dipakai laporan pada Bagian 4.CHECKpada keduanya menolak nilai negatif — stok minimum negatif dan harga beli nol sama-sama tidak bermakna.PRIMARY KEY (barang_id, lokasi_id)distok_lokasi: kunci primer gabungan sekaligus menjadi targetON CONFLICTsaat stok ditambah. Satu pasangan barang–lokasi hanya boleh punya satu baris stok; itulah yang menjaga tabel tetap rapi.CONSTRAINT stok_qty_nonneg CHECK (qty >= 0): penjaga gerbang terpenting proyek ini. Stok negatif berarti barang keluar tanpa pernah masuk — data yang mustahil secara fisik, dan database menolaknya sebelum sempat tersimpan.num_nonnulls(lokasi_asal_id, lokasi_tujuan_id) >= 1: satu baris detail mutasi wajib menyebut minimal satu lokasi. Barang masuk cukup menulis lokasi tujuan, barang keluar cukup lokasi asal, dan perpindahan menulis keduanya.- Tiga index di akhir berkas: query laporan sering menyaring
mutasiberdasarkantanggal, dan detail mutasi sering di-join lewatbarang_idmaupunmutasi_id. Index pada tiga kolom itu mempercepat jalur-jalur tersebut.
Data Master #
Skema kosong belum bisa diuji dengan nyaman, jadi kita isi dua tabel master dengan data yang akan menemani seluruh proyek: sembilan barang dalam tiga kategori (Alat Tulis, Rumah Tangga, Pangan) dengan satuan yang beragam — box, lusin, pcs, dus, karung — serta lima lokasi: tiga rak di Gudang Utama dan dua rak di Gudang Cadangan.
-- data-awal.sql — data master: daftar barang dan lokasi penyimpanan.
INSERT INTO barang (kode, nama, kategori, satuan, stok_minimum, harga_beli) VALUES
('BRG-001', 'Kertas HVS A4 80gsm', 'Alat Tulis', 'box', 10, 185000.00),
('BRG-002', 'Pulpen Gel Hitam', 'Alat Tulis', 'box', 20, 42000.00),
('BRG-003', 'Buku Tulis 58 halaman', 'Alat Tulis', 'lusin', 30, 38500.00),
('BRG-004', 'Tinta Printer Hitam', 'Alat Tulis', 'pcs', 8, 95000.00),
('BRG-005', 'Sabun Cuci Cair 1L', 'Rumah Tangga','dus', 12, 87000.00),
('BRG-006', 'Deterjen Bubuk 800g', 'Rumah Tangga','dus', 15, 72000.00),
('BRG-007', 'Minyak Goreng 2L', 'Pangan', 'dus', 25, 115000.00),
('BRG-008', 'Beras Premium 5kg', 'Pangan', 'karung', 10, 320000.00),
('BRG-009', 'Kopi Sachet Instan', 'Pangan', 'dus', 40, 96000.00);
INSERT INTO lokasi (kode, gudang, rak) VALUES
('G1-A-01', 'Gudang Utama', 'Rak A-01'),
('G1-B-02', 'Gudang Utama', 'Rak B-02'),
('G1-C-03', 'Gudang Utama', 'Rak C-03'),
('G2-A-01', 'Gudang Cadangan', 'Rak A-01'),
('G2-B-02', 'Gudang Cadangan', 'Rak B-02');
SELECT id, kode, nama, satuan, stok_minimum, harga_beli
FROM barang
ORDER BY id;
SELECT id, kode, gudang, rak
FROM lokasi
ORDER BY id;Output yang Dihasilkan:
INSERT 0 9
INSERT 0 5
id | kode | nama | satuan | stok_minimum | harga_beli
----+---------+-----------------------+--------+--------------+------------
1 | BRG-001 | Kertas HVS A4 80gsm | box | 10 | 185000.00
2 | BRG-002 | Pulpen Gel Hitam | box | 20 | 42000.00
3 | BRG-003 | Buku Tulis 58 halaman | lusin | 30 | 38500.00
4 | BRG-004 | Tinta Printer Hitam | pcs | 8 | 95000.00
5 | BRG-005 | Sabun Cuci Cair 1L | dus | 12 | 87000.00
6 | BRG-006 | Deterjen Bubuk 800g | dus | 15 | 72000.00
7 | BRG-007 | Minyak Goreng 2L | dus | 25 | 115000.00
8 | BRG-008 | Beras Premium 5kg | karung | 10 | 320000.00
9 | BRG-009 | Kopi Sachet Instan | dus | 40 | 96000.00
(9 rows)
id | kode | gudang | rak
----+---------+-----------------+----------
1 | G1-A-01 | Gudang Utama | Rak A-01
2 | G1-B-02 | Gudang Utama | Rak B-02
3 | G1-C-03 | Gudang Utama | Rak C-03
4 | G2-A-01 | Gudang Cadangan | Rak A-01
5 | G2-B-02 | Gudang Cadangan | Rak B-02
(5 rows)Dua hal perlu dicermati dari data ini. Pertama, harga_beli bertipe numeric(12, 2) dan ditulis dengan dua desimal (185000.00) — uang disimpan sebagai numeric, bukan float, karena pembulatan pecahan biner bisa membuat laporan nilai persediaan meleset beberapa rupiah yang lama-lama menumpuk. Kedua, stok_minimum sengaja bervariasi (8 sampai 40): pada Bagian 4 kita akan melihat beberapa barang jatuh di bawah ambangnya dan beberapa lagi justru menumpuk tak tersentuh.
Menguji Constraint Sebelum Lanjut #
Skema yang belum diuji adalah skema yang belum selesai. Berkas berikut mencoba enam pelanggaran secara sengaja di dalam blok EXCEPTION, menangkap SQLSTATE yang dikembalikan PostgreSQL, lalu membandingkannya dengan yang diharapkan — pola yang sama dengan chapter Pengujian Integritas Data
.
-- uji-constraint.sql — bukti bahwa constraint bisnis benar-benar ditegakkan.
-- Setiap pelanggaran ditangkap dalam blok EXCEPTION, lalu dibandingkan
-- dengan SQLSTATE yang diharapkan.
CREATE TEMP TABLE hasil_uji (
nomor integer,
kasus text,
diharapkan text,
aktual text,
status text
);
DO $$
DECLARE
v_state text;
BEGIN
-- satu mutasi sah sebagai induk untuk kasus yang mengujinya
INSERT INTO mutasi (kode, tipe, tanggal, petugas, keterangan)
VALUES ('MUT-UJI-1', 'masuk', DATE '2026-02-01', 'Budi', 'mutasi untuk pengujian');
-- Kasus 1: kode barang duplikat (UNIQUE)
BEGIN
INSERT INTO barang (kode, nama, kategori, satuan, stok_minimum, harga_beli)
VALUES ('BRG-001', 'Kertas duplikat', 'Alat Tulis', 'box', 5, 1000.00);
INSERT INTO hasil_uji VALUES (1, 'kode barang duplikat', '23505', NULL, 'GAGAL: diterima');
EXCEPTION WHEN OTHERS THEN
GET STACKED DIAGNOSTICS v_state = RETURNED_SQLSTATE;
INSERT INTO hasil_uji VALUES (1, 'kode barang duplikat', '23505', v_state,
CASE WHEN v_state = '23505' THEN 'LULUS' ELSE 'GAGAL' END);
END;
-- Kasus 2: stok_minimum negatif (CHECK)
BEGIN
INSERT INTO barang (kode, nama, kategori, satuan, stok_minimum, harga_beli)
VALUES ('BRG-100', 'Barang rusak', 'Pangan', 'pcs', -5, 1000.00);
INSERT INTO hasil_uji VALUES (2, 'stok_minimum negatif', '23514', NULL, 'GAGAL: diterima');
EXCEPTION WHEN OTHERS THEN
GET STACKED DIAGNOSTICS v_state = RETURNED_SQLSTATE;
INSERT INTO hasil_uji VALUES (2, 'stok_minimum negatif', '23514', v_state,
CASE WHEN v_state = '23514' THEN 'LULUS' ELSE 'GAGAL' END);
END;
-- Kasus 3: tipe mutasi tidak dikenal (CHECK)
BEGIN
INSERT INTO mutasi (kode, tipe, tanggal, petugas)
VALUES ('MUT-UJI-2', 'hilang', DATE '2026-02-01', 'Budi');
INSERT INTO hasil_uji VALUES (3, 'tipe mutasi tidak dikenal', '23514', NULL, 'GAGAL: diterima');
EXCEPTION WHEN OTHERS THEN
GET STACKED DIAGNOSTICS v_state = RETURNED_SQLSTATE;
INSERT INTO hasil_uji VALUES (3, 'tipe mutasi tidak dikenal', '23514', v_state,
CASE WHEN v_state = '23514' THEN 'LULUS' ELSE 'GAGAL' END);
END;
-- Kasus 4: item mutasi tanpa lokasi asal maupun tujuan (CHECK arah_lokasi_terisi)
BEGIN
INSERT INTO mutasi_item (mutasi_id, barang_id, qty)
VALUES ((SELECT id FROM mutasi WHERE kode = 'MUT-UJI-1'),
(SELECT id FROM barang WHERE kode = 'BRG-001'),
10);
INSERT INTO hasil_uji VALUES (4, 'item tanpa lokasi asal/tujuan', '23514', NULL, 'GAGAL: diterima');
EXCEPTION WHEN OTHERS THEN
GET STACKED DIAGNOSTICS v_state = RETURNED_SQLSTATE;
INSERT INTO hasil_uji VALUES (4, 'item tanpa lokasi asal/tujuan', '23514', v_state,
CASE WHEN v_state = '23514' THEN 'LULUS' ELSE 'GAGAL' END);
END;
-- Kasus 5: item mutasi merujuk barang yang tidak ada (FOREIGN KEY)
BEGIN
INSERT INTO mutasi_item (mutasi_id, barang_id, qty, lokasi_tujuan_id)
VALUES ((SELECT id FROM mutasi WHERE kode = 'MUT-UJI-1'),
999,
10,
(SELECT id FROM lokasi WHERE kode = 'G1-A-01'));
INSERT INTO hasil_uji VALUES (5, 'item merujuk barang 999', '23503', NULL, 'GAGAL: diterima');
EXCEPTION WHEN OTHERS THEN
GET STACKED DIAGNOSTICS v_state = RETURNED_SQLSTATE;
INSERT INTO hasil_uji VALUES (5, 'item merujuk barang 999', '23503', v_state,
CASE WHEN v_state = '23503' THEN 'LULUS' ELSE 'GAGAL' END);
END;
-- Kasus 6: qty mutasi bernilai nol (CHECK qty > 0)
BEGIN
INSERT INTO mutasi_item (mutasi_id, barang_id, qty, lokasi_tujuan_id)
VALUES ((SELECT id FROM mutasi WHERE kode = 'MUT-UJI-1'),
(SELECT id FROM barang WHERE kode = 'BRG-001'),
0,
(SELECT id FROM lokasi WHERE kode = 'G1-A-01'));
INSERT INTO hasil_uji VALUES (6, 'qty mutasi bernilai nol', '23514', NULL, 'GAGAL: diterima');
EXCEPTION WHEN OTHERS THEN
GET STACKED DIAGNOSTICS v_state = RETURNED_SQLSTATE;
INSERT INTO hasil_uji VALUES (6, 'qty mutasi bernilai nol', '23514', v_state,
CASE WHEN v_state = '23514' THEN 'LULUS' ELSE 'GAGAL' END);
END;
END $$;
SELECT nomor, kasus, diharapkan, aktual, status
FROM hasil_uji
ORDER BY nomor;Output yang Dihasilkan:
CREATE TABLE
DO
nomor | kasus | diharapkan | aktual | status
-------+-------------------------------+------------+--------+--------
1 | kode barang duplikat | 23505 | 23505 | LULUS
2 | stok_minimum negatif | 23514 | 23514 | LULUS
3 | tipe mutasi tidak dikenal | 23514 | 23514 | LULUS
4 | item tanpa lokasi asal/tujuan | 23514 | 23514 | LULUS
5 | item merujuk barang 999 | 23503 | 23503 | LULUS
6 | qty mutasi bernilai nol | 23514 | 23514 | LULUS
(6 rows)Enam kasus, enam vonis LULUS: kode duplikat ditolak UNIQUE (SQLSTATE 23505), stok minimum negatif dan tipe mutasi asing ditolak CHECK (23514), item tanpa lokasi ditolak num_nonnulls, barang yang tidak ada ditolak foreign key (23503), dan qty nol ditolak sebelum sempat tercatat. Bila kelak ada aturan bisnis baru — misalnya batas maksimum per satu mutasi — tempat menambahkannya sudah jelas: constraint di tabel, bukan if-else yang tersebar di banyak aplikasi.
Struktur penyimpanan kini berdiri dan teruji. Namun tabel stok_lokasi masih kosong dan mutasi belum berisi apa-apa — gudang ini belum pernah menerima barang. Bagian Pencatatan Stok
menghidupkannya: barang mulai datang dari supplier, keluar menuju toko, dan setiap pergerakan berlaku sebagai satu transaksi utuh.
Referensi #
- Constraints pada Tabel — spesifikasi CHECK, UNIQUE, FOREIGN KEY, dan pilihan ON DELETE.
- CREATE TABLE — sintaks lengkap pembuatan tabel, termasuk table constraint bernama.
- Comparison Functions
— dokumentasi
num_nonnulls()untuk memeriksa banyaknya argumen non-NULL. - Numeric Types
— alasan
numeric(12, 2)dipilih untuk kolom uang.
Berkas Sumber
Berkas pendukung halaman ini dapat diunduh dan dijalankan langsung sesuai penjelasan di atas.
buat-tabel.sql
Unduh berkas-- buat-tabel.sql — struktur database inventaris gudang.
-- Lima tabel: barang, lokasi, stok_lokasi, mutasi, dan mutasi_item.
CREATE TABLE barang (
id integer GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
kode text NOT NULL UNIQUE,
nama text NOT NULL,
kategori text NOT NULL,
satuan text NOT NULL,
stok_minimum integer NOT NULL CHECK (stok_minimum >= 0),
harga_beli numeric(12, 2) NOT NULL CHECK (harga_beli > 0)
);
CREATE TABLE lokasi (
id integer GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
kode text NOT NULL UNIQUE,
gudang text NOT NULL,
rak text NOT NULL,
UNIQUE (gudang, rak)
);
CREATE TABLE stok_lokasi (
barang_id integer NOT NULL REFERENCES barang (id) ON DELETE CASCADE,
lokasi_id integer NOT NULL REFERENCES lokasi (id),
qty integer NOT NULL CONSTRAINT stok_qty_nonneg CHECK (qty >= 0),
PRIMARY KEY (barang_id, lokasi_id)
);
CREATE TABLE mutasi (
id integer GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
kode text NOT NULL UNIQUE,
tipe text NOT NULL CHECK (tipe IN ('masuk', 'keluar', 'pindah')),
tanggal date NOT NULL,
petugas text NOT NULL,
keterangan text
);
CREATE TABLE mutasi_item (
id integer GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
mutasi_id integer NOT NULL REFERENCES mutasi (id) ON DELETE CASCADE,
barang_id integer NOT NULL REFERENCES barang (id),
qty integer NOT NULL CHECK (qty > 0),
lokasi_asal_id integer REFERENCES lokasi (id),
lokasi_tujuan_id integer REFERENCES lokasi (id),
CONSTRAINT arah_lokasi_terisi
CHECK (num_nonnulls(lokasi_asal_id, lokasi_tujuan_id) >= 1)
);
CREATE INDEX idx_mutasi_tanggal ON mutasi (tanggal);
CREATE INDEX idx_mutasi_item_barang ON mutasi_item (barang_id);
CREATE INDEX idx_mutasi_item_mutasi ON mutasi_item (mutasi_id);
data-awal.sql
Unduh berkas-- data-awal.sql — data master: daftar barang dan lokasi penyimpanan.
INSERT INTO barang (kode, nama, kategori, satuan, stok_minimum, harga_beli) VALUES
('BRG-001', 'Kertas HVS A4 80gsm', 'Alat Tulis', 'box', 10, 185000.00),
('BRG-002', 'Pulpen Gel Hitam', 'Alat Tulis', 'box', 20, 42000.00),
('BRG-003', 'Buku Tulis 58 halaman', 'Alat Tulis', 'lusin', 30, 38500.00),
('BRG-004', 'Tinta Printer Hitam', 'Alat Tulis', 'pcs', 8, 95000.00),
('BRG-005', 'Sabun Cuci Cair 1L', 'Rumah Tangga','dus', 12, 87000.00),
('BRG-006', 'Deterjen Bubuk 800g', 'Rumah Tangga','dus', 15, 72000.00),
('BRG-007', 'Minyak Goreng 2L', 'Pangan', 'dus', 25, 115000.00),
('BRG-008', 'Beras Premium 5kg', 'Pangan', 'karung', 10, 320000.00),
('BRG-009', 'Kopi Sachet Instan', 'Pangan', 'dus', 40, 96000.00);
INSERT INTO lokasi (kode, gudang, rak) VALUES
('G1-A-01', 'Gudang Utama', 'Rak A-01'),
('G1-B-02', 'Gudang Utama', 'Rak B-02'),
('G1-C-03', 'Gudang Utama', 'Rak C-03'),
('G2-A-01', 'Gudang Cadangan', 'Rak A-01'),
('G2-B-02', 'Gudang Cadangan', 'Rak B-02');
SELECT id, kode, nama, satuan, stok_minimum, harga_beli
FROM barang
ORDER BY id;
SELECT id, kode, gudang, rak
FROM lokasi
ORDER BY id;
uji-constraint.sql
Unduh berkas-- uji-constraint.sql — bukti bahwa constraint bisnis benar-benar ditegakkan.
-- Setiap pelanggaran ditangkap dalam blok EXCEPTION, lalu dibandingkan
-- dengan SQLSTATE yang diharapkan.
CREATE TEMP TABLE hasil_uji (
nomor integer,
kasus text,
diharapkan text,
aktual text,
status text
);
DO $$
DECLARE
v_state text;
BEGIN
-- satu mutasi sah sebagai induk untuk kasus yang mengujinya
INSERT INTO mutasi (kode, tipe, tanggal, petugas, keterangan)
VALUES ('MUT-UJI-1', 'masuk', DATE '2026-02-01', 'Budi', 'mutasi untuk pengujian');
-- Kasus 1: kode barang duplikat (UNIQUE)
BEGIN
INSERT INTO barang (kode, nama, kategori, satuan, stok_minimum, harga_beli)
VALUES ('BRG-001', 'Kertas duplikat', 'Alat Tulis', 'box', 5, 1000.00);
INSERT INTO hasil_uji VALUES (1, 'kode barang duplikat', '23505', NULL, 'GAGAL: diterima');
EXCEPTION WHEN OTHERS THEN
GET STACKED DIAGNOSTICS v_state = RETURNED_SQLSTATE;
INSERT INTO hasil_uji VALUES (1, 'kode barang duplikat', '23505', v_state,
CASE WHEN v_state = '23505' THEN 'LULUS' ELSE 'GAGAL' END);
END;
-- Kasus 2: stok_minimum negatif (CHECK)
BEGIN
INSERT INTO barang (kode, nama, kategori, satuan, stok_minimum, harga_beli)
VALUES ('BRG-100', 'Barang rusak', 'Pangan', 'pcs', -5, 1000.00);
INSERT INTO hasil_uji VALUES (2, 'stok_minimum negatif', '23514', NULL, 'GAGAL: diterima');
EXCEPTION WHEN OTHERS THEN
GET STACKED DIAGNOSTICS v_state = RETURNED_SQLSTATE;
INSERT INTO hasil_uji VALUES (2, 'stok_minimum negatif', '23514', v_state,
CASE WHEN v_state = '23514' THEN 'LULUS' ELSE 'GAGAL' END);
END;
-- Kasus 3: tipe mutasi tidak dikenal (CHECK)
BEGIN
INSERT INTO mutasi (kode, tipe, tanggal, petugas)
VALUES ('MUT-UJI-2', 'hilang', DATE '2026-02-01', 'Budi');
INSERT INTO hasil_uji VALUES (3, 'tipe mutasi tidak dikenal', '23514', NULL, 'GAGAL: diterima');
EXCEPTION WHEN OTHERS THEN
GET STACKED DIAGNOSTICS v_state = RETURNED_SQLSTATE;
INSERT INTO hasil_uji VALUES (3, 'tipe mutasi tidak dikenal', '23514', v_state,
CASE WHEN v_state = '23514' THEN 'LULUS' ELSE 'GAGAL' END);
END;
-- Kasus 4: item mutasi tanpa lokasi asal maupun tujuan (CHECK arah_lokasi_terisi)
BEGIN
INSERT INTO mutasi_item (mutasi_id, barang_id, qty)
VALUES ((SELECT id FROM mutasi WHERE kode = 'MUT-UJI-1'),
(SELECT id FROM barang WHERE kode = 'BRG-001'),
10);
INSERT INTO hasil_uji VALUES (4, 'item tanpa lokasi asal/tujuan', '23514', NULL, 'GAGAL: diterima');
EXCEPTION WHEN OTHERS THEN
GET STACKED DIAGNOSTICS v_state = RETURNED_SQLSTATE;
INSERT INTO hasil_uji VALUES (4, 'item tanpa lokasi asal/tujuan', '23514', v_state,
CASE WHEN v_state = '23514' THEN 'LULUS' ELSE 'GAGAL' END);
END;
-- Kasus 5: item mutasi merujuk barang yang tidak ada (FOREIGN KEY)
BEGIN
INSERT INTO mutasi_item (mutasi_id, barang_id, qty, lokasi_tujuan_id)
VALUES ((SELECT id FROM mutasi WHERE kode = 'MUT-UJI-1'),
999,
10,
(SELECT id FROM lokasi WHERE kode = 'G1-A-01'));
INSERT INTO hasil_uji VALUES (5, 'item merujuk barang 999', '23503', NULL, 'GAGAL: diterima');
EXCEPTION WHEN OTHERS THEN
GET STACKED DIAGNOSTICS v_state = RETURNED_SQLSTATE;
INSERT INTO hasil_uji VALUES (5, 'item merujuk barang 999', '23503', v_state,
CASE WHEN v_state = '23503' THEN 'LULUS' ELSE 'GAGAL' END);
END;
-- Kasus 6: qty mutasi bernilai nol (CHECK qty > 0)
BEGIN
INSERT INTO mutasi_item (mutasi_id, barang_id, qty, lokasi_tujuan_id)
VALUES ((SELECT id FROM mutasi WHERE kode = 'MUT-UJI-1'),
(SELECT id FROM barang WHERE kode = 'BRG-001'),
0,
(SELECT id FROM lokasi WHERE kode = 'G1-A-01'));
INSERT INTO hasil_uji VALUES (6, 'qty mutasi bernilai nol', '23514', NULL, 'GAGAL: diterima');
EXCEPTION WHEN OTHERS THEN
GET STACKED DIAGNOSTICS v_state = RETURNED_SQLSTATE;
INSERT INTO hasil_uji VALUES (6, 'qty mutasi bernilai nol', '23514', v_state,
CASE WHEN v_state = '23514' THEN 'LULUS' ELSE 'GAGAL' END);
END;
END $$;
SELECT nomor, kasus, diharapkan, aktual, status
FROM hasil_uji
ORDER BY nomor;