【Oracle】游标
个人主页: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 游标的生命周期
游标的完整生命周期包含四个关键阶段:
1.3 游标的分类
Oracle提供了多种类型的游标来满足不同的需求:
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提供了多个游标属性来检查游标状态:
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 隐式游标的特点
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游标类型
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 游标性能考虑因素
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;
/
结语
感谢您的阅读!期待您的一键三连!欢迎指正!
相关文章:

【Oracle】游标
个人主页: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…...
MySQL 中 char 与 varchar 的区别
在 MySQL 的字段类型中,char和varchar是用来处理字符串。本文来学习二者区别 一、本质区别:空间分配的 “固执” 与 “灵活” 1. char:空间占满 固定长度特性: 定义时指定长度(如char(10)),无…...
DeepSeek 赋能智能零售,解锁动态定价新范式
目录 一、引言二、智能零售动态定价策略概述2.1 动态定价的概念与原理2.2 动态定价在智能零售中的重要性2.3 传统动态定价策略的局限性 三、DeepSeek 技术解析3.1 DeepSeek 的技术原理与架构3.2 DeepSeek 的优势与特点 四、DeepSeek 在智能零售动态定价中的应用机制4.1 数据收集…...
在Flutter中定义全局对象(如$http)而不需要import
在Flutter中定义全局对象(如$http)而不需要import 在Flutter中,有几种方法可以定义全局可访问的对象(如$http)而不需要在每个文件中import: 方法1:使用GetX的依赖注入(推荐&#x…...

<4>, Qt窗口
目录 一,菜单栏 二,工具栏 三,状态栏 四,浮动窗口 五,对话框 一,菜单栏 MainWindow::MainWindow(QWidget *parent): QMainWindow(parent), ui(new Ui::MainWindow) {ui->setupUi(this);// 创建菜单栏…...

6.04打卡
浙大疏锦行 DAY 43 复习日 作业: kaggle找到一个图像数据集,用cnn网络进行训练并且用grad-cam做可视化 进阶:并拆分成多个文件 损失: 0.502 | 准确率: 75.53% 训练完成 import torch import torch.nn as nn import torch.optim as optim from…...

【基于SpringBoot的图书购买系统】操作Jedis对图书图书的增-删-改:从设计到实战的全栈开发指南
引言 在当今互联网应用开发中,缓存技术已成为提升系统性能和用户体验的关键组件。Redis作为一款高性能的键值存储数据库,以其丰富的数据结构、快速的读写能力和灵活的扩展性,被广泛应用于各类系统的缓存层设计。本文将围绕一个基于Redis的图…...
Ubuntu中TFTP服务器安装使用
TFTP服务器 在 Ubuntu 下使用 TFTP(Trivial File Transfer Protocol) 服务,通常用于简单的文件传输(如网络设备固件更新、嵌入式开发等)。 1 TFTP服务器安装 sudo apt-get install tftp-hpa sudo apt-get install…...

Spring Boot微服务架构(十):Docker与K8S部署的区别
Spring Boot微服务在Docker与Kubernetes(K8S)中的部署存在显著差异,主要体现在技术定位、管理能力、扩展性及适用场景等方面。以下是两者的核心区别及实践对比: 一、技术定位与核心功能 Docker 功能:专注于单节点容器化…...
接口重试的7种常用方案!
前言 记得五年前的一个深夜,某个电商平台的订单退款接口突发异常,因为银行系统网络抖动,退款请求连续失败。 原本技术团队只是想“好心重试几次”,结果开发小哥写的重试代码竟疯狂调用了银行的退款接口 82次! 最终导致…...

vue3:Table组件动态的字段(列)权限、显示隐藏和左侧固定
效果展示 根据后端接口返回,当前登录用户详情中的页面中el-table组件的显示隐藏等功能。根据菜单id查询该菜单下能后显示的列。 后端返回的数据类型: 接收到后端返回的数据后处理数据结构. Table组件文件 <!-- 自己封装的Table组件文件 --> onMounted(()>…...

pikachu靶场通关笔记13 XSS关卡09-XSS之href输出
目录 一、href 1、常见取值类型 2、使用示例 3、安全风险 二、源码分析 1、进入靶场 2、代码审计 3、渗透思路 三、渗透实战 1、注入payload1 2、注入payload2 3、注入payload3 本系列为通过《pikachu靶场通关笔记》的XSS关卡(共10关)渗透集合ÿ…...

MCP客户端Client开发流程
1. uv工具入门使用指南 1.1 uv入门介绍 MCP开发要求借助uv进行虚拟环境创建和依赖管理。 uv 是一个Python 依赖管理工具,类似于pip 和 conda ,但它更快、更高效,并且可以更好地管理 Python 虚拟环境和依赖项。它的核心目标是 替代 pip 、…...

学习日记-day21-6.3
完成目标: 目录 知识点: 1.集合_哈希表存储过程说明 2.集合_哈希表源码查看 3.集合_哈希表无索引&哈希表有序无序详解 4.集合_TreeSet和TreeMap 5.集合_Hashtable和Vector&Vector源码分析 6.集合_Properties属性集 7.集合_集合嵌套 8.…...

C语言探索之旅:深入理解结构体的奥秘
目录 引言 一、什么是结构体? 二、结构体类型的声明和初始化 1、结构体的声明 2、结构体的初始化 3、结构体的特殊声明 4、结构体的自引用 5、结构体的重命名 三、结构体的内存对齐 1、对齐规则 2、为什么存在内存对齐? 3、修改默认对齐数 三…...
uniapp 开发企业微信小程序,如何区别生产环境和测试环境?来处理不同的服务请求
在 uniapp 开发企业微信小程序时,区分生产环境和测试环境是常见需求。以下是几种可靠的方法,帮助你根据环境处理不同的服务请求: 一、通过条件编译区分(推荐) 使用 uniapp 的 条件编译 语法,在代码中标记…...
Dockerfile常用指令介绍
Dockerfile常用指令介绍 Dockerfile是一个文本文件,用于定义Docker镜像的构建过程。下面介绍一些最常用的Dockerfile指令及其用法: 基础指令 FROM - 指定基础镜像 FROM python:3.9-slim这是Dockerfile的第一个指令,用于指定构建镜像的基础镜…...
Docker 容器化:核心技术原理与实践
哈喽,大家好,我是左手python! Docker 的基本概念与核心组件 Docker 是一个开源的容器化平台,能够将应用程序及其依赖项打包成一个容器,确保在任何环境中都能一致运行。Docker 的核心在于其容器化技术,这种…...
不确定性分析在LEAP能源-环境系统建模中的整合与应用
本内容突出与实例结合,紧密结合国家能源统计制度及《省级温室气体排放编制指南》,深入浅出地介绍针对不同级别研究对象时如何根据数据结构、可获取性、研究目的,构建合适的能源生产、转换、消费、温室气体排放(以碳排放为主&#…...

经典算法回顾之最小生成树
最小生成树(Minimum Spanning Tree,简称MST)是图论中的一个重要概念,主要用于解决加权无向图中连接所有顶点且总权重最小的树结构问题。本文对两种经典的算法即Prim算法和Kruskal算法进行回顾,并对后者的正确性给出简单…...

Ubuntu下实现nginx反向代理
1. 多个ngx实例安装 脚本已经在deepseek的指导下完成啦! deepseek写的脚本支持ubuntu/centos两种系统。 ins_prefix"/usr/local/" makefile_gen() {ngx$1 ngx_log_dir"/var/log/"$ngx"/"ngx_temp_path"/var/temp/"${ngx}…...

c++ QicsTable使用实例
效果图: #include <QicsTable.h> #include <QicsDataModelDefault.h> #include <QVBoxLayout> Demo1::Demo1(QWidget *parent) : QWidget(parent) { ui.setupUi(this); const int numRows 10; const int numCols 5; // create th…...

在WordPress上添加隐私政策页面
在如今的互联网时代,保护用户隐私已经成为每个网站管理员的责任。隐私政策不仅是法律要求,还能提高用户对网站的信任。本文将介绍两种常用方法,帮助你在WordPress上轻松创建并发布隐私政策页面。这些方法简单易行,符合中国用户的阅…...
二维 根据矩阵变换计算镜像旋转角度
在二维变换中,镜像(Reflection) 是一种特殊的线性变换,它会将图形对称地翻转到某个轴线或点。镜像的存在会显著影响圆弧变换后的参数(圆心、半径、起始角度),尤其是在角度方向和旋转方向的处理上…...
你工作中涉及的安全方面的测试有哪些怎么回答
在面试或工作总结中,回答 **“工作中涉及的安全测试”** 时,可以结合具体场景、测试方法和工具,突出你的技术广度和深度。以下是结构化回答建议: --- ### **1. 分类说明安全测试范围** #### **(1) Web 应用安全测试** - **OWASP…...

阿里云ACP云计算备考笔记 (3)——云服务器ECS
目录 第一章 整体概览 第二章 ECS简介 1、产品概念 2、ECS对比本地IDC 3、BGP机房优势 第三章 ECS实例 1、实例规格族 2、实例系列 3、应用场景推荐选型 4、实例状态 5、创建实例 ① 完成基础配置 ② 完成网络和安全组配置 ③ 完成管理配置和高级选项 ④ 确认下单…...
Eigen实现非线性最小二乘拟合 + Gauss-Newton算法
下面是使用 Eigen 实现的 非线性最小二乘拟合 Gauss-Newton 算法 的完整示例,拟合模型为: 拟合目标模型: y exp ( a x 2 b x c ) y \exp(a x^2 b x c) yexp(ax2bxc) 已知一组带噪声数据点 ( x i , y i ) (x_i, y_i) (xi,yi)&…...
区块链技术:原理、应用与发展趋势
区块链技术:原理、应用与发展趋势 引言 区块链作为一种去中心化的分布式账本技术,自2008年比特币白皮书发布以来,已经从简单的加密货币底层技术发展成为具有广泛应用前景的创新技术。区块链通过独特的数据结构和加密机制,实现了…...

从零开始:用Tkinter打造你的第一个Python桌面应用
目录 一、界面搭建:像搭积木一样组合控件 二、菜单系统:给应用装上“控制中枢” 三、事件驱动:让界面“活”起来 四、进阶技巧:打造专业级体验 五、部署发布:让作品触手可及 六、学习路径建议 在Python生态中&am…...

Web开发主流前后端框架总结
🖥 一、前端主流框架 前端框架的核心是提升用户界面开发效率,实现高交互性应用。当前三大主流框架各有侧重: React (Meta/Facebook) 核心特点:采用组件化架构与虚拟DOM技术(减少真实DOM操作,优化渲染性能&…...