PL/SQL游标完全指南:从基础到实战代码

一、游标定义

游标(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批量处理
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值