下面是Oracle数据库游标使用的一段代码,看不大明白,
讲解一下。怎么前面是‘SALEMAN’,后面又成了‘MANAGER’了?
set serveroutput on
DECLARE
/*声明游标,查询员工信息*/
CURSOR cur_emp (var_job in varchar2:='SALESMAN')
IS select empno,ename,sal
from emp
where job=var_job;
type record_emp is record --声明一个记录类型(RECORD类型)
(
/*定义当前记录的成员变量*/
var_empno emp.empno%type,
var_ename emp.ename%type,
var_sal emp.sal%type
);
emp_row record_emp; --声明一个record_emp类型的变量
BEGIN
OPEN cur_emp('MANAGER'); --打开游标
FETCH cur_emp INTO emp_row; --先让指针指向结果集中的第一行,并将值保存到emp_row中
WHILE cur_emp%found LOOP
dbms_output.put_line(emp_row.var_ename||'的编号是'||emp_row.var_empno||',工资是'||emp_row.var_sal);
FETCH cur_emp INTO emp_row; --让指针指向结果集中的下一行,并将值保存到emp_row中
END LOOP;
CLOSE cur_emp; --关闭游标
END;
/Oracle 游标代码逐行详解
你观察得非常仔细!'SALESMAN' 和 'MANAGER' 这个对比正是理解游标参数机制的关键。让我从头讲清楚。
一、核心问题:为什么前面是 'SALESMAN',后面是 'MANAGER'?
位置 | 代码 | 含义 |
游标声明 | var_job in varchar2:='SALESMAN' | 默认值:如果不传参数,就用 'SALESMAN' |
打开游标 | OPEN cur_emp('MANAGER') | 实际传参:这里传入了 'MANAGER',覆盖默认值 |
一句话:默认值是 'SALESMAN',但调用时传了 'MANAGER',所以实际查询的是经理,不是销售员。
二、逐行详细讲解
第1行:set serveroutput on
sql
set serveroutput on
作用:允许输出 dbms_output 的内容到屏幕。没有这行,你看不到任何输出。
第2行:DECLARE
sql
DECLARE
作用:PL/SQL 块的声明部分开始。这里要声明游标、类型、变量。
第3-7行:声明游标(带参数和默认值)
sql
/*声明游标,查询员工信息*/ CURSOR cur_emp (var_job in varchar2:='SALESMAN') IS select empno,ename,sal from emp where job=var_job;
这部分可以拆解为:
| 部分 | 含义 |
|---|---|
CURSOR cur_emp | 声明一个名叫 cur_emp 的游标 |
(var_job in varchar2:='SALESMAN') | 游标有一个输入参数 var_job,类型是字符串,默认值是 'SALESMAN' |
IS SELECT ... WHERE job = var_job | 游标执行的查询,用参数作为过滤条件 |
关键理解:
参数
var_job的值可以在打开游标时指定如果不指定,就用默认值
'SALESMAN'
第8-12行:声明 RECORD 类型
sql
type record_emp is record --声明一个记录类型(RECORD类型) ( /*定义当前记录的成员变量*/ var_empno emp.empno%type, var_ename emp.ename%type, var_sal emp.sal%type );
| 部分 | 含义 |
|---|---|
type record_emp is record | 声明一个名叫 record_emp 的记录类型 |
var_empno emp.empno%type | 第一个字段,类型和 emp.empno 一样 |
var_ename emp.ename%type | 第二个字段,类型和 emp.ename 一样 |
var_sal emp.sal%type | 第三个字段,类型和 emp.sal 一样 |
%type 的含义:取某列的数据类型。比如 emp.empno%type 就是 emp 表中 empno 列的数据类型(NUMBER)。
第13行:声明 RECORD 变量
sql
emp_row record_emp; --声明一个record_emp类型的变量
作用:创建一个 record_emp 类型的变量,名叫 emp_row。这个变量可以存放一行数据(三个字段)。
第14行:BEGIN
sql
BEGIN
作用:PL/SQL 块的执行部分开始。
第15行:打开游标(关键!)
sql
OPEN cur_emp('MANAGER'); --打开游标这是理解你疑问的关键行:
| 写法 | 实际使用的参数 | 查询结果 |
|---|---|---|
OPEN cur_emp | 使用默认值 'SALESMAN' | 查销售员 |
OPEN cur_emp('MANAGER') | 使用传入值 'MANAGER' | 查经理 |
OPEN cur_emp('CLERK') | 使用传入值 'CLERK' | 查办事员 |
所以:虽然游标声明时默认是 'SALESMAN',但这里传入了 'MANAGER',覆盖了默认值,实际查询的是经理,不是销售员。
第16行:第一次取数据
sql
FETCH cur_emp INTO emp_row; --先让指针指向结果集中的第一行,并将值保存到emp_row中
作用:
从游标中取第一行数据
把这一行数据放入
emp_row变量中游标指针下移
访问方式:emp_row.var_empno、emp_row.var_ename、emp_row.var_sal
第17行:WHILE cur_emp%found LOOP
sql
WHILE cur_emp%found LOOP
| 部分 | 含义 |
|---|---|
WHILE | 当...的时候 |
cur_emp%found | 游标属性,判断上一次 FETCH 是否取到了数据 |
LOOP | 循环开始 |
逻辑:只要上一次 FETCH 成功取到了数据,就继续循环。
第18行:输出当前行的数据
sql
dbms_output.put_line(emp_row.var_ename||'的编号是'||emp_row.var_empno||',工资是'||emp_row.var_sal);
作用:打印当前员工的信息。
第19行:取下一行数据
sql
FETCH cur_emp INTO emp_row; --让指针指向结果集中的下一行,并将值保存到emp_row中
作用:取下一行数据,覆盖 emp_row 中的旧值。这是循环能继续的关键。
第20行:END LOOP;
sql
END LOOP;
作用:循环结束,跳回 WHILE 继续判断。
第21行:关闭游标
sql
CLOSE cur_emp; --关闭游标
作用:释放游标占用的资源。游标关闭后不能再 FETCH。
第22-23行:结束
sql
END;/
作用:PL/SQL 块结束,/ 执行这个块。
三、执行流程图

四、%FOUND 和 %NOTFOUND 对比
代码位置 | %FOUND 值 | %NOTFOUND 值 | 说明 |
OPEN 之后、第一次 FETCH 之前 | NULL | NULL | 还没取过数据 |
第一次 FETCH 成功(有数据) | TRUE | FALSE | 取到一行 |
中间 FETCH 成功 | TRUE | FALSE | 取到一行 |
最后一次 FETCH 失败(没数据了) | FALSE | TRUE | 循环应该退出 |
五、为什么这个 RECORD 类型要手动定义?
方式 | 代码 | 优缺点 |
手动定义 | type record_emp is record (...) | 可以自定义字段名(如 var_ename) |
自动定义 | emp_row cur_emp%ROWTYPE | 更简洁,字段名就是原表列名 |
这个代码之所以手动定义,可能是因为:
想把字段名改成
var_xxx的命名风格或者教学演示 RECORD 类型的定义语法
更常见的简洁写法:
DECLARE
CURSOR cur_emp(var_job IN VARCHAR2 := 'SALESMAN') IS
SELECT empno, ename, sal FROM emp WHERE job = var_job;
emp_row cur_emp%ROWTYPE; -- 不需要手动定义 RECORD
BEGIN
OPEN cur_emp('MANAGER');
LOOP
FETCH cur_emp INTO emp_row;
EXIT WHEN cur_emp%NOTFOUND;
DBMS_OUTPUT.PUT_LINE(emp_row.ename || '的编号是' || emp_row.empno || ',工资是' || emp_row.sal);
END LOOP;
CLOSE cur_emp;
END;
/六、总结
| 问题 | 答案 |
|---|---|
为什么有 'SALESMAN'? | 游标声明的默认值——如果不传参,就查销售员 |
为什么又成了 'MANAGER'? | 打开游标时传入了 'MANAGER',覆盖了默认值 |
| 实际查的是什么? | 经理(job = 'MANAGER') |
| RECORD 类型是什么? | 一种自定义的复合类型,可以包含多个字段 |
| 为什么要用 RECORD? | 一次 FETCH 就能接收整行数据,不用声明多个变量 |
一句话总结:游标声明时的 'SALESMAN' 只是默认值,打开游标时传入的 'MANAGER' 会覆盖它,所以实际查询的是经理。这就是游标参数的灵活之处——同一个游标可以查不同职位,只需在 OPEN 时传入不同的参数值