Oracle PL/SQL 从入门到精通
作者:Chenxi Hu
PL/SQL 简介
什么是PL/SQL?
PL/SQL(Procedural Language/Structured Query Language)是Oracle数据库的过程化扩展语言,它将SQL的数据操作能力与过程化语言的流程控制能力相结合。PL/SQL允许开发者在数据库服务器端编写复杂的业务逻辑,减少网络传输,提高应用程序性能。
PL/SQL 的主要特点
块结构
-- PL/SQL程序由块组成 [DECLARE] -- 声明部分 BEGIN -- 执行部分 [EXCEPTION] -- 异常处理部分 END;
过程化结构
PL/SQL支持完整的过程化编程结构:
- 条件判断(IF-THEN-ELSE,CASE)
- 循环(LOOP,WHILE,FOR)
- 顺序控制(GOTO,NULL)
错误处理机制
EXCEPTION
WHEN exception1 THEN
-- 处理特定异常
WHEN OTHERS THEN
-- 处理其他所有异常
高性能特性
- 减少网络流量:在数据库服务器端执行复杂逻辑
- 预编译:代码在首次执行时编译,后续执行使用编译后的版本
- 批量处理:支持BULK COLLECT和FORALL进行批量操作
可移植性
PL/SQL代码可以在所有支持Oracle数据库的平台上运行,无需修改。
PL/SQL与SQL的区别
| 特性 | SQL | PL/SQL |
|---|---|---|
| 语言类型 | 声明式语言 | 过程化语言 |
| 执行方式 | 单条语句执行 | 块执行 |
| 流程控制 | 无 | 完整的流程控制 |
| 错误处理 | 有限 | 强大的异常处理 |
| 变量支持 | 无 | 支持变量和常量 |
PL/SQL的应用场景
- 数据库触发器
- 存储过程和函数
- 包(Package)
- 数据库作业(DBMS_JOB)
- 复杂的业务逻辑实现
Oracle APEX
Oracle APEX简介
Oracle APEX(Oracle Application Express)是Oracle推出的低代码Web应用开发平台。
核心优势
- 低代码开发:通过可视化界面(如App Builder)和SQL/PL/SQL语言,大幅减少代码量,非专业开发人员也能快速上手,显著缩短应用开发周期。
- 深度集成Oracle数据库:作为Oracle生态的核心组件,与Oracle数据库(包括自治数据库)无缝集成,充分利用数据库的性能、安全性、事务管理能力,适合构建数据密集型应用(如ERP、CRM、报表系统等)。
**核心功能模块 **
- App Builder:可视化拖拽式界面,支持页面设计、表单创建、报表生成、流程定义等,快速构建Web应用的前端与业务逻辑。
- SQL Workshop:Object Browser、SQL Commands等工具,用于数据库对象管理、SQL/PL/SQL语句执行、脚本管理等,是“数据库到应用”的桥梁。
Oracle APEX - SQL Workshop - SQL Commands第一个PL/SQL程序
-- 简单的PL/SQL块
BEGIN
DBMS_OUTPUT.PUT_LINE('Hello, PL/SQL World!');
DBMS_OUTPUT.PUT_LINE('当前时间: ' || TO_CHAR(SYSDATE, 'YYYY-MM-DD HH24:MI:SS'));
END;
/
-- “/” 是 PL/SQL 块特有的执行指令,用来明确告诉 Oracle 执行环境:“当前编辑的 PL/SQL 块已完整,请执行它”。
-- 带变量的PL/SQL块
DECLARE
v_message VARCHAR2(100) := '欢迎学习PL/SQL';
v_counter NUMBER := 1;
BEGIN
WHILE v_counter <= 5 LOOP
DBMS_OUTPUT.PUT_LINE(v_counter || ': ' || v_message);
v_counter := v_counter + 1;
END LOOP;
END;
/

PL/SQL 块结构
PL/SQL 块的基本组成
完整块结构
[DECLARE] -- 声明部分:变量、常量、游标、异常等 BEGIN -- 执行部分:PL/SQL和SQL语句 [EXCEPTION] -- 异常处理部分:错误处理逻辑 END;
各部分的详细说明
声明部分(DECLARE)
DECLARE -- 变量声明 v_employee_id NUMBER(6) := 100; v_employee_name VARCHAR2(50); v_salary NUMBER(8,2); v_hire_date DATE; v_is_active BOOLEAN := TRUE; -- 常量声明 c_company_name CONSTANT VARCHAR2(30) := '甲骨文公司'; c_tax_rate CONSTANT NUMBER := 0.1; -- 异常声明 e_salary_too_high EXCEPTION; -- 声明一个自定义异常 -- 将自定义异常与特定错误编号绑定 -- -20001是 Oracle 预留的用户自定义错误编号(范围:-20000 到 -20999),用于区分系统错误(如 ORA-00001 是主键冲突)和用户业务错误 PRAGMA EXCEPTION_INIT(e_salary_too_high, -20001);
执行部分(BEGIN)
BEGIN
-- 数据查询
SELECT first_name || ' ' || last_name, salary, hire_date
INTO v_employee_name, v_salary, v_hire_date
FROM employees
WHERE employee_id = v_employee_id;
-- 数据处理
IF v_salary > 10000 THEN
RAISE e_salary_too_high;
END IF;
-- 数据输出
DBMS_OUTPUT.PUT_LINE('员工: ' || v_employee_name);
DBMS_OUTPUT.PUT_LINE('薪资: ' || v_salary);
DBMS_OUTPUT.PUT_LINE('入职日期: ' || TO_CHAR(v_hire_date, 'YYYY-MM-DD'));异常处理部分(EXCEPTION)
EXCEPTION
WHEN NO_DATA_FOUND THEN
DBMS_OUTPUT.PUT_LINE('错误: 未找到指定的员工记录');
WHEN TOO_MANY_ROWS THEN
DBMS_OUTPUT.PUT_LINE('错误: 查询返回了多条记录');
WHEN e_salary_too_high THEN
DBMS_OUTPUT.PUT_LINE('错误: 员工薪资超过限制');
WHEN OTHERS THEN
-- SQLCODE:返回当前错误的错误编号(如系统错误NO_DATA_FOUND对应100,用户自定义错误-20001等)。
-- SQLERRM:返回与SQLCODE对应的错误描述信息(如ORA-01403: 未找到数据、ORA-20001: 工资过高等)。
DBMS_OUTPUT.PUT_LINE('系统错误: ' || SQLCODE || ' - ' || SQLERRM);
-- 事务回滚
ROLLBACK;
END;
匿名块详解
匿名块的特点
- 没有名称,不被数据库存储
- 每次执行都需要重新编译
- 适合一次性任务和测试
- PL/SQL 匿名块的固定语法是
DECLARE...BEGIN...END;/,没有任何 “命名定义”(如存储过程的CREATE PROCEDURE 名称)
匿名块的使用场景
-- 场景1:数据验证和清理
DECLARE
v_invalid_count NUMBER;
BEGIN
-- 统计无效数据
SELECT COUNT(*) INTO v_invalid_count
FROM employees
WHERE department_id NOT IN (SELECT department_id FROM departments);
DBMS_OUTPUT.PUT_LINE('发现 ' || v_invalid_count || ' 条无效记录');
-- 清理无效数据
IF v_invalid_count > 0 THEN
DELETE FROM employees
WHERE department_id NOT IN (SELECT department_id FROM departments);
DBMS_OUTPUT.PUT_LINE('已清理 ' || SQL%ROWCOUNT || ' 条记录');
COMMIT;
END IF;
END;
/
-- 场景2:数据转换
DECLARE
CURSOR c_employees IS
SELECT employee_id, salary
FROM employees
WHERE salary < 3000;
v_raise_percentage NUMBER := 0.1;
BEGIN
FOR emp_rec IN c_employees LOOP
UPDATE employees
SET salary = salary * (1 + v_raise_percentage)
WHERE employee_id = emp_rec.employee_id;
DBMS_OUTPUT.PUT_LINE('员工 ' || emp_rec.employee_id ||
' 薪资从 ' || emp_rec.salary ||
' 调整为 ' || emp_rec.salary * (1 + v_raise_percentage));
END LOOP;
COMMIT;
END;
/命名块详解
命名块的类型
- 存储过程(Procedure):执行特定任务,不返回值
- 函数(Function):执行计算并返回一个值
- 触发器(Trigger):响应数据库事件自动执行
- 包(Package):相关程序单元的集合
命名块的优势
- 代码重用:一次编写,多次调用
- 模块化:将复杂系统分解为小模块
- 安全性:通过权限控制访问
- 性能:预编译,执行效率高
- 维护性:集中管理,易于维护
块的嵌套和标签
嵌套块
DECLARE
v_outer_variable VARCHAR2(50) := '外部变量';
BEGIN
DBMS_OUTPUT.PUT_LINE('外层块: ' || v_outer_variable);
-- 内层块开始
DECLARE
v_inner_variable VARCHAR2(50) := '内部变量';
BEGIN
DBMS_OUTPUT.PUT_LINE('内层块: ' || v_outer_variable); -- 可以访问外部变量
DBMS_OUTPUT.PUT_LINE('内层块: ' || v_inner_variable);
-- 可以重新定义外部变量(不推荐)
v_outer_variable := '在内层块修改的外部变量';
END;
DBMS_OUTPUT.PUT_LINE('外层块: ' || v_outer_variable); -- 显示修改后的值
END;
/块标签
<<outer_block>>
DECLARE
v_counter NUMBER := 1;
BEGIN
DBMS_OUTPUT.PUT_LINE('外部块计数器: ' || v_counter);
<<inner_block>>
DECLARE
v_counter NUMBER := 100; -- 与外部块变量同名
BEGIN
DBMS_OUTPUT.PUT_LINE('内部块计数器: ' || v_counter); -- 显示100
DBMS_OUTPUT.PUT_LINE('外部块计数器: ' || outer_block.v_counter); -- 显示1
outer_block.v_counter := outer_block.v_counter + 1; -- 修改外部块变量
END inner_block;
DBMS_OUTPUT.PUT_LINE('外部块计数器: ' || v_counter); -- 显示2
END outer_block;
/变量和数据类型
变量声明
变量声明语法
variable_name [CONSTANT] datatype [NOT NULL] [:= | DEFAULT initial_value];
变量声明示例
DECLARE
-- 基本变量声明
v_employee_id NUMBER(6);
v_first_name VARCHAR2(20);
v_last_name VARCHAR2(25);
v_salary NUMBER(8,2);
v_commission_pct NUMBER(2,2);
v_hire_date DATE;
v_is_manager BOOLEAN;
-- 带初始化的变量声明
v_department_id NUMBER(4) := 50;
v_job_id VARCHAR2(10) DEFAULT 'IT_PROG';
v_active_status BOOLEAN := TRUE;
-- 常量声明
c_company_name CONSTANT VARCHAR2(30) := 'Oracle Corporation';
c_max_salary CONSTANT NUMBER := 100000;
c_pi CONSTANT NUMBER := 3.14159;
-- NOT NULL约束
v_required_field VARCHAR2(50) NOT NULL := '默认值';
BEGIN
-- 变量使用示例
v_employee_id := 100;
v_first_name := 'Steven';
v_last_name := 'King';
v_salary := 24000;
v_hire_date := SYSDATE;
v_is_manager := TRUE;
DBMS_OUTPUT.PUT_LINE('员工: ' || v_first_name || ' ' || v_last_name);
DBMS_OUTPUT.PUT_LINE('薪资: ' || v_salary);
DBMS_OUTPUT.PUT_LINE('公司: ' || c_company_name);
END;
/标量数据类型
数值类型
DECLARE
-- NUMBER类型
v_integer NUMBER(5); -- 整数,最多5位
v_decimal NUMBER(8,2); -- 小数,总位数8,小数位2
v_float NUMBER; -- 浮点数
v_scientific NUMBER; -- 科学计数法
-- 整数子类型
v_pls_integer PLS_INTEGER; -- PL/SQL整数,性能更好
v_binary_int BINARY_INTEGER; -- 二进制整数
v_simple_int SIMPLE_INTEGER; -- 简单整数(NOT NULL)
-- 其他数值类型
v_double BINARY_DOUBLE; -- 二进制双精度
v_float_num BINARY_FLOAT; -- 二进制单精度
BEGIN
v_integer := 12345;
v_decimal := 123456.78;
v_float := 3.1415926535;
v_scientific := 1.23E+5; -- 123000
v_pls_integer := 100;
v_binary_int := -50;
v_simple_int := 200;
v_double := 3.141592653589793;
v_float_num := 2.71828;
DBMS_OUTPUT.PUT_LINE('整数: ' || v_integer);
DBMS_OUTPUT.PUT_LINE('小数: ' || v_decimal);
DBMS_OUTPUT.PUT_LINE('科学计数: ' || v_scientific);
END;
/字符类型
DECLARE
-- VARCHAR2类型
v_varchar2 VARCHAR2(50); -- 可变长度字符串
v_varchar2_def VARCHAR2(100) := '默认值';
-- CHAR类型
v_char CHAR(10); -- 定长字符串,自动填充空格
v_char_def CHAR(5) := 'ABC';
-- 长文本类型
v_long LONG; -- 长文本(已过时)
v_clob CLOB; -- 字符大对象
-- 其他字符类型
v_nchar NCHAR(10); -- 国家字符集定长
v_nvarchar2 NVARCHAR2(50); -- 国家字符集变长
v_raw RAW(100); -- 原始二进制数据
v_long_raw LONG RAW; -- 长原始二进制(已过时)
v_blob BLOB; -- 二进制大对象
BEGIN
v_varchar2 := '这是一个变长字符串';
v_char := '定长'; -- 实际存储为'定长 '
v_clob := '这是一个非常大的文本内容...';
DBMS_OUTPUT.PUT_LINE('VARCHAR2: ' || v_varchar2);
DBMS_OUTPUT.PUT_LINE('CHAR: [' || v_char || ']'); -- 显示填充的空格
DBMS_OUTPUT.PUT_LINE('CLOB长度: ' || DBMS_LOB.GETLENGTH(v_clob));
END;
/日期和时间类型
DECLARE
-- 日期时间类型
v_date DATE; -- 日期和时间
v_timestamp TIMESTAMP; -- 时间戳
v_timestamp_tz TIMESTAMP WITH TIME ZONE;-- 带时区的时间戳
v_timestamp_ltz TIMESTAMP WITH LOCAL TIME ZONE; -- 本地时区时间戳
-- 时间间隔类型
v_interval_ym INTERVAL YEAR TO MONTH; -- 年月间隔
v_interval_ds INTERVAL DAY TO SECOND; -- 日秒间隔
BEGIN
v_date := SYSDATE;
v_timestamp := SYSTIMESTAMP;
v_timestamp_tz := CURRENT_TIMESTAMP;
v_timestamp_ltz := LOCALTIMESTAMP;
v_interval_ym := INTERVAL '1-6' YEAR TO MONTH; -- 1年6个月
v_interval_ds := INTERVAL '5 12:30:15' DAY TO SECOND; -- 5天12小时30分15秒
DBMS_OUTPUT.PUT_LINE('当前日期: ' || TO_CHAR(v_date, 'YYYY-MM-DD HH24:MI:SS'));
DBMS_OUTPUT.PUT_LINE('当前时间戳: ' || v_timestamp);
DBMS_OUTPUT.PUT_LINE('时间间隔: ' || v_interval_ym);
DBMS_OUTPUT.PUT_LINE('日秒间隔: ' || v_interval_ds);
END;
/布尔类型
DECLARE
v_flag1 BOOLEAN;
v_flag2 BOOLEAN := TRUE;
v_flag3 BOOLEAN := FALSE;
v_flag4 BOOLEAN := NULL;
BEGIN
v_flag1 := (10 > 5); -- 赋值为TRUE
-- 布尔值的使用
IF v_flag1 THEN
DBMS_OUTPUT.PUT_LINE('条件1为真');
END IF;
IF NOT v_flag3 THEN
DBMS_OUTPUT.PUT_LINE('条件3为假');
END IF;
-- 注意:布尔值不能直接输出
-- DBMS_OUTPUT.PUT_LINE(v_flag1); -- 这会报错
-- 可以通过条件判断输出
DBMS_OUTPUT.PUT_LINE('flag1: ' || CASE WHEN v_flag1 THEN 'TRUE'
WHEN NOT v_flag1 THEN 'FALSE'
ELSE 'NULL' END);
END;
/复合数据类型
记录类型(RECORD)
在 Oracle 的 PL/SQL 中,RECORD(记录)类型是一种复合数据类型,用于将多个相关但数据类型可能不同的 “字段(Field)” 组合成一个整体,类似其他编程语言中的 “结构体(Struct)”。
DECLARE
-- 定义记录类型
TYPE employee_rec IS RECORD (
employee_id employees.employee_id%TYPE,
first_name employees.first_name%TYPE,
last_name employees.last_name%TYPE,
salary employees.salary%TYPE,
hire_date employees.hire_date%TYPE
);
-- 声明记录变量
v_employee employee_rec;
-- 使用%ROWTYPE定义记录
v_emp_row employees%ROWTYPE;
BEGIN
-- 为记录字段赋值
v_employee.employee_id := 100;
v_employee.first_name := 'Steven';
v_employee.last_name := 'King';
v_employee.salary := 24000;
v_employee.hire_date := SYSDATE;
-- 使用SELECT INTO为记录赋值
SELECT employee_id, first_name, last_name, salary, hire_date
INTO v_employee
FROM employees
WHERE employee_id = 101;
-- 使用%ROWTYPE
SELECT * INTO v_emp_row
FROM employees
WHERE employee_id = 102;
-- 输出记录内容
DBMS_OUTPUT.PUT_LINE('员工: ' || v_employee.first_name || ' ' || v_employee.last_name);
DBMS_OUTPUT.PUT_LINE('薪资: ' || v_employee.salary);
DBMS_OUTPUT.PUT_LINE('ROW员工: ' || v_emp_row.first_name || ' ' || v_emp_row.last_name);
END;
/集合类型
关联数组(INDEX-BY TABLE)
在 Oracle 的 PL/SQL 中,关联数组(Associative Array) 也称为INDEX-BY TABLE(索引表),是一种集合类型,用于存储多个同类型元素的 “键值对” 集合,类似其他编程语言中的 “哈希表(Hash Table)” 或 “字典(Dictionary)”。
DECLARE -- 声明区:定义类型、变量等(仅声明不执行)
-- 定义第一个关联数组类型:salary_table
-- 元素类型为NUMBER(存储薪资),索引类型为PLS_INTEGER(整数索引)
TYPE salary_table IS TABLE OF NUMBER
INDEX BY PLS_INTEGER;
-- 定义第二个关联数组类型:name_table
-- 元素类型为VARCHAR2(50)(存储姓名),索引类型为VARCHAR2(10)(字符串索引,长度不超过10)
TYPE name_table IS TABLE OF VARCHAR2(50)
INDEX BY VARCHAR2(10);
-- 声明关联数组变量:基于上面定义的类型创建实例
v_salaries salary_table; -- 用于存储“整数索引->薪资”的关联数组
v_names name_table; -- 用于存储“字符串索引->姓名”的关联数组
-- 声明遍历用的变量:存储当前索引/键
v_index PLS_INTEGER; -- 用于遍历整数索引的v_salaries
v_key VARCHAR2(10); -- 用于遍历字符串索引的v_names
BEGIN -- 执行区:编写具体逻辑(实际执行的代码)
-- 为v_salaries赋值(整数索引)
v_salaries(1) := 5000; -- 索引1对应的值为5000
v_salaries(2) := 7500; -- 索引2对应的值为7500
v_salaries(5) := 10000; -- 索引5对应的值为10000(演示:关联数组索引可以不连续)
-- 为v_names赋值(字符串索引)
v_names('emp001') := '张三'; -- 索引'emp001'对应的值为'张三'
v_names('emp002') := '李四'; -- 索引'emp002'对应的值为'李四'
v_names('manager') := '王经理';-- 索引'manager'对应的值为'王经理'
-- 遍历v_salaries(整数索引的关联数组)
v_index := v_salaries.FIRST; -- 获取v_salaries的第一个索引(此处为1)
-- 循环条件:当前索引不为空(即还有元素未遍历)
WHILE v_index IS NOT NULL LOOP
-- 输出当前索引和对应的值
DBMS_OUTPUT.PUT_LINE('索引 ' || v_index || ': ' || v_salaries(v_index));
v_index := v_salaries.NEXT(v_index); -- 获取当前索引的下一个索引(1的下一个是2,2的下一个是5,5的下一个为空)
END LOOP; -- 结束循环
-- 遍历v_names(字符串索引的关联数组)
v_key := v_names.FIRST; -- 获取v_names的第一个键(字符串索引按字典序排序,此处为'emp001')
-- 循环条件:当前键不为空(即还有元素未遍历)
WHILE v_key IS NOT NULL LOOP
-- 输出当前键和对应的值
DBMS_OUTPUT.PUT_LINE('键 ' || v_key || ': ' || v_names(v_key));
v_key := v_names.NEXT(v_key); -- 获取当前键的下一个键('emp001'的下一个是'emp002',再下一个是'manager',最后为空)
END LOOP; -- 结束循环
-- 检查v_salaries中是否存在索引3的元素
IF v_salaries.EXISTS(3) THEN -- EXISTS(索引):判断指定索引是否存在
DBMS_OUTPUT.PUT_LINE('索引3存在');
ELSE
DBMS_OUTPUT.PUT_LINE('索引3不存在'); -- 此处因未给索引3赋值,会执行该分支
END IF;
-- 获取并输出关联数组的元素数量(COUNT方法)
DBMS_OUTPUT.PUT_LINE('v_salaries元素数量: ' || v_salaries.COUNT); -- 共3个元素(索引1、2、5)
DBMS_OUTPUT.PUT_LINE('v_names元素数量: ' || v_names.COUNT); -- 共3个元素(键'emp001'、'emp002'、'manager')
END; -- 结束PL/SQL块
/ -- 执行该PL/SQL块嵌套表(NESTED TABLE)
在Oracle数据库中,嵌套表(Nested Table) 是一种用户定义的集合数据类型,它允许将整个表作为另一张表的列进行存储。
嵌套表是一种:
- 无序的数据集合(元素没有固定顺序)
- 可以动态扩展(元素数量不固定)
- 存储在数据库表中的特殊列类型
- 实际数据存储在单独的存储表中
DECLARE -- 声明区:定义类型、变量(仅声明不执行)
-- 定义第一个嵌套表类型:number_table,元素类型为NUMBER(存储数字)
TYPE number_table IS TABLE OF NUMBER;
-- 定义第二个嵌套表类型:string_table,元素类型为VARCHAR2(50)(存储字符串,长度不超过50)
TYPE string_table IS TABLE OF VARCHAR2(50);
-- 声明嵌套表变量(基于上面定义的类型)
-- v_numbers:number_table类型的变量,通过构造函数number_table()初始化(创建空嵌套表,非NULL)
v_numbers number_table := number_table(); -- 嵌套表必须初始化,否则操作会报错
-- v_names:string_table类型的变量,声明时直接通过构造函数赋值(初始包含3个元素:'张三'、'李四'、'王五')
v_names string_table := string_table('张三', '李四', '王五');
-- 声明遍历用的变量:存储嵌套表的索引(整数类型)
v_index NUMBER;
BEGIN -- 执行区:编写具体逻辑(实际执行的代码)
-- 为v_numbers添加元素(第一种方式:先扩展容量再赋值)
v_numbers.EXTEND(3); -- 扩展3个空元素(此时v_numbers有3个可用索引:1、2、3)
v_numbers(1) := 100; -- 给索引1的元素赋值100
v_numbers(2) := 200; -- 给索引2的元素赋值200
v_numbers(3) := 300; -- 给索引3的元素赋值300
-- 为v_numbers重新赋值(第二种方式:直接通过构造函数覆盖原有值)
-- 此时v_numbers的元素变为10、20、30、40、50(索引1-5,覆盖了之前的100、200、300)
v_numbers := number_table(10, 20, 30, 40, 50);
-- 遍历v_numbers(使用FOR循环,基于元素数量COUNT)
-- 循环范围:从1到v_numbers的元素总数(COUNT=5,即i=1→2→3→4→5)
FOR i IN 1..v_numbers.COUNT LOOP
-- 输出当前索引i和对应的值
DBMS_OUTPUT.PUT_LINE('数字[' || i || ']: ' || v_numbers(i));
END LOOP; -- 结束循环
-- 遍历v_names(使用WHILE循环,基于FIRST和NEXT方法)
v_index := v_names.FIRST; -- 获取v_names的第一个索引(初始为1,因初始元素是按顺序添加的)
-- 循环条件:当前索引不为空(即还有元素未遍历)
WHILE v_index IS NOT NULL LOOP
-- 输出当前索引v_index和对应的值
DBMS_OUTPUT.PUT_LINE('姓名[' || v_index || ']: ' || v_names(v_index));
v_index := v_names.NEXT(v_index); -- 获取当前索引的下一个索引(1→2→3→NULL)
END LOOP; -- 结束循环
-- 删除v_names中的元素(删除索引为2的元素,即'李四')
v_names.DELETE(2);
-- 输出删除后v_names的元素数量(原3个,删除1个,剩余2个)
DBMS_OUTPUT.PUT_LINE('删除后元素数量: ' || v_names.COUNT);
END; -- 结束PL/SQL块
/ -- 执行该PL/SQL块变长数组(VARRAY)
DECLARE -- 声明区:定义类型、变量(仅声明不执行)
-- 定义第一个VARRAY类型:number_varray
-- VARRAY(5)表示这是一个可变数组,最大容量为5个元素;OF NUMBER表示元素类型为数字
TYPE number_varray IS VARRAY(5) OF NUMBER; -- 最大可存储5个元素
-- 定义第二个VARRAY类型:name_varray
-- VARRAY(10)表示最大容量为10个元素;OF VARCHAR2(50)表示元素为字符串(最大长度50)
TYPE name_varray IS VARRAY(10) OF VARCHAR2(50);
-- 声明VARRAY变量(基于上面定义的类型)
-- v_numbers:number_varray类型的变量,通过构造函数number_varray()初始化(创建空数组,非NULL)
-- VARRAY必须初始化,否则操作会报错
v_numbers number_varray := number_varray();
-- v_names:name_varray类型的变量,声明时通过构造函数直接赋值(初始包含2个元素:'张三'、'李四')
v_names name_varray := name_varray('张三', '李四');
-- 声明遍历用的变量:存储VARRAY的索引(整数类型)
v_index NUMBER;
BEGIN -- 执行区:编写具体逻辑(实际执行的代码)
-- 为v_numbers添加元素(先扩展容量,再赋值)
v_numbers.EXTEND(3); -- 扩展3个空元素(此时v_numbers的容量变为3,未超过最大限制5)
v_numbers(1) := 100; -- 给第1个元素赋值100(VARRAY索引从1开始)
v_numbers(2) := 200; -- 给第2个元素赋值200
v_numbers(3) := 300; -- 给第3个元素赋值300
-- 遍历v_numbers(使用FOR循环,范围从1到当前元素总数COUNT)
-- 此时v_numbers.COUNT=3,即i=1→2→3
FOR i IN 1..v_numbers.COUNT LOOP
-- 输出当前索引i和对应的值
DBMS_OUTPUT.PUT_LINE('数字[' || i || ']: ' || v_numbers(i));
END LOOP; -- 结束循环
-- 获取并输出v_numbers的最大容量(LIMIT是VARRAY特有的方法,返回定义时指定的最大元素数)
DBMS_OUTPUT.PUT_LINE('v_numbers最大容量: ' || v_numbers.LIMIT); -- 输出5(定义时VARRAY(5))
-- 输出v_numbers当前的元素数量(COUNT返回实际存储的元素数)
DBMS_OUTPUT.PUT_LINE('v_numbers当前大小: ' || v_numbers.COUNT); -- 输出3(已添加3个元素)
-- 操作v_names数组
v_names.EXTEND; -- 扩展1个空元素(不指定参数时默认扩展1个,此时v_names容量从2变为3,未超过最大限制10)
v_names(3) := '王五'; -- 给第3个元素赋值'王五'
-- 输出v_names当前的元素数量(扩展并赋值后,COUNT=3)
DBMS_OUTPUT.PUT_LINE('v_names当前大小: ' || v_names.COUNT);
END; -- 结束PL/SQL块
/ -- 执行该PL/SQL块三种集合类型对比
| 特性 | 嵌套表(Nested Table) | 变长数组(VARRAY) | 关联数组(Index-by Table) |
|---|---|---|---|
| 定义 | 用户定义的无序集合 | 用户定义的有序集合 | PL/SQL中的键值对集合 |
| 存储位置 | 数据库表中 | 数据库表中 | 仅内存中 |
| 大小 | 动态,无限制 | 固定上限 | 动态,无限制 |
选择建议
- 需要持久化存储且数据量变化大 → 嵌套表
- 数据量固定且需要保持顺序 → 变长数组
- 临时数据处理和快速查找 → 关联数组
- 需要在SQL中查询集合内容 → 嵌套表或变长数组
- 仅PL/SQL内部使用 → 关联数组
数据类型属性
%TYPE属性
DECLARE
-- 使用%TYPE引用表列的类型
v_employee_id employees.employee_id%TYPE;
v_first_name employees.first_name%TYPE;
v_salary employees.salary%TYPE;
v_hire_date employees.hire_date%TYPE;
-- 引用其他变量的类型
v_bonus v_salary%TYPE;
BEGIN
v_employee_id := 100;
v_first_name := 'Steven';
v_salary := 24000;
v_hire_date := SYSDATE;
v_bonus := v_salary * 0.1;
DBMS_OUTPUT.PUT_LINE('员工ID: ' || v_employee_id);
DBMS_OUTPUT.PUT_LINE('姓名: ' || v_first_name);
DBMS_OUTPUT.PUT_LINE('薪资: ' || v_salary);
DBMS_OUTPUT.PUT_LINE('奖金: ' || v_bonus);
END;
/%ROWTYPE属性
DECLARE
-- 使用%ROWTYPE引用整行类型
v_employee employees%ROWTYPE;
v_department departments%ROWTYPE;
BEGIN
-- 为记录赋值
SELECT * INTO v_employee
FROM employees
WHERE employee_id = 100;
SELECT * INTO v_department
FROM departments
WHERE department_id = v_employee.department_id;
-- 访问记录字段
DBMS_OUTPUT.PUT_LINE('员工: ' || v_employee.first_name || ' ' || v_employee.last_name);
DBMS_OUTPUT.PUT_LINE('部门: ' || v_department.department_name);
DBMS_OUTPUT.PUT_LINE('薪资: ' || v_employee.salary);
DBMS_OUTPUT.PUT_LINE('入职日期: ' || TO_CHAR(v_employee.hire_date, 'YYYY-MM-DD'));
END;
/数据类型转换
隐式转换
DECLARE
v_number NUMBER;
v_varchar VARCHAR2(50);
v_date DATE;
BEGIN
-- 数字到字符串的隐式转换
v_varchar := 123.45;
DBMS_OUTPUT.PUT_LINE('数字转字符串: ' || v_varchar);
-- 字符串到数字的隐式转换
v_number := '456.78';
DBMS_OUTPUT.PUT_LINE('字符串转数字: ' || v_number);
-- 日期到字符串的隐式转换
v_varchar := SYSDATE;
DBMS_OUTPUT.PUT_LINE('日期转字符串: ' || v_varchar);
END;
/显式转换
DECLARE
v_number NUMBER := 123.456;
v_varchar VARCHAR2(50);
v_date DATE;
v_timestamp TIMESTAMP;
BEGIN
-- 使用TO_CHAR转换数字和日期
v_varchar := TO_CHAR(v_number, '999,999.99');
DBMS_OUTPUT.PUT_LINE('格式化数字: ' || v_varchar);
v_varchar := TO_CHAR(SYSDATE, 'YYYY-MM-DD HH24:MI:SS');
DBMS_OUTPUT.PUT_LINE('格式化日期: ' || v_varchar);
-- 使用TO_NUMBER转换字符串为数字
v_number := TO_NUMBER('$1,234.56', '$9,999.99');
DBMS_OUTPUT.PUT_LINE('字符串转数字: ' || v_number);
-- 使用TO_DATE转换字符串为日期
v_date := TO_DATE('2023-12-25 14:30:00', 'YYYY-MM-DD HH24:MI:SS');
DBMS_OUTPUT.PUT_LINE('字符串转日期: ' || TO_CHAR(v_date, 'YYYY-MM-DD'));
-- 使用CAST进行类型转换
v_timestamp := CAST(SYSDATE AS TIMESTAMP);
DBMS_OUTPUT.PUT_LINE('CAST转换: ' || v_timestamp);
END;
/到此这篇关于Oracle PL/SQL 从入门到精通的文章就介绍到这了,更多相关Oracle PL/SQL全解内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!
