트리거

트리거

1. 트리거 κ°œλ…

νŠΈλ¦¬κ±°λŠ” νŠΉμ • 이벀트(INSERT, UPDATE, DELETE)κ°€ λ°œμƒν•  λ•Œ μžλ™μœΌλ‘œ μ‹€ν–‰λ˜λŠ” ν•¨μˆ˜μž…λ‹ˆλ‹€.

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”     β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”     β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚   INSERT    β”‚ ──▢ β”‚   TRIGGER   β”‚ ──▢ β”‚  μžλ™ μ‹€ν–‰  β”‚
β”‚   UPDATE    β”‚     β”‚   (κ°μ‹œ)    β”‚     β”‚  (트리거    β”‚
β”‚   DELETE    β”‚     β”‚             β”‚     β”‚   ν•¨μˆ˜)     β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜     β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜     β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

2. 트리거 κ΅¬μ„±μš”μ†Œ

  1. 트리거 ν•¨μˆ˜: μ‹€ν–‰ν•  둜직
  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μ—μ„œ λ°±μ—…κ³Ό μš΄μ˜μ„ λ°°μ›Œλ΄…μ‹œλ‹€!

to navigate between lessons