KES 数据库存储过程、函数、触发器实战

KES数据库存储过程、函数、触发器实战

这是《KES数据库从入门到精通》全书第四篇内容,直接带你扎进数据库可编程开发的核心地带。我会用项目里最接地气、最实用的写法,把存储过程、函数、触发器讲得明明白白,内容完全贴合KES官方语法,还兼容Oracle风格,所有案例都来自政务、金融真实系统,看完就能直接上手写企业级代码。


一、📘 本章学习导读

1.1 学习目标

  1. 搞懂存储过程、函数、触发器到底干嘛用,分别适合用在什么场景
  2. 吃透KES PL/SQL语法结构,能独立写、独立调、独立排错
  3. 能用存储过程搞定复杂业务逻辑、批量数据处理、报表统计
  4. 能用函数封装通用逻辑,实现查询、计算、数据脱敏等复用效果
  5. 能用触发器做自动日志、自动更新、数据校验、级联操作
  6. 躲开存储过程滥用、触发器死循环、性能踩坑等常见问题

1.2 本章重点

  • PL/SQL基础语法:变量、判断、循环、异常处理
  • 存储过程:创建、调用、参数、事务、批量处理
  • 函数:标量函数、表函数、权限与调用方式
  • 触发器:DML触发器、触发时机、触发字段、常用业务场景
  • 真实业务:自动审计、自动计算、自动流水、数据校验完整案例

二、⌨️ KES PL/SQL编程基础

想写好存储过程、函数、触发器,PL/SQL是绕不开的基本功。KES对Oracle PL/SQL兼容性特别高,写法几乎一模一样,上手特别快。

2.1 什么是PL/SQL

说白了,它就是数据库里的可编程语言,能写判断、循环、异常处理、事务控制,把复杂业务直接放在数据库里跑。

2.2 基础结构

sql 复制代码
DECLARE
  -- 声明变量
  v_name VARCHAR2(50);
BEGIN
  -- 执行逻辑
  v_name := '测试';
EXCEPTION
  -- 异常处理
  WHEN OTHERS THEN
    DBMS_OUTPUT.PUT_LINE('出错了:'||SQLERRM);
END;
/

2.3 变量与赋值

sql 复制代码
DECLARE
  v_uid INT := 1001;
  v_username VARCHAR2(50);
BEGIN
  SELECT username INTO v_username FROM t_user WHERE uid = v_uid;
  DBMS_OUTPUT.PUT_LINE('用户名:'||v_username);
END;
/

2.4 判断语句 IF-ELSIF-ELSE

sql 复制代码
DECLARE
  v_score INT := 90;
BEGIN
  IF v_score >= 90 THEN
    DBMS_OUTPUT.PUT_LINE('优秀');
  ELSIF v_score >= 60 THEN
    DBMS_OUTPUT.PUT_LINE('及格');
  ELSE
    DBMS_OUTPUT.PUT_LINE('不及格');
  END IF;
END;
/

2.5 循环语句 LOOP / WHILE / FOR

sql 复制代码
-- FOR循环最常用
DECLARE
BEGIN
  FOR i IN 1..10 LOOP
    DBMS_OUTPUT.PUT_LINE('循环:'||i);
  END LOOP;
END;
/

2.6 异常处理

💡 企业级代码一定要加异常处理,不然一报错就直接中断业务,特别危险。


三、📦 存储过程 PROCEDURE:企业级业务封装神器

存储过程就是把一段SQL逻辑打包存在数据库里,写一次就能到处调用,性能比程序多次请求好太多。

3.1 存储过程的优势

💡 减少网络交互,一次调用跑完所有逻辑

💡 安全性高,只开放调用权限,不暴露表结构

💡 复用性强,多处业务共用一套逻辑

💡 支持事务、批量、循环、复杂计算

3.2 创建存储过程(基础版)

sql 复制代码
-- 根据用户ID查询用户名
CREATE OR REPLACE PROCEDURE proc_get_username(
  p_uid IN INT,
  p_username OUT VARCHAR2
) AS
BEGIN
  SELECT username INTO p_username
  FROM t_user
  WHERE uid = p_uid;
EXCEPTION
  WHEN NO_DATA_FOUND THEN
    p_username := '无此用户';
  WHEN OTHERS THEN
    p_username := '查询异常';
END;
/

3.3 调用存储过程

sql 复制代码
-- 调用
DECLARE
  v_name VARCHAR2(50);
BEGIN
  proc_get_username(1001, v_name);
  DBMS_OUTPUT.PUT_LINE('结果:'||v_name);
END;
/

3.4 带事务的存储过程(生产常用)

sql 复制代码
-- 创建订单+扣库存:事务保证要么都成功,要么都回滚
CREATE OR REPLACE PROCEDURE proc_create_order(
  p_uid INT,
  p_gid INT,
  p_num INT,
  p_result OUT VARCHAR2
) AS
  v_price NUMERIC(10,2);
BEGIN
  -- 查单价
  SELECT price INTO v_price FROM t_goods WHERE gid = p_gid;
  -- 插入订单
  INSERT INTO t_order(oid,user_id,total_price)
  VALUES(seq_order_oid.NEXTVAL, p_uid, v_price*p_num);
  -- 扣库存
  UPDATE t_goods SET stock = stock - p_num WHERE gid = p_gid;
  -- 提交
  COMMIT;
  p_result := '成功';
EXCEPTION
  WHEN OTHERS THEN
    ROLLBACK;
    p_result := '失败:'||SQLERRM;
END;
/

3.5 批量插入存储过程

sql 复制代码
-- 批量造测试数据
CREATE OR REPLACE PROCEDURE proc_batch_insert_user(
  p_count INT
) AS
BEGIN
  FOR i IN 1..p_count LOOP
    INSERT INTO t_user(uid,username)
    VALUES(seq_user_uid.NEXTVAL, 'user'||i);
  END LOOP;
  COMMIT;
END;
/

3.6 删除存储过程

sql 复制代码
DROP PROCEDURE IF EXISTS proc_get_username;

四、⚙️ 函数 FUNCTION:通用逻辑复用工具

函数和存储过程长得很像,但必须有返回值,特别适合写通用计算、脱敏、格式转换、查询这类复用逻辑。

4.1 标量函数(返回单个值)

sql 复制代码
-- 根据ID获取用户名
CREATE OR REPLACE FUNCTION func_get_username(p_uid INT)
RETURN VARCHAR2 AS
  v_username VARCHAR2(50);
BEGIN
  SELECT username INTO v_username FROM t_user WHERE uid = p_uid;
  RETURN v_username;
EXCEPTION
  WHEN OTHERS THEN
    RETURN '未知';
END;
/

4.2 调用函数

sql 复制代码
-- 查询里直接用
SELECT func_get_username(1001) FROM DUAL;
-- 结合业务表
SELECT oid, func_get_username(user_id) FROM t_order;

4.3 脱敏函数(政务/金融必备)

sql 复制代码
-- 手机号脱敏
CREATE OR REPLACE FUNCTION func_mask_phone(p_phone VARCHAR2)
RETURN VARCHAR2 AS
BEGIN
  RETURN SUBSTR(p_phone,1,3)||'****'||SUBSTR(p_phone,8);
END;
/

4.4 删除函数

sql 复制代码
DROP FUNCTION IF EXISTS func_get_username;

五、⚡ 触发器 TRIGGER:自动执行的"隐形助手"

触发器就是表发生INSERT/UPDATE/DELETE时自动触发的代码,不用程序手动调用,数据库自己就会跑。

5.1 触发器典型场景

💡 记录操作日志(谁、什么时候、改了什么)

💡 自动填充字段(创建时间、更新时间)

💡 数据校验(禁止删除、禁止超卖、禁止负数)

💡 级联更新/同步数据

5.2 触发器基础结构

sql 复制代码
CREATE OR REPLACE TRIGGER 触发器名
BEFORE/AFTER INSERT/UPDATE/DELETE ON 表名
FOR EACH ROW -- 行级触发
BEGIN
  -- 触发逻辑
END;
/

5.3 实战1:自动填充更新时间

sql 复制代码
CREATE OR REPLACE TRIGGER tri_user_update_time
BEFORE UPDATE ON t_user
FOR EACH ROW
BEGIN
  :NEW.update_time := SYSDATE;
END;
/

5.4 实战2:记录操作日志

sql 复制代码
-- 先建日志表
CREATE TABLE t_user_log(
  id INT PRIMARY KEY,
  opt_type VARCHAR2(10),
  opt_uid INT,
  opt_time DATE DEFAULT SYSDATE
);
-- 触发器记录日志
CREATE OR REPLACE TRIGGER tri_user_log
AFTER INSERT OR UPDATE OR DELETE ON t_user
FOR EACH ROW
BEGIN
  IF INSERTING THEN
    INSERT INTO t_user_log(id,opt_type,opt_uid)
    VALUES(seq_log_id.NEXTVAL,'INSERT',:NEW.uid);
  ELSIF UPDATING THEN
    INSERT INTO t_user_log(id,opt_type,opt_uid)
    VALUES(seq_log_id.NEXTVAL,'UPDATE',:NEW.uid);
  ELSIF DELETING THEN
    INSERT INTO t_user_log(id,opt_type,opt_uid)
    VALUES(seq_log_id.NEXTVAL,'DELETE',:OLD.uid);
  END IF;
END;
/

5.5 实战3:数据校验(禁止负库存)

sql 复制代码
CREATE OR REPLACE TRIGGER tri_goods_stock
BEFORE UPDATE OF stock ON t_goods
FOR EACH ROW
BEGIN
  IF :NEW.stock < 0 THEN
    RAISE_APPLICATION_ERROR(-20001,'库存不能为负数');
  END IF;
END;
/

5.6 删除触发器

sql 复制代码
DROP TRIGGER IF EXISTS tri_user_update_time;

六、📝 真实企业案例:政务用户注册+审计一体化

这是政务服务平台真实上线的案例,用存储过程+函数+触发器,把注册、校验、脱敏、审计整套流程打通,直接就能落地用。

6.1 业务需求

  • 用户注册自动校验手机号、身份证
  • 自动脱敏敏感信息
  • 自动记录操作日志
  • 自动生成唯一ID
  • 事务保证数据安全

6.2 完整代码(可直接运行)

sql 复制代码
-- 1. 序列
CREATE SEQUENCE seq_user_uid START WITH 100001;
CREATE SEQUENCE seq_log_id START WITH 1;
-- 2. 用户表
CREATE TABLE t_gov_user(
  uid INT PRIMARY KEY,
  username VARCHAR2(50) NOT NULL,
  phone VARCHAR2(11) NOT NULL,
  id_card VARCHAR2(18) NOT NULL,
  create_time DATE DEFAULT SYSDATE,
  update_time DATE
);
-- 3. 日志表
CREATE TABLE t_gov_user_log(
  id INT PRIMARY KEY,
  opt_type VARCHAR2(10),
  opt_uid INT,
  opt_time DATE DEFAULT SYSDATE
);
-- 4. 手机号脱敏函数
CREATE OR REPLACE FUNCTION func_mask(p_str VARCHAR2)
RETURN VARCHAR2 AS
BEGIN
  RETURN SUBSTR(p_str,1,3)||'****'||SUBSTR(p_str,8);
END;
/
-- 5. 注册存储过程
CREATE OR REPLACE PROCEDURE proc_gov_user_reg(
  p_username VARCHAR2,
  p_phone VARCHAR2,
  p_id_card VARCHAR2,
  p_result OUT VARCHAR2
) AS
BEGIN
  -- 插入用户
  INSERT INTO t_gov_user(uid,username,phone,id_card)
  VALUES(seq_user_uid.NEXTVAL, p_username, p_phone, p_id_card);
  COMMIT;
  p_result := '注册成功';
EXCEPTION
  WHEN OTHERS THEN
    ROLLBACK;
    p_result := '失败:'||SQLERRM;
END;
/
-- 6. 自动更新时间触发器
CREATE OR REPLACE TRIGGER tri_gov_user_update
BEFORE UPDATE ON t_gov_user
FOR EACH ROW
BEGIN
  :NEW.update_time := SYSDATE;
END;
/
-- 7. 自动日志触发器
CREATE OR REPLACE TRIGGER tri_gov_user_log
AFTER INSERT ON t_gov_user
FOR EACH ROW
BEGIN
  INSERT INTO t_gov_user_log(id,opt_type,opt_uid)
  VALUES(seq_log_id.NEXTVAL,'REG',:NEW.uid);
END;
/
-- 8. 调用注册
DECLARE
  v_res VARCHAR2(100);
BEGIN
  proc_gov_user_reg('张三','13800138000','110101199001011234',v_res);
  DBMS_OUTPUT.PUT_LINE(v_res);
END;
/
-- 9. 查询脱敏数据
SELECT uid,username,func_mask(phone) FROM t_gov_user;

6.3 案例效果

✅ 存储过程实现注册,事务安全可靠

✅ 函数实现脱敏,符合安全规范

✅ 触发器自动记录日志,不用程序插手

✅ 完全兼容Oracle,迁移零成本

✅ 政务系统可直接上线使用


七、⚠️ 高频错误与避坑指南

  1. 存储过程写得太复杂
    逻辑超过200行最好拆分开,不然难维护、难调试。
  2. 触发器滥用
    一张表别超过2个触发器,很容易死循环、拖慢性能。
  3. 触发器里写事务
    触发器不能用COMMIT/ROLLBACK,一用就报错。
  4. 函数里写DML
    标量函数不能做增删改,只能查询和计算。
  5. 不处理异常
    生产环境必须加EXCEPTION,不然一出错就崩。
  6. 存储过程返回大量数据
    大数据量用分页或临时表,别一次性全返回。

八、✅ 本章总结与下一步学习计划

学完这一章,你已经拿下KES可编程开发核心能力,能独立完成企业级存储过程、函数、触发器开发。

你已经掌握:

  • PL/SQL基础语法:变量、判断、循环、异常
  • 存储过程:业务封装、事务、批量处理
  • 函数:通用逻辑、脱敏、查询复用
  • 触发器:自动日志、校验、填充、审计
  • 政务系统真实一体化案例
相关推荐
m0_596406372 小时前
mysql如何配置审计日志输出_mysql audit_log_format设置
jvm·数据库·python
geBR OTTE2 小时前
flask后端开发(8):Flask连接MySQL数据库+ORM增删改查
数据库·mysql·flask
识君啊2 小时前
中小厂数据库事务高频面试题
java·数据库·mysql·隔离级别·数据库事务·acid
2301_816660212 小时前
Bootstrap框架的最小宽度限制是多少
jvm·数据库·python
OtIo TALL2 小时前
SQL-触发器(trigger)的详解以及代码演示
服务器·数据库·sql
lwx572802 小时前
MySQL 数据库自动化备份脚本:从入门到生产实践
数据库·后端
abc123456sdggfd2 小时前
HTML5中Vuex持久化插件中WebStorage的底层配置
jvm·数据库·python
pele2 小时前
Go语言如何发GET请求_Go语言HTTP GET请求教程【总结】
jvm·数据库·python