补充: 我们之前介绍的都是 标量, 一个变量只能有一个值, 那么如何实现一个变量存放多个值? 类似数组?-> 复合变量composite
1. pl/sql 记录
pl/sql 记录 类似高级语言中的结构体, 引用时为 记录变量.记录成员
declare-- 定义一个记录, 名字为emp_record_type, 其中可以存放三种类型的数据 type emp_record_type is record (name emp.name%type, salary emp.sal%type, title emp.job%type); xy emp_record_type; -- 定义xy这个变量类型就是 emp_record_type 型 begin select ename, sal, job into xy from emp where empno='1234'; dbms_output.put_line('员工名字:'|| xy.name || '工资:'|| xy.salary); end;
2.pl/sql 表
pl/sql表类似高级语言中的数组, 但是略有不同的是 高级语言中 表的下标不会是负数, plsql 表的下标可以是负数.
declare-- 定义一个plsql表, 名字为xy_table_type, 存放emp.ename%type类型的数据 type xy_table_type is table of emp.name%type-- 或者就写varchar2(10) index by binary_integer; -- 下标是整数 xy xy_table_type; -- 定义xy为表变量, 存放的数据类型就是 emp_record_type 型 begin select ename into xy(-1) from emp where empno='1234'; dbms_output.put_line('员工名字:'|| xy(-1));-- 下标要对应! end;
那么, 如果上述没有where empno='1234', 则会报错 实际返回行数超过请求行数!
如何解决这个问题, 参照变量
参照变量是用来存放数值指针的变量, 有两种: 游标变量 ref cursor, 和对象型变量, ref obj_type
常用游标!
DECLARE type xy_emp_cursor is ref cursor; --定义类型 xy_cursor xy_emp_cursor; -- 定义游标 名字为xy_cursor v_name emp.ename%type; v_sal emp.sal%type; begin open xy_cursor for select ename, sal from emp where deptno=&no; LOOP FETCH xy_cursor INTO v_ename, v_sal; --提取数据赋给变量 DBMS_OUTPUT.PUT_LINE('员工姓名' || v_ename || ',工资' || v_sal); EXIT WHEN emp_cursor%NOTFOUND; END LOOP; CLOSE xy_cursor; --关闭游标 end;
3. 嵌套表 ; 4. varray.. 先不讲这个
(一) 什么是游标?
之前在嵌入式SQL语言中已经介绍了游标, 本文介绍的都是在PL\SQL中的语法, 与之前的略有不同!
游标是SQL的一个内存工作区,由系统或者用户以变量的形式定义。
游标的作用就是用于临时存储从数据库中提取的数据块。
通俗的来讲,游标就是一个结果集。游标的类型分为显式游标和隐式游标。
显示游标
1. 显示游标处理的四个步骤:
(1)定义游标:CURSOR cursor_name[(parameter_name datatype)] IS select_statement;
(2)打开游标:OPEN cursor_name
(3)提取数据:FETCH cursor_name INTO variable1[, variable2, ...];-- 提取游标中的数据赋给变量!!
(4)关闭游标:CLOSE cursor_name。
实例1: 查询所有员工的员工号、姓名和职位的信息。
DECLARE CURSOR emp_cursor IS SELECT empno, ename, job FROM emp; --定义游标 不用create! v_empno emp.empno%TYPE; --定义变量 v_ename emp.ename%TYPE; v_job emp.job%TYPE; BEGIN OPEN emp_cursor; --打开游标,执行查询 LOOP FETCH emp_cursor INTO v_empno, v_ename, v_job; --提取数据赋给变量 DBMS_OUTPUT.PUT_LINE('员工号:' || v_empno || ',姓名' || v_ename || ',职位' || v_job); --什么时候退出循环?%FOUND,%NOTFOUND EXIT WHEN emp_cursor%NOTFOUND; --EXIT WHEN NOT emp_cursor%FOUND; END LOOP; CLOSE emp_cursor; --关闭游标 END;
注意: FETCH的作用1、把当前指针指向的记录返回;2、将指针指向下一条记录。
2. 显示游标的四个属性
(1) %FOUND:该属性用于检测游标结果集是否存在数据,如果存在数据,返回TRUE。
(2) %NOTFOUND:该属性用于检测结果集是否存在数据,如果不存在数据,返回TRUE。
(3) %ISOPEN:该属性用于检测游标是否已经打开,如果已经打开返回TURE。
(4) %ROWCOUNT:该属性用于返回已经提取的实际行数。(如EXIT WHEN emp_cursor%ROWCOUNT=5;)
实例2: 按职工的职称涨工资,总裁涨1000元,经理涨500元,其他员工涨300元。
DECLARE CURSOR emp_cursor IS SELECT empno, job FROM emp; --定义游标 v_empno emp.empno%TYPE; v_job emp.job%TYPE; BEGIN OPEN emp_cursor; --打开游标,执行查询 LOOP FETCH emp_cursor INTO v_empno, v_job; --提取数据 IF v_job ='PRESIDENT' THEN UPDATE emp SET sal = sal + 1000 WHERE empno = v_empno; ELSIF v_job = 'MANAGER' THEN UPDATE emp SET sal = sal + 500 WHERE empno = v_empno; ELSE UPDATE emp SET sal = sal + 300 WHERE empno = v_empno; END IF; --什么时候退出循环?%FOUND,%NOTFOUND EXIT WHEN NOT emp_cursor%FOUND;-- 相当于 exit when emp_cursor%notfound; END LOOP;
COMMIT; -- update的操作需要提交事务!! CLOSE emp_cursor; --关闭游标 END;
3. 游标的for 循环语法
当使用游标FOR循环时,Oracle会隐含地打开游标,提取数据并关闭游标。 因此不用再写open close!
--语法格式 FOR record_name IN cursor_name(或者可以使用子查询) LOOP statement; END LOOP; --查询员工的员工号、姓名和职位。 DECLARE CURSOR emp_cursor IS SELECT empno, ename, job FROM emp; BEGIN FOR emp_record IN emp_cursor LOOP DBMS_OUTPUT.put_line('员工号:' || emp_record.empno || ', 姓名' || ',职位' || emp_record.job); END LOOP; END; -- 也可以不定义cursor 直接用子查询!! BEGIN FOR emp_record IN (SELECT empno, ename, job FROM emp) LOOP DBMS_OUTPUT.put_line('员工号:' || emp_record.empno || ', 姓名' || ',职位' || emp_record.job); END LOOP; END;
实例2: 按职工的职称涨工资,总裁涨1000元,经理涨500元,其他员工涨300元。
DECLARE CURSOR emp_cursor IS SELECT empno, job FROM emp; --定义游标 BEGIN FOR emp_record IN emp_cursor LOOP DBMS_OUTPUT.put_line(emp_record.empno || '----' || emp_record.job); IF emp_record.job = 'PRECIDENT' THEN UPDATE emp SET sal = sal + 1000 WHERE empno = emp_record.empno; ELSIF emp_record.job = 'MANAGER' THEN UPDATE emp SET sal = sal + 500 WHERE empno = emp_record.empno; ELSE UPDATE emp SET sal = sal + 300 WHERE empno = emp_record.empno; END IF; END LOOP; COMMIT; END;
4.参数游标
参数游标是指带有参数的游标。通过使用参数游标,使用不同参数值可以生成不同的游标结果集。
定义和打开参数游标的语法如下:
--定义参数游标 CURSOR cursor_name (parameter_name datatype) IS select_statement; OPEN cursor_name (parameter_value); --实例: 显示10号部门的所有员工。 DECLARE CURSOR emp_cursor(dno NUMBER) IS SELECT empno, ename, job FROM emp WHERE deptno = dno; BEGIN FOR emp_record IN emp_cursor(10) LOOP DBMS_OUTPUT.put_line('员工号:' || emp_record.empno || ',姓名:' || emp_record.ename || ',职位:' || emp_record.job); END LOOP; END;
隐式游标
1.隐式游标与显示游标的区别
显式游标是用户自定义的显式创建的游标,主要是用于对查询语句的处理。
隐式游标是由系统隐含创建的游标。主要是用于对非查询语句,如修改,删除等操作,则有Oracle系统自动地为这些操作设置游标并创建其工作区,对于隐式游标的操作,如定义、打开、取值以及关闭操作,都有Oracle系统自动完成,无需用户进行操作。
隐式游标的名字为SQL,这是由Oracle系统定义的。
2.隐式游标的说明
如DML操作和单行SELECT语句会使用隐式游标,他们是:
插入操作:INSERT、更新操作:UPDATE、删除操作:DELECT、单行查询操作:SELECT ... INTO。
当系统使用一个隐式游标时,可以通过隐式游标的属性来了解操作的状态和结果,进而控制程序的流程。
(需要注意:通过SQL游标名总是只能访问前一个DML操作或者单行SELECT操作的游标属性。)
3.隐式游标的属性
--根据用户输入的员工号,更新指定员工的工资。(比如说工资涨100) BEGIN UPDATE emp SET sal = 100 + sal WHERE empno = &n1; IF SQL%FOUND THEN dbms_output.put_line('成功修改员工的工资'); ELSE dbms_output.put_line('修改员工工资失败'); ROLLBACK; END IF; END;
(二)异常处理
异常:是程序在正常执行过程中发生的未预料的事件。
异常处理是为了提高程序的健壮性,异常处理可以解决程序正常执行过程中可能出现的各种错误,使程序正常运行。
1. 异常处理的语法格式
EXCEPTION WHEN first_exception THEN statement1; ...... WHEN second_exception THEN statement1; ...... WHEN OTHERS THEN -- others 是固定格式!! statement1; ......
2. 异常处理在PL/SQL 代码中的位置
DECLARE /*声明部分--声明变量、常量、复杂数据类型、游标等*/ BEGIN /*执行部分--PL/SQL语句和SQL语句 */ EXCEPTION /*异常处理部分--处理运行错误*/ END; --块结束标记
实例1: 查询指定编号的员工姓名和工资。如果输入的编号有问题, 视为异常
DECLARE v_name emp.ename%TYPE;--定义变量 v_sal emp.sal%TYPE; BEGIN SELECT ename, sal INTO v_name, v_sal FROM emp WHERE empno = &no; IF v_sal < 3000 THEN dbms_output.put_line(v_name || '的工资是:' || v_sal); END IF; EXCEPTION WHEN NO_DATA_FOUND THEN dbms_output.put_line('员工号输入错误!'); WHEN OTHERS THEN dbms_output.put_line('其他错误!'); END;
3. 异常的分类
(1) 预定义异常
预定义异常是指由PL/SQL所提供的系统异常,Oracle提供了20多个预定义异常,每个预定义异常对应一个特定的Oracle错误,
当PL/SQL块出现这些Oracle错误时,会隐含地触发相应的预定义异常。
对于预定义异常情况的处理,无需在程序中定义,
只需要在PL/SQL块中的异常处理部分,直接引用相应的异常情况名,并对其完成相应的异常错误处理即可。
实例2: 根据输入的工资,查询员工的姓名。并输出员工的姓名以及工资。
DECLARE v_name emp.ename%TYPE; v_sal emp.sal%TYPE := &salary; -- 输入工资 BEGIN SELECT ename INTO v_name FROM emp WHERE sal = v_sal; DBMS_OUTPUT.put_line(v_name || '的工资是:' || v_sal); EXCEPTION WHEN NO_DATA_FOUND THEN DBMS_OUTPUT.put_line('没有该工资的员工'); WHEN TOO_MANY_ROWS THEN DBMS_OUTPUT.put_line('多个员工具有该工资'); WHEN OTHERS THEN DBMS_OUTPUT.put_line('其他错误'); END;
(2) 非预定义异常
用于处理预定义异常所不能够处理的ORACLE错误,此种异常需要在程序中定义。
1. 非预定义异常的处理步骤
(1) 在PL/SQL块中定义部分定义异常情况:<异常情况> EXCEPTION;
(2) 将其定义好的异常情况与标准的ORACLE错误联系起来,
使用PRAGMA EXCEPTION_INIT语句:PRAGMA EXCEPTION_INIT(<异常情况>, <错误代码>);
(3) 在PL/SQL块的异常情况处理部分对异常情况做出相应的处理。
实例3: 要求删除部门时应该保证该部门中没有员工,因为dept和emp表存在主外键关系
首先获取错误号: 不妨删除 有员工的部门,来获取错误号:
BEGIN DELETE FROM dept WHERE deptno = &deptno; EXCEPTION WHEN OTHERS THEN dbms_output.put_line(SQLCODE || '###' || SQLERRM); END;
F8 运行上述, 输入20(下面有员工) , 得到结果为:
DECLARE --1、定义非预定义异常的标识符 e_fk EXCEPTION; --2、把Oracle错误和异常信息建立关联 -- -2292 违反外键约束的错误号 PRAGMA EXCEPTION_INIT(e_fk, -2292); BEGIN DELETE FROM dept WHERE deptno = &deptno; EXCEPTION WHEN e_fk THEN -- 3、捕捉并处理异常 dbms_output.put_line('此部门下有员工,不能删除!'); WHEN OTHERS THEN dbms_output.put_line(SQLCODE || '###' || SQLERRM); END;
(3)自定义异常
如果你想在某个特定事件发生时向应用程序的用户发出一些警告信息。而事件本身不会抛出Oracle内部异常,
这个异常是属于应用程序的特定异常。那么就需要自定义异常。用户定义的异常错误时通过显式使用RAISE语句来触发的。
当引发一个异常错误时,控制就转向到EXCEPTION块异常错误部分,执行错误处理代码。
1.自定义异常的处理步骤
(1) 在PL/SQL块的声明部分定义异常情况:<异常情况> EXCEPTION;
(2) RAISE <异常情况>
(3) 在PL/SQL块的异常处理部分对异常情况做出相应的处理。
实例4: 更新指定员工编号的工资
DECLARE v_empno emp.empno%TYPE := &empno; e_no_result EXCEPTION; BEGIN UPDATE emp SET sal = sal + 100 WHERE empno = v_empno; IF SQL%NOTFOUND THEN RAISE e_no_result; ELSE COMMIT; END IF; EXCEPTION WHEN e_no_result THEN dbms_output.put_line('数据更新失败!'); WHEN OTHERS THEN dbms_output.put_line('其他错误'); END;
4.补充: SQLCODE和SQLERRM 异常处理函数
异常处理函数用于取得Oracle错误号和错误信息,其中函数SQLCODE用于取得错误号,SQLERRM用于取得错误信息。
当编写PL/SQL块时,通过在异常处理部分引用函数SQLCODE和SQLERRM,可以取得未预计到的Oracle错误。
另外,通过使用内置过程RAISE_APPLICATION_ERROR,可以在建立子程序(过程、函数、包)时自定义错误号和错误信息。
实例5: 插入员工信息(包括员工号、员工名和员工所属部门号)
DECLARE v_empno emp.empno%TYPE := &empno; v_ename emp.ename%TYPE := '&ename'; v_deptno emp.deptno%TYPE := &deptno; BEGIN INSERT INTO emp(empno, ename, deptno) VALUES(v_empno, v_ename, v_deptno); IF SQL%FOUND THEN DBMS_OUTPUT.put_line('数据插入成功'); COMMIT; END IF; EXCEPTION WHEN OTHERS THEN DBMS_OUTPUT.put_line('错误号:' || SQLCODE); DBMS_OUTPUT.put_line('错误信息:' || SQLERRM); END;
RAISE_APPLICATION_ERROR
该过程用于在PL/SQL子程序中自定义错误信息。
语法格式为:
raise_application_error(error_number, message);
error_number:用于定义错误号(-20000~-20999)
message:用于指定错误信息,长度不能超过2048个字节.