oracle

关注公众号 jb51net

关闭
首页 > 数据库 > oracle > Oracle PL/SQL全解

Oracle PL/SQL 从入门到精通

作者:Chenxi Hu

文章介绍了PL/SQL语言的基本概念、特点、与SQL的区别、应用场景和语法结构,PL/SQL是Oracle数据库中用于编写复杂业务逻辑的程序化扩展语言,而Oracle APEX则是一个基于PL/SQL的低代码Web应用开发平台

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支持完整的过程化编程结构:

错误处理机制

EXCEPTION
  WHEN exception1 THEN
    -- 处理特定异常
  WHEN OTHERS THEN
    -- 处理其他所有异常

高性能特性

可移植性

PL/SQL代码可以在所有支持Oracle数据库的平台上运行,无需修改。

PL/SQL与SQL的区别

特性SQLPL/SQL
语言类型声明式语言过程化语言
执行方式单条语句执行块执行
流程控制完整的流程控制
错误处理有限强大的异常处理
变量支持支持变量和常量

PL/SQL的应用场景

Oracle APEX

Oracle APEX简介

Oracle APEX(Oracle Application Express)是Oracle推出的低代码Web应用开发平台

核心优势

**核心功能模块 **

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;

匿名块详解

匿名块的特点

匿名块的使用场景

-- 场景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;
/

命名块详解

命名块的类型

命名块的优势

  1. 代码重用:一次编写,多次调用
  2. 模块化:将复杂系统分解为小模块
  3. 安全性:通过权限控制访问
  4. 性能:预编译,执行效率高
  5. 维护性:集中管理,易于维护

块的嵌套和标签

嵌套块

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中的键值对集合
存储位置数据库表中数据库表中仅内存中
大小动态,无限制固定上限动态,无限制

选择建议

数据类型属性

%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全解内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

您可能感兴趣的文章:
阅读全文