未记录事实核验 · 状态:维护中
记录仅说明核验时间与维护状态,不保证适用于所有环境;使用前请留意正文中的版本、适用条件与风险提示。
warning 适用边界与维护说明
本文适合学习PL/SQL块、变量、分支、循环及存储过程IN/OUT参数,不依赖业务表的金额、字符串、日期、CASE和存款增长示例可作为独立练习,但需自行配置运行客户端及DBMS_OUTPUT显示。客户查询、统计和参数过程缺少customer_info、account_info的建表定义、约束、测试数据及创建过程所需权限说明,不能据本文直接复现全部练习。FOR循环仅为客户1001—1010各任取一个活跃账户,输出中的“账户”编号实际是客户ID,且未排序的ROWNUM = 1不保证选取结果固定,不能用于完整计息或生产报表;客户等级图与代码、VIP阈值注释与查询条件及部分状态标签也存在口径差异,应以实际条件和字段来源理解示例。银行等级、利率和服务规则均为教学假设,transfer_money仅展示调用形式,不提供转账实现;20-50%是未测量的优化目标,不是性能收益结论。本次仅作全文结构审阅,未执行代码、渲染图表或作外部事实核验。
Oracle存储过程基础 - 零基础入门教程
🎯 什么是存储过程(用最简单的话说)
存储过程就像是一个"程序包",把很多SQL语句和逻辑打包在一起,存在数据库里。就像你把常用的操作写成一个脚本,需要的时候直接运行,而不用每次都重复写同样的代码。
TIP 💡 生活中的类比
存储过程就像咖啡机的"一键制作"功能:
- 普通方式:每次都要量豆子、磨粉、加水、调温度...
- 存储过程:按一个按钮,咖啡机自动完成所有步骤
银行的存储过程也是这样:把复杂的业务流程"一键化"!
🏦 银行转账的教学示例场景
让我们用一个具体的银行转账例子来理解存储过程的价值:
%%{init: {"theme":"base","themeVariables":{"fontFamily":"PingFang SC, -apple-system, Source Han Serif SC, serif","fontSize":"16px","background":"#FDFBF5","primaryColor":"#FDFBF5","primaryTextColor":"#2D2A26","primaryBorderColor":"#6E6A60","secondaryColor":"#E8EFE8","secondaryBorderColor":"#8FA890","secondaryTextColor":"#2D2A26","tertiaryColor":"#FDFBF5","tertiaryBorderColor":"#9A9488","tertiaryTextColor":"#6E6A60","lineColor":"#6E6A60","textColor":"#2D2A26","mainBkg":"#FDFBF5","nodeBorder":"#2D5A3D","clusterBkg":"#F3EEDF","clusterBorder":"#9A9488","edgeLabelBackground":"#FDFBF5"},"flowchart":{"htmlLabels":true,"curve":"basis","padding":20,"nodeSpacing":60,"rankSpacing":70,"useMaxWidth":false},"themeCSS":".node rect, .node polygon, .node circle { stroke-width: 1.5px; } .edgePath .path { stroke-width: 1.5px; } .cluster-label .nodeLabel, .cluster-label foreignObject { color: #2D2A26 !important; } .edgeLabel, .edgeLabel foreignObject { color: #2D2A26 !important; background-color: #FDFBF5; } .edgeLabel rect { fill: #FDFBF5; }"}}%%
flowchart TD
A[用户发起转账请求]
B{检查转出账户余额}
B -->|余额不足| C[返回错误信息]
A --> B -->|余额充足| D[从转出账户扣钱]
D --> E[往转入账户加钱]
E --> F[记录交易流水]
F --> G[发送成功通知]
style A fill:#e1f5fe
style G fill:#e8f5e8
style C fill:#ffebee
如果没有存储过程,每次转账都需要:
- 写SQL检查余额
- 写SQL扣钱
- 写SQL加钱
- 写SQL记录流水
- 处理各种错误情况
有了存储过程,只需要:
EXEC transfer_money('账户A', '账户B', 1000);
WARNING ⚠️ 重要提醒
银行业务对数据一致性要求极高!转账过程中任何一步出错,都必须回滚所有操作。在正确设计事务边界、异常传播和回滚处理的前提下,转账操作才能实现"要么全成功,要么全失败",这叫做事务的原子性;仅封装为存储过程并不会自动提供这一保证。
📈 存储过程的核心优势
%%{init: {"theme":"base","themeVariables":{"fontFamily":"PingFang SC, -apple-system, Source Han Serif SC, serif","fontSize":"16px","background":"#FDFBF5","primaryColor":"#FDFBF5","primaryTextColor":"#2D2A26","primaryBorderColor":"#6E6A60","secondaryColor":"#E8EFE8","secondaryBorderColor":"#8FA890","secondaryTextColor":"#2D2A26","tertiaryColor":"#FDFBF5","tertiaryBorderColor":"#9A9488","tertiaryTextColor":"#6E6A60","lineColor":"#6E6A60","textColor":"#2D2A26","mainBkg":"#FDFBF5","nodeBorder":"#2D5A3D","clusterBkg":"#F3EEDF","clusterBorder":"#9A9488","edgeLabelBackground":"#FDFBF5"},"flowchart":{"htmlLabels":true,"curve":"basis","padding":20,"nodeSpacing":60,"rankSpacing":70,"useMaxWidth":false},"themeCSS":".node rect, .node polygon, .node circle { stroke-width: 1.5px; } .edgePath .path { stroke-width: 1.5px; } .cluster-label .nodeLabel, .cluster-label foreignObject { color: #2D2A26 !important; } .edgeLabel, .edgeLabel foreignObject { color: #2D2A26 !important; background-color: #FDFBF5; } .edgeLabel rect { fill: #FDFBF5; }"}}%%
mindmap
root((存储过程优势))
性能优势
PL/SQL编译后执行
减少网络传输
内部SQL可复用执行计划
安全优势
隐藏业务逻辑
配合绑定变量降低SQL注入风险
权限精确控制
维护优势
代码复用
集中管理
版本控制
业务优势
配合事务边界与回滚设计
复杂逻辑封装
错误处理统一
INFO 📊 性能对比
- 普通SQL:可复用已解析的游标和执行计划,并非每次都必须重新完成全部解析工作
- 存储过程:可封装服务器端逻辑,减少多次调用带来的网络往返,但内部SQL仍需执行
- 性能提升:若将20-50%设为示例性的优化目标,需要在明确的工作负载与对照条件下测试;本文未提供测量依据,不能将该区间视为通常可获得的收益
📚 第一步:认识PL/SQL语言
PL/SQL是Oracle的专属编程语言,全称是"Procedural Language extension to SQL"(SQL的过程化语言扩展)。
NOTE 🤔 为什么需要PL/SQL?
SQL很强大,但它是声明式的(告诉数据库你要什么),不能处理复杂的业务逻辑。 PL/SQL是过程化的(告诉数据库怎么做),可以实现条件判断、循环、异常处理等复杂功能。
🏗️ PL/SQL的三层结构
PL/SQL代码块就像一栋三层楼的建筑:
%%{init: {"theme":"base","themeVariables":{"fontFamily":"PingFang SC, -apple-system, Source Han Serif SC, serif","fontSize":"16px","background":"#FDFBF5","primaryColor":"#FDFBF5","primaryTextColor":"#2D2A26","primaryBorderColor":"#6E6A60","secondaryColor":"#E8EFE8","secondaryBorderColor":"#8FA890","secondaryTextColor":"#2D2A26","tertiaryColor":"#FDFBF5","tertiaryBorderColor":"#9A9488","tertiaryTextColor":"#6E6A60","lineColor":"#6E6A60","textColor":"#2D2A26","mainBkg":"#FDFBF5","nodeBorder":"#2D5A3D","clusterBkg":"#F3EEDF","clusterBorder":"#9A9488","edgeLabelBackground":"#FDFBF5"},"flowchart":{"htmlLabels":true,"curve":"basis","padding":20,"nodeSpacing":60,"rankSpacing":70,"useMaxWidth":false},"themeCSS":".node rect, .node polygon, .node circle { stroke-width: 1.5px; } .edgePath .path { stroke-width: 1.5px; } .cluster-label .nodeLabel, .cluster-label foreignObject { color: #2D2A26 !important; } .edgeLabel, .edgeLabel foreignObject { color: #2D2A26 !important; background-color: #FDFBF5; } .edgeLabel rect { fill: #FDFBF5; }"}}%%
flowchart TD
B[Process]
subgraph "PL/SQL代码块结构"
A["📝 DECLARE 声明区<br/>(准备工具)"]
B["⚡ BEGIN...END 执行区<br/>(干活区域)"]
C["🚨 EXCEPTION 异常处理区<br/>(错误处理)"]
end
A --> B --> C
style A fill:#fff3e0
style B fill:#e8f5e8
style C fill:#ffebee
详细说明:
| 区域 | 作用 | 必需性 | 类比 |
|---|---|---|---|
| DECLARE | 定义变量、常量、游标等 | 可选 | 厨师准备食材和工具 |
| BEGIN...END | 实际执行的代码逻辑 | 必需 | 厨师按菜谱做菜 |
| EXCEPTION | 处理运行时错误 | 可选 | 厨师处理意外情况 |
🎯 第一个PL/SQL程序
让我们通过一个银行查询客户信息的例子来理解PL/SQL:
-- 🎯 查询客户信息的完整示例
DECLARE
-- 📝 声明区:准备我们需要的"工具"
v_customer_name VARCHAR2(100); -- 客户姓名变量
v_phone VARCHAR2(20); -- 电话号码变量
v_balance NUMBER(15,2); -- 账户余额变量
BEGIN
-- ⚡ 执行区:开始干活
DBMS_OUTPUT.PUT_LINE('=== 开始查询客户信息 ===');
-- 查询客户基本信息
SELECT customer_name, phone
INTO v_customer_name, v_phone
FROM customer_info
WHERE customer_id = 1001;
-- 查询客户账户余额
SELECT SUM(balance)
INTO v_balance
FROM account_info
WHERE customer_id = 1001;
-- 显示查询结果
DBMS_OUTPUT.PUT_LINE('客户姓名:' || v_customer_name);
DBMS_OUTPUT.PUT_LINE('联系电话:' || NVL(v_phone, '未登记'));
DBMS_OUTPUT.PUT_LINE('账户余额:' || TO_CHAR(v_balance, '999,999,999.99') || ' 元');
DBMS_OUTPUT.PUT_LINE('=== 查询完成 ===');
EXCEPTION
-- 🚨 异常处理区:出错了怎么办
WHEN NO_DATA_FOUND THEN
DBMS_OUTPUT.PUT_LINE('❌ 错误:找不到客户ID为1001的客户');
WHEN TOO_MANY_ROWS THEN
DBMS_OUTPUT.PUT_LINE('❌ 错误:找到多个客户记录,数据异常');
WHEN OTHERS THEN
DBMS_OUTPUT.PUT_LINE('❌ 系统错误:' || SQLERRM);
END;
/
TIP 💡 代码解读技巧
- INTO子句:把查询结果"装进"变量里
- ||操作符:用来连接字符串,像胶水一样
- NVL函数:如果值是NULL,就用默认值替代
- TO_CHAR函数:把数字格式化成带千分位的字符串
- 最后的"/":在SQL*Plus或SQL Developer脚本运行模式中提交并执行缓冲区中的PL/SQL块;它是客户端命令,不是PL/SQL语法的一部分
程序执行流程:
%%{init: {"theme":"base","themeVariables":{"fontFamily":"PingFang SC, -apple-system, Source Han Serif SC, serif","fontSize":"16px","background":"#FDFBF5","primaryColor":"#FDFBF5","primaryTextColor":"#2D2A26","primaryBorderColor":"#6E6A60","secondaryColor":"#E8EFE8","secondaryBorderColor":"#8FA890","secondaryTextColor":"#2D2A26","tertiaryColor":"#FDFBF5","tertiaryBorderColor":"#9A9488","tertiaryTextColor":"#6E6A60","lineColor":"#6E6A60","textColor":"#2D2A26","mainBkg":"#FDFBF5","nodeBorder":"#2D5A3D","clusterBkg":"#F3EEDF","clusterBorder":"#9A9488","edgeLabelBackground":"#FDFBF5"},"flowchart":{"htmlLabels":true,"curve":"basis","padding":20,"nodeSpacing":60,"rankSpacing":70,"useMaxWidth":false}}%%
sequenceDiagram
participant U as 用户
participant O as Oracle数据库
participant C as customer_info表
participant A as account_info表
U->>O: 执行PL/SQL程序
O->>O: 声明变量
O->>C: 查询客户信息
C-->>O: 返回姓名和电话
O->>A: 查询账户余额
A-->>O: 返回余额总和
O->>O: 格式化输出
O-->>U: 显示结果
🧰 第二步:学会使用变量(数据的容器)
变量就像是各种尺寸的盒子,用来临时存放不同类型的数据。在银行系统中,我们需要处理各种数据:金额、姓名、日期、状态等。
📋 Oracle数据类型详解
%%{init: {"theme":"base","themeVariables":{"fontFamily":"PingFang SC, -apple-system, Source Han Serif SC, serif","fontSize":"16px","background":"#FDFBF5","primaryColor":"#FDFBF5","primaryTextColor":"#2D2A26","primaryBorderColor":"#6E6A60","secondaryColor":"#E8EFE8","secondaryBorderColor":"#8FA890","secondaryTextColor":"#2D2A26","tertiaryColor":"#FDFBF5","tertiaryBorderColor":"#9A9488","tertiaryTextColor":"#6E6A60","lineColor":"#6E6A60","textColor":"#2D2A26","mainBkg":"#FDFBF5","nodeBorder":"#2D5A3D","clusterBkg":"#F3EEDF","clusterBorder":"#9A9488","edgeLabelBackground":"#FDFBF5"},"flowchart":{"htmlLabels":true,"curve":"basis","padding":20,"nodeSpacing":60,"rankSpacing":70,"useMaxWidth":false},"themeCSS":".node rect, .node polygon, .node circle { stroke-width: 1.5px; } .edgePath .path { stroke-width: 1.5px; } .cluster-label .nodeLabel, .cluster-label foreignObject { color: #2D2A26 !important; } .edgeLabel, .edgeLabel foreignObject { color: #2D2A26 !important; background-color: #FDFBF5; } .edgeLabel rect { fill: #FDFBF5; }"}}%%
flowchart TD
subgraph "Oracle数据类型家族"
A[数字类型<br/>NUMBER, INTEGER]
B[字符类型<br/>VARCHAR2, CHAR]
C[日期类型<br/>DATE, TIMESTAMP]
D[布尔类型<br/>BOOLEAN]
E[大对象类型<br/>CLOB, BLOB]
end
style A fill:#e3f2fd
style B fill:#f3e5f5
style C fill:#e8f5e8
style D fill:#fff8e1
style E fill:#fce4ec
💰 数字类型变量
在银行系统中,金额处理是最重要的!
DECLARE
-- 💰 银行金额相关变量
v_balance NUMBER(15,2) := 1000000.50; -- 账户余额(15位数,2位小数)
v_interest_rate NUMBER(5,4) := 0.0325; -- 利率(5位数,4位小数)
v_transaction_count INTEGER := 0; -- 交易笔数(整数)
v_customer_id NUMBER(10) := 1001; -- 客户ID
-- 计算利息
v_interest_amount NUMBER(15,2); -- 利息金额
BEGIN
-- 计算年利息
v_interest_amount := v_balance * v_interest_rate;
DBMS_OUTPUT.PUT_LINE('=== 利息计算结果 ===');
DBMS_OUTPUT.PUT_LINE('本金:' || TO_CHAR(v_balance, '999,999,999.99') || ' 元');
DBMS_OUTPUT.PUT_LINE('利率:' || TO_CHAR(v_interest_rate * 100, '90.99') || '%');
DBMS_OUTPUT.PUT_LINE('年利息:' || TO_CHAR(v_interest_amount, '999,999,999.99') || ' 元');
END;
/
WARNING ⚠️ 金额处理的重要提醒
- 本例使用NUMBER(15,2):总精度为15位、小数位数为2,可容纳千万级金额;实际精度、币种小数位和计算中间精度应按业务要求选择
- 区分数值类型:Oracle FLOAT是NUMBER的子类型,以二进制位数声明精度,不同于采用二进制浮点表示的BINARY_FLOAT和BINARY_DOUBLE;金额通常使用明确精度和小数位的NUMBER,并按业务要求设计中间计算精度与舍入规则
- 格式化显示:使用TO_CHAR函数格式化金额显示
📝 字符类型变量
DECLARE
-- 📝 客户信息相关变量
v_customer_name VARCHAR2(100); -- 客户姓名(可变长度)
v_id_card CHAR(18); -- 身份证号(固定18位)
v_bank_code CHAR(4) := 'ICBC'; -- 银行代码(固定长度)
v_account_type VARCHAR2(20); -- 账户类型
v_description VARCHAR2(500); -- 备注说明
BEGIN
-- 字符串操作示例
v_customer_name := '张三';
v_account_type := '储蓄账户';
v_description := '这是一个' || v_account_type || ',客户姓名:' || v_customer_name;
DBMS_OUTPUT.PUT_LINE('客户信息:' || v_description);
DBMS_OUTPUT.PUT_LINE('姓名长度:' || LENGTH(v_customer_name) || ' 个字符');
DBMS_OUTPUT.PUT_LINE('银行代码:' || v_bank_code);
END;
/
INFO 📚 VARCHAR2 vs CHAR 的区别
- VARCHAR2:可变长度,节省空间,适合存储姓名、地址等
- CHAR:固定长度,会补空格,适合存储编码、状态等
银行应用场景:
VARCHAR2:客户姓名、地址、备注CHAR:银行代码、账户状态、性别标识
📅 日期时间类型
银行业务中,时间记录至关重要!
DECLARE
-- 📅 时间相关变量
v_today DATE := SYSDATE; -- 当前日期时间
v_birthday DATE := DATE '1990-05-15'; -- 生日
v_account_open_date DATE; -- 开户日期
v_last_trans_time TIMESTAMP; -- 最后交易时间(精确到毫秒)
-- 计算变量
v_age NUMBER(3); -- 年龄
v_account_years NUMBER(5,2); -- 开户年数
BEGIN
-- 设置开户日期为3年前
v_account_open_date := ADD_MONTHS(SYSDATE, -36);
v_last_trans_time := SYSTIMESTAMP - INTERVAL '5' DAY; -- 5天前,使用时间间隔运算
-- 计算年龄
v_age := FLOOR(MONTHS_BETWEEN(SYSDATE, v_birthday) / 12);
-- 计算开户年数
v_account_years := MONTHS_BETWEEN(SYSDATE, v_account_open_date) / 12;
DBMS_OUTPUT.PUT_LINE('=== 时间信息 ===');
DBMS_OUTPUT.PUT_LINE('今天:' || TO_CHAR(v_today, 'YYYY-MM-DD HH24:MI:SS'));
DBMS_OUTPUT.PUT_LINE('生日:' || TO_CHAR(v_birthday, 'YYYY-MM-DD'));
DBMS_OUTPUT.PUT_LINE('年龄:' || v_age || ' 岁');
DBMS_OUTPUT.PUT_LINE('开户日期:' || TO_CHAR(v_account_open_date, 'YYYY-MM-DD'));
DBMS_OUTPUT.PUT_LINE('开户年数:' || ROUND(v_account_years, 1) || ' 年');
DBMS_OUTPUT.PUT_LINE('最后交易:' || TO_CHAR(v_last_trans_time, 'YYYY-MM-DD HH24:MI:SS.FF3'));
END;
/
TIP 💡 日期处理技巧
常用日期函数:
SYSDATE:当前日期时间SYSTIMESTAMP:当前时间戳(包含毫秒)ADD_MONTHS(日期, 月数):增加月份MONTHS_BETWEEN(日期1, 日期2):计算月份差TO_CHAR(日期, 格式):格式化日期显示
🎯 聪明的变量定义方法
Oracle提供了一个超级实用的功能:让变量自动使用表字段的数据类型!
DECLARE
-- 🎯 使用%TYPE自动获取字段类型
v_customer_id customer_info.customer_id%TYPE;
v_customer_name customer_info.customer_name%TYPE;
v_balance account_info.balance%TYPE;
v_create_time customer_info.create_time%TYPE;
-- 🎯 使用%ROWTYPE获取整行结构
v_customer_rec customer_info%ROWTYPE;
BEGIN
-- 给单个变量赋值
v_customer_id := 1001;
-- 查询单个字段
SELECT customer_name, create_time
INTO v_customer_name, v_create_time
FROM customer_info
WHERE customer_id = v_customer_id;
-- 查询整行数据
SELECT * INTO v_customer_rec
FROM customer_info
WHERE customer_id = v_customer_id;
DBMS_OUTPUT.PUT_LINE('=== 单字段查询结果 ===');
DBMS_OUTPUT.PUT_LINE('客户姓名:' || v_customer_name);
DBMS_OUTPUT.PUT_LINE('创建时间:' || TO_CHAR(v_create_time, 'YYYY-MM-DD'));
DBMS_OUTPUT.PUT_LINE('=== 整行查询结果 ===');
DBMS_OUTPUT.PUT_LINE('客户ID:' || v_customer_rec.customer_id);
DBMS_OUTPUT.PUT_LINE('客户姓名:' || v_customer_rec.customer_name);
DBMS_OUTPUT.PUT_LINE('电话:' || NVL(v_customer_rec.phone, '未登记'));
EXCEPTION
WHEN NO_DATA_FOUND THEN
DBMS_OUTPUT.PUT_LINE('未找到客户信息');
END;
/
SUCCESS ✅ %TYPE和%ROWTYPE的优势
%TYPE的好处:
- 自动适应表结构变化
- 避免数据类型不匹配错误
- 代码更易维护
%ROWTYPE的好处:
- 一次性获取整行数据
- 代码更简洁
- 适合需要多个字段的场景
🎮 第三步:程序流程控制
程序需要根据不同条件做不同的事情,这就需要用到流程控制语句。就像银行柜员根据不同情况采取不同操作一样。
🤔 条件判断:IF-THEN-ELSE
银行最常见的场景:根据客户余额判断服务等级
%%{init: {"theme":"base","themeVariables":{"fontFamily":"PingFang SC, -apple-system, Source Han Serif SC, serif","fontSize":"16px","background":"#FDFBF5","primaryColor":"#FDFBF5","primaryTextColor":"#2D2A26","primaryBorderColor":"#6E6A60","secondaryColor":"#E8EFE8","secondaryBorderColor":"#8FA890","secondaryTextColor":"#2D2A26","tertiaryColor":"#FDFBF5","tertiaryBorderColor":"#9A9488","tertiaryTextColor":"#6E6A60","lineColor":"#6E6A60","textColor":"#2D2A26","mainBkg":"#FDFBF5","nodeBorder":"#2D5A3D","clusterBkg":"#F3EEDF","clusterBorder":"#9A9488","edgeLabelBackground":"#FDFBF5"},"flowchart":{"htmlLabels":true,"curve":"basis","padding":20,"nodeSpacing":60,"rankSpacing":70,"useMaxWidth":false},"themeCSS":".node rect, .node polygon, .node circle { stroke-width: 1.5px; } .edgePath .path { stroke-width: 1.5px; } .cluster-label .nodeLabel, .cluster-label foreignObject { color: #2D2A26 !important; } .edgeLabel, .edgeLabel foreignObject { color: #2D2A26 !important; background-color: #FDFBF5; } .edgeLabel rect { fill: #FDFBF5; }"}}%%
flowchart TD
A[获取账户余额]
A --> B{余额 >= 100万?}
B -->|是| C[钻石客户服务]
B -->|否| D{余额 >= 50万?}
D -->|是| E[白金客户服务]
D -->|否| F{余额 >= 10万?}
F -->|是| G[金卡客户服务]
F -->|否| H[普通客户服务]
style C fill:#ffd700
style E fill:#c0c0c0
style G fill:#cd7f32
style H fill:#87ceeb
完整的客户等级判断程序:
-- 🏆 客户等级判断系统
DECLARE
v_customer_id NUMBER := 1001;
v_customer_name customer_info.customer_name%TYPE;
v_total_balance NUMBER(15,2) := 0;
v_customer_level VARCHAR2(20);
v_service_suggestion VARCHAR2(500);
BEGIN
DBMS_OUTPUT.PUT_LINE('=== 客户等级评估系统 ===');
-- 🔍 第1步:获取客户基本信息
SELECT customer_name INTO v_customer_name
FROM customer_info
WHERE customer_id = v_customer_id;
-- 🔍 第2步:计算客户总资产
SELECT NVL(SUM(balance), 0) INTO v_total_balance
FROM account_info
WHERE customer_id = v_customer_id
AND status = 'ACTIVE';
DBMS_OUTPUT.PUT_LINE('客户姓名:' || v_customer_name);
DBMS_OUTPUT.PUT_LINE('总资产:' || TO_CHAR(v_total_balance, '999,999,999.99') || ' 元');
DBMS_OUTPUT.PUT_LINE('');
-- 🏆 第3步:根据资产等级进行分类
IF v_total_balance >= 1000000 THEN
-- 钻石客户:100万以上
v_customer_level := '钻石客户';
v_service_suggestion := '享受专属理财经理一对一服务,VIP通道优先办理,专属理财产品推荐';
ELSIF v_total_balance >= 500000 THEN
-- 白金客户:50万-100万
v_customer_level := '白金客户';
v_service_suggestion := '享受理财经理服务,优先办理业务,高端理财产品推荐';
ELSIF v_total_balance >= 100000 THEN
-- 金卡客户:10万-50万
v_customer_level := '金卡客户';
v_service_suggestion := '享受专属客服热线,定期理财产品推荐,免部分手续费';
ELSIF v_total_balance >= 10000 THEN
-- 银卡客户:1万-10万
v_customer_level := '银卡客户';
v_service_suggestion := '享受网银优惠服务,基础理财产品推荐';
ELSE
-- 普通客户:1万以下
v_customer_level := '普通客户';
v_service_suggestion := '建议增加资产投入,享受更多银行优质服务';
END IF;
-- 🎯 第4步:输出评估结果
DBMS_OUTPUT.PUT_LINE('客户等级:' || v_customer_level);
DBMS_OUTPUT.PUT_LINE('服务建议:' || v_service_suggestion);
-- 📊 第5步:生成个性化建议
DBMS_OUTPUT.PUT_LINE('');
DBMS_OUTPUT.PUT_LINE('=== 个性化服务建议 ===');
IF v_customer_level IN ('钻石客户', '白金客户') THEN
DBMS_OUTPUT.PUT_LINE('🌟 高端客户专享:');
DBMS_OUTPUT.PUT_LINE(' • 私人银行服务');
DBMS_OUTPUT.PUT_LINE(' • 海外投资咨询');
DBMS_OUTPUT.PUT_LINE(' • 税务筹划建议');
ELSIF v_customer_level = '金卡客户' THEN
DBMS_OUTPUT.PUT_LINE('💎 金卡客户推荐:');
DBMS_OUTPUT.PUT_LINE(' • 定期存款优惠利率');
DBMS_OUTPUT.PUT_LINE(' • 基金定投服务');
DBMS_OUTPUT.PUT_LINE(' • 保险理财规划');
ELSE
DBMS_OUTPUT.PUT_LINE('📈 成长建议:');
DBMS_OUTPUT.PUT_LINE(' • 开通定期存款');
DBMS_OUTPUT.PUT_LINE(' • 考虑基金投资');
DBMS_OUTPUT.PUT_LINE(' • 关注理财讲座');
END IF;
EXCEPTION
WHEN NO_DATA_FOUND THEN
DBMS_OUTPUT.PUT_LINE('❌ 客户不存在或无有效账户');
WHEN OTHERS THEN
DBMS_OUTPUT.PUT_LINE('❌ 系统错误:' || SQLERRM);
END;
/
INFO 🎯 条件判断的最佳实践
IF语句结构:
IF 条件 THEN:如果条件成立ELSIF 条件 THEN:否则如果(可以有多个)ELSE:其他所有情况END IF:结束条件判断
注意事项:
- 条件从上到下依次检查
- 一旦匹配就执行,后面的不再检查
- 条件的顺序很重要!
🔄 CASE语句:更优雅的多分支判断
CASE语句是另一种处理多分支的方法,代码更简洁:
DECLARE
v_account_type VARCHAR2(20) := 'SAVINGS';
v_interest_rate NUMBER(5,4);
v_min_balance NUMBER(10,2);
v_service_fee NUMBER(8,2);
BEGIN
DBMS_OUTPUT.PUT_LINE('=== 账户类型信息查询 ===');
-- 🎯 使用CASE语句设置利率
v_interest_rate := CASE v_account_type
WHEN 'SAVINGS' THEN 0.0250 -- 储蓄账户 2.5%
WHEN 'CHECKING' THEN 0.0100 -- 支票账户 1.0%
WHEN 'TIME_DEPOSIT' THEN 0.0350 -- 定期存款 3.5%
WHEN 'VIP_SAVINGS' THEN 0.0300 -- VIP储蓄 3.0%
ELSE 0.0200 -- 默认利率 2.0%
END;
-- 🎯 使用CASE语句设置最低余额要求
v_min_balance := CASE
WHEN v_account_type = 'SAVINGS' THEN 100
WHEN v_account_type = 'CHECKING' THEN 500
WHEN v_account_type = 'TIME_DEPOSIT' THEN 1000
WHEN v_account_type = 'VIP_SAVINGS' THEN 50000
ELSE 0
END;
-- 🎯 使用CASE语句计算服务费
v_service_fee := CASE
WHEN v_account_type IN ('VIP_SAVINGS', 'TIME_DEPOSIT') THEN 0 -- 免费
WHEN v_account_type = 'SAVINGS' THEN 5 -- 储蓄账户 5元/月
WHEN v_account_type = 'CHECKING' THEN 10 -- 支票账户 10元/月
ELSE 8 -- 其他类型 8元/月
END;
-- 显示结果
DBMS_OUTPUT.PUT_LINE('账户类型:' || v_account_type);
DBMS_OUTPUT.PUT_LINE('年利率:' || TO_CHAR(v_interest_rate * 100, '90.99') || '%');
DBMS_OUTPUT.PUT_LINE('最低余额:' || TO_CHAR(v_min_balance, '999,999') || ' 元');
DBMS_OUTPUT.PUT_LINE('月服务费:' || TO_CHAR(v_service_fee, '999') || ' 元');
END;
/
🔁 循环语句:重复执行任务
银行经常需要批量处理数据,比如计算所有账户的利息:
📍 FOR循环:最简单的循环
-- 🔄 批量计算多个账户的利息
DECLARE
v_balance NUMBER(15,2);
v_interest NUMBER(15,2);
v_interest_rate CONSTANT NUMBER(5,4) := 0.0325; -- 年利率3.25%
v_total_interest NUMBER(15,2) := 0;
BEGIN
DBMS_OUTPUT.PUT_LINE('=== 批量利息计算 ===');
DBMS_OUTPUT.PUT_LINE('年利率:' || TO_CHAR(v_interest_rate * 100, '90.99') || '%');
DBMS_OUTPUT.PUT_LINE('');
-- 🔄 为客户ID 1001-1010各任取一个活跃账户计算示例利息,不代表全部账户计息
FOR account_id IN 1001..1010 LOOP
BEGIN
-- 查询账户余额
SELECT balance INTO v_balance
FROM account_info
WHERE customer_id = account_id
AND status = 'ACTIVE'
AND ROWNUM = 1; -- 只取第一个账户
-- 计算年利息
v_interest := v_balance * v_interest_rate;
v_total_interest := v_total_interest + v_interest;
-- 显示计算结果
DBMS_OUTPUT.PUT_LINE('账户 ' || account_id ||
':余额 ' || TO_CHAR(v_balance, '999,999,999.99') ||
' 元,利息 ' || TO_CHAR(v_interest, '999,999.99') || ' 元');
EXCEPTION
WHEN NO_DATA_FOUND THEN
DBMS_OUTPUT.PUT_LINE('账户 ' || account_id || ':无有效账户');
END;
END LOOP;
DBMS_OUTPUT.PUT_LINE('');
DBMS_OUTPUT.PUT_LINE('利息总计:' || TO_CHAR(v_total_interest, '999,999,999.99') || ' 元');
END;
/
📍 WHILE循环:条件控制的循环
-- 🎯 模拟存款增长计算
DECLARE
v_principal NUMBER(15,2) := 10000; -- 本金1万元
v_target_amount NUMBER(15,2) := 20000; -- 目标2万元
v_annual_rate NUMBER(5,4) := 0.0325; -- 年利率3.25%
v_current_amount NUMBER(15,2);
v_years NUMBER(3) := 0;
BEGIN
v_current_amount := v_principal;
DBMS_OUTPUT.PUT_LINE('=== 存款增长模拟 ===');
DBMS_OUTPUT.PUT_LINE('初始本金:' || TO_CHAR(v_principal, '999,999.99') || ' 元');
DBMS_OUTPUT.PUT_LINE('目标金额:' || TO_CHAR(v_target_amount, '999,999.99') || ' 元');
DBMS_OUTPUT.PUT_LINE('年利率:' || TO_CHAR(v_annual_rate * 100, '90.99') || '%');
DBMS_OUTPUT.PUT_LINE('');
-- 🔄 循环计算,直到达到目标金额
WHILE v_current_amount < v_target_amount LOOP
v_years := v_years + 1;
v_current_amount := v_current_amount * (1 + v_annual_rate);
DBMS_OUTPUT.PUT_LINE('第 ' || v_years || ' 年:' ||
TO_CHAR(v_current_amount, '999,999.99') || ' 元');
-- 安全机制:避免无限循环
IF v_years > 50 THEN
DBMS_OUTPUT.PUT_LINE('超过50年,停止计算');
EXIT;
END IF;
END LOOP;
DBMS_OUTPUT.PUT_LINE('');
IF v_current_amount >= v_target_amount THEN
DBMS_OUTPUT.PUT_LINE('🎉 达成目标!需要 ' || v_years || ' 年时间');
ELSE
DBMS_OUTPUT.PUT_LINE('⚠️ 未能在合理时间内达成目标');
END IF;
END;
/
TIP 💡 循环使用技巧
循环类型选择:
- FOR循环:已知循环次数时使用
- WHILE循环:根据条件决定是否继续
- LOOP...EXIT WHEN:更灵活的循环控制
注意事项:
- 始终确保有退出条件,避免无限循环
- 在数据库循环中处理大量数据时要小心性能
- 考虑使用批量操作替代单条记录循环
🏗️ 第四步:创建你的第一个存储过程
现在我们要把前面学的知识组合起来,创建真正的存储过程!
🎯 存储过程的基本语法
%%{init: {"theme":"base","themeVariables":{"fontFamily":"PingFang SC, -apple-system, Source Han Serif SC, serif","fontSize":"16px","background":"#FDFBF5","primaryColor":"#FDFBF5","primaryTextColor":"#2D2A26","primaryBorderColor":"#6E6A60","secondaryColor":"#E8EFE8","secondaryBorderColor":"#8FA890","secondaryTextColor":"#2D2A26","tertiaryColor":"#FDFBF5","tertiaryBorderColor":"#9A9488","tertiaryTextColor":"#6E6A60","lineColor":"#6E6A60","textColor":"#2D2A26","mainBkg":"#FDFBF5","nodeBorder":"#2D5A3D","clusterBkg":"#F3EEDF","clusterBorder":"#9A9488","edgeLabelBackground":"#FDFBF5"},"flowchart":{"htmlLabels":true,"curve":"basis","padding":20,"nodeSpacing":60,"rankSpacing":70,"useMaxWidth":false},"themeCSS":".node rect, .node polygon, .node circle { stroke-width: 1.5px; } .edgePath .path { stroke-width: 1.5px; } .cluster-label .nodeLabel, .cluster-label foreignObject { color: #2D2A26 !important; } .edgeLabel, .edgeLabel foreignObject { color: #2D2A26 !important; background-color: #FDFBF5; } .edgeLabel rect { fill: #FDFBF5; }"}}%%
flowchart TD
A[CREATE OR REPLACE PROCEDURE]
A --> B[存储过程名]
B --> C{有参数吗?}
C -->|有| D[参数声明]
C -->|无| E["AS/IS"]
D --> E
E --> F[变量声明区]
F --> G[BEGIN]
G --> H[执行逻辑]
H --> I[EXCEPTION处理]
I --> J[END]
J --> K["/"]
style A fill:#e3f2fd
style G fill:#e8f5e8
style I fill:#ffebee
🏦 银行客户统计存储过程
让我们创建一个查询银行客户统计信息的存储过程:
-- 🎯 银行客户统计存储过程
CREATE OR REPLACE PROCEDURE show_customer_statistics
AS
-- 📊 统计变量
v_total_customers NUMBER(10); -- 总客户数
v_active_customers NUMBER(10); -- 活跃客户数
v_vip_customers NUMBER(10); -- VIP客户数
v_total_balance NUMBER(18,2); -- 总资产
v_avg_balance NUMBER(15,2); -- 活跃账户平均余额
v_largest_balance NUMBER(15,2); -- 最大余额
v_smallest_balance NUMBER(15,2); -- 最小余额
BEGIN
DBMS_OUTPUT.PUT_LINE('=====================================');
DBMS_OUTPUT.PUT_LINE(' 银行客户统计报表');
DBMS_OUTPUT.PUT_LINE('=====================================');
DBMS_OUTPUT.PUT_LINE('生成时间: ' || TO_CHAR(SYSDATE, 'YYYY-MM-DD HH24:MI:SS'));
DBMS_OUTPUT.PUT_LINE('');
-- 📊 第1部分:客户数量统计
SELECT COUNT(*) INTO v_total_customers
FROM customer_info;
SELECT COUNT(*) INTO v_active_customers
FROM customer_info
WHERE status = 'ACTIVE';
-- 🏆 VIP客户定义:总资产超过50万
SELECT COUNT(*) INTO v_vip_customers
FROM (
SELECT c.customer_id
FROM customer_info c
INNER JOIN account_info a ON c.customer_id = a.customer_id
WHERE c.status = 'ACTIVE'
AND a.status = 'ACTIVE'
GROUP BY c.customer_id
HAVING SUM(a.balance) >= 500000
);
DBMS_OUTPUT.PUT_LINE('📊 客户数量统计:');
DBMS_OUTPUT.PUT_LINE(' 总客户数: ' || TO_CHAR(v_total_customers, '999,999') || ' 人');
DBMS_OUTPUT.PUT_LINE(' 活跃客户数: ' || TO_CHAR(v_active_customers, '999,999') || ' 人');
DBMS_OUTPUT.PUT_LINE(' VIP客户数: ' || TO_CHAR(v_vip_customers, '999,999') || ' 人');
DBMS_OUTPUT.PUT_LINE(' VIP占比: ' ||
TO_CHAR(ROUND(CASE WHEN v_active_customers = 0 THEN 0 ELSE v_vip_customers/v_active_customers*100 END, 2), '990.99') || '%');
DBMS_OUTPUT.PUT_LINE('');
-- 💰 第2部分:资产统计
SELECT
NVL(SUM(balance), 0),
NVL(AVG(balance), 0),
NVL(MAX(balance), 0),
NVL(MIN(balance), 0)
INTO v_total_balance, v_avg_balance, v_largest_balance, v_smallest_balance
FROM account_info
WHERE status = 'ACTIVE';
DBMS_OUTPUT.PUT_LINE('💰 资产统计:');
DBMS_OUTPUT.PUT_LINE(' 总资产: ' || TO_CHAR(v_total_balance, '999,999,999,999.99') || ' 元');
DBMS_OUTPUT.PUT_LINE(' 活跃账户平均余额:' || TO_CHAR(v_avg_balance, '999,999,999.99') || ' 元');
DBMS_OUTPUT.PUT_LINE(' 最大余额: ' || TO_CHAR(v_largest_balance, '999,999,999.99') || ' 元');
DBMS_OUTPUT.PUT_LINE(' 最小余额: ' || TO_CHAR(v_smallest_balance, '999,999,999.99') || ' 元');
DBMS_OUTPUT.PUT_LINE('');
-- 📈 第3部分:客户等级分布
DBMS_OUTPUT.PUT_LINE('🏆 客户等级分布:');
FOR level_rec IN (
SELECT
CASE
WHEN total_balance >= 1000000 THEN '钻石客户'
WHEN total_balance >= 500000 THEN '白金客户'
WHEN total_balance >= 100000 THEN '金卡客户'
WHEN total_balance >= 10000 THEN '银卡客户'
ELSE '普通客户'
END as customer_level,
COUNT(*) as customer_count
FROM (
SELECT c.customer_id, NVL(SUM(a.balance), 0) as total_balance
FROM customer_info c
LEFT JOIN account_info a ON c.customer_id = a.customer_id AND a.status = 'ACTIVE'
WHERE c.status = 'ACTIVE'
GROUP BY c.customer_id
)
GROUP BY
CASE
WHEN total_balance >= 1000000 THEN '钻石客户'
WHEN total_balance >= 500000 THEN '白金客户'
WHEN total_balance >= 100000 THEN '金卡客户'
WHEN total_balance >= 10000 THEN '银卡客户'
ELSE '普通客户'
END
ORDER BY MIN(total_balance) DESC
) LOOP
DBMS_OUTPUT.PUT_LINE(' ' || RPAD(level_rec.customer_level, 10) ||
TO_CHAR(level_rec.customer_count, '999,999') || ' 人 (' ||
TO_CHAR(ROUND(CASE WHEN v_active_customers = 0 THEN 0 ELSE level_rec.customer_count/v_active_customers*100 END, 1), '990.9') || '%)');
END LOOP;
DBMS_OUTPUT.PUT_LINE('');
DBMS_OUTPUT.PUT_LINE('=====================================');
DBMS_OUTPUT.PUT_LINE(' 报表生成完成');
DBMS_OUTPUT.PUT_LINE('=====================================');
EXCEPTION
WHEN OTHERS THEN
DBMS_OUTPUT.PUT_LINE('❌ 统计过程中发生错误:' || SQLERRM);
DBMS_OUTPUT.PUT_LINE('请联系系统管理员');
END show_customer_statistics;
/
如何调用这个存储过程:
-- 📞 调用方法1:在PL/SQL块中调用
BEGIN
show_customer_statistics;
END;
/
-- 📞 调用方法2:直接执行(在SQL*Plus或SQL Developer中)
EXEC show_customer_statistics;
SUCCESS ✅ 存储过程创建成功!
恭喜你创建了第一个完整的存储过程!这个过程展示了:
- 📊 数据统计和分析
- 🔄 游标循环处理
- 📋 格式化输出
- 🚨 异常处理
- 🎯 复杂的业务逻辑
🎁 第五步:存储过程参数详解
参数让存储过程更加灵活,就像函数的输入和输出一样。
📥 输入参数(IN):传递信息给存储过程
%%{init: {"theme":"base","themeVariables":{"fontFamily":"PingFang SC, -apple-system, Source Han Serif SC, serif","fontSize":"16px","background":"#FDFBF5","primaryColor":"#FDFBF5","primaryTextColor":"#2D2A26","primaryBorderColor":"#6E6A60","secondaryColor":"#E8EFE8","secondaryBorderColor":"#8FA890","secondaryTextColor":"#2D2A26","tertiaryColor":"#FDFBF5","tertiaryBorderColor":"#9A9488","tertiaryTextColor":"#6E6A60","lineColor":"#6E6A60","textColor":"#2D2A26","mainBkg":"#FDFBF5","nodeBorder":"#2D5A3D","clusterBkg":"#F3EEDF","clusterBorder":"#9A9488","edgeLabelBackground":"#FDFBF5"},"flowchart":{"htmlLabels":true,"curve":"basis","padding":20,"nodeSpacing":60,"rankSpacing":70,"useMaxWidth":false},"themeCSS":".node rect, .node polygon, .node circle { stroke-width: 1.5px; } .edgePath .path { stroke-width: 1.5px; } .cluster-label .nodeLabel, .cluster-label foreignObject { color: #2D2A26 !important; } .edgeLabel, .edgeLabel foreignObject { color: #2D2A26 !important; background-color: #FDFBF5; } .edgeLabel rect { fill: #FDFBF5; }"}}%%
flowchart TD
A[调用程序] -->|传入参数| B[存储过程]
B --> C[处理逻辑]
C --> D[返回结果]
style A fill:#e3f2fd
style B fill:#e8f5e8
style D fill:#fff3e0
示例:根据客户ID查询详细信息
-- 🔍 客户信息查询存储过程
CREATE OR REPLACE PROCEDURE get_customer_details(
p_customer_id IN NUMBER -- 📥 输入参数:客户ID
)
AS
-- 📋 客户信息变量
v_customer_name VARCHAR2(100);
v_phone VARCHAR2(20);
v_email VARCHAR2(100);
v_create_time DATE;
v_status VARCHAR2(20);
-- 📊 账户信息变量
v_account_count NUMBER(5);
v_total_balance NUMBER(15,2);
v_avg_balance NUMBER(15,2);
-- 🏆 客户等级
v_customer_level VARCHAR2(20);
BEGIN
DBMS_OUTPUT.PUT_LINE('🔍 正在查询客户ID: ' || p_customer_id);
DBMS_OUTPUT.PUT_LINE('=====================================');
-- 📋 第1步:查询客户基本信息
SELECT customer_name, phone, email, create_time, status
INTO v_customer_name, v_phone, v_email, v_create_time, v_status
FROM customer_info
WHERE customer_id = p_customer_id;
-- 📊 第2步:统计账户信息
SELECT
COUNT(*),
NVL(SUM(balance), 0),
NVL(AVG(balance), 0)
INTO v_account_count, v_total_balance, v_avg_balance
FROM account_info
WHERE customer_id = p_customer_id
AND status = 'ACTIVE';
-- 🏆 第3步:确定客户等级
IF v_total_balance >= 1000000 THEN
v_customer_level := '钻石客户';
ELSIF v_total_balance >= 500000 THEN
v_customer_level := '白金客户';
ELSIF v_total_balance >= 100000 THEN
v_customer_level := '金卡客户';
ELSIF v_total_balance >= 10000 THEN
v_customer_level := '银卡客户';
ELSE
v_customer_level := '普通客户';
END IF;
-- 🎯 第4步:输出详细信息
DBMS_OUTPUT.PUT_LINE('👤 客户基本信息:');
DBMS_OUTPUT.PUT_LINE(' 姓名: ' || v_customer_name);
DBMS_OUTPUT.PUT_LINE(' 电话: ' || NVL(v_phone, '未登记'));
DBMS_OUTPUT.PUT_LINE(' 邮箱: ' || NVL(v_email, '未登记'));
DBMS_OUTPUT.PUT_LINE(' 开户时间: ' || TO_CHAR(v_create_time, 'YYYY-MM-DD'));
DBMS_OUTPUT.PUT_LINE(' 账户状态: ' || CASE v_status
WHEN 'ACTIVE' THEN '正常'
WHEN 'FROZEN' THEN '冻结'
WHEN 'CLOSED' THEN '关闭'
ELSE v_status END);
DBMS_OUTPUT.PUT_LINE('');
DBMS_OUTPUT.PUT_LINE('💰 资产信息:');
DBMS_OUTPUT.PUT_LINE(' 账户数量: ' || v_account_count || ' 个');
DBMS_OUTPUT.PUT_LINE(' 总资产: ' || TO_CHAR(v_total_balance, '999,999,999.99') || ' 元');
DBMS_OUTPUT.PUT_LINE(' 平均余额: ' || TO_CHAR(v_avg_balance, '999,999,999.99') || ' 元');
DBMS_OUTPUT.PUT_LINE(' 客户等级: 🏆 ' || v_customer_level);
-- 🎯 第5步:提供个性化建议
DBMS_OUTPUT.PUT_LINE('');
DBMS_OUTPUT.PUT_LINE('💡 服务建议:');
CASE v_customer_level
WHEN '钻石客户' THEN
DBMS_OUTPUT.PUT_LINE(' ⭐ 专属理财经理服务');
DBMS_OUTPUT.PUT_LINE(' ⭐ VIP通道优先办理');
DBMS_OUTPUT.PUT_LINE(' ⭐ 高端投资产品推荐');
WHEN '白金客户' THEN
DBMS_OUTPUT.PUT_LINE(' 💎 理财经理定期服务');
DBMS_OUTPUT.PUT_LINE(' 💎 优质理财产品推荐');
WHEN '金卡客户' THEN
DBMS_OUTPUT.PUT_LINE(' 🥇 专属客服热线');
DBMS_OUTPUT.PUT_LINE(' 🥇 定期理财产品推荐');
ELSE
DBMS_OUTPUT.PUT_LINE(' 📈 建议增加资产投入');
DBMS_OUTPUT.PUT_LINE(' 📈 关注基础理财产品');
END CASE;
DBMS_OUTPUT.PUT_LINE('=====================================');
EXCEPTION
WHEN NO_DATA_FOUND THEN
DBMS_OUTPUT.PUT_LINE('❌ 错误:客户ID ' || p_customer_id || ' 不存在');
WHEN TOO_MANY_ROWS THEN
DBMS_OUTPUT.PUT_LINE('❌ 错误:找到多个客户记录,数据异常');
WHEN OTHERS THEN
DBMS_OUTPUT.PUT_LINE('❌ 查询过程中发生错误:' || SQLERRM);
END get_customer_details;
/
调用示例:
-- 查询不同客户的信息
BEGIN
get_customer_details(1001); -- 查询客户1001
DBMS_OUTPUT.PUT_LINE('');
get_customer_details(1002); -- 查询客户1002
END;
/
📤 输出参数(OUT):从存储过程返回信息
%%{init: {"theme":"base","themeVariables":{"fontFamily":"PingFang SC, -apple-system, Source Han Serif SC, serif","fontSize":"16px","background":"#FDFBF5","primaryColor":"#FDFBF5","primaryTextColor":"#2D2A26","primaryBorderColor":"#6E6A60","secondaryColor":"#E8EFE8","secondaryBorderColor":"#8FA890","secondaryTextColor":"#2D2A26","tertiaryColor":"#FDFBF5","tertiaryBorderColor":"#9A9488","tertiaryTextColor":"#6E6A60","lineColor":"#6E6A60","textColor":"#2D2A26","mainBkg":"#FDFBF5","nodeBorder":"#2D5A3D","clusterBkg":"#F3EEDF","clusterBorder":"#9A9488","edgeLabelBackground":"#FDFBF5"},"flowchart":{"htmlLabels":true,"curve":"basis","padding":20,"nodeSpacing":60,"rankSpacing":70,"useMaxWidth":false},"themeCSS":".node rect, .node polygon, .node circle { stroke-width: 1.5px; } .edgePath .path { stroke-width: 1.5px; } .cluster-label .nodeLabel, .cluster-label foreignObject { color: #2D2A26 !important; } .edgeLabel, .edgeLabel foreignObject { color: #2D2A26 !important; background-color: #FDFBF5; } .edgeLabel rect { fill: #FDFBF5; }"}}%%
flowchart TD
D[Data]
A[调用程序]
B[存储过程]
A --> B --> C[处理逻辑]
C -->|返回结果| D[输出参数]
D --> A
style A fill:#e3f2fd
style B fill:#e8f5e8
style D fill:#fff3e0
示例:账户余额查询,返回多个结果
-- 💰 账户余额查询存储过程(带输出参数)
CREATE OR REPLACE PROCEDURE get_account_summary(
p_customer_id IN NUMBER, -- 📥 输入:客户ID
p_total_balance OUT NUMBER, -- 📤 输出:总余额
p_account_count OUT NUMBER, -- 📤 输出:账户数量
p_avg_balance OUT NUMBER, -- 📤 输出:平均余额
p_customer_level OUT VARCHAR2, -- 📤 输出:客户等级
p_status OUT VARCHAR2 -- 📤 输出:操作状态
)
AS
v_customer_exists NUMBER;
BEGIN
-- 🔍 第1步:检查客户是否存在
SELECT COUNT(*) INTO v_customer_exists
FROM customer_info
WHERE customer_id = p_customer_id
AND status = 'ACTIVE';
IF v_customer_exists = 0 THEN
-- 客户不存在
p_total_balance := 0;
p_account_count := 0;
p_avg_balance := 0;
p_customer_level := 'NONE';
p_status := 'CUSTOMER_NOT_FOUND';
RETURN; -- 提前退出
END IF;
-- 📊 第2步:统计账户信息
SELECT
NVL(SUM(balance), 0),
COUNT(*),
NVL(AVG(balance), 0)
INTO p_total_balance, p_account_count, p_avg_balance
FROM account_info
WHERE customer_id = p_customer_id
AND status = 'ACTIVE';
-- 🏆 第3步:确定客户等级
IF p_total_balance >= 1000000 THEN
p_customer_level := 'DIAMOND';
ELSIF p_total_balance >= 500000 THEN
p_customer_level := 'PLATINUM';
ELSIF p_total_balance >= 100000 THEN
p_customer_level := 'GOLD';
ELSIF p_total_balance >= 10000 THEN
p_customer_level := 'SILVER';
ELSE
p_customer_level := 'BRONZE';
END IF;
-- ✅ 操作成功
p_status := 'SUCCESS';
EXCEPTION
WHEN OTHERS THEN
-- 出错时设置输出参数
p_total_balance := 0;
p_account_count := 0;
p_avg_balance := 0;
p_customer_level := 'ERROR';
p_status := 'ERROR: ' || SQLERRM;
END get_account_summary;
/
如何使用输出参数:
-- 📱 使用输出参数的示例
DECLARE
-- 📋 声明变量接收输出参数
v_total_balance NUMBER;
v_account_count NUMBER;
v_avg_balance NUMBER;
v_customer_level VARCHAR2(20);
v_status VARCHAR2(100);
BEGIN
DBMS_OUTPUT.PUT_LINE('=== 账户摘要查询测试 ===');
-- 🚀 调用存储过程
get_account_summary(
p_customer_id => 1001, -- 输入参数
p_total_balance => v_total_balance, -- 输出参数
p_account_count => v_account_count, -- 输出参数
p_avg_balance => v_avg_balance, -- 输出参数
p_customer_level => v_customer_level, -- 输出参数
p_status => v_status -- 输出参数
);
-- 📊 根据状态显示结果
IF v_status = 'SUCCESS' THEN
DBMS_OUTPUT.PUT_LINE('✅ 查询成功!');
DBMS_OUTPUT.PUT_LINE('总余额: ' || TO_CHAR(v_total_balance, '999,999,999.99') || ' 元');
DBMS_OUTPUT.PUT_LINE('账户数量: ' || v_account_count || ' 个');
DBMS_OUTPUT.PUT_LINE('平均余额: ' || TO_CHAR(v_avg_balance, '999,999,999.99') || ' 元');
DBMS_OUTPUT.PUT_LINE('客户等级: ' || v_customer_level);
ELSIF v_status = 'CUSTOMER_NOT_FOUND' THEN
DBMS_OUTPUT.PUT_LINE('❌ 客户不存在或已注销');
ELSE
DBMS_OUTPUT.PUT_LINE('❌ 查询失败:' || v_status);
END IF;
END;
/
INFO 💡 输出参数的优势
为什么使用输出参数?
- 📤 一次调用返回多个结果
- 📊 便于错误状态传递
- 🎯 提高程序效率
- 🔧 便于调试和测试
注意事项:
- 必须用变量接收输出参数
- 输出参数在存储过程内部必须被赋值
- 可以在异常处理中设置输出参数的值