一、游标定义
游标(Cursor)是PL/SQL中用于逐行处理查询结果集的数据库对象,相当于指向结果集的指针。主要分为两种类型:
类型 | 特点 | 生命周期 |
---|---|---|
隐式游标 | 自动创建,处理单行查询/DML操作 | 自动管理 |
显式游标 | 手动声明,处理多行结果集 | 开发者控制 |
二、显式游标完整用法
1. 基础四步操作
DECLARE
-- 1.声明游标
CURSOR emp_cursor IS
SELECT employee_id, first_name, salary
FROM employees
WHERE department_id = 60;
v_id employees.employee_id%TYPE;
v_name employees.first_name%TYPE;
v_sal employees.salary%TYPE;
BEGIN
-- 2.打开游标
OPEN emp_cursor;
LOOP
-- 3.提取数据
FETCH emp_cursor INTO v_id, v_name, v_sal;
-- 退出条件
EXIT WHEN emp_cursor%NOTFOUND;
-- 处理数据(示例:薪资增加10%)
DBMS_OUTPUT.PUT_LINE(
v_id || ' | ' ||
RPAD(v_name, 15) || ' | ' ||
TO_CHAR(v_sal*1.1, '$999,999.99')
);
END LOOP;
-- 4.关闭游标
CLOSE emp_cursor;
END;
/
2. 参数化游标实例
DECLARE
-- 声明带参数的游标
CURSOR dept_emp_cursor (p_dept_id NUMBER, p_min_sal NUMBER) IS
SELECT * FROM employees
WHERE department_id = p_dept_id
AND salary > p_min_sal;
emp_record employees%ROWTYPE;
BEGIN
DBMS_OUTPUT.PUT_LINE('--- IT部门高薪员工 ---');
OPEN dept_emp_cursor(60, 10000); -- IT部门
LOOP
FETCH dept_emp_cursor INTO emp_record;
EXIT WHEN dept_emp_cursor%NOTFOUND;
DBMS_OUTPUT.PUT_LINE(
RPAD(emp_record.first_name || ' ' || emp_record.last_name, 25) ||
TO_CHAR(emp_record.salary, '$999,999.99')
);
END LOOP;
CLOSE dept_emp_cursor;
END;
/
三、FOR循环游标
DECLARE
CURSOR product_cursor IS
SELECT product_id, product_name, list_price
FROM products
WHERE list_price > 5000
ORDER BY list_price DESC;
BEGIN
FOR prod IN product_cursor LOOP
DBMS_OUTPUT.PUT_LINE(
'产品ID: ' || RPAD(prod.product_id, 6) ||
' | 名称: ' || RPAD(prod.product_name, 30) ||
' | 价格: ' || TO_CHAR(prod.list_price, '$999,999.99')
);
END LOOP;
END;
/
四、批量处理实战(BULK COLLECT)
DECLARE
TYPE emp_table IS TABLE OF employees%ROWTYPE;
v_employees emp_table;
CURSOR high_sal_cursor IS
SELECT *
FROM employees
WHERE salary > 15000;
BEGIN
OPEN high_sal_cursor;
-- 每次提取100条
LOOP
FETCH high_sal_cursor
BULK COLLECT INTO v_employees LIMIT 100;
EXIT WHEN v_employees.COUNT = 0;
-- 批量处理示例
FOR i IN 1..v_employees.COUNT LOOP
UPDATE employees
SET email = LOWER(first_name || '.' || last_name || '@company.com')
WHERE employee_id = v_employees(i).employee_id;
END LOOP;
COMMIT; -- 批量提交
END LOOP;
CLOSE high_sal_cursor;
END;
/
五、综合实战:数据校验与迁移
DECLARE
CURSOR validate_cursor IS
SELECT o.order_id, c.customer_name, o.order_total
FROM orders o
JOIN customers c ON o.customer_id = c.customer_id
WHERE o.order_status = 'PENDING';
v_order_id orders.order_id%TYPE;
v_cust_name customers.customer_name%TYPE;
v_order_total orders.order_total%TYPE;
BEGIN
OPEN validate_cursor;
LOOP
FETCH validate_cursor INTO v_order_id, v_cust_name, v_order_total;
EXIT WHEN validate_cursor%NOTFOUND;
BEGIN
-- 数据验证规则
IF v_order_total > 100000 THEN
INSERT INTO audit_table
VALUES (SYSDATE, '订单' || v_order_id || '金额超限');
ELSIF v_cust_name IS NULL THEN
UPDATE orders
SET order_status = 'HOLD'
WHERE order_id = v_order_id;
ELSE
-- 正常处理流程
UPDATE orders
SET order_status = 'PROCESSING'
WHERE order_id = v_order_id;
END IF;
EXCEPTION
WHEN OTHERS THEN
DBMS_OUTPUT.PUT_LINE('订单' || v_order_id || '处理异常: ' || SQLERRM);
END;
END LOOP;
CLOSE validate_cursor;
COMMIT;
END;
/
六、游标属性速查表
IF emp_cursor%ISOPEN THEN -- 检查游标是否打开
DBMS_OUTPUT.PUT_LINE('已提取行数: ' || emp_cursor%ROWCOUNT);
END IF;
-- 典型用法:
EXIT WHEN emp_cursor%NOTFOUND;
七、常见错误解决方案
错误1:忘记关闭游标
-- ❌ 错误代码
OPEN emp_cursor;
-- 处理逻辑...
-- 忘记CLOSE
-- ✅ 正确做法
BEGIN
OPEN emp_cursor;
-- 处理逻辑...
EXCEPTION
WHEN OTHERS THEN
IF emp_cursor%ISOPEN THEN CLOSE emp_cursor; END IF;
CLOSE emp_cursor;
END;
错误2:循环内执行DML
-- ❌ 性能低下
FOR emp IN (SELECT * FROM employees) LOOP
UPDATE departments... -- 每次循环都执行DML
END LOOP;
-- ✅ 优化方案
-- 使用BULK COLLECT+FORALL批量处理