【Oracle】游标

article/2025/6/10 16:43:32

在这里插入图片描述

个人主页:Guiat
归属专栏:Oracle

在这里插入图片描述

文章目录

  • 1. 游标基础概述
    • 1.1 游标的概念与作用
    • 1.2 游标的生命周期
    • 1.3 游标的分类
  • 2. 显式游标
    • 2.1 显式游标的基本语法
      • 2.1.1 声明游标
      • 2.1.2 带参数的游标
    • 2.2 游标的基本操作
      • 2.2.1 完整的游标操作示例
    • 2.3 游标属性
      • 2.3.1 游标属性应用示例
    • 2.4 游标FOR循环
      • 2.4.1 基本游标FOR循环
      • 2.4.2 内联游标FOR循环
      • 2.4.3 带参数的游标FOR循环
  • 3. 隐式游标
    • 3.1 隐式游标的特点
    • 3.2 隐式游标应用示例
      • 3.2.1 DML操作中的隐式游标
      • 3.2.2 SELECT INTO语句中的隐式游标
    • 3.3 隐式游标与异常处理
  • 4. REF游标
    • 4.1 REF游标类型
    • 4.2 强类型REF游标
      • 4.2.1 声明和使用强类型REF游标
      • 4.2.2 自定义记录类型的REF游标
    • 4.3 弱类型REF游标
      • 4.3.1 使用SYS_REFCURSOR
      • 4.3.2 动态查询处理
    • 4.4 REF游标作为参数传递
      • 4.4.1 函数返回REF游标
      • 4.4.2 存储过程的OUT参数REF游标
  • 5. 游标高级特性
    • 5.1 可更新游标
      • 5.1.1 FOR UPDATE子句
      • 5.1.2 选择性锁定
    • 5.2 批量操作(BULK COLLECT)
      • 5.2.1 基本BULK COLLECT
      • 5.2.2 带LIMIT的BULK COLLECT
      • 5.2.3 FORALL批量DML操作
  • 6. 游标性能优化
    • 6.1 游标性能考虑因素
    • 6.2 性能对比示例
      • 6.2.1 传统处理 vs BULK COLLECT

正文

1. 游标基础概述

游标是Oracle PL/SQL中用于处理查询结果集的重要机制,它允许我们逐行处理SQL查询返回的数据,为复杂的数据处理提供了强大的控制能力。

1.1 游标的概念与作用

游标本质上是指向查询结果集中某一行的指针,通过移动指针来逐行访问和处理数据。

查询结果集
第1行
第2行
第3行
第n行
游标指针

1.2 游标的生命周期

游标的完整生命周期包含四个关键阶段:

声明游标
打开游标
读取数据
还有数据?
关闭游标

1.3 游标的分类

Oracle提供了多种类型的游标来满足不同的需求:

Oracle游标类型
显式游标
隐式游标
REF游标
游标变量
用户定义和控制
手动管理生命周期
Oracle自动管理
单行SELECT或DML
强类型REF游标
弱类型REF游标
动态查询支持
基于游标的变量
可传递参数

2. 显式游标

显式游标是程序员显式声明、打开、读取和关闭的游标,提供了对查询结果集的完全控制。

2.1 显式游标的基本语法

2.1.1 声明游标

-- 基本游标声明
DECLARECURSOR emp_cursor ISSELECT employee_id, first_name, last_name, salaryFROM employeesWHERE department_id = 10;v_emp_id employees.employee_id%TYPE;v_first_name employees.first_name%TYPE;v_last_name employees.last_name%TYPE;v_salary employees.salary%TYPE;
BEGIN-- 游标操作NULL;
END;
/

2.1.2 带参数的游标

DECLARE-- 带参数的游标声明CURSOR emp_dept_cursor(p_dept_id NUMBER) ISSELECT employee_id, first_name, last_name, salaryFROM employeesWHERE department_id = p_dept_idORDER BY salary DESC;-- 使用%ROWTYPE简化变量声明emp_record emp_dept_cursor%ROWTYPE;
BEGIN-- 打开游标时传递参数OPEN emp_dept_cursor(20);LOOPFETCH emp_dept_cursor INTO emp_record;EXIT WHEN emp_dept_cursor%NOTFOUND;DBMS_OUTPUT.PUT_LINE('员工ID: ' || emp_record.employee_id || ', 姓名: ' || emp_record.first_name || ' ' || emp_record.last_name ||', 工资: ' || emp_record.salary);END LOOP;CLOSE emp_dept_cursor;
END;
/

2.2 游标的基本操作

2.2.1 完整的游标操作示例

DECLARE-- 声明游标CURSOR salary_cursor ISSELECT employee_id, first_name, last_name, salary, department_idFROM employeesWHERE salary > 5000ORDER BY salary DESC;-- 声明记录类型变量emp_rec salary_cursor%ROWTYPE;v_counter NUMBER := 0;v_total_salary NUMBER := 0;BEGINDBMS_OUTPUT.PUT_LINE('=== 高薪员工报告 ===');-- 打开游标OPEN salary_cursor;-- 读取数据LOOPFETCH salary_cursor INTO emp_rec;-- 检查是否还有数据EXIT WHEN salary_cursor%NOTFOUND;v_counter := v_counter + 1;v_total_salary := v_total_salary + emp_rec.salary;DBMS_OUTPUT.PUT_LINE(v_counter || '. ' || emp_rec.first_name || ' ' || emp_rec.last_name ||' (ID: ' || emp_rec.employee_id || ')' ||' - 工资: $' || emp_rec.salary ||' - 部门: ' || emp_rec.department_id);END LOOP;-- 关闭游标CLOSE salary_cursor;-- 统计信息DBMS_OUTPUT.PUT_LINE('====================');DBMS_OUTPUT.PUT_LINE('总计: ' || v_counter || ' 名高薪员工');DBMS_OUTPUT.PUT_LINE('平均工资: $' || ROUND(v_total_salary / v_counter, 2));EXCEPTIONWHEN OTHERS THEN-- 确保游标关闭IF salary_cursor%ISOPEN THENCLOSE salary_cursor;END IF;RAISE;
END;
/

2.3 游标属性

Oracle提供了多个游标属性来检查游标状态:

游标属性
%FOUND
%NOTFOUND
%ROWCOUNT
%ISOPEN
返回TRUE如果上次FETCH成功
返回TRUE如果上次FETCH失败
返回已读取的行数
返回TRUE如果游标已打开

2.3.1 游标属性应用示例

DECLARECURSOR dept_cursor ISSELECT department_id, department_name, manager_idFROM departmentsWHERE department_id BETWEEN 10 AND 50;dept_rec dept_cursor%ROWTYPE;BEGIN-- 检查游标是否已打开IF NOT dept_cursor%ISOPEN THENOPEN dept_cursor;DBMS_OUTPUT.PUT_LINE('游标已打开');END IF;LOOPFETCH dept_cursor INTO dept_rec;-- 使用%FOUND属性IF dept_cursor%FOUND THENDBMS_OUTPUT.PUT_LINE('第 ' || dept_cursor%ROWCOUNT || ' 行: ' ||dept_rec.department_name || ' (ID: ' || dept_rec.department_id || ')');END IF;-- 使用%NOTFOUND属性退出循环EXIT WHEN dept_cursor%NOTFOUND;END LOOP;DBMS_OUTPUT.PUT_LINE('总共处理了 ' || dept_cursor%ROWCOUNT || ' 个部门');-- 关闭游标CLOSE dept_cursor;-- 验证游标已关闭IF NOT dept_cursor%ISOPEN THENDBMS_OUTPUT.PUT_LINE('游标已关闭');END IF;END;
/

2.4 游标FOR循环

游标FOR循环是处理游标的简化语法,自动处理游标的打开、读取和关闭:

2.4.1 基本游标FOR循环

DECLARECURSOR emp_cursor ISSELECT employee_id, first_name, last_name, hire_date, salaryFROM employeesWHERE department_id = 20ORDER BY hire_date;BEGINDBMS_OUTPUT.PUT_LINE('=== 部门20员工信息 ===');-- 游标FOR循环 - 自动管理游标生命周期FOR emp_rec IN emp_cursor LOOPDBMS_OUTPUT.PUT_LINE('员工: ' || emp_rec.first_name || ' ' || emp_rec.last_name ||', 入职日期: ' || TO_CHAR(emp_rec.hire_date, 'YYYY-MM-DD') ||', 工资: $' || emp_rec.salary);END LOOP;END;
/

2.4.2 内联游标FOR循环

BEGINDBMS_OUTPUT.PUT_LINE('=== 各部门平均工资统计 ===');-- 内联游标FOR循环 - 无需显式声明游标FOR dept_rec IN (SELECT d.department_name, ROUND(AVG(e.salary), 2) as avg_salary,COUNT(e.employee_id) as emp_countFROM departments dJOIN employees e ON d.department_id = e.department_idGROUP BY d.department_nameORDER BY avg_salary DESC) LOOPDBMS_OUTPUT.PUT_LINE('部门: ' || dept_rec.department_name ||', 平均工资: $' || dept_rec.avg_salary ||', 员工数: ' || dept_rec.emp_count);END LOOP;END;
/

2.4.3 带参数的游标FOR循环

DECLARECURSOR salary_range_cursor(p_min_sal NUMBER, p_max_sal NUMBER) ISSELECT employee_id, first_name, last_name, salary, department_idFROM employeesWHERE salary BETWEEN p_min_sal AND p_max_salORDER BY salary;BEGINDBMS_OUTPUT.PUT_LINE('=== 工资范围 $5000-$10000 的员工 ===');FOR emp_rec IN salary_range_cursor(5000, 10000) LOOPDBMS_OUTPUT.PUT_LINE('ID: ' || emp_rec.employee_id ||', 姓名: ' || emp_rec.first_name || ' ' || emp_rec.last_name ||', 工资: $' || emp_rec.salary ||', 部门: ' || emp_rec.department_id);END LOOP;END;
/

3. 隐式游标

隐式游标是Oracle自动为每个DML语句和单行SELECT语句创建的游标,由系统自动管理。

3.1 隐式游标的特点

隐式游标特点
系统自动管理
SQL%属性访问
单行操作优化
无需显式声明
自动打开和关闭
SQL%FOUND
SQL%NOTFOUND
SQL%ROWCOUNT
SQL%ISOPEN
INSERT/UPDATE/DELETE
单行SELECT INTO

3.2 隐式游标应用示例

3.2.1 DML操作中的隐式游标

DECLAREv_dept_id NUMBER := 90;v_location_id NUMBER := 1700;v_affected_rows NUMBER;BEGIN-- 插入操作INSERT INTO departments (department_id, department_name, location_id)VALUES (v_dept_id, 'New Department', v_location_id);-- 检查插入是否成功IF SQL%FOUND THENDBMS_OUTPUT.PUT_LINE('部门插入成功,影响行数: ' || SQL%ROWCOUNT);ELSEDBMS_OUTPUT.PUT_LINE('部门插入失败');END IF;-- 更新操作UPDATE employeesSET salary = salary * 1.05WHERE department_id = 20 AND salary < 8000;v_affected_rows := SQL%ROWCOUNT;IF v_affected_rows > 0 THENDBMS_OUTPUT.PUT_LINE('成功给 ' || v_affected_rows || ' 名员工加薪5%');ELSEDBMS_OUTPUT.PUT_LINE('没有符合条件的员工需要加薪');END IF;-- 删除操作DELETE FROM departments WHERE department_id = v_dept_id;IF SQL%FOUND THENDBMS_OUTPUT.PUT_LINE('部门删除成功');END IF;-- 注意:隐式游标的%ISOPEN始终返回FALSE-- 因为它在语句执行后立即关闭DBMS_OUTPUT.PUT_LINE('隐式游标是否打开: ' || CASE WHEN SQL%ISOPEN THEN 'TRUE' ELSE 'FALSE' END);END;
/

3.2.2 SELECT INTO语句中的隐式游标

DECLAREv_emp_name VARCHAR2(100);v_emp_salary NUMBER;v_emp_id NUMBER := 100;BEGIN-- 单行SELECT INTO使用隐式游标BEGINSELECT first_name || ' ' || last_name, salaryINTO v_emp_name, v_emp_salaryFROM employeesWHERE employee_id = v_emp_id;-- 检查是否找到记录IF SQL%FOUND THENDBMS_OUTPUT.PUT_LINE('找到员工: ' || v_emp_name || ', 工资: $' || v_emp_salary);END IF;EXCEPTIONWHEN NO_DATA_FOUND THENDBMS_OUTPUT.PUT_LINE('没有找到员工ID为 ' || v_emp_id || ' 的记录');WHEN TOO_MANY_ROWS THENDBMS_OUTPUT.PUT_LINE('查询返回了多行记录');END;END;
/

3.3 隐式游标与异常处理

隐式游标的使用需要特别注意异常处理:

CREATE OR REPLACE PROCEDURE process_employee_bonus(p_emp_id NUMBER, p_bonus_pct NUMBER)
ASv_current_salary NUMBER;v_new_bonus NUMBER;v_emp_name VARCHAR2(100);BEGIN-- 获取员工信息BEGINSELECT salary, first_name || ' ' || last_nameINTO v_current_salary, v_emp_nameFROM employeesWHERE employee_id = p_emp_id;-- 计算奖金v_new_bonus := v_current_salary * p_bonus_pct / 100;DBMS_OUTPUT.PUT_LINE('员工 ' || v_emp_name || ' 当前工资: $' || v_current_salary);DBMS_OUTPUT.PUT_LINE('计算奖金 ' || p_bonus_pct || '%: $' || v_new_bonus);-- 更新奖金(假设有bonus列)-- UPDATE employees SET bonus = v_new_bonus WHERE employee_id = p_emp_id;IF SQL%ROWCOUNT > 0 THENDBMS_OUTPUT.PUT_LINE('奖金更新成功');END IF;EXCEPTIONWHEN NO_DATA_FOUND THENDBMS_OUTPUT.PUT_LINE('错误: 员工ID ' || p_emp_id || ' 不存在');WHEN TOO_MANY_ROWS THENDBMS_OUTPUT.PUT_LINE('错误: 查询返回多个员工记录');END;END;
/-- 调用存储过程
BEGINprocess_employee_bonus(100, 10); -- 给员工100发放10%奖金process_employee_bonus(999, 5);  -- 不存在的员工ID
END;
/

4. REF游标

REF游标(游标变量)是一种特殊的游标类型,支持动态SQL和在子程序之间传递游标。

4.1 REF游标类型

REF游标类型
强类型REF游标
弱类型REF游标
指定返回类型
编译时类型检查
更好的性能
SYS_REFCURSOR
运行时确定类型
最大灵活性

4.2 强类型REF游标

4.2.1 声明和使用强类型REF游标

DECLARE-- 定义强类型REF游标TYPE emp_cursor_type IS REF CURSOR RETURN employees%ROWTYPE;-- 声明游标变量emp_cursor emp_cursor_type;emp_record employees%ROWTYPE;v_dept_id NUMBER := 20;BEGIN-- 打开游标OPEN emp_cursor FORSELECT * FROM employees WHERE department_id = v_dept_idORDER BY salary DESC;DBMS_OUTPUT.PUT_LINE('=== 部门 ' || v_dept_id || ' 员工列表 ===');LOOPFETCH emp_cursor INTO emp_record;EXIT WHEN emp_cursor%NOTFOUND;DBMS_OUTPUT.PUT_LINE('ID: ' || emp_record.employee_id ||', 姓名: ' || emp_record.first_name || ' ' || emp_record.last_name ||', 工资: $' || emp_record.salary);END LOOP;CLOSE emp_cursor;DBMS_OUTPUT.PUT_LINE('总共处理了 ' || emp_cursor%ROWCOUNT || ' 名员工');END;
/

4.2.2 自定义记录类型的REF游标

DECLARE-- 定义自定义记录类型TYPE emp_summary_rec IS RECORD (emp_id NUMBER,full_name VARCHAR2(100),department VARCHAR2(50),salary NUMBER,hire_year NUMBER);-- 定义基于记录类型的REF游标TYPE emp_summary_cursor_type IS REF CURSOR RETURN emp_summary_rec;emp_cursor emp_summary_cursor_type;emp_rec emp_summary_rec;BEGIN-- 打开游标OPEN emp_cursor FORSELECT e.employee_id,e.first_name || ' ' || e.last_name,d.department_name,e.salary,EXTRACT(YEAR FROM e.hire_date)FROM employees eJOIN departments d ON e.department_id = d.department_idWHERE e.salary > 8000ORDER BY e.salary DESC;DBMS_OUTPUT.PUT_LINE('=== 高薪员工摘要报告 ===');LOOPFETCH emp_cursor INTO emp_rec;EXIT WHEN emp_cursor%NOTFOUND;DBMS_OUTPUT.PUT_LINE('员工: ' || emp_rec.full_name ||', 部门: ' || emp_rec.department ||', 工资: $' || emp_rec.salary ||', 入职年份: ' || emp_rec.hire_year);END LOOP;CLOSE emp_cursor;END;
/

4.3 弱类型REF游标

4.3.1 使用SYS_REFCURSOR

DECLARE-- 声明弱类型REF游标my_cursor SYS_REFCURSOR;v_sql VARCHAR2(1000);v_table_name VARCHAR2(30) := 'employees';v_condition VARCHAR2(100) := 'department_id = 10';-- 动态处理不同的查询结果v_employee_id NUMBER;v_first_name VARCHAR2(50);v_last_name VARCHAR2(50);v_department_id NUMBER;v_salary NUMBER;BEGIN-- 构建动态SQLv_sql := 'SELECT employee_id, first_name, last_name, department_id, salary FROM ' || v_table_name || ' WHERE ' || v_condition || 'ORDER BY salary DESC';DBMS_OUTPUT.PUT_LINE('执行SQL: ' || v_sql);DBMS_OUTPUT.PUT_LINE('======================');-- 打开游标OPEN my_cursor FOR v_sql;LOOPFETCH my_cursor INTO v_employee_id, v_first_name, v_last_name, v_department_id, v_salary;EXIT WHEN my_cursor%NOTFOUND;DBMS_OUTPUT.PUT_LINE('ID: ' || v_employee_id ||', 姓名: ' || v_first_name || ' ' || v_last_name ||', 部门: ' || v_department_id ||', 工资: $' || v_salary);END LOOP;CLOSE my_cursor;END;
/

4.3.2 动态查询处理

CREATE OR REPLACE PROCEDURE dynamic_query_processor(p_table_name IN VARCHAR2,p_where_clause IN VARCHAR2 DEFAULT NULL,p_order_clause IN VARCHAR2 DEFAULT NULL
)
ASquery_cursor SYS_REFCURSOR;v_sql VARCHAR2(4000);-- 使用DBMS_SQL.DESCRIBE_COLUMNS来处理不同的列类型v_desc_tab DBMS_SQL.DESC_TAB;v_col_cnt NUMBER;v_cursor_id NUMBER;BEGIN-- 构建基本SQLv_sql := 'SELECT * FROM ' || p_table_name;IF p_where_clause IS NOT NULL THENv_sql := v_sql || ' WHERE ' || p_where_clause;END IF;IF p_order_clause IS NOT NULL THENv_sql := v_sql || ' ORDER BY ' || p_order_clause;END IF;DBMS_OUTPUT.PUT_LINE('执行动态查询: ' || v_sql);DBMS_OUTPUT.PUT_LINE('===========================================');-- 打开游标OPEN query_cursor FOR v_sql;-- 这里简化处理,实际应用中可能需要更复杂的元数据处理DBMS_OUTPUT.PUT_LINE('查询执行成功,结果集已准备就绪');CLOSE query_cursor;EXCEPTIONWHEN OTHERS THENDBMS_OUTPUT.PUT_LINE('查询执行出错: ' || SQLERRM);IF query_cursor%ISOPEN THENCLOSE query_cursor;END IF;
END;
/-- 调用动态查询处理器
BEGINdynamic_query_processor('employees', 'salary > 5000', 'salary DESC');dynamic_query_processor('departments', NULL, 'department_name');
END;
/

4.4 REF游标作为参数传递

4.4.1 函数返回REF游标

CREATE OR REPLACE FUNCTION get_employees_by_dept(p_dept_id NUMBER)
RETURN SYS_REFCURSOR
ASemp_cursor SYS_REFCURSOR;
BEGINOPEN emp_cursor FORSELECT employee_id, first_name, last_name, email, salary, hire_dateFROM employeesWHERE department_id = p_dept_idORDER BY hire_date;RETURN emp_cursor;
END;
/-- 使用返回的REF游标
DECLAREemp_cursor SYS_REFCURSOR;v_emp_id NUMBER;v_first_name VARCHAR2(50);v_last_name VARCHAR2(50);v_email VARCHAR2(100);v_salary NUMBER;v_hire_date DATE;BEGIN-- 获取游标emp_cursor := get_employees_by_dept(20);DBMS_OUTPUT.PUT_LINE('=== 部门20员工列表 ===');LOOPFETCH emp_cursor INTO v_emp_id, v_first_name, v_last_name, v_email, v_salary, v_hire_date;EXIT WHEN emp_cursor%NOTFOUND;DBMS_OUTPUT.PUT_LINE('ID: ' || v_emp_id ||', 姓名: ' || v_first_name || ' ' || v_last_name ||', 邮箱: ' || v_email ||', 工资: $' || v_salary ||', 入职: ' || TO_CHAR(v_hire_date, 'YYYY-MM-DD'));END LOOP;CLOSE emp_cursor;END;
/

4.4.2 存储过程的OUT参数REF游标

CREATE OR REPLACE PROCEDURE get_salary_statistics(p_dept_id IN NUMBER,p_emp_cursor OUT SYS_REFCURSOR,p_total_employees OUT NUMBER,p_avg_salary OUT NUMBER,p_min_salary OUT NUMBER,p_max_salary OUT NUMBER
)
AS
BEGIN-- 获取统计信息SELECT COUNT(*), ROUND(AVG(salary), 2),MIN(salary),MAX(salary)INTO p_total_employees, p_avg_salary, p_min_salary, p_max_salaryFROM employeesWHERE department_id = p_dept_id;-- 打开游标返回详细信息OPEN p_emp_cursor FORSELECT employee_id, first_name || ' ' || last_name as full_name,salary,ROUND((salary - p_avg_salary), 2) as salary_diff,CASE WHEN salary > p_avg_salary THEN '高于平均'WHEN salary < p_avg_salary THEN '低于平均'ELSE '等于平均'END as salary_levelFROM employeesWHERE department_id = p_dept_idORDER BY salary DESC;END;
/-- 使用OUT参数REF游标
DECLAREemp_cursor SYS_REFCURSOR;v_total_count NUMBER;v_avg_sal NUMBER;v_min_sal NUMBER;v_max_sal NUMBER;v_emp_id NUMBER;v_full_name VARCHAR2(100);v_salary NUMBER;v_salary_diff NUMBER;v_salary_level VARCHAR2(20);BEGIN-- 调用存储过程get_salary_statistics(20, emp_cursor, v_total_count, v_avg_sal, v_min_sal, v_max_sal);-- 显示统计信息DBMS_OUTPUT.PUT_LINE('=== 部门20工资统计 ===');DBMS_OUTPUT.PUT_LINE('员工总数: ' || v_total_count);DBMS_OUTPUT.PUT_LINE('平均工资: $' || v_avg_sal);DBMS_OUTPUT.PUT_LINE('最低工资: $' || v_min_sal);DBMS_OUTPUT.PUT_LINE('最高工资: $' || v_max_sal);DBMS_OUTPUT.PUT_LINE('========================');-- 显示详细信息LOOPFETCH emp_cursor INTO v_emp_id, v_full_name, v_salary, v_salary_diff, v_salary_level;EXIT WHEN emp_cursor%NOTFOUND;DBMS_OUTPUT.PUT_LINE('员工: ' || v_full_name ||', 工资: $' || v_salary ||', 与平均差: $' || v_salary_diff ||' (' || v_salary_level || ')');END LOOP;CLOSE emp_cursor;END;
/

5. 游标高级特性

5.1 可更新游标

可更新游标允许通过游标直接更新或删除当前行。

5.1.1 FOR UPDATE子句

DECLARECURSOR emp_cursor ISSELECT employee_id, first_name, last_name, salary, department_idFROM employeesWHERE department_id = 20FOR UPDATE; -- 锁定查询的行emp_rec emp_cursor%ROWTYPE;v_new_salary NUMBER;BEGINDBMS_OUTPUT.PUT_LINE('=== 部门20员工工资调整 ===');OPEN emp_cursor;LOOPFETCH emp_cursor INTO emp_rec;EXIT WHEN emp_cursor%NOTFOUND;-- 根据当前工资计算新工资IF emp_rec.salary < 5000 THENv_new_salary := emp_rec.salary * 1.15; -- 加薪15%ELSIF emp_rec.salary < 8000 THENv_new_salary := emp_rec.salary * 1.10; -- 加薪10%ELSEv_new_salary := emp_rec.salary * 1.05; -- 加薪5%END IF;-- 使用WHERE CURRENT OF更新当前行UPDATE employees SET salary = v_new_salaryWHERE CURRENT OF emp_cursor;DBMS_OUTPUT.PUT_LINE('员工 ' || emp_rec.first_name || ' ' || emp_rec.last_name ||': $' || emp_rec.salary || ' -> $' || v_new_salary ||' (涨幅: ' || ROUND(((v_new_salary - emp_rec.salary) / emp_rec.salary * 100), 1) || '%)');END LOOP;CLOSE emp_cursor;COMMIT;DBMS_OUTPUT.PUT_LINE('所有工资调整已提交');END;
/

5.1.2 选择性锁定

DECLARECURSOR emp_cursor ISSELECT employee_id, first_name, last_name, salary, commission_pctFROM employeesWHERE department_id IN (80, 90)FOR UPDATE OF salary NOWAIT; -- 只锁定salary列,不等待emp_rec emp_cursor%ROWTYPE;v_bonus NUMBER;BEGINDBMS_OUTPUT.PUT_LINE('=== 销售和管理部门绩效奖金计算 ===');OPEN emp_cursor;LOOPFETCH emp_cursor INTO emp_rec;EXIT WHEN emp_cursor%NOTFOUND;-- 计算绩效奖金IF emp_rec.commission_pct IS NOT NULL THENv_bonus := emp_rec.salary * emp_rec.commission_pct; -- 有提成的员工ELSEv_bonus := emp_rec.salary * 0.05; -- 无提成员工给5%奖金END IF;DBMS_OUTPUT.PUT_LINE('员工 ' || emp_rec.first_name || ' ' || emp_rec.last_name ||', 基本工资: $' || emp_rec.salary ||', 绩效奖金: $' || ROUND(v_bonus, 2));-- 可以在这里更新奖金字段-- UPDATE employees SET bonus = v_bonus WHERE CURRENT OF emp_cursor;END LOOP;CLOSE emp_cursor;EXCEPTIONWHEN OTHERS THENIF emp_cursor%ISOPEN THENCLOSE emp_cursor;END IF;IF SQLCODE = -54 THEN -- Resource busyDBMS_OUTPUT.PUT_LINE('错误: 记录正被其他会话使用');ELSEDBMS_OUTPUT.PUT_LINE('错误: ' || SQLERRM);END IF;
END;
/

5.2 批量操作(BULK COLLECT)

BULK COLLECT允许一次获取多行数据,提高性能。

5.2.1 基本BULK COLLECT

DECLARETYPE emp_id_array IS TABLE OF employees.employee_id%TYPE;TYPE emp_name_array IS TABLE OF VARCHAR2(100);TYPE emp_salary_array IS TABLE OF employees.salary%TYPE;v_emp_ids emp_id_array;v_emp_names emp_name_array;v_emp_salaries emp_salary_array;v_total_salary NUMBER := 0;BEGIN-- 使用BULK COLLECT一次获取所有数据SELECT employee_id, first_name || ' ' || last_name,salaryBULK COLLECT INTO v_emp_ids, v_emp_names, v_emp_salariesFROM employeesWHERE department_id = 50ORDER BY salary DESC;DBMS_OUTPUT.PUT_LINE('=== 部门50员工信息(共' || v_emp_ids.COUNT || '人)===');-- 处理批量数据FOR i IN 1..v_emp_ids.COUNT LOOPv_total_salary := v_total_salary + v_emp_salaries(i);DBMS_OUTPUT.PUT_LINE(i || '. ID: ' || v_emp_ids(i) ||', 姓名: ' || v_emp_names(i) ||', 工资: $' || v_emp_salaries(i));END LOOP;DBMS_OUTPUT.PUT_LINE('==============================');DBMS_OUTPUT.PUT_LINE('工资总额: $' || v_total_salary);DBMS_OUTPUT.PUT_LINE('平均工资: $' || ROUND(v_total_salary / v_emp_ids.COUNT, 2));END;
/

5.2.2 带LIMIT的BULK COLLECT

DECLARECURSOR large_table_cursor ISSELECT employee_id, first_name, last_name, salaryFROM employees;TYPE emp_record_array IS TABLE OF large_table_cursor%ROWTYPE;v_emp_batch emp_record_array;v_batch_size CONSTANT PLS_INTEGER := 100; -- 每批处理100行v_total_processed NUMBER := 0;BEGINDBMS_OUTPUT.PUT_LINE('=== 批量处理员工数据 ===');OPEN large_table_cursor;LOOP-- 使用LIMIT控制每次获取的行数FETCH large_table_cursor BULK COLLECT INTO v_emp_batch LIMIT v_batch_size;-- 处理当前批次的数据FOR i IN 1..v_emp_batch.COUNT LOOPv_total_processed := v_total_processed + 1;-- 这里可以进行复杂的业务处理-- 例如:数据转换、验证、插入到其他表等IF MOD(v_total_processed, 50) = 0 THENDBMS_OUTPUT.PUT_LINE('已处理 ' || v_total_processed || ' 条记录...');END IF;END LOOP;-- 可以在这里提交事务,避免长事务-- COMMIT;-- 如果这批数据少于批次大小,说明已到末尾EXIT WHEN v_emp_batch.COUNT < v_batch_size;END LOOP;CLOSE large_table_cursor;DBMS_OUTPUT.PUT_LINE('批量处理完成,总共处理 ' || v_total_processed || ' 条记录');END;
/

5.2.3 FORALL批量DML操作

DECLARETYPE emp_id_array IS TABLE OF employees.employee_id%TYPE;TYPE salary_array IS TABLE OF employees.salary%TYPE;v_emp_ids emp_id_array;v_old_salaries salary_array;v_new_salaries salary_array;BEGIN-- 获取需要调薪的员工信息SELECT employee_id, salaryBULK COLLECT INTO v_emp_ids, v_old_salariesFROM employeesWHERE department_id = 30AND salary < 6000;-- 计算新工资v_new_salaries := salary_array();v_new_salaries.EXTEND(v_emp_ids.COUNT);FOR i IN 1..v_emp_ids.COUNT LOOPv_new_salaries(i) := v_old_salaries(i) * 1.12; -- 加薪12%END LOOP;DBMS_OUTPUT.PUT_LINE('=== 批量工资调整 ===');DBMS_OUTPUT.PUT_LINE('准备调整 ' || v_emp_ids.COUNT || ' 名员工的工资');-- 使用FORALL进行批量更新FORALL i IN 1..v_emp_ids.COUNTUPDATE employeesSET salary = v_new_salaries(i)WHERE employee_id = v_emp_ids(i);DBMS_OUTPUT.PUT_LINE('批量更新完成,影响行数: ' || SQL%ROWCOUNT);-- 显示调整详情FOR i IN 1..v_emp_ids.COUNT LOOPDBMS_OUTPUT.PUT_LINE('员工ID ' || v_emp_ids(i) ||': $' || v_old_salaries(i) ||' -> $' || v_new_salaries(i));END LOOP;COMMIT;END;
/

6. 游标性能优化

6.1 游标性能考虑因素

游标性能优化
选择合适的游标类型
合理使用BULK COLLECT
优化SQL查询
控制游标作用域
避免频繁开关游标
显式 vs 隐式
强类型 vs 弱类型
批量处理
内存使用控制
索引优化
执行计划分析
及时关闭游标
游标变量传递
游标缓存
连接池使用

6.2 性能对比示例

6.2.1 传统处理 vs BULK COLLECT

-- 传统逐行处理方式
CREATE OR REPLACE PROCEDURE process_employees_traditional
ASCURSOR emp_cursor ISSELECT employee_id, salaryFROM employees;emp_rec emp_cursor%ROWTYPE;v_start_time NUMBER;v_end_time NUMBER;v_count NUMBER := 0;BEGINv_start_time := DBMS_UTILITY.GET_TIME;OPEN emp_cursor;LOOPFETCH emp_cursor INTO emp_rec;EXIT WHEN emp_cursor%NOTFOUND;-- 模拟处理操作v_count := v_count + 1;-- 可以在这里进行具体的业务处理NULL;END LOOP;CLOSE emp_cursor;v_end_time := DBMS_UTILITY.GET_TIME;DBMS_OUTPUT.PUT_LINE('传统方式处理 ' || v_count || ' 条记录');DBMS_OUTPUT.PUT_LINE('耗时: ' || (v_end_time - v_start_time) / 100 || ' 秒');END;
/-- BULK COLLECT批量处理方式
CREATE OR REPLACE PROCEDURE process_employees_bulk
ASTYPE emp_record_array IS TABLE OF employees%ROWTYPE;v_employees emp_record_array;v_start_time NUMBER;v_end_time NUMBER;v_count NUMBER := 0;BEGINv_start_time := DBMS_UTILITY.GET_TIME;SELECT * BULK COLLECT INTO v_employees FROM employees;FOR i IN 1..v_employees.COUNT LOOPv_count := v_count + 1;-- 处理每条记录NULL;END LOOP;v_end_time := DBMS_UTILITY.GET_TIME;DBMS_OUTPUT.PUT_LINE('BULK COLLECT方式处理 ' || v_count || ' 条记录');DBMS_OUTPUT.PUT_LINE('耗时: ' || (v_end_time - v_start_time) / 100 || ' 秒');END;
/-- 性能测试
BEGINDBMS_OUTPUT.PUT_LINE('=== 游标性能对比测试 ===');process_employees_traditional;DBMS_OUTPUT.PUT_LINE('---');process_employees_bulk;
END;
/

结语
感谢您的阅读!期待您的一键三连!欢迎指正!

在这里插入图片描述


http://www.hkcw.cn/article/GeLkDVgpwk.shtml

相关文章

Ethernet/IP转DeviceNet网关:驱动大型矿山自动化升级的核心纽带

在大型矿山自动化系统中&#xff0c;如何高效整合新老设备、打通数据孤岛、实现统一控制&#xff0c;是提升效率与安全的关键挑战。JH-EIP-DVN疆鸿智能EtherNet/IP转DeviceNet网关&#xff0c;正是解决这一难题的核心桥梁&#xff0c;为矿山各环节注入强劲连接力&#xff1a; …

Nginx + Tomcat 负载均衡、动静分离群集

一、 nginx 简介 Nginx 是一款轻量级的高性能 Web 服务器、反向代理服务器及电子邮件&#xff08;IMAP/POP3&#xff09;代理服务器&#xff0c;在 BSD-like 协议下发行。其特点是占有内存少&#xff0c;并发能力强&#xff0c;在同类型的网页服务器中表现优异&#xff0c;常用…

5.Nginx+Tomcat负载均衡群集

Tomcat服务器应用场景&#xff1a;tomcat服务器是一个免费的开放源代码的Web应用服务器&#xff0c;属于轻量级应用服务器&#xff0c;在中小型系统和并发访问用户不是很多的场合下被普遍使用&#xff0c;是开发和调试JSP程序的首选。一般来说&#xff0c;Tomcat虽然和Apache或…

【算法设计与分析】实验——汽车加油问题, 删数问题(算法实现:代码,测试用例,结果分析,算法思路分析,总结)

说明&#xff1a;博主是大学生&#xff0c;有一门课是算法设计与分析&#xff0c;这是博主记录课程实验报告的内容&#xff0c;题目是老师给的&#xff0c;其他内容和代码均为原创&#xff0c;可以参考学习&#xff0c;转载和搬运需评论吱声并注明出处哦。 4-1算法实现题 汽车…

网络爬虫 - App爬虫及代理的使用(十一)

App爬虫及代理的使用 一、App抓包1. App爬虫原理2. reqable的安装与配置1. reqable安装教程2. reqable的配置3. 模拟器的安装与配置1. 夜神模拟器的安装2. 夜神模拟器的配置4. 内联调试及注意事项1. 软件启动顺序2. 开启抓包功能3. reqable面板功能4. 夜神模拟器设置项5. 注意事…

SQLite详细解读

一、SQLite 是什么&#xff1f; SQLite 是一个嵌入式关系型数据库管理系统&#xff08;RDBMS&#xff09;。它不是像 MySQL 或 PostgreSQL 那样的客户端-服务器数据库引擎&#xff0c;而是一个自包含的、无服务器的、零配置的、事务性的 SQL 数据库引擎。 核心特点 嵌入式/库…

线程池详细解析(三)

本章我们来讲一讲线程池的最后一个方法shutdown&#xff0c;这个方法的主要作用就是将线程池进行关闭 shutdown&#xff1a; public void shutdown() {ReentrantLock var1 this.mainLock;var1.lock();try {this.checkShutdownAccess();this.advanceRunState(0);this.interrup…

口碑对比:杭州白塔岭画室和燕壹画室哪个好?

从口碑方面来看&#xff0c;杭州燕壹画室和白塔岭画室各有特点&#xff0c;以下是具体分析&#xff1a; 燕壹画室 教学成果突出&#xff1a; 其前身燕壹设计工作室在2019 - 2023年专注美院校考设计&#xff0c;有一定的教学积淀&#xff0c;2023年转型后第一年攻联考就斩获浙…

车载雷达:超声波雷达、毫米波雷达、激光雷达相关技术场景介绍和技术比较

随着技术发展,如今的汽车智能化程度越来越高,配备的传感器也越来越多,特别是与辅助驾驶相关的汽车雷达,它们如同汽车的 “眼睛”,帮助车辆感知周围环境。为了适配不同的使用场景和功能需求,汽车雷达也分为很多类型,并且各具特点。 一、技术特点 一)超声波雷达 超声波…

Spring AI Advisor机制

Spring AI Advisors 是 Spring AI 框架中用于拦截和增强 AI 交互的核心组件&#xff0c;其设计灵感类似于 WebFilter&#xff0c;通过链式调用实现对请求和响应的处理5。以下是关键特性与实现细节&#xff1a; 核心功能 ‌1. 请求/响应拦截‌ 通过 AroundAdvisor 接口动态修…

GPTBots在AI大语言模型应用中敏感数据匿名化探索和实践

背景 随着人工智能技术的快速发展&#xff0c;尤其是大语言模型&#xff08;LLM-large language model&#xff09;在金融、医疗、客服等领域的广泛应用&#xff0c;处理海量数据已成为常态。然而&#xff0c;这些数据中往往包含个人可识别信息&#xff08;PII-Personally Ide…

使用 C++/OpenCV 制作跳动的爱心动画

使用 C/OpenCV 制作跳动的爱心动画 本文将引导你如何使用 C 和 OpenCV 库创建一个简单但有趣的跳动爱心动画。我们将通过绘制参数方程定义的爱心形状&#xff0c;并利用正弦函数来模拟心跳的缩放效果。 目录 简介先决条件核心概念 参数方程绘制爱心动画循环模拟心跳效果 代码…

入门AJAX——XMLHttpRequest(Get)

一、什么是 AJAX AJAX Asynchronous JavaScript And XML&#xff08;异步的 JavaScript 和 XML&#xff09;。 1、XML与异步JS XML: 是一种比较老的前后端数据传输格式&#xff08;已经几乎被 JSON 代替&#xff09;。它的格式与HTML类似&#xff0c;通过严格的闭合自定义标…

MDP的observations部分

文章目录 1.isaaclab的observations1.1 根状态相关观测base_pos_zbase_lin_vel &#xff08;use&#xff09;base_ang_vel &#xff08;use&#xff09;projected_gravity (use)root_pos_wroot_quat_wroot_lin_vel_wroot_ang_vel_w 1.2 关节状态相关观测joint_posjoint_pos_rel…

Rhino插件大全下载指南:解锁犀牛潜能,提升设计效率

Rhinoceros&#xff08;简称Rhino&#xff0c;犀牛&#xff09;以其强大的NURBS曲面建模能力、灵活的脚本环境以及与Grasshopper参数化设计工具的无缝集成&#xff0c;在全球工业设计、建筑设计、珠宝设计、船舶设计等领域备受推崇。为了进一步拓展Rhino的功能&#xff0c;满足…

百万级临床试验数据库TrialPanorama发布!AI助力新药研发与临床评价迎来新基石

2025年5月22日&#xff0c;伊利诺伊大学厄巴纳-香槟分校的研究团队在《arXiv》上发表了一篇前瞻性研究论文《TrialPanorama: Database and Benchmark for Systematic Review and Design of Clinical Trials》&#xff0c;该研究建立了一个临床试验数据库TrialPanorama&#xff…

运维 vm 虚拟机ip设置

虚拟网络设置 nat 模式 网卡 主机设置网卡地址 虚拟机绑定网卡

问题七、isaacsim中添加IMU传感器

0 前言 NVIDIA Isaac Sim 中的 IMU 传感器可跟踪车身运动并输出模拟加速度计和陀螺仪读数。与真实 IMU 传感器一样,模拟 IMU 可通过平台单元提供局部 x、y、z 轴的加速度和角速度测量值。 1 创建IMU传感器 按照下述步骤依次点击 使用python创建 基于IsaacSensorCreateImuSe…

AutoGenTestCase - 借助AI大模型生成测试用例

想象一下&#xff0c;你正在为一个复杂的支付系统编写测试用例&#xff0c;需求文档堆积如山&#xff0c;边缘场景层出不穷&#xff0c;手动编写让你焦头烂额。现在&#xff0c;有了AutoGenTestCase&#xff0c;这个AI驱动的“测试用例生成机”可以从需求文档中自动生成数百个测…

警惕假冒 CAPTCHA 攻击通过多阶段payload链部署远控和盗窃信息

在过去几个月中&#xff0c;Trend Micro 托管检测与响应&#xff08;MDR&#xff09;调查中发现假冒 CAPTCHA 的案例激增。这些 CAPTCHA 通过钓鱼邮件、URL 重定向、恶意广告或 SEO 污染投放。所有观察到的案例均表现出类似行为&#xff1a;指导用户将恶意命令复制粘贴到 Windo…