创建触发器
在PostgreSQL中创建触发器需要先创建触发器函数,然后使用CREATE TRIGGER语句将函数与特定的表和事件关联起来。触发器函数必须是一个返回trigger类型的函数,它定义了触发器被激活时要执行的逻辑。本章将详细介绍如何创建各种类型的触发器。
创建触发器函数
在创建触发器之前,必须先创建触发器函数。触发器函数是一个特殊的函数,它必须满足以下要求:
- 函数必须声明为无参数函数
- 函数返回类型必须是TRIGGER
- 函数体中可以访问触发器相关的特殊变量
基本触发器函数语法
-- 创建基本的触发器函数
CREATE OR REPLACE FUNCTION trigger_function_name()
RETURNS TRIGGER AS $$
BEGIN
-- 触发器逻辑
RETURN NEW; -- 或者 RETURN OLD; 或者 RETURN NULL;
END;
$$ LANGUAGE plpgsql;
触发器函数中的特殊变量:
- NEW:INSERT和UPDATE操作中的新行数据
- OLD:UPDATE和DELETE操作中的旧行数据
- TG_OP:触发触发器的操作类型('INSERT', 'UPDATE', 'DELETE', 'TRUNCATE')
- TG_TABLE_NAME:触发触发器的表名
- TG_TABLE_SCHEMA:触发触发器的表模式名
- TG_WHEN:触发时机('BEFORE', 'AFTER', 'INSTEAD OF')
- TG_LEVEL:触发器级别('ROW', 'STATEMENT')
简单触发器函数示例
-- 创建记录用户插入时间的触发器函数
CREATE OR REPLACE FUNCTION set_user_insert_time()
RETURNS TRIGGER AS $$
BEGIN
NEW.created_at = CURRENT_TIMESTAMP;
NEW.updated_at = CURRENT_TIMESTAMP;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
-- 创建验证数据的触发器函数
CREATE OR REPLACE FUNCTION validate_user_data()
RETURNS TRIGGER AS $$
BEGIN
-- 验证邮箱格式
IF NEW.email !~* '^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}$' THEN
RAISE EXCEPTION '无效的邮箱格式';
END IF;
-- 验证用户名长度
IF LENGTH(NEW.username) < 3 THEN
RAISE EXCEPTION '用户名长度不能少于3个字符';
END IF;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER语句语法
创建触发器使用CREATE TRIGGER语句,其基本语法如下:
CREATE TRIGGER语法
CREATE [ CONSTRAINT ] TRIGGER name { BEFORE | AFTER | INSTEAD OF } { event [ OR ... ] }
ON table_name
[ FROM referenced_table_name ]
[ NOT DEFERRABLE | [ DEFERRABLE ] [ INITIALLY IMMEDIATE | INITIALLY DEFERRED ] ]
[ FOR [ EACH ] { ROW | STATEMENT } ]
[ WHEN ( condition ) ]
EXECUTE { FUNCTION | PROCEDURE } function_name ( arguments )
语法参数说明:
- name:触发器名称
- BEFORE/AFTER/INSTEAD OF:触发时机
- event:触发事件(INSERT、UPDATE、DELETE、TRUNCATE)
- table_name:触发器关联的表名
- FOR EACH ROW/STATEMENT:触发器级别
- WHEN (condition):触发条件(可选)
- function_name:触发器函数名称
创建不同类型的触发器
根据不同的分类标准,可以创建多种类型的触发器。
创建行级触发器
-- 创建行级BEFORE触发器
CREATE TRIGGER user_before_insert_trigger
BEFORE INSERT ON users
FOR EACH ROW
EXECUTE FUNCTION set_user_insert_time();
-- 创建行级AFTER触发器
CREATE TRIGGER user_after_insert_trigger
AFTER INSERT ON users
FOR EACH ROW
EXECUTE FUNCTION send_welcome_email();
-- 创建UPDATE行级触发器
CREATE TRIGGER user_update_trigger
BEFORE UPDATE ON users
FOR EACH ROW
EXECUTE FUNCTION set_user_update_time();
创建语句级触发器
-- 创建语句级触发器记录操作摘要
CREATE OR REPLACE FUNCTION log_statement_summary()
RETURNS TRIGGER AS $$
BEGIN
INSERT INTO operation_log (table_name, operation, timestamp, user_name)
VALUES (TG_TABLE_NAME, TG_OP, CURRENT_TIMESTAMP, USER);
RETURN NULL;
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER user_statement_trigger
AFTER INSERT OR UPDATE OR DELETE ON users
FOR EACH STATEMENT
EXECUTE FUNCTION log_statement_summary();
创建多事件触发器
-- 创建处理多种操作的触发器
CREATE OR REPLACE FUNCTION handle_user_changes()
RETURNS TRIGGER AS $$
BEGIN
CASE TG_OP
WHEN 'INSERT' THEN
-- 处理插入操作
NEW.created_at = CURRENT_TIMESTAMP;
NEW.updated_at = CURRENT_TIMESTAMP;
RETURN NEW;
WHEN 'UPDATE' THEN
-- 处理更新操作
NEW.updated_at = CURRENT_TIMESTAMP;
RETURN NEW;
WHEN 'DELETE' THEN
-- 处理删除操作
INSERT INTO user_archive SELECT OLD.*;
RETURN OLD;
ELSE
RETURN NULL;
END CASE;
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER user_multi_event_trigger
BEFORE INSERT OR UPDATE OR DELETE ON users
FOR EACH ROW
EXECUTE FUNCTION handle_user_changes();
创建带条件的触发器
-- 创建带条件的触发器
CREATE TRIGGER salary_update_trigger
BEFORE UPDATE ON employees
FOR EACH ROW
WHEN (OLD.salary IS DISTINCT FROM NEW.salary)
EXECUTE FUNCTION log_salary_change();
-- 复杂条件触发器
CREATE TRIGGER important_customer_trigger
AFTER UPDATE ON customers
FOR EACH ROW
WHEN (NEW.total_purchases > 10000 OR NEW.customer_level = 'VIP')
EXECUTE FUNCTION notify_important_customer_update();
创建复杂的触发器示例
以下是一些实际应用中的复杂触发器示例。
审计日志触发器
-- 创建审计日志表
CREATE TABLE audit_log (
id SERIAL PRIMARY KEY,
table_name VARCHAR(50),
operation VARCHAR(10),
row_data JSONB,
changed_data JSONB,
user_name VARCHAR(50),
timestamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- 创建审计触发器函数
CREATE OR REPLACE FUNCTION audit_trigger_function()
RETURNS TRIGGER AS $$
BEGIN
IF (TG_OP = 'DELETE') THEN
INSERT INTO audit_log (table_name, operation, row_data, user_name)
VALUES (TG_TABLE_NAME, TG_OP, row_to_json(OLD), USER);
RETURN OLD;
ELSIF (TG_OP = 'INSERT') THEN
INSERT INTO audit_log (table_name, operation, row_data, user_name)
VALUES (TG_TABLE_NAME, TG_OP, row_to_json(NEW), USER);
RETURN NEW;
ELSIF (TG_OP = 'UPDATE') THEN
INSERT INTO audit_log (table_name, operation, row_data, changed_data, user_name)
VALUES (TG_TABLE_NAME, TG_OP, row_to_json(OLD), row_to_json(NEW), USER);
RETURN NEW;
END IF;
RETURN NULL;
END;
$$ LANGUAGE plpgsql;
-- 为多个表创建审计触发器
CREATE TRIGGER users_audit_trigger
AFTER INSERT OR UPDATE OR DELETE ON users
FOR EACH ROW
EXECUTE FUNCTION audit_trigger_function();
CREATE TRIGGER products_audit_trigger
AFTER INSERT OR UPDATE OR DELETE ON products
FOR EACH ROW
EXECUTE FUNCTION audit_trigger_function();
数据同步触发器
-- 创建数据同步触发器函数
CREATE OR REPLACE FUNCTION sync_user_data()
RETURNS TRIGGER AS $$
BEGIN
-- 同步到历史表
INSERT INTO user_history (
user_id, username, email, created_at, updated_at, sync_time
) VALUES (
NEW.id, NEW.username, NEW.email, NEW.created_at, NEW.updated_at, CURRENT_TIMESTAMP
) ON CONFLICT (user_id) DO UPDATE SET
username = EXCLUDED.username,
email = EXCLUDED.email,
updated_at = EXCLUDED.updated_at,
sync_time = EXCLUDED.sync_time;
-- 同步到统计表
INSERT INTO user_statistics (user_id, registration_date)
VALUES (NEW.id, NEW.created_at)
ON CONFLICT (user_id) DO NOTHING;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER user_sync_trigger
AFTER INSERT OR UPDATE ON users
FOR EACH ROW
EXECUTE FUNCTION sync_user_data();
业务规则触发器
-- 创建库存检查触发器函数
CREATE OR REPLACE FUNCTION check_inventory_before_order()
RETURNS TRIGGER AS $$
DECLARE
available_stock INTEGER;
BEGIN
-- 检查产品库存
SELECT stock_quantity INTO available_stock
FROM products
WHERE id = NEW.product_id;
-- 如果库存不足,抛出异常
IF available_stock < NEW.quantity THEN
RAISE EXCEPTION '产品ID % 库存不足,当前库存:% ,需求数量:%',
NEW.product_id, available_stock, NEW.quantity;
END IF;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER order_inventory_check_trigger
BEFORE INSERT ON order_items
FOR EACH ROW
EXECUTE FUNCTION check_inventory_before_order();
-- 创建订单总价计算触发器
CREATE OR REPLACE FUNCTION calculate_order_total()
RETURNS TRIGGER AS $$
BEGIN
-- 计算订单总价
SELECT SUM(quantity * price) INTO NEW.total_amount
FROM order_items
WHERE order_id = NEW.id;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER order_total_trigger
BEFORE INSERT OR UPDATE ON orders
FOR EACH ROW
EXECUTE FUNCTION calculate_order_total();
触发器创建的最佳实践
创建触发器时应遵循一些最佳实践,以确保触发器的有效性和可维护性。
命名规范
-- 推荐的触发器命名规范
-- {表名}_{操作}_{触发时机}_{功能}_trigger
CREATE TRIGGER users_insert_before_validation_trigger
BEFORE INSERT ON users
FOR EACH ROW
EXECUTE FUNCTION validate_user_data();
-- {表名}_{功能}_trigger
CREATE TRIGGER orders_audit_trigger
AFTER INSERT OR UPDATE OR DELETE ON orders
FOR EACH ROW
EXECUTE FUNCTION audit_order_changes();
错误处理
-- 带错误处理的触发器函数
CREATE OR REPLACE FUNCTION safe_trigger_function()
RETURNS TRIGGER AS $$
BEGIN
BEGIN
-- 主要逻辑
INSERT INTO log_table (message, timestamp)
VALUES ('Trigger executed for ' || TG_TABLE_NAME, CURRENT_TIMESTAMP);
EXCEPTION
WHEN OTHERS THEN
-- 记录错误但不中断主操作
INSERT INTO error_log (error_message, table_name, operation, timestamp)
VALUES (SQLERRM, TG_TABLE_NAME, TG_OP, CURRENT_TIMESTAMP);
-- 根据需要决定是否继续执行
-- RETURN NULL; -- 如果是AFTER触发器,可以返回NULL
END;
RETURN NEW; -- BEFORE触发器返回NEW,AFTER触发器可以返回NULL
END;
$$ LANGUAGE plpgsql;
创建触发器的最佳实践:
- 明确触发器目的:每个触发器应该有明确的、单一的职责
- 合理选择触发时机:根据业务需求选择BEFORE、AFTER或INSTEAD OF
- 正确设置触发级别:行级触发器适合需要处理每行数据的场景,语句级触发器适合汇总处理
- 添加条件限制:使用WHEN子句限制触发器的执行条件,提高性能
- 处理异常:在触发器函数中合理处理异常,避免影响主操作
- 性能考虑:避免在触发器中执行耗时操作,如复杂查询或外部调用
- 文档化:为每个触发器添加注释,说明其功能和使用场景