Oracle学习6

Oracle学习6

  • 1 PLSQL中的游标
  • 2 游标的属性
  • 3 游标的使用
  • 4 游标的其他使用
  • 5 PLSQL中的异常处理

  

1 PLSQL中的游标

1、游标是一段私有的SQL 工作区,也就是一段内存区域,用于暂时存放受SQL语句影响到的数据
2、有两种类型的游标:
  隐式游标
  显式游标
3、隐式游标被用于描述执行的SQL命令.在PL/SQL中出现的SQL语句,oracle都会为其分配一段私有的内存空间,也就是游标区域.所有的DML语句或PL/SQL SELECT语句都有隐式游标
4、显式游标由开发人员通过程序显式控制,用于从表中取出多行数据,并将多行数据一行一行的单独进行处理.
5、两种游标具有相同的属性,可以使用游标属性取得SQL语句的相关信息

2 游标的属性

游标属性包括四种:

表示 含义
%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 到目前为止,提取的总行数

3 游标的使用

-- 在执行增删改查语句的时候,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;

Oracle学习6_第1张图片

-- 使用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;

4 游标的其他使用

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;

5 PLSQL中的异常处理

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捕获所有的非预期的异常,取到错误编号和错误信息,记录到数据库中

Oracle学习6_第2张图片

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类型

你可能感兴趣的:(Oracle)