PL/pgSQL 入门教程(五):触发器

简介: PostgreSQL触发器是数据库的“自动服务员”,可在INSERT/UPDATE/DELETE等操作时自动执行校验、日志记录、汇总更新等逻辑。支持BEFORE/AFTER/INSTEAD OF时机,ROW/STATEMENT级别,配合NEW/OLD变量实现灵活数据管控,大幅提升数据一致性与运维效率。

你是否想让数据库自动完成这些操作:插入数据时自动校验格式、修改数据时自动记录日志、删除数据时自动更新汇总表?PostgreSQL的触发器(Trigger)就是干这个的——它像数据库里的“自动服务员”,当你对表执行插入、更新、删除等操作时,会自动触发预设的函数,完成你想要的逻辑。

一、触发器核心概念(5分钟)

在写代码前,先搞懂3个核心问题:触发器是什么、什么时候触发、触发后做什么

1. 触发器的本质

触发器 = 「触发条件」 + 「触发函数」

  • 触发条件:规定“在什么时间、对什么表、做什么操作时触发”;
  • 触发函数:触发后要执行的具体逻辑(用PL/pgSQL编写)。

2. 关键术语(通俗解释)

术语 通俗说明
触发时机 BEFORE(操作前触发):比如插入前校验数据;
AFTER(操作后触发):比如插入后记录日志;
INSTEAD OF(替代操作):仅用于视图,让不可更新的视图能修改
触发级别 ROW(行级):每修改1行触发1次(比如更新10行触发10次);
STATEMENT(语句级):不管改多少行,整个SQL只触发1次
触发操作 INSERT/UPDATE/DELETE/TRUNCATE(对表的操作类型)
特殊变量(核心) NEW:插入/更新后的新行数据;
OLD:更新/删除前的旧行数据;
TG_OP:触发的操作类型(INSERT/UPDATE/DELETE)

3. 核心规则

  • 触发器函数必须返回 trigger(数据变更触发器)或 event_trigger(事件触发器);
  • 行级触发器的返回值会影响操作:BEFORE触发器返回NULL会跳过当前行,AFTER触发器返回值无意义(建议返回NULL)。

二、触发器基础语法(5分钟)

触发器的创建分两步:先写「触发函数」(逻辑核心),再创建「触发器」(绑定条件)。

步骤1:创建触发器函数(通用模板)

CREATE OR REPLACE FUNCTION 函数名() RETURNS trigger AS {mathJaxContainer[0]} LANGUAGE plpgsql; -- 固定用plpgsql语言

步骤2:创建触发器(绑定函数与触发条件)

CREATE TRIGGER 触发器名
触发时机 BEFORE/AFTER/INSTEAD OF -- 比如BEFORE
触发操作 INSERT/UPDATE/DELETE -- 比如INSERT OR UPDATE
ON 表名/视图名 -- 要监控的表/视图
FOR EACH ROW/STATEMENT -- 触发级别,比如ROW
EXECUTE FUNCTION 触发器函数名(); -- 绑定第一步的函数

三、实战案例(15分钟)

下面5个案例覆盖触发器最常用的场景,每个案例都有完整代码和测试步骤,你可以直接复制到PostgreSQL中运行。

案例1:基础场景——数据校验+自动填充

需求:员工表(emp)插入/更新时,自动完成3件事:

  1. 校验员工姓名非空;
  2. 校验薪资非负;
  3. 自动记录最后修改时间和修改人。

步骤1:创建员工表

CREATE TABLE emp (
    empname           text,       -- 员工姓名
    salary            integer,    -- 薪资
    last_date         timestamp,  -- 最后修改时间
    last_user         text        -- 最后修改人
);

步骤2:编写触发器函数

CREATE OR REPLACE FUNCTION emp_check_and_fill() RETURNS trigger AS {mathJaxContainer[1]} LANGUAGE plpgsql;

步骤3:创建触发器

CREATE TRIGGER emp_trigger
BEFORE INSERT OR UPDATE ON emp -- 插入/更新前触发
FOR EACH ROW -- 行级触发,每改一行执行一次
EXECUTE FUNCTION emp_check_and_fill();

步骤4:测试效果

-- 测试1:插入合法数据(成功)
INSERT INTO emp (empname, salary) VALUES ('张三', 5000);

-- 测试2:插入空姓名(失败,触发校验)
INSERT INTO emp (empname, salary) VALUES (NULL, 5000);

-- 测试3:插入负薪资(失败,触发校验)
INSERT INTO emp (empname, salary) VALUES ('李四', -1000);

-- 测试4:更新数据,查看自动填充的字段
UPDATE emp SET salary = 6000 WHERE empname = '张三';
SELECT * FROM emp; -- 能看到last_date和last_user自动填充

案例2:高频场景——数据审计(记录所有变更)

需求:记录员工表的所有插入/更新/删除操作,保存到审计表(emp_audit),包含:操作类型、操作时间、操作用户、变更的数据。

版本1:行级触发器(适合少量数据)

步骤1:创建审计表
CREATE TABLE emp_audit(
    operation         char(1)   NOT NULL, -- 操作类型:I(插入)/U(更新)/D(删除)
    stamp             timestamp NOT NULL, -- 操作时间
    userid            text      NOT NULL, -- 操作用户
    empname           text      NOT NULL, -- 员工姓名
    salary            integer             -- 薪资
);
步骤2:编写审计函数
CREATE OR REPLACE FUNCTION emp_audit_func() RETURNS trigger AS {mathJaxContainer[2]} LANGUAGE plpgsql;
步骤3:创建审计触发器
CREATE TRIGGER emp_audit_trigger
AFTER INSERT OR UPDATE OR DELETE ON emp -- 操作后触发
FOR EACH ROW -- 行级触发
EXECUTE FUNCTION emp_audit_func();
步骤4:测试审计效果
-- 插入数据
INSERT INTO emp (empname, salary) VALUES ('王五', 8000);
-- 更新数据
UPDATE emp SET salary = 8500 WHERE empname = '王五';
-- 删除数据
DELETE FROM emp WHERE empname = '王五';

-- 查看审计记录
SELECT * FROM emp_audit;

版本2:语句级+过渡表(适合批量数据,性能更好)

如果一次修改10万行数据,行级触发器会触发10万次,性能很差。此时用「语句级触发器+过渡表」,整个SQL只触发1次,批量写入审计数据。

步骤1:重新创建审计函数(适配过渡表)
CREATE OR REPLACE FUNCTION emp_audit_batch_func() RETURNS trigger AS {mathJaxContainer[3]} LANGUAGE plpgsql;
步骤2:创建3个语句级触发器(分别绑定插入/更新/删除)
-- 插入触发器:关联新数据过渡表new_table
CREATE TRIGGER emp_audit_ins
AFTER INSERT ON emp
REFERENCING NEW TABLE AS new_table
FOR EACH STATEMENT -- 语句级触发
EXECUTE FUNCTION emp_audit_batch_func();

-- 更新触发器:关联旧/新数据过渡表
CREATE TRIGGER emp_audit_upd
AFTER UPDATE ON emp
REFERENCING OLD TABLE AS old_table NEW TABLE AS new_table
FOR EACH STATEMENT
EXECUTE FUNCTION emp_audit_batch_func();

-- 删除触发器:关联旧数据过渡表old_table
CREATE TRIGGER emp_audit_del
AFTER DELETE ON emp
REFERENCING OLD TABLE AS old_table
FOR EACH STATEMENT
EXECUTE FUNCTION emp_audit_batch_func();
步骤3:测试批量审计
-- 批量插入10条数据
INSERT INTO emp (empname, salary) 
VALUES ('赵六', 7000), ('钱七', 7500), ('孙八', 9000);

-- 查看审计记录(只触发1次,批量写入)
SELECT * FROM emp_audit;

案例3:进阶场景——视图更新(INSTEAD OF触发器)

PostgreSQL的普通视图默认不能插入/更新/删除,用「INSTEAD OF触发器」可以让视图变成“可更新”,同时保留审计逻辑。

步骤1:重建主表+审计表

CREATE TABLE emp (
    empname           text PRIMARY KEY, -- 主键
    salary            integer
);

CREATE TABLE emp_audit(
    operation         char(1)   NOT NULL,
    userid            text      NOT NULL,
    empname           text      NOT NULL,
    salary            integer,
    stamp             timestamp NOT NULL
);

步骤2:创建视图(展示员工+最后修改时间)

CREATE VIEW emp_view AS
    SELECT e.empname,
           e.salary,
           max(ea.stamp) AS last_updated -- 最后修改时间
      FROM emp e
      LEFT JOIN emp_audit ea ON ea.empname = e.empname
     GROUP BY 1, 2;

步骤3:编写视图触发器函数

CREATE OR REPLACE FUNCTION emp_view_update_func() RETURNS trigger AS {mathJaxContainer[4]} LANGUAGE plpgsql;

步骤4:创建INSTEAD OF触发器

CREATE TRIGGER emp_view_trigger
INSTEAD OF INSERT OR UPDATE OR DELETE ON emp_view -- 替代原操作
FOR EACH ROW
EXECUTE FUNCTION emp_view_update_func();

步骤5:测试视图更新

-- 向视图插入数据(实际插入主表)
INSERT INTO emp_view (empname, salary) VALUES ('周九', 10000);

-- 更新视图数据
UPDATE emp_view SET salary = 11000 WHERE empname = '周九';

-- 删除视图数据
DELETE FROM emp_view WHERE empname = '周九';

-- 查看视图和审计表
SELECT * FROM emp_view;
SELECT * FROM emp_audit;

案例4:数据仓库场景——汇总表自动维护

需求:销售事实表(sales_fact)变更时,自动更新按时间维度的汇总表(sales_summary_bytime),避免每次查询都全表统计,提升查询效率。

步骤1:创建基础表

-- 时间维度表
CREATE TABLE time_dimension (
    time_key        integer NOT NULL, -- 时间主键(比如20240101)
    day_of_week     integer NOT NULL, -- 星期几
    day_of_month    integer NOT NULL, -- 几号
    month           integer NOT NULL, -- 月份
    year            integer NOT NULL  -- 年份
);
CREATE UNIQUE INDEX time_dimension_key ON time_dimension(time_key);

-- 销售事实表(核心业务表)
CREATE TABLE sales_fact (
    time_key        integer NOT NULL, -- 关联时间维度表
    product_key     integer NOT NULL, -- 产品主键
    amount_sold     numeric(12,2) NOT NULL, -- 销售额
    units_sold      integer NOT NULL -- 销售量
);

-- 时间维度汇总表(用于快速查询)
CREATE TABLE sales_summary_bytime (
    time_key        integer NOT NULL,
    amount_sold     numeric(15,2) NOT NULL, -- 累计销售额
    units_sold      numeric(12) NOT NULL    -- 累计销售量
);
CREATE UNIQUE INDEX sales_summary_bytime_key ON sales_summary_bytime(time_key);

步骤2:编写汇总表维护函数

CREATE OR REPLACE FUNCTION sales_summary_func() RETURNS trigger AS {mathJaxContainer[5]} LANGUAGE plpgsql;

步骤3:创建汇总触发器

CREATE TRIGGER sales_summary_trigger
AFTER INSERT OR UPDATE OR DELETE ON sales_fact
FOR EACH ROW
EXECUTE FUNCTION sales_summary_func();

步骤4:测试汇总效果

-- 插入销售数据
INSERT INTO sales_fact VALUES (20240101, 1, 100.00, 10);
INSERT INTO sales_fact VALUES (20240101, 2, 200.00, 20);
INSERT INTO sales_fact VALUES (20240102, 3, 150.00, 15);

-- 查看汇总表(20240101的销售额=300,销售量=30)
SELECT * FROM sales_summary_bytime;

-- 删除一条销售数据
DELETE FROM sales_fact WHERE product_key = 1;

-- 查看汇总表(20240101的销售额=200,销售量=20)
SELECT * FROM sales_summary_bytime;

案例5:运维场景——DDL事件监控(事件触发器)

需求:监控数据库的DDL操作(比如建表、删表、改表),输出提示信息,防止误操作。

步骤1:编写事件触发器函数

CREATE OR REPLACE FUNCTION ddl_monitor_func() RETURNS event_trigger AS {mathJaxContainer[6]} LANGUAGE plpgsql;

步骤2:创建事件触发器

-- 监控所有DDL命令开始时的操作
CREATE EVENT TRIGGER ddl_monitor_trigger
ON ddl_command_start
EXECUTE FUNCTION ddl_monitor_func();

步骤3:测试DDL监控

-- 创建测试表(会触发监控)
CREATE TABLE test (id integer);

-- 修改测试表(会触发监控)
ALTER TABLE test ADD COLUMN name text;

-- 删除测试表(会触发监控)
DROP TABLE test;

运行后会看到类似提示:监控到DDL操作:事件=ddl_command_start,命令=CREATE TABLE

相关文章
|
7天前
|
JSON API 数据格式
OpenCode入门使用教程
本教程介绍如何通过安装OpenCode并配置Canopy Wave API来使用开源模型。首先全局安装OpenCode,然后设置API密钥并创建配置文件,最后在控制台中连接模型并开始交互。
3366 8
|
4天前
|
人工智能 API 开发者
Claude Code 国内保姆级使用指南:实测 GLM-4.7 与 Claude Opus 4.5 全方案解
Claude Code是Anthropic推出的编程AI代理工具。2026年国内开发者可通过配置`ANTHROPIC_BASE_URL`实现本地化接入:①极速平替——用Qwen Code v0.5.0或GLM-4.7,毫秒响应,适合日常编码;②满血原版——经灵芽API中转调用Claude Opus 4.5,胜任复杂架构与深度推理。
|
13天前
|
人工智能 JavaScript Linux
【Claude Code 全攻略】终端AI编程助手从入门到进阶(2026最新版)
Claude Code是Anthropic推出的终端原生AI编程助手,支持40+语言、200k超长上下文,无需切换IDE即可实现代码生成、调试、项目导航与自动化任务。本文详解其安装配置、四大核心功能及进阶技巧,助你全面提升开发效率,搭配GitHub Copilot使用更佳。
|
15天前
|
存储 人工智能 自然语言处理
OpenSpec技术规范+实例应用
OpenSpec 是面向 AI 智能体的轻量级规范驱动开发框架,通过“提案-审查-实施-归档”工作流,解决 AI 编程中的需求偏移与不可预测性问题。它以机器可读的规范为“单一真相源”,将模糊提示转化为可落地的工程实践,助力开发者高效构建稳定、可审计的生产级系统,实现从“凭感觉聊天”到“按规范开发”的跃迁。
2293 18
|
7天前
|
人工智能 前端开发 Docker
Huobao Drama 开源短剧生成平台:从剧本到视频
Huobao Drama 是一个基于 Go + Vue3 的开源 AI 短剧自动化生成平台,支持剧本解析、角色与分镜生成、图生视频及剪辑合成,覆盖短剧生产全链路。内置角色管理、分镜设计、视频合成、任务追踪等功能,支持本地部署与多模型接入(如 OpenAI、Ollama、火山等),搭配 FFmpeg 实现高效视频处理,适用于短剧工作流验证与自建 AI 创作后台。
1161 5
|
6天前
|
人工智能 运维 前端开发
Claude Code 30k+ star官方插件,小白也能写专业级代码
Superpowers是Claude Code官方插件,由核心开发者Jesse打造,上线3个月获3万star。它集成brainstorming、TDD、系统化调试等专业开发流程,让AI写代码更规范高效。开源免费,安装简单,实测显著提升开发质量与效率,值得开发者尝试。
|
2天前
|
人工智能 前端开发 安全
Claude Code这周这波更新有点猛,一次性给你讲清楚
Claude Code 2.1.19重磅更新:7天连发8版!npm安装已弃用,全面转向更安全稳定的原生安装(brew/curl/WinGet等)。新增bash历史补全、自定义快捷键、任务依赖追踪、搜索过滤等功能,并修复内存泄漏、崩溃及多项安全漏洞。老用户建议尽快迁移。
|
17天前
|
人工智能 测试技术 开发者
AI Coding后端开发实战:解锁AI辅助编程新范式
本文系统阐述了AI时代开发者如何高效协作AI Coding工具,强调破除认知误区、构建个人上下文管理体系,并精准判断AI输出质量。通过实战流程与案例,助力开发者实现从编码到架构思维的跃迁,成为人机协同的“超级开发者”。
1303 104