Trigger bagaikan pisau bermata dua. Salah penggunaan bisa berakibat fatal, dan tidak sedikit developer software antipati dengan trigger karena sering terjadi hal-hal yang tidak disangka-sangka dan bahkan di luar nalar, berhari-hari melakukan debugging, pada akhirnya problem ditemukan karena adanya sebuat trigger yang tidak terdokumentasi dengan baik. Walaupun sebuah pisau bisa membunuh kita, bukan berarti kita harus membuangnya jauh-jauh dan tidak mau menggunakannya lagi, karena penggunaan yang benar tentunya akan sangat membantu mempermudah dan meningkatkan kualitas hidup kita 🙂
Dalam artikel ini, akan kita mempelajari cara penggunaan trigger yang benar, dalam rangka membantu mempermudah kita membuat sebuah Aplikasi Inventory Sederhana Aplikasi Stok Barang ini. Trigger akan kita gunakan untuk melakukan update status kuantitas stok barang setiap kali terjadi transaksi penambahan dan pengurangan barang, atau transaksi barang masuk atau barang keluar. Jika kita sederhanakan sampai level database, maka akan ada perintah SQL INSERT, UPDATE, dan DELETE di table transaksi yang secara otomatis, mengubah status stok barang bersangkutan.
Desain database sudah kita buat pada langkah analisa dan desain database pada artikel sebelumnya. Maka langkah selanjutnya adalah membuat trigger untuk setiap jenis SQL DML. Trigger akan kita tempelkan ke table transaction_details, karena di table itulah informasi ID dan jumlah kuantitas barang tersedia. Untuk detail sintaks CREATE TRIGGER dapat mengacu ke dokumentasi MySQL CREATE TRIGGER di sini.
Referensi Data
Untuk tutorial ini, diasumsikan sudah tersedia data untuk transaction_types dan items seperti berikut ini,
mysql> -- ----------------------------- mysql> -- data jenis transaksi mysql> -- ----------------------------- mysql> select * from transaction_types ; +----+------+---------------+ | id | code | name | +----+------+---------------+ | 1 | IN | BARANG MASUK | | 2 | OUT | BARANG KELUAR | +----+------+---------------+ 2 rows in set (0.00 sec) mysql> -- ----------------------------- mysql> -- data master barang mysql> -- ----------------------------- mysql> select * from items ; +----+--------+-------------------------+------------------+---------+ | id | code | name | quantity_on_hand | remarks | +----+--------+-------------------------+------------------+---------+ | 1 | PC.001 | PC Jangkrik Tipe 1 | 0.00 | NULL | | 2 | PC.002 | PC Jangkrik Tipe 2 | 0.00 | NULL | | 3 | SV.001 | Server Rainer Tipe 1 | 0.00 | NULL | | 4 | SV.002 | Server Rainer Rackmount | 0.00 | NULL | +----+--------+-------------------------+------------------+---------+ 4 rows in set (0.00 sec)
INSERT Data Transaksi
Untuk proses INSERT ini akan kita set berjalan setiap kali ada sebuah row dimasukkan ke dalam database, diindikasikan dengan keywork FOR EACH ROW, dan setiap row yang baru masuk bisa kita ketahui di variable NEW. Logikanya adalah seperti ini,
- Setiap kali ada transaksi barang masuk, maka field stok barang di table items akan kita tambahkan,
- Sebaliknya, setiap kali ada transaksi barang keluar, maka field stok barang di table items akan kita kurangi,
maka trigger INSERT akan seperti ini.
mysql> delimiter $$ mysql> drop trigger if exists trg_transaction_details_after_insert$$ Query OK, 0 rows affected (0.00 sec) mysql> -- ----------------------------- mysql> -- after insert trigger mysql> -- ----------------------------- mysql> create trigger trg_transaction_details_after_insert -> after insert on transaction_details -> for each row -> begin -> -- ----------------------------- -> -- ambil jenis transaksi -> -- ----------------------------- -> declare tipe varchar(20); -> set tipe = ( -> select tt.code -> from transactions tr -> join transaction_types tt on tr.type_id=tt.id -> where tr.id=new.trans_id -> ) ; -> -- ----------------------------- -> -- update sesuai jenis transaksi -> -- ----------------------------- -> update items -> set quantity_on_hand -> = quantity_on_hand -> + case when tipe='IN' then new.quantity -> when tipe='OUT' then -new.quantity -> else 0 end -> where items.id=new.item_id ; -> end$$ Query OK, 0 rows affected (0.12 sec) mysql> delimiter ; mysql>
[collapsed title=Spoiler: Hasil Test INSERT Transaksi Barang Masuk dan Barang Keluar]
Test INSERT data transaksi barang masuk.
mysql> ----------------------------- mysql> -- insert header transaksi barang masuk mysql> ----------------------------- mysql> insert into transactions (type_id, trans_code, trans_date, remarks) -> values (1, 'BM.00001/2014', '2014-01-10', 'Penerimaan Barang') ; Query OK, 1 row affected (0.05 sec) mysql> ----------------------------- mysql> -- lihat data transaksi mysql> -- pastikan data sudah masuk mysql> ----------------------------- mysql> select * from transactions; +----+---------+---------------+------------+-------------------+ | id | type_id | trans_code | trans_date | remarks | +----+---------+---------------+------------+-------------------+ | 1 | 1 | BM.00001/2014 | 2014-01-10 | Penerimaan Barang | +----+---------+---------------+------------+-------------------+ 1 row in set (0.00 sec) mysql> ----------------------------- mysql> -- insert detail transaksi barang masuk mysql> ----------------------------- mysql> insert into transaction_details (trans_id, item_id, quantity, remarks) -> values (1, 1, 20, 'Penerimaan Barang 1') -> , (1, 2, 30, 'Penerimaan Barang 2') -> , (1, 3, 10, 'Penerimaan Barang 3') -> , (1, 4, 15, 'Penerimaan Barang 4') ; Query OK, 4 rows affected (0.07 sec) Records: 4 Duplicates: 0 Warnings: 0 mysql> ----------------------------- mysql> -- lihat detail transaksi mysql> -- pastikan data sudah masuk mysql> ----------------------------- mysql> select * from transaction_details where trans_id=1 ; +----+----------+---------+----------+---------------------+ | id | trans_id | item_id | quantity | remarks | +----+----------+---------+----------+---------------------+ | 22 | 1 | 1 | 20 | Penerimaan Barang 1 | | 23 | 1 | 2 | 30 | Penerimaan Barang 2 | | 24 | 1 | 3 | 10 | Penerimaan Barang 3 | | 25 | 1 | 4 | 15 | Penerimaan Barang 4 | +----+----------+---------+----------+---------------------+ 4 rows in set (0.00 sec) mysql> ----------------------------- mysql> -- dengan asumsi awal semua stok barang masih kosong mysql> -- maka saat ini quantity on hand pasti sudah berubah mysql> ----------------------------- mysql> select * from items ; +----+--------+-------------------------+------------------+---------+ | id | code | name | quantity_on_hand | remarks | +----+--------+-------------------------+------------------+---------+ | 1 | PC.001 | PC Jangkrik Tipe 1 | 20.00 | NULL | | 2 | PC.002 | PC Jangkrik Tipe 2 | 30.00 | NULL | | 3 | SV.001 | Server Rainer Tipe 1 | 10.00 | NULL | | 4 | SV.002 | Server Rainer Rackmount | 15.00 | NULL | +----+--------+-------------------------+------------------+---------+ 4 rows in set (0.00 sec) mysql> ----------------------------- mysql> -- YAY!!! Berhasil!!! mysql> ----------------------------- mysql>
Test INSERT data transaksi barang keluar.
mysql> ----------------------------- mysql> -- insert header transaksi barang keluar mysql> ----------------------------- mysql> insert into transactions (type_id, trans_code, trans_date, remarks) -> values (2, 'BK.00001/2014', '2014-01-11', 'Pengeluaran Barang') ; Query OK, 1 row affected (0.05 sec) mysql> ----------------------------- mysql> -- lihat data transaksi mysql> -- pastikan data sudah masuk mysql> ----------------------------- mysql> select * from transactions ; +----+---------+---------------+------------+--------------------+ | id | type_id | trans_code | trans_date | remarks | +----+---------+---------------+------------+--------------------+ | 1 | 1 | BM.00001/2014 | 2014-01-10 | Penerimaan Barang | | 2 | 2 | BK.00001/2014 | 2014-01-11 | Pengeluaran Barang | +----+---------+---------------+------------+--------------------+ 2 rows in set (0.00 sec) mysql> ----------------------------- mysql> -- insert detail transaksi barang keluar mysql> ----------------------------- mysql> insert into transaction_details (trans_id, item_id, quantity, remarks) -> values (2, 1, 4, 'Pengeluaran Barang 1') -> , (2, 4, 5, 'Pengeluaran Barang 4') ; Query OK, 2 rows affected (0.07 sec) Records: 2 Duplicates: 0 Warnings: 0 mysql> ----------------------------- mysql> -- lihat detail transaksi mysql> -- pastikan data sudah masuk mysql> ----------------------------- mysql> select * from transaction_details where trans_id=2 ; +----+----------+---------+----------+----------------------+ | id | trans_id | item_id | quantity | remarks | +----+----------+---------+----------+----------------------+ | 29 | 2 | 1 | 4 | Pengeluaran Barang 1 | | 30 | 2 | 4 | 5 | Pengeluaran Barang 4 | +----+----------+---------+----------+----------------------+ 2 rows in set (0.00 sec) mysql> ----------------------------- mysql> -- dengan asumsi awal semua stok barang masih kosong mysql> -- maka saat ini quantity on hand pasti sudah berubah mysql> -- item_id=1 yang semula 20, dikurangi 4 menjadi 16 mysql> -- item_id=4 yang semula 15, dikurangi 5 menjadi 10 mysql> ----------------------------- mysql> select * from items ; +----+--------+-------------------------+------------------+---------+ | id | code | name | quantity_on_hand | remarks | +----+--------+-------------------------+------------------+---------+ | 1 | PC.001 | PC Jangkrik Tipe 1 | 16.00 | NULL | | 2 | PC.002 | PC Jangkrik Tipe 2 | 30.00 | NULL | | 3 | SV.001 | Server Rainer Tipe 1 | 10.00 | NULL | | 4 | SV.002 | Server Rainer Rackmount | 10.00 | NULL | +----+--------+-------------------------+------------------+---------+ 4 rows in set (0.00 sec) mysql> ----------------------------- mysql> -- YAY!!! Berhasil Lagi!!! mysql> ----------------------------- mysql>
[/collapsed]
UPDATE (Edit) Data Transaksi
- Setiap kali ada update data transaksi barang masuk, maka field stok barang di table items akan kita kurangi dengan kuantitas lama kemudian kita tambahkan dengan kuantitas yang baru,
- Sebaliknya, setiap kali ada update data transaksi barang keluar, maka field stok barang di table items akan kita tambahkan dengan kuantitas lama kemudian kita kurangi dengan kuantitas yang baru,
maka trigger UPDATE akan seperti ini,
mysql> delimiter $$ mysql> mysql> drop trigger if exists trg_transaction_details_after_update$$ Query OK, 0 rows affected (0.06 sec) mysql> -- ----------------------------- mysql> -- after update trigger mysql> -- ----------------------------- mysql> create trigger trg_transaction_details_after_update -> after update on transaction_details -> for each row -> begin -> -- ----------------------------- -> -- ambil jenis transaksi -> -- ----------------------------- -> declare tipe varchar(20); -> set tipe = ( -> select tt.code -> from transactions tr -> join transaction_types tt on tr.type_id=tt.id -> where tr.id=old.trans_id -> ) ; -> -- ----------------------------- -> -- update sesuai jenis transaksi -> -- => kurangi dengan old quantity -> -- ----------------------------- -> update items -> set quantity_on_hand -> = quantity_on_hand -> + case when tipe='IN' then -old.quantity -> when tipe='OUT' then old.quantity -> else 0 end -> where items.id=old.item_id ; -> -- ----------------------------- -> -- update sesuai jenis transaksi -> -- => tambahkan dengan new quantity -> -- ----------------------------- -> update items -> set quantity_on_hand -> = quantity_on_hand -> + case when tipe='IN' then new.quantity -> when tipe='OUT' then -new.quantity -> else 0 end -> where items.id=new.item_id ; -> end$$ Query OK, 0 rows affected (0.14 sec) mysql> delimiter ; mysql>
[collapsed title=Spoiler: Hasil Test UPDATE Transaksi Barang Masuk dan Barang Keluar]
Status data sebelum Test UPDATE.
mysql> ----------------------------- mysql> -- status stok mysql> -- sebelum proses UPDATE mysql> ----------------------------- mysql> select * from items; +----+--------+-------------------------+------------------+---------+ | id | code | name | quantity_on_hand | remarks | +----+--------+-------------------------+------------------+---------+ | 1 | PC.001 | PC Jangkrik Tipe 1 | 16.00 | NULL | | 2 | PC.002 | PC Jangkrik Tipe 2 | 30.00 | NULL | | 3 | SV.001 | Server Rainer Tipe 1 | 10.00 | NULL | | 4 | SV.002 | Server Rainer Rackmount | 10.00 | NULL | +----+--------+-------------------------+------------------+---------+ 4 rows in set (0.00 sec) mysql> ----------------------------- mysql> -- status detil transaksi mysql> -- sebelum proses UPDATE mysql> ----------------------------- mysql> select * from transaction_details ; +----+----------+---------+----------+----------------------+ | id | trans_id | item_id | quantity | remarks | +----+----------+---------+----------+----------------------+ | 22 | 1 | 1 | 20 | Penerimaan Barang 1 | | 23 | 1 | 2 | 30 | Penerimaan Barang 2 | | 24 | 1 | 3 | 10 | Penerimaan Barang 3 | | 25 | 1 | 4 | 15 | Penerimaan Barang 4 | | 29 | 2 | 1 | 4 | Pengeluaran Barang 1 | | 30 | 2 | 4 | 5 | Pengeluaran Barang 4 | +----+----------+---------+----------+----------------------+ 6 rows in set (0.00 sec)
Test UPDATE data transaksi barang masuk.
mysql> ----------------------------- mysql> -- ubah detil transaksi masuk mysql> -- untuk id transaksi 22, mysql> -- dari quantity awal 20 menjadi 50 mysql> ----------------------------- mysql> update transaction_details -> set quantity = 50 -> where id = 22 ; Query OK, 1 row affected (0.07 sec) Rows matched: 1 Changed: 1 Warnings: 0 mysql> ----------------------------- mysql> -- maka untuk item_id 1 (dari transaction detail id 22) mysql> -- ada update quantity dari 20 menjadi 50 ada selisih +30 mysql> -- maka stok barang berubah, 16 -20 +30 = 46 mysql> ----------------------------- mysql> select * from items ; +----+--------+-------------------------+------------------+---------+ | id | code | name | quantity_on_hand | remarks | +----+--------+-------------------------+------------------+---------+ | 1 | PC.001 | PC Jangkrik Tipe 1 | 46.00 | NULL | | 2 | PC.002 | PC Jangkrik Tipe 2 | 30.00 | NULL | | 3 | SV.001 | Server Rainer Tipe 1 | 10.00 | NULL | | 4 | SV.002 | Server Rainer Rackmount | 10.00 | NULL | +----+--------+-------------------------+------------------+---------+ 4 rows in set (0.00 sec) Query OK, 0 rows affected (0.04 sec) mysql> ----------------------------- mysql> -- YAY!!! Sukses!!! mysql> ----------------------------- mysql>
Test UPDATE data transaksi barang keluar.
mysql> ----------------------------- mysql> -- ubah detil transaksi keluar mysql> -- untuk id transaksi 29, mysql> -- dari quantity awal 4 menjadi 25 mysql> ----------------------------- mysql> update transaction_details -> set quantity = 25 -> where id = 29 ; Query OK, 1 row affected (0.07 sec) Rows matched: 1 Changed: 1 Warnings: 0 mysql> ----------------------------- mysql> -- maka untuk item_id 1 (dari transaction detail id 29) mysql> -- ada update quantity keluar dari 4 menjadi 25 ada selisih 21 mysql> -- dari transaksi sebelumnya saldo akhir item_id 1 adalah 46 mysql> -- maka saldo stok barang berubah, 46 +4 -25 = 25 mysql> ----------------------------- mysql> select * from items ; +----+--------+-------------------------+------------------+---------+ | id | code | name | quantity_on_hand | remarks | +----+--------+-------------------------+------------------+---------+ | 1 | PC.001 | PC Jangkrik Tipe 1 | 25.00 | NULL | | 2 | PC.002 | PC Jangkrik Tipe 2 | 30.00 | NULL | | 3 | SV.001 | Server Rainer Tipe 1 | 10.00 | NULL | | 4 | SV.002 | Server Rainer Rackmount | 10.00 | NULL | +----+--------+-------------------------+------------------+---------+ 4 rows in set (0.00 sec) mysql> ----------------------------- mysql> -- YAY!!! Awesome!!! mysql> ----------------------------- mysql>
[/collapsed]
DELETE Data Transaksi
- Setiap kali ada penghapusan data transaksi barang masuk, maka field stok barang di table items akan kita kurangi,
- Sebaliknya, setiap kali ada penghapusan data transaksi barang keluar, maka field stok barang di table items akan kita tambahkan,
maka trigger DELETE akan seperti ini,
mysql> delimiter $$ mysql> drop trigger if exists trg_transaction_details_after_delete$$ Query OK, 0 rows affected (0.06 sec) mysql> -- ----------------------------- mysql> -- after delete trigger mysql> -- ----------------------------- mysql> create trigger trg_transaction_details_after_delete -> after delete on transaction_details -> for each row -> begin -> -- ----------------------------- -> -- ambil jenis transaksi -> -- ----------------------------- -> declare tipe varchar(20); -> set tipe = ( -> select tt.code -> from transactions tr -> join transaction_types tt on tr.type_id=tt.id -> where tr.id=old.trans_id -> ) ; -> -- ----------------------------- -> -- update sesuai jenis transaksi -> -- ----------------------------- -> update items -> set quantity_on_hand -> = quantity_on_hand -> + case when tipe='IN' then -old.quantity -> when tipe='OUT' then old.quantity -> else 0 end -> where items.id=old.item_id ; -> end$$ Query OK, 0 rows affected (0.23 sec) mysql> delimiter ; mysql>
[collapsed title=Spoiler: Hasil Test DELETE Transaksi Barang Masuk dan Barang Keluar]
Status database sebelum proses DELETE.
mysql> ---------------------------- mysql> -- status stok mysql> -- sebelum proses DELETE mysql> ----------------------------- mysql> select * from items ; +----+--------+-------------------------+------------------+---------+ | id | code | name | quantity_on_hand | remarks | +----+--------+-------------------------+------------------+---------+ | 1 | PC.001 | PC Jangkrik Tipe 1 | 25.00 | NULL | | 2 | PC.002 | PC Jangkrik Tipe 2 | 30.00 | NULL | | 3 | SV.001 | Server Rainer Tipe 1 | 10.00 | NULL | | 4 | SV.002 | Server Rainer Rackmount | 10.00 | NULL | +----+--------+-------------------------+------------------+---------+ 4 rows in set (0.00 sec) mysql> ----------------------------- mysql> -- status detil transaksi mysql> -- sebelum proses DELETE mysql> ----------------------------- mysql> select * from transaction_details; +----+----------+---------+----------+----------------------+ | id | trans_id | item_id | quantity | remarks | +----+----------+---------+----------+----------------------+ | 22 | 1 | 1 | 50 | Penerimaan Barang 1 | | 23 | 1 | 2 | 30 | Penerimaan Barang 2 | | 24 | 1 | 3 | 10 | Penerimaan Barang 3 | | 25 | 1 | 4 | 15 | Penerimaan Barang 4 | | 29 | 2 | 1 | 25 | Pengeluaran Barang 1 | | 30 | 2 | 4 | 5 | Pengeluaran Barang 4 | +----+----------+---------+----------+----------------------+ 6 rows in set (0.00 sec)
Test DELETE data transaksi.
Untuk test delete, kita lakukan sederhanan saja, yaitu hapus semua data transactions_details sehingga seluruh data transaksi akaterhapus, maka status stok seharusnya berubah menjadi seperti awal lagi yaitu seluruh saldo stok bernilai 0 (nol)
mysql> ----------------------------- mysql> -- hapus detil transaksi mysql> ----------------------------- mysql> delete from transaction_details ; Query OK, 4 rows affected (0.03 sec) mysql> ----------------------------- mysql> -- pastikan table detil transaksi kososng mysql> ----------------------------- mysql> select * from transaction_details; Empty set (0.00 sec) mysql> ----------------------------- mysql> -- status saldo stok kembali nol mysql> ----------------------------- mysql> select * from items; +----+--------+-------------------------+------------------+---------+ | id | code | name | quantity_on_hand | remarks | +----+--------+-------------------------+------------------+---------+ | 1 | PC.001 | PC Jangkrik Tipe 1 | 0.00 | NULL | | 2 | PC.002 | PC Jangkrik Tipe 2 | 0.00 | NULL | | 3 | SV.001 | Server Rainer Tipe 1 | 0.00 | NULL | | 4 | SV.002 | Server Rainer Rackmount | 0.00 | NULL | +----+--------+-------------------------+------------------+---------+ 4 rows in set (0.00 sec) mysql> ----------------------------- mysql> -- YAY!!! I'm Invincible!!! mysql> ----------------------------- mysql>
[/collapsed]
[collapsed title=Spoiler: Source Code Trigger Lengkap]
delimiter $$ drop trigger if exists trg_transaction_details_after_insert$$ -- ----------------------------- -- after insert trigger -- ----------------------------- create trigger trg_transaction_details_after_insert after insert on transaction_details for each row begin -- ----------------------------- -- ambil jenis transaksi -- ----------------------------- declare tipe varchar(20); set tipe = ( select tt.code from transactions tr join transaction_types tt on tr.type_id=tt.id where tr.id=new.trans_id ) ; -- ----------------------------- -- update sesuai jenis transaksi -- ----------------------------- update items set quantity_on_hand = quantity_on_hand + case when tipe='IN' then new.quantity when tipe='OUT' then -new.quantity else 0 end where items.id=new.item_id ; end$$ drop trigger if exists trg_transaction_details_after_update$$ -- ----------------------------- -- after update trigger -- ----------------------------- create trigger trg_transaction_details_after_update after update on transaction_details for each row begin -- ----------------------------- -- ambil jenis transaksi -- ----------------------------- declare tipe varchar(20); set tipe = ( select tt.code from transactions tr join transaction_types tt on tr.type_id=tt.id where tr.id=old.trans_id ) ; -- ----------------------------- -- update sesuai jenis transaksi -- => kurangi dengan old quantity -- ----------------------------- update items set quantity_on_hand = quantity_on_hand + case when tipe='IN' then -old.quantity when tipe='OUT' then old.quantity else 0 end where items.id=old.item_id ; -- ----------------------------- -- update sesuai jenis transaksi -- => tambahkan dengan new quantity -- ----------------------------- update items set quantity_on_hand = quantity_on_hand + case when tipe='IN' then new.quantity when tipe='OUT' then -new.quantity else 0 end where items.id=new.item_id ; end$$ drop trigger if exists trg_transaction_details_after_delete$$ -- ----------------------------- -- after delete trigger -- ----------------------------- create trigger trg_transaction_details_after_delete after delete on transaction_details for each row begin -- ----------------------------- -- ambil jenis transaksi -- ----------------------------- declare tipe varchar(20); set tipe = ( select tt.code from transactions tr join transaction_types tt on tr.type_id=tt.id where tr.id=old.trans_id ) ; -- ----------------------------- -- update sesuai jenis transaksi -- ----------------------------- update items set quantity_on_hand = quantity_on_hand + case when tipe='IN' then -old.quantity when tipe='OUT' then old.quantity else 0 end where items.id=old.item_id ; end$$ delimiter ;
[/collapsed]
Simpulan
Setelah kita perhatikan hasil test di atas, maka proses pengerjaan coding di aplikasi akan jauh lebih mudah karena tidak perlu lagi memikirkan code untuk mengubah status stok barang, karena perubahan status stok barang seluruhnya sudah ditangani oleh trigger yang kita buat ini.
Salam PojokProgrammer
^_^