1、游标是一段私有的SQL 工作区,也就是一段内存区域,用于暂时存放受SQL语句影响到的数据
2、有两种类型的游标:
隐式游标
显式游标
3、隐式游标被用于描述执行的SQL命令.在PL/SQL中出现的SQL语句,oracle都会为其分配一段私有的内存空间,也就是游标区域.所有的DML语句或PL/SQL SELECT语句都有隐式游标
4、显式游标由开发人员通过程序显式控制,用于从表中取出多行数据,并将多行数据一行一行的单独进行处理.
5、两种游标具有相同的属性,可以使用游标属性取得SQL语句的相关信息
游标属性包括四种:
表示 | 含义 |
---|---|
%ROWCOUNT | 受SQL影响的行数 |
%FOUND | Boolean 值,是否还有数据 |
%NOTFOUND | Boolean 值,是否已无数据 |
%ISOPEN | 游标是否打开 |
显示游标和隐式游标都有这四种属性。但是使用方法和含义却不相同。
在使用游标的时候,需要使用游标名称作为前缀。但是隐式游标没有名称,所以在使用隐式游标的时候采取统一的一个名称SQL。
表示 | 含义 |
---|---|
SQL%ROWCOUNT | 受SQL影响的行数 |
SQL%FOUND | Boolean 值,是否还有数据 |
SQL%NOTFOUND | Boolean 值,是否已无数据 |
SQL%ISOPEN | 总是为FALSE |
通过使用游标属性,获取游标的状态信息.显示游标得属性和隐式游标属性在调用方法上略有区别,在调用隐式游标时,通过SQL前缀调用(SQL%ROWCOUNT),而显示游标都有自己的名称,在调用时使用显示游标的名称作为属性的前缀(游标名%ROWCOUNT)
属性 | 类型 | 描述 |
---|---|---|
%ISOPEN | Boolean | 如果游标打开,则为TRUE |
%NOTFOUND | Boolean | 如果最近的提取没有返回一条记录,则为TRUE |
%FOUND | Boolean | 一直为TRUE ,直到最近提取没有取回行记录 |
%ROWCOUNT | Number | 到目前为止,提取的总行数 |
-- 在执行增删改查语句的时候,Oralce都会开辟一块内存空间,用来暂时存放受到SQL语句影响的数据
-- 这块内存空间就被称为游标区域.我们可以借助于游标来分析这些受到影响的数据;
-- 游标的分类:
-- 1.隐式游标:增删改查语句都会有隐式游标,也就是说,我们可以通过隐式游标来分析受到增删改查语句影响的数据
-- 隐式游标统一使用SQL前缀,例如:SQL%ROWCOUNT;
DECLARE
V_COUNT NUMBER(3);
BEGIN
DELETE FROM EMP WHERE DEPTNO = 10;
V_COUNT := SQL%ROWCOUNT;
DBMS_OUTPUT.put_line('被删除的数据的条数是:' || V_COUNT);
END;
-- 2.显示游标:专门用来从数据库中查询多条数据的
-- 在PL/SQL中执行SELECT 语句的特殊要求:1.SELECT语句只能返回一条记录;2.必须要搭配使用INTO
DECLARE
--1.声明游标.一个显示游标,就是和一个有效的SELECT语句绑死的
CURSOR CUR_EMP IS SELECT * FROM EMP;
V_REC_EMP EMP%ROWTYPE;
BEGIN
--2.打开游标,就是执行类游标绑定的SQL语句,并且把受到影响的数据放入到了游标区域中
OPEN CUR_EMP;
--3.取出游标中的一条数据装入记录类型的变量中
FETCH CUR_EMP
INTO V_REC_EMP;
-- 从记录类型的变量中取出查询数据
DBMS_OUTPUT.put_line(V_REC_EMP.EMPNO || ',' || V_REC_EMP.ENAME || ',' ||
V_REC_EMP.JOB || ',' || V_REC_EMP.MGR || ',' ||
V_REC_EMP.HIREDATE || ',' || V_REC_EMP.SAL || ',' ||
NVL(V_REC_EMP.COMM,0) || ',' || V_REC_EMP.DEPTNO);
-- 关闭游标,清空游标区域
CLOSE CUR_EMP;
END;
-- 使用LOOP循环和 %NOTFOUND属性来遍历游标
DECLARE
CURSOR CUR_EMP IS SELECT * FROM EMP;
V_REC_EMP EMP%ROWTYPE;
BEGIN
--dbms_output.put_line('查询数据的总条数是:' || CUR_EMP%ROWCOUNT);
-- 游标在没有打开之前,在关闭之后,是无法使用的,就会导致无效的游标错误
-- 在关闭之后,如果需要重新使用游标,需要重新打开游标
OPEN CUR_EMP;
FETCH CUR_EMP INTO V_REC_EMP;
LOOP
DBMS_OUTPUT.put_line(V_REC_EMP.EMPNO || ',' || V_REC_EMP.ENAME || ',' ||
V_REC_EMP.JOB || ',' || V_REC_EMP.MGR || ',' ||
V_REC_EMP.HIREDATE || ',' || V_REC_EMP.SAL || ',' ||
NVL(V_REC_EMP.COMM,0) || ',' || V_REC_EMP.DEPTNO);
FETCH CUR_EMP INTO V_REC_EMP; -- 装入另一条数据
EXIT WHEN CUR_EMP%NOTFOUND; -- 当游标中没有数据的时候,退出循环
END LOOP;
dbms_output.put_line('查询数据的总条数是:' || CUR_EMP%ROWCOUNT);
CLOSE CUR_EMP;
--dbms_output.put_line('查询数据的总条数是:' || CUR_EMP%ROWCOUNT);
END;
7499,ALLEN,SALESMAN,7698,20-FEB-81,1600,300,30
7521,WARD,SALESMAN,7698,22-FEB-81,1250,500,30
7654,MARTIN,SALESMAN,7698,28-SEP-81,1250,1400,30
7698,BLAKE,MANAGER,7839,01-MAY-81,2850,0,30
7782,CLARK,MANAGER,7839,09-JUN-81,2450,0,10
7839,KING,PRESIDENT,,17-NOV-81,5000,0,10
7844,TURNER,SALESMAN,7698,08-SEP-81,1500,0,30
7900,JAMES,CLERK,7698,03-DEC-81,950,0,30
7934,MILLER,CLERK,7782,23-JAN-82,1300,0,10
查询数据的总条数是:9
-- 用WHILE循环和%FOUND搭配使用分析游标数据
--CREATE OR REPLACE PROCEDURE CUR_EMP IS
DECLARE
CURSOR CUR_EMP IS
SELECT * FROM EMP;
V_REC_EMP EMP%ROWTYPE;
BEGIN
OPEN CUR_EMP;
-- 在没有执行FETCH操作之前,游标中是没有数据的,也就是CUR_EMP%FOUND 为false;
FETCH CUR_EMP
INTO V_REC_EMP; -- 取出游标中的一条数据装入记录类型的变量中
WHILE (CUR_EMP%FOUND) LOOP
DBMS_OUTPUT.put_line(V_REC_EMP.EMPNO || ',' || V_REC_EMP.ENAME || ',' ||
V_REC_EMP.JOB || ',' || V_REC_EMP.MGR || ',' ||
V_REC_EMP.HIREDATE || ',' || V_REC_EMP.SAL || ',' ||
NVL(V_REC_EMP.COMM, 0) || ',' || V_REC_EMP.DEPTNO);
FETCH CUR_EMP
INTO V_REC_EMP; -- 取出游标中的一条数据装入记录类型的变量中
END LOOP;
dbms_output.put_line('查询数据的总条数是:' || CUR_EMP%ROWCOUNT);
CLOSE CUR_EMP;
END;
-- 使用FOR循环,可以简化游标的开发,ORACLE会自动的声明记录类型的变量;ORACLE会自动的OPEN,FETCH,CLOSE游标
DECLARE
CURSOR CUR_EMP IS
SELECT * FROM EMP;
BEGIN
-- Oracle会自动的声明记录类型的变量V_REC_EMP,类型是EMP%ROWTYPE;
FOR V_REC_EMP IN CUR_EMP LOOP
DBMS_OUTPUT.put_line(V_REC_EMP.EMPNO || ',' || V_REC_EMP.ENAME || ',' ||
V_REC_EMP.JOB || ',' || V_REC_EMP.MGR || ',' ||
V_REC_EMP.HIREDATE || ',' || V_REC_EMP.SAL || ',' ||
NVL(V_REC_EMP.COMM, 0) || ',' || V_REC_EMP.DEPTNO);
END LOOP;
--dbms_output.put_line('查询数据的总条数是:' || CUR_EMP%ROWCOUNT);
END;
CREATE OR REPLACE PROCEDURE CUR_EMP IS
--declare
CURSOR CUR_EMP IS
SELECT * FROM EMP;
V_REC_EMP EMP%ROWTYPE;
BEGIN
OPEN CUR_EMP;
WHILE (CUR_EMP%FOUND) LOOP
FETCH CUR_EMP
INTO V_REC_EMP; -- 取出游标中的一条数据装入记录类型的变量中
DBMS_OUTPUT.put_line(V_REC_EMP.EMPNO || ',' || V_REC_EMP.ENAME || ',' ||
V_REC_EMP.JOB || ',' || V_REC_EMP.MGR || ',' ||
V_REC_EMP.HIREDATE || ',' || V_REC_EMP.SAL || ',' ||
NVL(V_REC_EMP.COMM, 0) || ',' || V_REC_EMP.DEPTNO);
END LOOP;
dbms_output.put_line('查询数据的总条数是:' || CUR_EMP%ROWCOUNT);
CLOSE CUR_EMP;
END;
---------------------------------------------------------------
-- 制定带参数的游标
SELECT * FROM EMP WHERE DEPTNO = 30 ;
DECLARE
-- 注意,在声明邮编参数的时候,只有制定参数的数据类型就可以了,类型的长度是不能制定的
CURSOR CUR_EMP(P_DEPTNO NUMBER) IS
SELECT * FROM EMP WHERE DEPTNO = P_DEPTNO ;
V_REC_EMP EMP%ROWTYPE;
BEGIN
-- 打开游标的时候,要传入具体的参数
OPEN CUR_EMP (30);
-- 在没有执行FETCH操作之前,游标中是没有数据的,也就是CUR_EMP%FOUND 为false;
FETCH CUR_EMP
INTO V_REC_EMP; -- 取出游标中的一条数据装入记录类型的变量中
WHILE (CUR_EMP%FOUND) LOOP
DBMS_OUTPUT.put_line(V_REC_EMP.EMPNO || ',' || V_REC_EMP.ENAME || ',' ||
V_REC_EMP.JOB || ',' || V_REC_EMP.MGR || ',' ||
V_REC_EMP.HIREDATE || ',' || V_REC_EMP.SAL || ',' ||
NVL(V_REC_EMP.COMM, 0) || ',' || V_REC_EMP.DEPTNO);
FETCH CUR_EMP
INTO V_REC_EMP; -- 取出游标中的一条数据装入记录类型的变量中
END LOOP;
dbms_output.put_line('查询数据的总条数是:' || CUR_EMP%ROWCOUNT);
CLOSE CUR_EMP;
END;
---------------------------------------------------------------
SELECT * FROM EMP FOR UPDATE;
SELECT DISTINCT JOB FROM EMP;
UPDATE EMP SET COMM = NULL;
-- 通过表来更新数据
--根据员工的职务和工资,给所有的员工设置佣金
DECLARE
CURSOR CUR_UPDATE_COMM IS
SELECT * FROM EMP FOR UPDATE;
V_COMM EMP.COMM%TYPE;
BEGIN
FOR V_REC_EMP IN CUR_UPDATE_COMM LOOP
IF (V_REC_EMP.JOB = 'CLERK') THEN
V_COMM := V_REC_EMP.SAL * 0.5;
ELSIF (V_REC_EMP.JOB = 'SALESMAN') THEN
V_COMM := V_REC_EMP.SAL * 0.6;
ELSIF (V_REC_EMP.JOB = 'PRESIDENT') THEN
V_COMM := V_REC_EMP.SAL * 0.7;
ELSIF (V_REC_EMP.JOB = 'ANALYST') THEN
V_COMM := V_REC_EMP.SAL * 0.8;
ELSE
V_COMM := V_REC_EMP.SAL * 1.0;
END IF;
-- 更新游标正在分析的这条数据
UPDATE EMP SET COMM = V_COMM WHERE CURRENT OF CUR_UPDATE_COMM;
END LOOP;
COMMIT;
END;
---------------------------------------------------------------
-- 查询指定名称的用户表是否存在
select COUNT(*) from user_tables where Lower(table_name) =Lower('empS')
select * from user_tables where table_name ='EMP'
-- 查询用户表的表结构
select column_name, data_type, data_length from user_tab_columns
where Lower(TABLE_NAME) = Lower('EMP');
-- 查询emp表中数据的总条数
SELECT COUNT(*)FROM EMP;
-- 用户输出一个表名,查询用户输入的表名在数据中是否存在,
-- 如果该表存在,则查询该表中数据的总条数,查询表的结构(字段名称,字段类型)
DECLARE
V_TABLE_NAME VARCHAR2(20) := '&表名';
V_COUNT NUMBER(4);
V_SQL VARCHAR2(100) ;
-- 声明游标,来查询表的字段的信息
CURSOR CUR_SELECT_TABLE_INFO IS
select column_name, data_type, data_length
from user_tab_columns
where Lower(TABLE_NAME) = Lower(V_TABLE_NAME);
BEGIN
-- 查询指定名称的表是否存在
select COUNT(*)
INTO V_COUNT
from user_tables
where Lower(table_name) = Lower(V_TABLE_NAME);
IF (V_COUNT > 0) THEN
-- 自己拼接动态的SQL语句
V_SQL := 'SELECT COUNT(*) FROM ' || V_TABLE_NAME;
-- 执行动态的SQL语句 ,把查询语句的结果赋值给V_COUNT变量
EXECUTE IMMEDIATE V_SQL INTO V_COUNT;
dbms_output.put_line(V_TABLE_NAME || ' 中数据的总条数是 ' || V_COUNT);
-- 如果表存在,则继续查询表的字段的信息
FOR V_RES_COLUMN IN CUR_SELECT_TABLE_INFO LOOP
DBMS_OUTPUT.put_line('column_name' || ',data_type' || ',data_length');
DBMS_OUTPUT.put_line(V_RES_COLUMN.column_name || ','|| V_RES_COLUMN.data_type || ',' ||V_RES_COLUMN.data_length);
DBMS_OUTPUT.put_line('----------------------------------------------');
END LOOP;
ELSE
DBMS_OUTPUT.put_line('输入的表名 ' || V_TABLE_NAME || ' 不存在');
END IF;
END;
1、预定义异常就是Oracle中已经预先定义好名称的异常,在异常处理部分中,通过异常的名称来捕获,预定义的一些异常名称:
异常 | 含义 |
---|---|
NO_DATA_FOUND | 没有找到数据 |
TOO_MANY_ROWS | 数据太多 |
INVALID_CURSOR | 失效的游标 |
ZERO_DIVIDE | 除数为0 |
DUP_VAL_ON_INDEX | 唯一索引中插入重复值 |
VALUE_ERROR | 赋值异常 |
2、非预定义异常就是在数据库中没有定义异常名称的异常,这种异常都是数据库的错误,这些错误通常只有错误编号,而没有错误的异常名称,所以不能直接捕获.
为了捕获非预定义的异常,必须先创建一个异常名称,然后将错误编号和刚创建的异常关联起来
3、用户定义异常与前两种异常完全不同,前两种异常都是数据库的错误,而用户定义异常是对数据库的操作不符合用户的业务时,人为定义的异常.这类异常不是数据库的错误,所以没有对应的错误代码.而且数据库在执行时不会主动的认为是异常
-- 在PL/SQL中出现的异常,如果我们没有处理,异常会被传递给调用环境,中断我们程序的执行
-- PL/SQL程序会从发生异常的代码处中断,以后的代码是无法执行的
-- 当PL/SQL程序中,在BEGIN部门的语句引起异常之后,就会进入到EXCEPTION部分执行异常处理功能
-- 当PLSQL中出现的异常,我们采取了处理措施之后,就不会在产生报错信息
--ORACLE的异常类型分为三类
-- 1.预定义异常,Oracle以及为这种异常定义好了名称,我们在异常处理部分直接通过异常名称进行捕获
-- 2.非预定义异常,也是因为违反了Oracle的规则,Oracle会产生报错信息(有错误编号和错误信息),
-- 但是Oracle并没有为这类错误定义好异常名称,那么我们可以自己定义一种异常名称,并将这种
-- 异常名称和错误编号进行绑定
-- 3.用户定义异常,我们操作的的时候,并没有违反Oracle的规则,而是违反了用户定义的规则
-- 由于没有违反Oracle的规则,Oracle不会自己主动的产生报错信息,需要我们自己手动的提升一个异常
-- 注意:预定义异常和非预定义异常都是因为违反了Oracle的规则,Oracle会自动的产生异常
-- 而用户定义异常并没有违反Oracle的规则,oracle不会自动的产生异常
DECLARE
V_JOB EMP.JOB%TYPE;
CURSOR CUR_UPDATE_COMM IS
SELECT * FROM EMP FOR UPDATE;
V_RES NUMBER(8);
BEGIN
--SELECT JOB INTO V_JOB FROM EMP WHERE DEPTNO = 10;
--DBMS_OUTPUT.put_line('V_JOB=' || V_JOB);
--DBMS_OUTPUT.put_line('查询数据的总条数是:' || CUR_UPDATE_COMM%ROWCOUNT);
--V_RES := 10/0;
--INSERT INTO DEPT VALUES (10,'销售部','北京');
V_RES := '1000'; -- Oracle自动把字符型转换为数值型
V_RES := 'HELLO';
EXCEPTION
WHEN NO_DATA_FOUND THEN
dbms_output.put_line('执行的SELECT语句没有查询到结果');
WHEN TOO_MANY_ROWS THEN
dbms_output.put_line('执行的SELECT语句不能查询多条结果');
WHEN INVALID_CURSOR THEN
dbms_output.put_line('无效的游标');
WHEN ZERO_DIVIDE THEN
dbms_output.put_line('0不能作为分母');
WHEN DUP_VAL_ON_INDEX THEN
dbms_output.put_line('违反主键约束');
WHEN VALUE_ERROR THEN
dbms_output.put_line('赋值错误');
-- 可以捕获上面没有捕获的所有的异常,不会有一个漏网之鱼
WHEN OTHERS THEN
DBMS_OUTPUT.put_line('PL/SQL中发生了其他异常');
END;
DECLARE
v_emp_remaining EXCEPTION; -- 自己定义一种异常名称
PRAGMA EXCEPTION_INIT(v_emp_remaining ,-02292);
BEGIN
DELETE FROM DEPT WHERE DEPTNO = 10;
EXCEPTION
WHEN v_emp_remaining THEN
DBMS_OUTPUT.put_line('违反外键约束');
WHEN OTHERS THEN
DBMS_OUTPUT.put_line('PL/SQL中发生了其他异常');
END;
drop table sm_emp;
CREATE TABLE sm_emp(
no char(4),
name char(10),
salary number(6,2),
phone char(8)
);
--insert TOM
INSERT INTO sm_emp VALUES ('001','TOM',999.99,'62543678');
INSERT INTO sm_emp VALUES ('002','TOM',999.99,'62543678');
INSERT INTO sm_emp VALUES ('003','TOM3',999.99,NULL);
commit;
SELECT * FROM sm_emp;
DECLARE
V_NAME VARCHAR2(20);
E_PHONE_LOST_EXCEPTION EXCEPTION;-- 自己定义一种异常
CURSOR CUR_SM_EMP IS SELECT * FROM sm_emp;
BEGIN
FOR V_REC_EMP IN CUR_SM_EMP LOOP
IF(V_REC_EMP.PHONE IS NULL) THEN
V_NAME := V_REC_EMP.NAME;
-- 当判断到违反了用户的规则,需要我们自己提升一个异常
RAISE E_PHONE_LOST_EXCEPTION;
END IF;
END LOOP;
EXCEPTION
WHEN E_PHONE_LOST_EXCEPTION THEN
DBMS_OUTPUT.put_line(V_NAME || '的电话为null');
WHEN OTHERS THEN
DBMS_OUTPUT.put_line('PL/SQL中发生了其他异常');
END;
-- 对于非预测的异常,如何处理?
-- 用WHEN OTHERS THEN捕获所有的非预期的异常,取到错误编号和错误信息,记录到数据库中
DROP TABLE ERR_LOG;
CREATE TABLE ERR_LOG (ID NUMBER(5) PRIMARY KEY, CODE NUMBER(10), MESSAGE VARCHAR2(255),DATETIME DATE);
SELECT * FROM ERR_LOG;
DECLARE
V_CODE NUMBER(10);
V_MSG VARCHAR2(255);
BEGIN
DELETE FROM DEPT WHERE DEPTNO = 10;
EXCEPTION
WHEN OTHERS THEN
V_CODE := SQLCODE; -- 获取错误编号
V_MSG := SQLERRM;
--把错误编号和错误信息插入到日志表中
INSERT INTO ERR_LOG VALUES (SEQ_ERRLOG.NEXTVAL,V_CODE,V_MSG,SYSDATE);
COMMIT;
DBMS_OUTPUT.put_line('PLSQL程序中遇到异常,请查询日志表获取详细信息');
END;
异常捕获相关的函数
1、在进行异常捕获的时候,在WHEN子句中捕获大多数的预先可以预测到的异常。但是在数据库中存在太多的不可预期的异常。
2、所以在程序中很可能异常发生时我们没有捕获。这时就要借助WHEN OTHERS THEN子句在处理这些的非预期的异常
3、WHEN OTHERS THEN可以捕获到预先没有定义的异常。为了有效的处理这些异常,以便在以后的程序中减少这些异常,通常会将这些异常写入错误日志表中
4、为了能够记录发生的异常信息,Oracle提供了两个函数
SQLCODE:返回错误代码,NUMBER类型
SQLERRM :返回与错误代码关联的消息;VARCHAR2类型