Trigger selalu terdiri dari dua objek terpisah: pendaftaran yang ditulis CREATE TRIGGER, dan kode yang benar-benar berjalan — trigger function. Pendaftaran sudah kita kuasai di tiga chapter sebelumnya; sekarang saatnya membuka isi kotak hitamnya. Function jenis ini punya kerangka wajib RETURNS trigger, dan begitu server memanggilnya, sedusin variabel otomatis mengisi diri sendiri: record NEW dan OLD yang sudah kita pakai, nama tabel dan operasi yang sedang berlangsung, sampai argumen bebas yang dibawa dari CREATE TRIGGER. Dengan bekal itu, satu function dapat melayani banyak tabel sekaligus — trik yang mengubah auditor Toko Maju Jaya dari kode yang digandalkan per tabel menjadi kode yang ditulis satu kali.
Kerangka Wajib: RETURNS trigger #
Trigger function adalah function PL/pgSQL biasa dengan dua syarat bentuk: tanpa parameter, dan tipe kembalian trigger. Kerangkanya:
CREATE FUNCTION nama_function() RETURNS trigger
LANGUAGE plpgsql
AS $$
BEGIN
-- badan function: membaca NEW/OLD/TG_*, bebas memodifikasi NEW
RETURN NEW; -- atau NULL / OLD, lihat tabel semantik di bawah
END
$$;Dua syarat itu ditegakkan server, bukan sekadar gaya penulisan. Function yang mengembalikan tipe lain ditolak keras saat dipasang — kita lihat error persisnya di bagian akhir chapter. Dan meskipun CREATE TRIGGER boleh membawa argumen, argumen itu tidak pernah masuk sebagai parameter function; ia dikirim lewat variabel TG_ARGV yang kita bahas sebentar lagi.
Ketika server memanggil function, variabel-variabel berikut sudah terisi otomatis di blok teratas:
| Variabel | Isi |
|---|---|
NEW | record baris baru; terisi untuk INSERT/UPDATE row-level, NULL untuk DELETE |
OLD | record baris lama; terisi untuk UPDATE/DELETE row-level, NULL untuk INSERT |
TG_NAME | nama trigger yang sedang menyala |
TG_WHEN | BEFORE, AFTER, atau INSTEAD OF |
TG_LEVEL | ROW atau STATEMENT |
TG_OP | INSERT, UPDATE, DELETE, atau TRUNCATE |
TG_TABLE_NAME | nama tabel tempat trigger menempel |
TG_TABLE_SCHEMA | skema tabel tersebut |
TG_NARGS | jumlah argumen di CREATE TRIGGER |
TG_ARGV[] | array teks berisi nilai argumen tersebut |
Menangkap Identitas Pemanggilan #
Cara tercepat membuktikan variabel-variabel itu adalah menumpahkan semuanya ke sebuah tabel log. Demo chapter ini memakai tiga tabel: produk dan pelanggan sebagai tabel yang diawasi, serta log_identitas sebagai papan tumpahan:
-- Tabel demo dan tabel identitas untuk membedah variabel trigger
CREATE TABLE produk (
id integer PRIMARY KEY,
nama text NOT NULL,
harga numeric(12,2) NOT NULL
);
CREATE TABLE pelanggan (
id integer PRIMARY KEY,
nama text NOT NULL,
poin integer NOT NULL DEFAULT 0
);
CREATE TABLE log_identitas (
id integer GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
tabel text NOT NULL,
operasi text NOT NULL,
tingkat text NOT NULL,
saat text NOT NULL,
argumen text NOT NULL,
jumlah integer NOT NULL
);Output yang Dihasilkan:
CREATE TABLE
CREATE TABLE
CREATE TABLEFunction lacak_identitas() berikut dipasang dua kali dengan watak berbeda: pada tabel produk sebagai BEFORE INSERT dengan dua argumen string, dan pada tabel pelanggan sebagai AFTER UPDATE tanpa argumen:
-- Variabel otomatis TG_* : identitas lengkap pemanggilan trigger
CREATE FUNCTION lacak_identitas() RETURNS trigger
LANGUAGE plpgsql
AS $$
BEGIN
INSERT INTO log_identitas (tabel, operasi, tingkat, saat, argumen, jumlah)
VALUES (
TG_TABLE_NAME,
TG_OP,
TG_LEVEL,
TG_WHEN,
coalesce(TG_ARGV[0], '-') || '+' || coalesce(TG_ARGV[1], '-'),
TG_NARGS
);
IF TG_OP = 'DELETE' THEN
RETURN OLD;
END IF;
RETURN NEW;
END
$$;
-- Trigger 1: INSERT produk, BEFORE, dengan dua argumen string
CREATE TRIGGER lacak_produk_insert
BEFORE INSERT ON produk
FOR EACH ROW EXECUTE FUNCTION lacak_identitas('gudang', 'reguler');
INSERT INTO produk (id, nama, harga) VALUES (1, 'Kopi Kapal', 18000.00);
-- Trigger 2: UPDATE pelanggan, AFTER, tanpa argumen
CREATE TRIGGER lacak_pelanggan_update
AFTER UPDATE ON pelanggan
FOR EACH ROW EXECUTE FUNCTION lacak_identitas();
INSERT INTO pelanggan (id, nama, poin) VALUES (1, 'Budi Santoso', 100);
UPDATE pelanggan SET poin = 150 WHERE id = 1;
SELECT tabel, operasi, tingkat, saat, argumen, jumlah
FROM log_identitas
ORDER BY id;Output yang Dihasilkan:
CREATE FUNCTION
CREATE TRIGGER
INSERT 0 1
CREATE TRIGGER
INSERT 0 1
UPDATE 1
tabel | operasi | tingkat | saat | argumen | jumlah
-----------+---------+---------+--------+----------------+--------
produk | INSERT | ROW | BEFORE | gudang+reguler | 2
pelanggan | UPDATE | ROW | AFTER | -+- | 0
(2 rows)Dua baris log membuktikan dua kepribadian pemanggilan yang berbeda dari function yang sama: produk + INSERT + ROW + BEFORE + gudang+reguler + 2 dan pelanggan + UPDATE + ROW + AFTER + -+- + 0. Poin pentingnya:
TG_ARGV[0]danTG_ARGV[1]berisi teks'gudang'dan'reguler'persis seperti ditulis diCREATE TRIGGER— itulah satu-satunya jalur argumen;TG_NARGSmenghitungnya menjadi 2. Pemasangan kedua tanpa argumen menghasilkanTG_NARGS = 0, danTG_ARGV[0]yangNULLdigantikan tanda-olehcoalesce().- Cabang
IF TG_OP = 'DELETE' THEN RETURN OLD;menjaga function tetap aman dipasang di event apa pun:RETURN OLDadalah nilai balik yang benar untuk DELETE,RETURN NEWuntuk sisanya — semantik lengkapnya di tabel bawah. INSERT INTO pelanggantidak menghasilkan log karena trigger kedua hanya menyala padaUPDATE— pendaftaran, bukan function, yang menentukan event.
NEW dan OLD: Dua Record Milik UPDATE #
Event UPDATE adalah satu-satunya momen NEW dan OLD terisi bersamaan: OLD memotret baris sebelum diubah, NEW memotret hasilnya. Cara paling jujur melihat keduanya adalah mengubah record menjadi JSON dan menyimpannya utuh:
-- NEW dan OLD saat UPDATE: harga lama dan harga baru dalam satu log
CREATE TABLE log_harga (
id integer GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
produk_id integer NOT NULL,
data_lama json NOT NULL,
data_baru json NOT NULL
);
CREATE FUNCTION bedah_old_new() RETURNS trigger
LANGUAGE plpgsql
AS $$
BEGIN
INSERT INTO log_harga (produk_id, data_lama, data_baru)
VALUES (NEW.id, row_to_json(OLD), row_to_json(NEW));
RETURN NEW;
END
$$;
CREATE TRIGGER bedah_harga_update
AFTER UPDATE ON produk
FOR EACH ROW EXECUTE FUNCTION bedah_old_new();
UPDATE produk SET harga = 21000.00 WHERE id = 1;
SELECT produk_id, data_lama, data_baru FROM log_harga ORDER BY id;Output yang Dihasilkan:
CREATE TABLE
CREATE FUNCTION
CREATE TRIGGER
UPDATE 1
produk_id | data_lama | data_baru
-----------+-----------------------------------------------+-----------------------------------------------
1 | {"id":1,"nama":"Kopi Kapal","harga":18000.00} | {"id":1,"nama":"Kopi Kapal","harga":21000.00}
(1 row)Satu UPDATE menghasilkan dua potret: data_lama memuat {"id":1,"nama":"Kopi Kapal","harga":18000.00}, data_baru memuat {"id":1,"nama":"Kopi Kapal","harga":21000.00}. Semua kolom ikut tersimpan, bukan hanya yang berubah — pola yang menjadi fondasi audit penuh di chapter Auditing dengan Trigger
. Fungsi row_to_json() mengubah record menjadi JSON dalam satu langkah; kolomnya bisa juga diakses satu per satu seperti OLD.harga yang kita pakai di chapter BEFORE dan AFTER Trigger
.
Satu Function, Banyak Tabel #
Karena TG_TABLE_NAME dan TG_OP memberi tahu function di tabel dan operasi apa ia dipanggil, function yang sama bisa dipasang berulang tanpa perlu tahu tabel tujuannya. Ini mengubah cara kita menulis auditor: satu function, banyak pendaftaran:
-- Satu function generik, dua tabel: auditor memakai TG_TABLE_NAME dan TG_OP
CREATE TABLE audit_umum (
id integer GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
tabel text NOT NULL,
operasi text NOT NULL
);
CREATE FUNCTION auditor_generik() RETURNS trigger
LANGUAGE plpgsql
AS $$
BEGIN
INSERT INTO audit_umum (tabel, operasi)
VALUES (TG_TABLE_NAME, TG_OP);
IF TG_OP = 'DELETE' THEN
RETURN OLD;
END IF;
RETURN NEW;
END
$$;
-- Pemasangan pertama: tabel produk
CREATE TRIGGER audit_produk_insert
AFTER INSERT ON produk
FOR EACH ROW EXECUTE FUNCTION auditor_generik();
-- Pemasangan kedua: tabel pelanggan, function yang sama persis
CREATE TRIGGER audit_pelanggan_insert
AFTER INSERT ON pelanggan
FOR EACH ROW EXECUTE FUNCTION auditor_generik();
INSERT INTO produk (id, nama, harga) VALUES (2, 'Teh Melati', 12000.00);
INSERT INTO pelanggan (id, nama, poin) VALUES (2, 'Siti Rahayu', 200);
SELECT tabel, operasi, count(*) AS kejadian
FROM audit_umum
GROUP BY tabel, operasi
ORDER BY tabel;Output yang Dihasilkan:
CREATE TABLE
CREATE FUNCTION
CREATE TRIGGER
CREATE TRIGGER
INSERT 0 1
INSERT 0 1
tabel | operasi | kejadian
-----------+---------+----------
pelanggan | INSERT | 1
produk | INSERT | 1
(2 rows)Satu function mencatat dua kejadian dari dua tabel berbeda — baris produk + INSERT dan pelanggan + INSERT dibedakan murni oleh TG_TABLE_NAME, tanpa satu pun nama tabel yang ditulis ulang di badan function. Tambahkan tabel ketiga, keempat, kesepuluh: pendaftarannya cukup satu baris CREATE TRIGGER per tabel. Ini pola yang dipakai tool audit generik — dan juga alasan mengapa memodifikasi trigger function harus dilakukan hati-hati: satu perubahan menular ke semua tabel yang memasangnya.
Semantik Nilai Balik #
Nilai yang dikembalikan function menentukan nasib baris — tetapi hanya untuk trigger BEFORE level baris. Untuk AFTER, nilai balik apa pun diabaikan:
| Nilai balik | Efek pada BEFORE ... FOR EACH ROW | Efek pada AFTER |
|---|---|---|
RETURN NEW | operasi lanjut dengan NEW sebagai baris final (hasil modifikasi ikut tersimpan) | diabaikan |
RETURN NULL | operasi untuk baris itu dibatalkan senyap | diabaikan |
RETURN OLD | untuk DELETE: lanjut menghapus; pada INSERT (saat OLD = NULL) berarti baris dibatalkan | diabaikan |
Demo berikut menguji ketiganya pada satu tabel barang: penyaring BEFORE INSERT yang membuang baris bernama DILARANG, dan pemberi izin BEFORE DELETE yang mengembalikan OLD:
-- Semantik nilai balik: RETURN NEW lanjut, RETURN NULL batalkan, RETURN OLD hapus
CREATE TABLE barang (
id integer PRIMARY KEY,
nama text NOT NULL
);
CREATE FUNCTION saring_barang() RETURNS trigger
LANGUAGE plpgsql
AS $$
BEGIN
IF NEW.nama = 'DILARANG' THEN
RETURN NULL; -- batalkan INSERT baris ini
END IF;
RETURN NEW; -- lanjutkan INSERT baris ini
END
$$;
CREATE TRIGGER saring_barang_insert
BEFORE INSERT ON barang
FOR EACH ROW EXECUTE FUNCTION saring_barang();
CREATE FUNCTION izin_hapus() RETURNS trigger
LANGUAGE plpgsql
AS $$
BEGIN
RETURN OLD; -- untuk DELETE: kembalikan OLD agar penghapusan jalan
END
$$;
CREATE TRIGGER izin_hapus_barang
BEFORE DELETE ON barang
FOR EACH ROW EXECUTE FUNCTION izin_hapus();
INSERT INTO barang (id, nama) VALUES (1, 'Pena Hitam');
INSERT INTO barang (id, nama) VALUES (2, 'DILARANG');
DELETE FROM barang WHERE id = 1;
SELECT id, nama FROM barang ORDER BY id;Output yang Dihasilkan:
CREATE TABLE
CREATE FUNCTION
CREATE TRIGGER
CREATE FUNCTION
CREATE TRIGGER
INSERT 0 1
INSERT 0 0
DELETE 1
id | nama
----+------
(0 rows)Urutan tag perintahnya membaca seperti cerita: INSERT 0 1 (Pena Hitam masuk lewat RETURN NEW), INSERT 0 0 (DILARANG dibatalkan oleh RETURN NULL), DELETE 1 (Pena Hitam keluar karena RETURN OLD mengizinkan penghapusan) — dan SELECT akhir yang kosong karena satu-satunya baris yang tersisa sebenarnya tidak pernah ada.
Function Salah Tipe Ditolak #
Apa yang terjadi jika function tanpa RETURNS trigger didaftarkan sebagai trigger? CREATE TRIGGER menolaknya di tempat:
-- Function non-trigger tidak bisa dipasang sebagai trigger
CREATE FUNCTION sapa() RETURNS text
LANGUAGE plpgsql
AS $$
BEGIN
RETURN 'selamat datang';
END
$$;
CREATE TRIGGER sapa_insert
BEFORE INSERT ON barang
FOR EACH ROW EXECUTE FUNCTION sapa();Output yang Dihasilkan:
psql:return-salah-tipe.sql:12: ERROR: function sapa must return type triggerError function sapa must return type trigger datang sebelum satu baris data pun tersentuh — pemeriksaan terjadi saat pendaftaran, bukan saat trigger menyala. Function sapa() sendiri tetap sah sebagai function biasa; ia hanya tidak boleh menempati kursi trigger.
Kerangka function kini lengkap: variabel identitas, record dua arah, dan kontrak nilai balik. Satu dimensi tersisa yang menentukan variabel mana yang boleh disentuh: event apa yang memicu trigger. Memasang trigger hanya untuk INSERT, untuk kombinasi INSERT OR UPDATE OR DELETE, atau hanya saat kolom tertentu di-UPDATE — beserta jebakan NEW yang NULL pada DELETE — adalah pokok chapter penutup rangkaian ini, INSERT UPDATE DELETE Trigger
.
Referensi #
Berkas Sumber
Berkas pendukung halaman ini dapat diunduh dan dijalankan langsung sesuai penjelasan di atas.
buat-tabel.sql
Unduh berkas-- Tabel demo dan tabel identitas untuk membedah variabel trigger
CREATE TABLE produk (
id integer PRIMARY KEY,
nama text NOT NULL,
harga numeric(12,2) NOT NULL
);
CREATE TABLE pelanggan (
id integer PRIMARY KEY,
nama text NOT NULL,
poin integer NOT NULL DEFAULT 0
);
CREATE TABLE log_identitas (
id integer GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
tabel text NOT NULL,
operasi text NOT NULL,
tingkat text NOT NULL,
saat text NOT NULL,
argumen text NOT NULL,
jumlah integer NOT NULL
);
fungsi-generik.sql
Unduh berkas-- Satu function generik, dua tabel: auditor memakai TG_TABLE_NAME dan TG_OP
CREATE TABLE audit_umum (
id integer GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
tabel text NOT NULL,
operasi text NOT NULL
);
CREATE FUNCTION auditor_generik() RETURNS trigger
LANGUAGE plpgsql
AS $$
BEGIN
INSERT INTO audit_umum (tabel, operasi)
VALUES (TG_TABLE_NAME, TG_OP);
IF TG_OP = 'DELETE' THEN
RETURN OLD;
END IF;
RETURN NEW;
END
$$;
-- Pemasangan pertama: tabel produk
CREATE TRIGGER audit_produk_insert
AFTER INSERT ON produk
FOR EACH ROW EXECUTE FUNCTION auditor_generik();
-- Pemasangan kedua: tabel pelanggan, function yang sama persis
CREATE TRIGGER audit_pelanggan_insert
AFTER INSERT ON pelanggan
FOR EACH ROW EXECUTE FUNCTION auditor_generik();
INSERT INTO produk (id, nama, harga) VALUES (2, 'Teh Melati', 12000.00);
INSERT INTO pelanggan (id, nama, poin) VALUES (2, 'Siti Rahayu', 200);
SELECT tabel, operasi, count(*) AS kejadian
FROM audit_umum
GROUP BY tabel, operasi
ORDER BY tabel;
new-old-record.sql
Unduh berkas-- NEW dan OLD saat UPDATE: harga lama dan harga baru dalam satu log
CREATE TABLE log_harga (
id integer GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
produk_id integer NOT NULL,
data_lama json NOT NULL,
data_baru json NOT NULL
);
CREATE FUNCTION bedah_old_new() RETURNS trigger
LANGUAGE plpgsql
AS $$
BEGIN
INSERT INTO log_harga (produk_id, data_lama, data_baru)
VALUES (NEW.id, row_to_json(OLD), row_to_json(NEW));
RETURN NEW;
END
$$;
CREATE TRIGGER bedah_harga_update
AFTER UPDATE ON produk
FOR EACH ROW EXECUTE FUNCTION bedah_old_new();
UPDATE produk SET harga = 21000.00 WHERE id = 1;
SELECT produk_id, data_lama, data_baru FROM log_harga ORDER BY id;
return-salah-tipe.sql
Unduh berkas-- Function non-trigger tidak bisa dipasang sebagai trigger
CREATE FUNCTION sapa() RETURNS text
LANGUAGE plpgsql
AS $$
BEGIN
RETURN 'selamat datang';
END
$$;
CREATE TRIGGER sapa_insert
BEFORE INSERT ON barang
FOR EACH ROW EXECUTE FUNCTION sapa();
return-semantik.sql
Unduh berkas-- Semantik nilai balik: RETURN NEW lanjut, RETURN NULL batalkan, RETURN OLD hapus
CREATE TABLE barang (
id integer PRIMARY KEY,
nama text NOT NULL
);
CREATE FUNCTION saring_barang() RETURNS trigger
LANGUAGE plpgsql
AS $$
BEGIN
IF NEW.nama = 'DILARANG' THEN
RETURN NULL; -- batalkan INSERT baris ini
END IF;
RETURN NEW; -- lanjutkan INSERT baris ini
END
$$;
CREATE TRIGGER saring_barang_insert
BEFORE INSERT ON barang
FOR EACH ROW EXECUTE FUNCTION saring_barang();
CREATE FUNCTION izin_hapus() RETURNS trigger
LANGUAGE plpgsql
AS $$
BEGIN
RETURN OLD; -- untuk DELETE: kembalikan OLD agar penghapusan jalan
END
$$;
CREATE TRIGGER izin_hapus_barang
BEFORE DELETE ON barang
FOR EACH ROW EXECUTE FUNCTION izin_hapus();
INSERT INTO barang (id, nama) VALUES (1, 'Pena Hitam');
INSERT INTO barang (id, nama) VALUES (2, 'DILARANG');
DELETE FROM barang WHERE id = 1;
SELECT id, nama FROM barang ORDER BY id;
variabel-tg.sql
Unduh berkas-- Variabel otomatis TG_* : identitas lengkap pemanggilan trigger
CREATE FUNCTION lacak_identitas() RETURNS trigger
LANGUAGE plpgsql
AS $$
BEGIN
INSERT INTO log_identitas (tabel, operasi, tingkat, saat, argumen, jumlah)
VALUES (
TG_TABLE_NAME,
TG_OP,
TG_LEVEL,
TG_WHEN,
coalesce(TG_ARGV[0], '-') || '+' || coalesce(TG_ARGV[1], '-'),
TG_NARGS
);
IF TG_OP = 'DELETE' THEN
RETURN OLD;
END IF;
RETURN NEW;
END
$$;
-- Trigger 1: INSERT produk, BEFORE, dengan dua argumen string
CREATE TRIGGER lacak_produk_insert
BEFORE INSERT ON produk
FOR EACH ROW EXECUTE FUNCTION lacak_identitas('gudang', 'reguler');
INSERT INTO produk (id, nama, harga) VALUES (1, 'Kopi Kapal', 18000.00);
-- Trigger 2: UPDATE pelanggan, AFTER, tanpa argumen
CREATE TRIGGER lacak_pelanggan_update
AFTER UPDATE ON pelanggan
FOR EACH ROW EXECUTE FUNCTION lacak_identitas();
INSERT INTO pelanggan (id, nama, poin) VALUES (1, 'Budi Santoso', 100);
UPDATE pelanggan SET poin = 150 WHERE id = 1;
SELECT tabel, operasi, tingkat, saat, argumen, jumlah
FROM log_identitas
ORDER BY id;