Database Triggers: Trigger Database 🔄
Di materi keempat ini, kita akan belajar cara membuat dan menggunakan database triggers di berbagai DBMS. Contoh yang diberikan mencakup PostgreSQL, MySQL, dan SQL Server.
Prerequisites 📋
Sebelum memulai materi ini, pastikan Anda sudah memahami:
- SQL Dasar (Basic SQL)
- DML (INSERT, UPDATE, DELETE)
- DDL (CREATE, ALTER, DROP)
- Event handling
- SQL Intermediate
- SQL Advanced
Dasar Triggers (Basic Triggers)
1. Before Insert Trigger
-- PostgreSQL
CREATE OR REPLACE FUNCTION validate_product()
RETURNS TRIGGER AS $$
BEGIN
IF NEW.price < 0 THEN
RAISE EXCEPTION 'Harga produk tidak boleh negatif';
END IF;
IF NEW.stock < 0 THEN
RAISE EXCEPTION 'Stok produk tidak boleh negatif';
END IF;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER check_product_before_insert
BEFORE INSERT ON products
FOR EACH ROW
EXECUTE FUNCTION validate_product();
-- MySQL
DELIMITER //
CREATE TRIGGER check_product_before_insert
BEFORE INSERT ON products
FOR EACH ROW
BEGIN
IF NEW.price < 0 THEN
SIGNAL SQLSTATE '45000'
SET MESSAGE_TEXT = 'Harga produk tidak boleh negatif';
END IF;
IF NEW.stock < 0 THEN
SIGNAL SQLSTATE '45000'
SET MESSAGE_TEXT = 'Stok produk tidak boleh negatif';
END IF;
END //
DELIMITER ;
-- SQL Server
CREATE TRIGGER check_product_before_insert
ON products
INSTEAD OF INSERT
AS
BEGIN
SET NOCOUNT ON;
IF EXISTS (
SELECT 1 FROM inserted
WHERE price < 0 OR stock < 0
)
BEGIN
THROW 50000, 'Harga atau stok produk tidak boleh negatif', 1;
RETURN;
END;
INSERT INTO products (name, price, stock)
SELECT name, price, stock
FROM inserted;
END;
-- Contoh penggunaan (valid):
INSERT INTO products (name, price, stock)
VALUES ('Laptop Pro', 1500000, 10);
-- Contoh penggunaan (invalid):
INSERT INTO products (name, price, stock)
VALUES ('Invalid Product', -1000, -5);
-- Error: Harga dan stok produk tidak boleh negatif
2. After Update Trigger
-- PostgreSQL
CREATE OR REPLACE FUNCTION log_price_changes()
RETURNS TRIGGER AS $$
BEGIN
INSERT INTO price_history (
product_id, old_price, new_price,
change_date, changed_by
)
VALUES (
NEW.id, OLD.price, NEW.price,
CURRENT_TIMESTAMP, CURRENT_USER
);
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER track_price_changes
AFTER UPDATE OF price ON products
FOR EACH ROW
EXECUTE FUNCTION log_price_changes();
-- MySQL
DELIMITER //
CREATE TRIGGER track_price_changes
AFTER UPDATE ON products
FOR EACH ROW
BEGIN
IF OLD.price <> NEW.price THEN
INSERT INTO price_history (
product_id, old_price, new_price,
change_date, changed_by
)
VALUES (
NEW.id, OLD.price, NEW.price,
NOW(), CURRENT_USER()
);
END IF;
END //
DELIMITER ;
-- SQL Server
CREATE TRIGGER log_price_changes
ON products
AFTER UPDATE
AS
BEGIN
SET NOCOUNT ON;
IF UPDATE(price)
BEGIN
INSERT INTO price_history (
product_id,
old_price,
new_price,
change_date
)
SELECT
i.id,
d.price,
i.price,
GETDATE()
FROM inserted i
JOIN deleted d ON i.id = d.id
WHERE i.price <> d.price;
END;
END;
-- Contoh penggunaan:
UPDATE products
SET price = 1800000
WHERE id = 1;
-- Data perubahan harga:
product_id | old_price | new_price | change_date | changed_by
---------- | --------- | --------- | -------------------- | ----------
1 | 1500000 | 1800000 | 2024-01-15 10:30:00 | admin
3. Complex Trigger with Multiple Events
-- SQL Server
CREATE TRIGGER maintain_inventory
ON order_items
AFTER INSERT, UPDATE, DELETE
AS
BEGIN
SET NOCOUNT ON;
-- Handle deletes (cancelled orders)
IF EXISTS (SELECT 1 FROM deleted)
BEGIN
UPDATE p
SET p.stock = p.stock + d.quantity
FROM products p
JOIN deleted d ON p.id = d.product_id;
END;
-- Handle inserts (new orders)
IF EXISTS (SELECT 1 FROM inserted)
BEGIN
-- Check if we have enough stock
IF EXISTS (
SELECT 1
FROM inserted i
JOIN products p ON i.product_id = p.id
WHERE p.stock < i.quantity
)
BEGIN
THROW 50000, 'Insufficient stock for order', 1;
RETURN;
END;
-- Update stock
UPDATE p
SET p.stock = p.stock - i.quantity
FROM products p
JOIN inserted i ON p.id = i.product_id;
END;
END;
4. DDL Trigger (SQL Server Specific)
-- SQL Server
CREATE TRIGGER prevent_table_deletion
ON DATABASE
FOR DROP_TABLE
AS
BEGIN
PRINT 'Table deletion is not allowed in production';
ROLLBACK;
END;
-- Audit schema changes
CREATE TRIGGER audit_schema_changes
ON DATABASE
FOR DDL_DATABASE_LEVEL_EVENTS
AS
BEGIN
SET NOCOUNT ON;
DECLARE @EventData XML = EVENTDATA();
INSERT INTO schema_changes_log (
event_type,
object_name,
object_type,
sql_command,
login_name,
event_date
)
VALUES (
@EventData.value('(/EVENT_INSTANCE/EventType)[1]', 'nvarchar(100)'),
@EventData.value('(/EVENT_INSTANCE/ObjectName)[1]', 'nvarchar(100)'),
@EventData.value('(/EVENT_INSTANCE/ObjectType)[1]', 'nvarchar(100)'),
@EventData.value('(/EVENT_INSTANCE/TSQLCommand/CommandText)[1]', 'nvarchar(max)'),
@EventData.value('(/EVENT_INSTANCE/LoginName)[1]', 'nvarchar(100)'),
GETDATE()
);
END;
3. Instead Of Delete Trigger
-- PostgreSQL
CREATE OR REPLACE FUNCTION handle_soft_delete()
RETURNS TRIGGER AS $$
BEGIN
UPDATE products
SET
status = 'deleted',
deleted_at = CURRENT_TIMESTAMP,
deleted_by = CURRENT_USER
WHERE id = OLD.id;
RETURN NULL;
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER soft_delete_products
INSTEAD OF DELETE ON products
FOR EACH ROW
EXECUTE FUNCTION handle_soft_delete();
-- MySQL (menggunakan BEFORE DELETE karena tidak mendukung INSTEAD OF)
DELIMITER //
CREATE TRIGGER soft_delete_products
BEFORE DELETE ON products
FOR EACH ROW
BEGIN
INSERT INTO deleted_products
SELECT
p.*,
NOW() as deleted_at,
CURRENT_USER() as deleted_by
FROM products p
WHERE p.id = OLD.id;
END //
DELIMITER ;
-- SQL Server
CREATE TRIGGER soft_delete_products
ON products
INSTEAD OF DELETE
AS
BEGIN
UPDATE p
SET
status = 'deleted',
deleted_at = GETDATE(),
deleted_by = SYSTEM_USER
FROM products p
JOIN deleted d ON p.id = d.id;
END;
-- Contoh penggunaan:
DELETE FROM products WHERE id = 1;
-- Data produk setelah soft delete:
id | name | status | deleted_at | deleted_by
-- | ---------- | ------- | ------------------- | ----------
1 | Laptop Pro | deleted | 2024-01-15 11:00:00 | admin
4. Multiple Event Trigger
-- PostgreSQL
CREATE OR REPLACE FUNCTION audit_inventory()
RETURNS TRIGGER AS $$
BEGIN
IF TG_OP = 'INSERT' THEN
INSERT INTO inventory_audit (
product_id, action, quantity_change,
audit_date, audit_user
)
VALUES (
NEW.id, 'INSERT', NEW.stock,
CURRENT_TIMESTAMP, CURRENT_USER
);
ELSIF TG_OP = 'UPDATE' THEN
IF NEW.stock <> OLD.stock THEN
INSERT INTO inventory_audit (
product_id, action, quantity_change,
audit_date, audit_user
)
VALUES (
NEW.id, 'UPDATE', NEW.stock - OLD.stock,
CURRENT_TIMESTAMP, CURRENT_USER
);
END IF;
ELSIF TG_OP = 'DELETE' THEN
INSERT INTO inventory_audit (
product_id, action, quantity_change,
audit_date, audit_user
)
VALUES (
OLD.id, 'DELETE', -OLD.stock,
CURRENT_TIMESTAMP, CURRENT_USER
);
END IF;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER track_inventory_changes
AFTER INSERT OR UPDATE OR DELETE ON products
FOR EACH ROW
EXECUTE FUNCTION audit_inventory();
-- MySQL
DELIMITER //
CREATE TRIGGER track_inventory_insert
AFTER INSERT ON products
FOR EACH ROW
BEGIN
INSERT INTO inventory_audit (
product_id, action, quantity_change,
audit_date, audit_user
)
VALUES (
NEW.id, 'INSERT', NEW.stock,
NOW(), CURRENT_USER()
);
END //
CREATE TRIGGER track_inventory_update
AFTER UPDATE ON products
FOR EACH ROW
BEGIN
IF NEW.stock <> OLD.stock THEN
INSERT INTO inventory_audit (
product_id, action, quantity_change,
audit_date, audit_user
)
VALUES (
NEW.id, 'UPDATE', NEW.stock - OLD.stock,
NOW(), CURRENT_USER()
);
END IF;
END //
CREATE TRIGGER track_inventory_delete
AFTER DELETE ON products
FOR EACH ROW
BEGIN
INSERT INTO inventory_audit (
product_id, action, quantity_change,
audit_date, audit_user
)
VALUES (
OLD.id, 'DELETE', -OLD.stock,
NOW(), CURRENT_USER()
);
END //
DELIMITER ;
-- SQL Server
CREATE TRIGGER track_inventory_changes
ON products
AFTER INSERT, UPDATE, DELETE
AS
BEGIN
-- Handle INSERT
INSERT INTO inventory_audit (
product_id, action, quantity_change,
audit_date, audit_user
)
SELECT
id, 'INSERT', stock,
GETDATE(), SYSTEM_USER
FROM inserted
WHERE id NOT IN (SELECT id FROM deleted);
-- Handle UPDATE
INSERT INTO inventory_audit (
product_id, action, quantity_change,
audit_date, audit_user
)
SELECT
i.id, 'UPDATE', i.stock - d.stock,
GETDATE(), SYSTEM_USER
FROM inserted i
JOIN deleted d ON i.id = d.id
WHERE i.stock <> d.stock;
-- Handle DELETE
INSERT INTO inventory_audit (
product_id, action, quantity_change,
audit_date, audit_user
)
SELECT
id, 'DELETE', -stock,
GETDATE(), SYSTEM_USER
FROM deleted
WHERE id NOT IN (SELECT id FROM inserted);
END;
-- Contoh penggunaan:
INSERT INTO products (name, stock)
VALUES ('New Product', 100);
UPDATE products
SET stock = 80
WHERE id = 1;
DELETE FROM products
WHERE id = 2;
-- Data audit inventory:
product_id | action | quantity_change | audit_date | audit_user
---------- | ------ | -------------- | ------------------- | ----------
3 | INSERT | 100 | 2024-01-15 12:00:00 | admin
1 | UPDATE | -20 | 2024-01-15 12:01:00 | admin
2 | DELETE | -50 | 2024-01-15 12:02:00 | admin
Tips Penting
- Perhatikan perbedaan fitur trigger antar DBMS
- Gunakan trigger dengan bijak karena dapat mempengaruhi performa
- Implementasikan error handling yang tepat
- Pertimbangkan penggunaan audit table untuk tracking perubahan
Latihan
- Buat trigger untuk validasi data sebelum insert/update
- Implementasikan sistem audit log menggunakan trigger
- Buat trigger untuk maintain history table