νΈλ¦¬κ±°
νΈλ¦¬κ±°¶
1. νΈλ¦¬κ±° κ°λ ¶
νΈλ¦¬κ±°λ νΉμ μ΄λ²€νΈ(INSERT, UPDATE, DELETE)κ° λ°μν λ μλμΌλ‘ μ€νλλ ν¨μμ λλ€.
βββββββββββββββ βββββββββββββββ βββββββββββββββ
β INSERT β βββΆ β TRIGGER β βββΆ β μλ μ€ν β
β UPDATE β β (κ°μ) β β (νΈλ¦¬κ±° β
β DELETE β β β β ν¨μ) β
βββββββββββββββ βββββββββββββββ βββββββββββββββ
2. νΈλ¦¬κ±° ꡬμ±μμ¶
- νΈλ¦¬κ±° ν¨μ: μ€νν λ‘μ§
- νΈλ¦¬κ±°: μΈμ , μ΄λ€ ν μ΄λΈμμ ν¨μλ₯Ό μ€νν μ§ μ μ
νΈλ¦¬κ±° ν¨μ μμ±¶
CREATE FUNCTION trigger_function_name()
RETURNS TRIGGER
AS $$
BEGIN
-- λ‘μ§
RETURN NEW; -- λλ RETURN OLD; λλ RETURN NULL;
END;
$$ LANGUAGE plpgsql;
νΈλ¦¬κ±° μμ±¶
CREATE TRIGGER trigger_name
{BEFORE | AFTER | INSTEAD OF} {INSERT | UPDATE | DELETE}
ON table_name
[FOR EACH ROW | FOR EACH STATEMENT]
EXECUTE FUNCTION trigger_function_name();
3. BEFORE vs AFTER¶
BEFORE νΈλ¦¬κ±°¶
μ΄λ²€νΈ λ°μ μ μ μ€νλ©λλ€. λ°μ΄ν°λ₯Ό κ²μ¦νκ±°λ μμ ν μ μμ΅λλ€.
-- κ°κ²©μ΄ 0 μ΄νλ©΄ μ€λ₯ λ°μ
CREATE FUNCTION check_price()
RETURNS TRIGGER AS $$
BEGIN
IF NEW.price <= 0 THEN
RAISE EXCEPTION 'κ°κ²©μ 0λ³΄λ€ μ»€μΌ ν©λλ€.';
END IF;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER before_insert_product
BEFORE INSERT ON products
FOR EACH ROW
EXECUTE FUNCTION check_price();
AFTER νΈλ¦¬κ±°¶
μ΄λ²€νΈ λ°μ νμ μ€νλ©λλ€. κ°μ¬ λ‘κ·Έ, μλ¦Ό λ±μ μ¬μ©ν©λλ€.
-- μ£Όλ¬Έ μμ± ν μ¬κ³ μ°¨κ°
CREATE FUNCTION update_stock()
RETURNS TRIGGER AS $$
BEGIN
UPDATE products
SET stock = stock - NEW.quantity
WHERE id = NEW.product_id;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER after_insert_order_item
AFTER INSERT ON order_items
FOR EACH ROW
EXECUTE FUNCTION update_stock();
4. NEW vs OLD¶
| λ³μ | INSERT | UPDATE | DELETE |
|---|---|---|---|
NEW |
μ ν | μ ν | μμ |
OLD |
μμ | κΈ°μ‘΄ ν | μμ λ ν |
-- UPDATE μ λ³κ²½ μ ν κ° λΉκ΅
CREATE FUNCTION log_price_change()
RETURNS TRIGGER AS $$
BEGIN
IF OLD.price <> NEW.price THEN
INSERT INTO price_history (product_id, old_price, new_price)
VALUES (NEW.id, OLD.price, NEW.price);
END IF;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER after_update_price
AFTER UPDATE OF price ON products
FOR EACH ROW
EXECUTE FUNCTION log_price_change();
5. FOR EACH ROW vs FOR EACH STATEMENT¶
FOR EACH ROW¶
κ° νλ§λ€ νΈλ¦¬κ±°κ° μ€νλ©λλ€.
-- κ° νμ λν΄ μ€ν
CREATE TRIGGER row_trigger
AFTER INSERT ON products
FOR EACH ROW
EXECUTE FUNCTION my_function();
-- INSERT INTO products VALUES (...), (...), (...);
-- β 3λ² μ€ν
FOR EACH STATEMENT¶
λ¬Έμ₯λΉ ν λ²λ§ μ€νλ©λλ€.
-- λ¬Έμ₯λΉ ν λ²λ§ μ€ν
CREATE TRIGGER statement_trigger
AFTER INSERT ON products
FOR EACH STATEMENT
EXECUTE FUNCTION my_function();
-- INSERT INTO products VALUES (...), (...), (...);
-- β 1λ² μ€ν
6. μ‘°κ±΄λΆ νΈλ¦¬κ±° (WHEN)¶
-- κ°κ²©μ΄ 100λ§μ μ΄μμΌ λλ§ μ€ν
CREATE TRIGGER high_price_alert
AFTER INSERT ON products
FOR EACH ROW
WHEN (NEW.price >= 1000000)
EXECUTE FUNCTION send_alert();
7. μ€μ΅ μμ ¶
μ€μ΅ 1: μλ νμμ€ν¬ν¶
-- updated_at μλ κ°±μ
CREATE FUNCTION update_timestamp()
RETURNS TRIGGER AS $$
BEGIN
NEW.updated_at = NOW();
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
-- ν
μ΄λΈμ μ μ©
CREATE TABLE articles (
id SERIAL PRIMARY KEY,
title VARCHAR(200),
content TEXT,
created_at TIMESTAMP DEFAULT NOW(),
updated_at TIMESTAMP DEFAULT NOW()
);
CREATE TRIGGER set_updated_at
BEFORE UPDATE ON articles
FOR EACH ROW
EXECUTE FUNCTION update_timestamp();
-- ν
μ€νΈ
INSERT INTO articles (title, content) VALUES ('μ λͺ©', 'λ΄μ©');
SELECT * FROM articles;
UPDATE articles SET content = 'μμ λ λ΄μ©' WHERE id = 1;
SELECT * FROM articles; -- updated_at μλ κ°±μ λ¨
μ€μ΅ 2: κ°μ¬ λ‘κ·Έ¶
-- κ°μ¬ λ‘κ·Έ ν
μ΄λΈ
CREATE TABLE audit_log (
id SERIAL PRIMARY KEY,
table_name VARCHAR(50),
operation VARCHAR(10),
old_data JSONB,
new_data JSONB,
changed_by VARCHAR(100),
changed_at TIMESTAMP DEFAULT NOW()
);
-- κ°μ¬ νΈλ¦¬κ±° ν¨μ
CREATE FUNCTION audit_trigger()
RETURNS TRIGGER AS $$
BEGIN
IF TG_OP = 'INSERT' THEN
INSERT INTO audit_log (table_name, operation, new_data, changed_by)
VALUES (TG_TABLE_NAME, 'INSERT', row_to_json(NEW)::JSONB, current_user);
RETURN NEW;
ELSIF TG_OP = 'UPDATE' THEN
INSERT INTO audit_log (table_name, operation, old_data, new_data, changed_by)
VALUES (TG_TABLE_NAME, 'UPDATE', row_to_json(OLD)::JSONB, row_to_json(NEW)::JSONB, current_user);
RETURN NEW;
ELSIF TG_OP = 'DELETE' THEN
INSERT INTO audit_log (table_name, operation, old_data, changed_by)
VALUES (TG_TABLE_NAME, 'DELETE', row_to_json(OLD)::JSONB, current_user);
RETURN OLD;
END IF;
END;
$$ LANGUAGE plpgsql;
-- νΈλ¦¬κ±° μ μ©
CREATE TRIGGER users_audit
AFTER INSERT OR UPDATE OR DELETE ON users
FOR EACH ROW
EXECUTE FUNCTION audit_trigger();
-- ν
μ€νΈ
INSERT INTO users (name, email) VALUES ('κ°μ¬ν
μ€νΈ', 'audit@test.com');
UPDATE users SET name = 'κ°μ¬μμ ' WHERE email = 'audit@test.com';
DELETE FROM users WHERE email = 'audit@test.com';
SELECT * FROM audit_log;
μ€μ΅ 3: μ¬κ³ κ΄λ¦¬¶
-- μ¬κ³ ν
μ΄λΈ
CREATE TABLE inventory (
product_id INTEGER PRIMARY KEY,
quantity INTEGER DEFAULT 0,
reserved INTEGER DEFAULT 0
);
-- μ£Όλ¬Έ μ μ¬κ³ μμ½
CREATE FUNCTION reserve_stock()
RETURNS TRIGGER AS $$
DECLARE
available INTEGER;
BEGIN
SELECT quantity - reserved INTO available
FROM inventory
WHERE product_id = NEW.product_id;
IF available < NEW.quantity THEN
RAISE EXCEPTION 'μ¬κ³ λΆμ‘±: κ°μ© μ¬κ³ %, μμ² %', available, NEW.quantity;
END IF;
UPDATE inventory
SET reserved = reserved + NEW.quantity
WHERE product_id = NEW.product_id;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER before_order_item
BEFORE INSERT ON order_items
FOR EACH ROW
EXECUTE FUNCTION reserve_stock();
-- μ£Όλ¬Έ μλ£ μ μ€μ μ¬κ³ μ°¨κ°
CREATE FUNCTION complete_stock()
RETURNS TRIGGER AS $$
BEGIN
IF NEW.status = 'completed' AND OLD.status <> 'completed' THEN
UPDATE inventory
SET quantity = quantity - oi.quantity,
reserved = reserved - oi.quantity
FROM order_items oi
WHERE oi.order_id = NEW.id
AND inventory.product_id = oi.product_id;
END IF;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER after_order_complete
AFTER UPDATE ON orders
FOR EACH ROW
EXECUTE FUNCTION complete_stock();
μ€μ΅ 4: λ°μ΄ν° μ ν¨μ± κ²μ¬¶
-- μ΄λ©μΌ μ€λ³΅ κ²μ¬ (λμλ¬Έμ 무μ)
CREATE FUNCTION check_email_unique()
RETURNS TRIGGER AS $$
BEGIN
IF EXISTS (
SELECT 1 FROM users
WHERE LOWER(email) = LOWER(NEW.email)
AND id <> COALESCE(NEW.id, -1)
) THEN
RAISE EXCEPTION 'μ΄λ©μΌμ΄ μ΄λ―Έ μ‘΄μ¬ν©λλ€: %', NEW.email;
END IF;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER before_user_email
BEFORE INSERT OR UPDATE OF email ON users
FOR EACH ROW
EXECUTE FUNCTION check_email_unique();
8. νΈλ¦¬κ±° κ΄λ¦¬¶
νΈλ¦¬κ±° λͺ©λ‘ νμΈ¶
-- ν
μ΄λΈμ νΈλ¦¬κ±° νμΈ
SELECT tgname, tgtype, proname
FROM pg_trigger t
JOIN pg_proc p ON t.tgfoid = p.oid
WHERE tgrelid = 'users'::regclass;
-- λλ
\dS users
νΈλ¦¬κ±° λΉνμ±ν/νμ±ν¶
-- νΉμ νΈλ¦¬κ±° λΉνμ±ν
ALTER TABLE users DISABLE TRIGGER users_audit;
-- λͺ¨λ νΈλ¦¬κ±° λΉνμ±ν
ALTER TABLE users DISABLE TRIGGER ALL;
-- νμ±ν
ALTER TABLE users ENABLE TRIGGER users_audit;
ALTER TABLE users ENABLE TRIGGER ALL;
νΈλ¦¬κ±° μμ ¶
DROP TRIGGER trigger_name ON table_name;
DROP TRIGGER IF EXISTS trigger_name ON table_name;
9. νΈλ¦¬κ±° TG_ λ³μ¶
| λ³μ | μ€λͺ |
|---|---|
TG_NAME |
νΈλ¦¬κ±° μ΄λ¦ |
TG_TABLE_NAME |
ν μ΄λΈ μ΄λ¦ |
TG_TABLE_SCHEMA |
μ€ν€λ§ μ΄λ¦ |
TG_OP |
μμ (INSERT, UPDATE, DELETE) |
TG_WHEN |
BEFORE λλ AFTER |
TG_LEVEL |
ROW λλ STATEMENT |
CREATE FUNCTION debug_trigger()
RETURNS TRIGGER AS $$
BEGIN
RAISE NOTICE 'Trigger: %, Table: %, Op: %, When: %',
TG_NAME, TG_TABLE_NAME, TG_OP, TG_WHEN;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
10. μ£Όμμ¬ν¶
무ν 루ν λ°©μ§¶
-- λμ μ: νΈλ¦¬κ±°κ° μμ μ λ€μ νΈμΆ
CREATE FUNCTION bad_trigger()
RETURNS TRIGGER AS $$
BEGIN
UPDATE same_table SET ...; -- κ°μ ν
μ΄λΈ UPDATE β 무ν 루ν!
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
μ±λ₯ κ³ λ €¶
-- νΈλ¦¬κ±°λ λͺ¨λ μμ
μ μ€λ²ν€λ μΆκ°
-- λλ λ°μ΄ν° μ²λ¦¬ μ νΈλ¦¬κ±° λΉνμ±ν κ³ λ €
ALTER TABLE users DISABLE TRIGGER ALL;
-- λλ INSERT/UPDATE
ALTER TABLE users ENABLE TRIGGER ALL;
λλ²κΉ ¶
-- RAISE NOTICEλ‘ λλ²κΉ
CREATE FUNCTION debug_function()
RETURNS TRIGGER AS $$
BEGIN
RAISE NOTICE 'OLD: %, NEW: %', OLD, NEW;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
λ€μ λ¨κ³¶
13_Backup_and_Operations.mdμμ λ°±μ κ³Ό μ΄μμ λ°°μλ΄ μλ€!