Staf Toko Maju Jaya menambah produk lewat banyak pintu: kasir mengisi formulir aplikasi, tim gudang mengimpor berkas CSV, programmer menjalankan skrip migrasi, sampai tugas terjadwal yang menyinkronkan stok tiap malam. Aturan “setiap produk baru harus meninggalkan catatan” tidak bisa digantungkan pada disiplin setiap penulis query — cukup satu jalur yang lupa memanggil pencatatan, dan riwayat gudang bocor tanpa jejak. Trigger memindahkan aturan itu ke dalam tabel itu sendiri: kode yang menempel pada event tabel dan dijalankan server secara otomatis, siapa pun dan dari jalur mana pun data ditulis. Chapter ini membongkar komponennya, membangun trigger pertama yang mencatat setiap INSERT ke tabel produk, lalu menunjukkan cara memeriksa dan menghapusnya.
Trigger Menempel pada Tabel, Bukan pada Query #
Sebuah trigger bukan program yang berdiri sendiri dan dipanggil dari aplikasi. Trigger adalah objek database yang menempel pada satu tabel dan menunggu satu kejadian — INSERT, UPDATE, DELETE, atau TRUNCATE. Ketika kejadian itu terjadi, server menjalankan sebuah trigger function yang sudah Anda daftarkan. Karena pendaftarannya hidup di dalam database, bukan di kode aplikasi, setiap penulis data otomatis tunduk pada aturan yang sama.
Lima komponen membentuk satu trigger:
| Komponen | Arti | Contoh di chapter ini |
|---|---|---|
| Tabel | tabel tempat trigger menempel | produk |
| Event | kejadian yang menyalakannya | INSERT |
| Timing | sebelum atau sesudah baris ditulis | AFTER |
| Level | sekali per baris atau per pernyataan | FOR EACH ROW |
| Function | kode PL/pgSQL yang dijalankan | catat_produk_baru() |
Function-nya sendiri adalah function PL/pgSQL biasa dari section Stored Programs
, dengan satu syarat khusus: klausa RETURNS trigger. Function berjenis ini tidak bisa dipanggil langsung — server memanggilnya sebagai trigger, dan mencoba SELECT catat_produk_baru(); ditolak dengan error trigger functions can only be called as triggers.
Anatomi CREATE TRIGGER #
Perintah pembuatnya adalah CREATE TRIGGER. Bentuk umumnya dulu sebagai peta, sebelum kita isi dengan contoh nyata:
CREATE TRIGGER nama_trigger
{ BEFORE | AFTER } { INSERT | UPDATE | DELETE | TRUNCATE } ON nama_tabel
[ FOR EACH ROW | FOR EACH STATEMENT ]
EXECUTE FUNCTION nama_fungsi_trigger();Sekarang trigger pertama kita. Chapter ini memakai dua tabel kecil milik Toko Maju Jaya: produk sebagai tabel utama yang diawasi, dan riwayat sebagai papan pencatat yang diisi otomatis oleh trigger:
-- Tabel utama dan tabel riwayat untuk demo trigger pertama
CREATE TABLE produk (
id integer PRIMARY KEY,
nama text NOT NULL,
harga numeric(12,2) NOT NULL
);
CREATE TABLE riwayat (
id integer GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
kejadian text NOT NULL
);Output yang Dihasilkan:
CREATE TABLE
CREATE TABLEAturannya ingin sederhana: setiap produk baru yang masuk ke tabel produk otomatis tercatat di tabel riwayat. Perhatikan bahwa tidak ada satu baris pun kode aplikasi yang ikut ditulis — seluruh logika hidup di server:
-- Trigger pertama: setiap INSERT ke produk otomatis tercatat di riwayat
CREATE FUNCTION catat_produk_baru() RETURNS trigger
LANGUAGE plpgsql
AS $$
BEGIN
INSERT INTO riwayat (kejadian)
VALUES ('produk baru masuk: ' || NEW.nama);
RETURN NEW;
END
$$;
CREATE TRIGGER catat_insert_produk
AFTER INSERT ON produk
FOR EACH ROW EXECUTE FUNCTION catat_produk_baru();
INSERT INTO produk (id, nama, harga) VALUES
(1, 'Kopi Kapal', 18000.00),
(2, 'Teh Melati', 12000.00);
SELECT id, kejadian FROM riwayat ORDER BY id;Output yang Dihasilkan:
CREATE FUNCTION
CREATE TRIGGER
INSERT 0 2
id | kejadian
----+-------------------------------
1 | produk baru masuk: Kopi Kapal
2 | produk baru masuk: Teh Melati
(2 rows)Dua INSERT di ujung file sengaja tidak menyentuh tabel riwayat sama sekali — dan tetap menghasilkan dua baris catatan. Bedah perannya:
CREATE FUNCTION catat_produk_baru() RETURNS trigger— deklarasi function tanpa parameter; tipe kembaliantriggeradalah tanda pengenal bagi server bahwa function ini layak dipasang pada event tabel.INSERT INTO riwayat ... VALUES ('produk baru masuk: ' || NEW.nama)—NEWadalah variabel khusus bertipe record yang berisi baris baru hasilINSERT;NEW.namamembaca kolomnamadari baris tersebut.RETURN NEW— untuk triggerAFTER ... FOR EACH ROW, nilai balik ini wajib dikembalikan agar operasi berlanjut normal; detail semantiknya dibahas tuntas di chapter BEFORE dan AFTER Trigger .CREATE TRIGGER catat_insert_produk AFTER INSERT ON produk FOR EACH ROW EXECUTE FUNCTION catat_produk_baru()— pendaftaran yang mengikat lima komponen: nama trigger, timingAFTER, eventINSERT, tabelproduk, level per baris, dan function yang dieksekusi.
Alur lengkapnya sejak perintah INSERT sampai catatan terbentuk:
Diagram di atas memetakan perjalanan satu baris: perintah INSERT masuk, trigger BEFORE (kalau dipasang) sempat memeriksa atau mengubah baris, baris kemudian benar-benar ditulis ke tabel, dan barulah trigger AFTER kita menyala untuk mencatat kejadian ke tabel riwayat. Pada contoh ini kita hanya memasang tahap AFTER, jadi tahap BEFORE dilewati begitu saja.
Trigger Adalah Bagian Struktur Tabel #
Karena trigger terdaftar sebagai milik tabel, meta-perintah psql ikut menampilkannya. Ini cara tercepat memastikan trigger memang menempel:
-- Trigger tampil sebagai bagian dari struktur tabel
\d produkOutput yang Dihasilkan:
Table "public.produk"
Column | Type | Collation | Nullable | Default
--------+---------------+-----------+----------+---------
id | integer | | not null |
nama | text | | not null |
harga | numeric(12,2) | | not null |
Indexes:
"produk_pkey" PRIMARY KEY, btree (id)
Triggers:
catat_insert_produk AFTER INSERT ON produk FOR EACH ROW EXECUTE FUNCTION catat_produk_baru()Bagian Triggers: pada output menampilkan satu baris ringkas per trigger: nama catat_insert_produk, timing AFTER, event INSERT, tabel produk, level FOR EACH ROW, dan function yang dipanggil — persis lima komponen yang kita rakit di atas.
Jaminan: Jalur Mana Pun, Trigger Tetap Berjalan #
Janji inti trigger adalah otomatisasi yang tidak bisa dilewati. Kasir yang menulis satu baris dengan INSERT ... VALUES, tim gudang yang memuat ratusan baris dengan INSERT ... SELECT, tugas malam yang menjalankan sinkronisasi — semuanya melewati gerbang yang sama. File berikut menulis data lewat dua jalur berbeda tanpa menyebut tabel riwayat sedikit pun:
-- Jaminan trigger: baris tetap tercatat dari jalur INSERT mana pun
INSERT INTO produk (id, nama, harga)
SELECT id, nama, harga
FROM (VALUES
(3, 'Gula Pasir', 14000.00),
(4, 'Roti Tawar', 15000.00)
) AS sumber(id, nama, harga);
INSERT INTO produk (id, nama, harga) VALUES (5, 'Susu Kotak', 6500.00);
SELECT id, kejadian FROM riwayat ORDER BY id;Output yang Dihasilkan:
INSERT 0 2
INSERT 0 1
id | kejadian
----+-------------------------------
1 | produk baru masuk: Kopi Kapal
2 | produk baru masuk: Teh Melati
3 | produk baru masuk: Gula Pasir
4 | produk baru masuk: Roti Tawar
5 | produk baru masuk: Susu Kotak
(5 rows)Tag INSERT 0 2 dan INSERT 0 1 hanya melaporkan baris yang masuk ke produk; tiga baris baru di riwayat muncul diam-diam dari dalam trigger. Tidak ada aplikasi yang bisa “lupa” memanggil pencatatan, karena pencatatan bukan lagi tanggung jawab aplikasi.
Menghapus Trigger #
Trigger yang tidak lagi dibutuhkan dijatuhkan dengan DROP TRIGGER, dan — berbeda dari DROP FUNCTION — namanya harus disertai tabel tempatnya menempel, karena nama trigger hanya unik per tabel:
-- Trigger dihapus: INSERT tidak lagi meninggalkan jejak
DROP TRIGGER catat_insert_produk ON produk;
INSERT INTO produk (id, nama, harga) VALUES (6, 'Sarden Kaleng', 25000.00);
SELECT id, kejadian FROM riwayat ORDER BY id;Output yang Dihasilkan:
DROP TRIGGER
INSERT 0 1
id | kejadian
----+-------------------------------
1 | produk baru masuk: Kopi Kapal
2 | produk baru masuk: Teh Melati
3 | produk baru masuk: Gula Pasir
4 | produk baru masuk: Roti Tawar
5 | produk baru masuk: Susu Kotak
(5 rows)Sarden Kaleng masuk ke produk dengan sukses (INSERT 0 1), tetapi jumlah baris riwayat tetap lima: trigger sudah tidak ada, jadi tidak ada lagi yang mendengarkan event INSERT pada tabel itu. Menjatuhkan trigger yang tidak ada, seperti jatuhnya objek lain, berakhir dengan error:
-- DROP TRIGGER gagal bila nama trigger tidak ada di tabel tersebut
DROP TRIGGER trigger_hantu ON produk;Output yang Dihasilkan:
psql:hapus-trigger-gagal.sql:2: ERROR: trigger "trigger_hantu" for table "produk" does not existFunction catat_produk_baru() masih hidup di database meski trigger-nya sudah dijatuhkan — DROP TRIGGER hanya melepas pendaftaran, bukan menghapus kodenya; trigger lain bisa memasangnya kembali kapan saja. Komponen timing yang tadi kita pilih begitu saja — AFTER — sebenarnya punya pasangan BEFORE dengan kekuatan yang berbeda: memodifikasi baris sebelum ditulis, atau membatalkannya sama sekali. Perbedaan keduanya adalah pokok chapter berikutnya, BEFORE dan AFTER Trigger
.
Referensi #
Berkas Sumber
Berkas pendukung halaman ini dapat diunduh dan dijalankan langsung sesuai penjelasan di atas.
buat-tabel.sql
Unduh berkas-- Tabel utama dan tabel riwayat untuk demo trigger pertama
CREATE TABLE produk (
id integer PRIMARY KEY,
nama text NOT NULL,
harga numeric(12,2) NOT NULL
);
CREATE TABLE riwayat (
id integer GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
kejadian text NOT NULL
);
hapus-trigger-gagal.sql
Unduh berkas-- DROP TRIGGER gagal bila nama trigger tidak ada di tabel tersebut
DROP TRIGGER trigger_hantu ON produk;
hapus-trigger.sql
Unduh berkas-- Trigger dihapus: INSERT tidak lagi meninggalkan jejak
DROP TRIGGER catat_insert_produk ON produk;
INSERT INTO produk (id, nama, harga) VALUES (6, 'Sarden Kaleng', 25000.00);
SELECT id, kejadian FROM riwayat ORDER BY id;
jaminan-otomatis.sql
Unduh berkas-- Jaminan trigger: baris tetap tercatat dari jalur INSERT mana pun
INSERT INTO produk (id, nama, harga)
SELECT id, nama, harga
FROM (VALUES
(3, 'Gula Pasir', 14000.00),
(4, 'Roti Tawar', 15000.00)
) AS sumber(id, nama, harga);
INSERT INTO produk (id, nama, harga) VALUES (5, 'Susu Kotak', 6500.00);
SELECT id, kejadian FROM riwayat ORDER BY id;
lihat-trigger.sql
Unduh berkas-- Trigger tampil sebagai bagian dari struktur tabel
\d produk
trigger-pertama.sql
Unduh berkas-- Trigger pertama: setiap INSERT ke produk otomatis tercatat di riwayat
CREATE FUNCTION catat_produk_baru() RETURNS trigger
LANGUAGE plpgsql
AS $$
BEGIN
INSERT INTO riwayat (kejadian)
VALUES ('produk baru masuk: ' || NEW.nama);
RETURN NEW;
END
$$;
CREATE TRIGGER catat_insert_produk
AFTER INSERT ON produk
FOR EACH ROW EXECUTE FUNCTION catat_produk_baru();
INSERT INTO produk (id, nama, harga) VALUES
(1, 'Kopi Kapal', 18000.00),
(2, 'Teh Melati', 12000.00);
SELECT id, kejadian FROM riwayat ORDER BY id;