harriets-zhang

一、PL/SQL 游标

PL/SQL 编程语言 Procedure Language/SQL

  是ORACLE对SQL过程化的扩展,使SQL语言具有过程处理能力

面向过程语言:C  PL/SQL

SCALA语言

HADOOP JAVA

SPARK SCALA

 

为什么要学习PL/SQL? 

  PL/SQL是对SQL语言的扩展创建的

  PL/SQL操作数据库效率最高

学习内容

  存储过程

  存储函数

  触发器

  包

  包体

开发模式:Spring mvc +存储过程

 

简单的PL/SQL

DECLARE
--说明部分
BEGIN
--程序
dbms_output.put_line(\'Hello world\');
END;

开始显示PL/SQL结果
SET SERVEROUTPUT ON;

PL/SQL中的变量与常量

数据类型:
    char,
    varchar2,
    date,
    number,
    boolean,
    long 

    var1 char(15)
    married boolean:=true; 
    psal number(7,2)
    PL/SQL中的:=为赋值,=为==
   
引用型变量
    my_name emp.ename%type; 
    引用emp表上的ename列的类型为my_name变量的类型

引用型变量,
    查询并打印7839的姓名和薪水:
    DECLARE
    --pname varchar2(20);
    --psal number;

    pname emp.ename%type;
    psal emp.sal%type;

    begin
        --得到7839的姓名和薪水
        select ename,sal into pname,psal from emp where empno=7839;
        --PL/SQL赋值除了使用:= 还可以使用INTO,INTO后面变量的顺序需要和前面列的顺序一致
        --打印
        dbms_output.put_line(pname||\'的薪水是\'||psal);
    end;

记录型变量
    emp_rec emp%rowtype 
        引用表中的一行,可以把记录型变量理解为一个数组

记录型变量,
    查询并打印7839的姓名和薪水:
    DECLARE
        --定义记录型变量:代表一行
        emp_rec emp%rowtype
        begin
            select * into emp_rec from emp where empno=7839;
            dbms_output.put_line(emp_rec.ename||\'的薪水是\'||emp_rec.sal);
        end;

常量:Constant

PL/SQL中的条件判断

    IF语句
        IF 条件 THEN 语句1;
        语句2;
        END IF;

        IF 条件 THEN 语句序列1;
        ELSE 语句序列2;
        END IF;

        IF 条件 THEN 语句;
        ELSIF 语句 THEN 语句;
        ELSE 语句;
        END IF;

例子:判断用户从键盘输入的数字    
--接收键盘输入
--变量num:是一个地址值,在改地址上保存了输入的值
accept num prompt \'请输入一个数字\'

DECLARE
--定义变量保存输入的数字,&地址符
pnum number :=&num    
BEGIN
IF pnum =0 then dbms_output.put_line(\'您输入的数字是0\');
ELSIF pnum =1 then dbms_output.put_line(\'您输入的数字是1\');
ELSIF pnum =2 then dbms_output.put_line(\'您输入的数字是2\');
END IF;
END;

PL/SQL循环

循环
一、
    WHILE total<=2500
    LOOP
    ...
    total :=total +salary;
    END LOOP;
二、
    LOOP
    EXIT [WHEN 条件];
    ...
    end loop;
三、
    FOR I IN 1..3
    LOOP
    ...
    END LOOP;

打印1~10
declare
--定义变量
pnum number :=1;
begin
  loop
    --退出条件
    exit when pnum>10;
    --打印
    dbms_output.put_line(pnum);
    --加一
    pnum :=pnum+1;
  end loop;
end;

 光标/游标(相当于JAVA中的ResultSet集合)

语法:
    CURSOR 游标名 [(参数名 数据类型,参数名 数据类型...)] IS SELECT 语句
例如:CURSOR C1 IS SELECT ENAME FROM EMP;

游标的使用步骤:
    打开游标:
        OPEN C1;(打开游标前执行查询)
    取一行游标的值:
        FETCH C1 INTO PJOB;(取一行到变量中,指针移动到下一个位置)
    关闭游标: 
        CLOSE C1;(关闭游标释放资源)

游标的结束方式:EXIT WHEN C1%NOTFOUND

注意:
    上面的PJOB 必须与emp表中的job列类型一致;
    定义:PJBO EMP.EMPJOB%TYPE;

游标的属性:
    %isopen 是否打开 true/false
    %rowcount 影响的行数(比如游标里面有100条记录,目前已经取值10个,直就为10)
    %found true/false
    %notfound true/false

例子:查询并打印员工的姓名和薪水
DECLARE
    --定义一个游标
    CURSOR cemp is select ename,sal from emp;
    --定义好一个游标后紧接着定义变量
    pename emp.ename%type;
    psal emp.sal%type;
    BEGIN
    --打开游标
    OPEN cemp;
        loop
            --取当前记录
            fetch cemp into pename,psal;
            exit when cemp%notfound; --没有取到记录
            dbms_output.put_line(pename||\'的薪水是\'||psal);
        end loop;
    --关闭
    close cemp;
    END;

PL/SQL 给员工涨工资
    DECLARE
    --定义游标
    CURSOR cemp is select empno,job form emp;
    pempno emp.empno%type;
    pjob emp.job%type;
    begin
        --打开游标
        open cemp;
        loop
        --取一个员工
            fetch cemp into pempno,pjob;
            exit when cemp%notfound;
            --判断职位
            if pjob =\'PERSIDENT\' THEN update emp set sal=sal+1000 where empno=pempno;
            elsif pjob =\'MANAGER\' THEN update emp set sal=sal+800 where empno=pempno;
            else update set sal=sal+400 where empno=pempno;
            end if;
        end loop;
    --关闭游标
    close cemp;
    --提交 事务的ACID
    commit;
    dbms_output.put_line(\'完成\');
    end;


带参数的游标
    --查询某个部门的员工姓名
    DECLARE
    cursor cemp(dno number) is select ename from emp where deptno=dno;--dno形式参数
    pename emp.ename%type;
    begin
        open cemp(10);--开启游标时需要参数实际参数
            loop
                fetch cemp into pename;
                exit when cemp%notfound;
                dbms_output.put_line(pename);
            end loop;
        close cemp;
    end;    

PL/SQL异常/例外

异常是程序设计语言提供的一种功能,用来增强程序的健壮性和容错性

no_data_found 没有找到数据
too_many_rows (select ..into 匹配多个行)
zero_divide 被零除
value_error 算数转换错误(类型转换等)
timeout_resource 在等待资源时发生超时(分布式数据库容易发生)

分布式数据库:物理上不是一个整体,但逻辑上是一个整体
--被零除
DECLARE
pnum number;
begin
  pnum :=1/0;
  exception --相当于javaz中的catch 在PL/SQL中必须catch住所有的例外
  when zero_divide then dbms_output.put_line(\'0不能做分母\');
  dbms_output.put_line(\'0不能做分母111\');
  when value_error then dbms_output.put_line(\'算术或者转换错误\');
  when other then dbms_output.put_line(\'其他例外\');
end;

PL/SQL:自定义例外
DECLARE
--定义例外
No_Data exception;
--抛出例外
raise No_Data

例子:查询50号部门的员工(表中不存在50号部门)
declare
cursor cemp is select ename from emp where deptno=50;
pname emp.ename%type;
--自定义例外
no_emp_found exception;
begin
  open cemp;

  --取第一条记录
  fetch cemp into pname;
  if cemp%notfound then
  --抛出例外
  raise no_emp_found;
  end if;

   --进程:pmon (process monitor 监事程序运行)
   close cemp;


   exception
   when no_emp_found then dbms_output.put_line(\'没有找到员工\');
   when other then dbms_output.put_line(\'其他例外\');
 end;


--进程
读、写
PMON进程
  当用户进程失败时执行进程恢复
    1、清除数据库缓冲区高速缓存
    2、释放该用户进程使用的资源
  监事会话:查看是否发生空闲会话超时
  在监听程序中动态注册数据库服务

 

练习例子

例子:
  统计每年入职的员工个数?
  需要什么样的SQL语句?
  需要员工入职年份
  select to_char(hiredate,\'yyyy\') from emp;
  集合==》游标==》循环==》退出:notfound

  需要什么变量?
  初始值/值最终如何得到
  每年入职年数
  count80 number :=0;

  DECLARE
  --定义一个游标
  cursor cemp is select to_char(hiredate,\'yyyy\') from emp;
  phiredate varchar2(4);

  --每年入职人数
  count80 number :=0;
  count81 number :=0;
  count82 number :=0;
  count87 number :=0;
  begin
  open cemp;

    loop
      fetch cemp into phiredate;
      exit when cemp%notfound;

      --判断年份是哪一年
      if phiredate =\'1980\' then count80=count80+1;
      elsif phiredate =\'1981\' then count81=count81+1;
      elsif phiredate =\'1982\' then count82=count82+1;
      else count87=count87+1;
    end loop;

  close cemp;

  dbms_output.put_line(\'Total:\'||(count80+count81+count82+count87));
  dbms_output.put_line(\'1980\'||count80);
  dbms_output.put_line(\'1981\'||count81);
  dbms_output.put_line(\'1982\'||count82);
  dbms_output.put_line(\'1987\'||count87);
end;
问题:
给员工涨工资,从最低工资调起每人涨10%,但工资总额不能超过5万元,请计算涨工资的人数和涨工资后的工资总额,并输出涨工资人数和工资总额

给哪些员工涨工资
select empno,sal from emp order by sal;
==>游标==》循环==》退出 1、总额》5万 2、NOTFOUND

变量 初始值 最终结果如何得到
涨工资的人数:countEmp number :=0;
涨后的工资总额:salTotal number;
  1、select sum(sal) into salTotal from emp;
  2、涨后=涨前+sal*01;
编程原则:
  尽量不操作数据库,第二种方式好

DECLARE
cursor cemp is select empno,sal from emp order by sal;
pempno emp.empno%type;
psal emp.sal%type;

--涨工资的人数
countEmp number :=0;
--涨后工资的总额:
salTotal number;
begin 
--得到工资总额的初始值
select sum(sal) into salTotal from emp;

open cemp;

  loop

    --总额大于5万退出
    exit when salTotal>50000;
    fetch cemp into pempno,psal;
    --没有找到
    exit when cemp%notfound;

    exit when salTotal+psal*0.1)>50000;

    --涨工资
    update emp set sal=sal+sal*0.1 where empno=pempno;
    --涨后工资的总额:涨后=涨前+sal*0.1
    salTotal :=salTotal+psal*0.1;
    --人数+1
    countEmp =countEmp+1;

  end loop;

close cemp;
dbms_output.put_line(\'人数\')
end;


用PL/SQL语言编写一程序,实现按部门分段(6000以上、(6000、3000)、3000以下)
统计各工资段的职工人数、以及各部门的工资总额(工资总额中不包括奖金)

create table msg
(
deptno number,
count1 number,
count2 number,
count3 number,
total number
)

1、存在哪些部门
部门:select deptno from dept; --游标
部门中员工的薪水:
select sal from emp where deptno=??; --带参数的游标

2、变量 初始值 最终结果如何得到
count1 number,count2 number,count3 number;
部门的工资总额
salTotal number :=0;
(1)select sum(sal) into salTotal from emp where deptno
(2)累加

declare 
--部门
cursor cdept is select deptno from dept;
pdeptno dept.deptno%type;

--部门中员工的薪水
cursor cemp(dno number) is select sal from emp where deptno=dno;
psal emp.sal%type;

--每个段的人数
count1 number;count2 number;count3 number;
--部门的工资总额
salTotal :=0;

begin
  open cdept;

  loop
    --取一个部门
    fetch cdept into pdeptno;
    exit when cdept%notfound;
    --初始化
    count1 :=0;
    count2 :=0;
    count3 :=0;
    --得到部门的工资总额
    select sum(sal) into salTotal from emp where deptno=pdeptno;
    --取部门中的员工薪水
    open cemp(pdeptno);

    loop
      --取一个员工的薪水
      fetch cemp into psal;
      exit when cemp%notfound;

      --判断
      ifpsal<3000 then count1 :=count1 +1;
      elsif psal>=300 and psal<6000 then count2 :=count2+1;
      else count3 :=count3+1;
      end if;
    end loop;

  close cemp;
  --保存结果
  insert into msg values(pdeptno,count1,count2,count3,nvl(salTotal,0));

  end loop;

  close cdept;

  commit;

  end;

 

存储过程、存储函数

定义:指存储在数据库中供所有用户调用的子程序

区别:存储函数可以通过return返回一个函数的值,存储过程没有返回值

创建存储过程
    CREATE [OR REPLACE] PROCEDURE 过程名(参数列表)
  AS(或IS)
  PL/SQL子程序

  打印Hello World
  CREATE OR REPLACE PROCEDURE sayHelloWorld
  as --相当于declare
  --说明部分
  begin

    dbms_output.put_line(\'Hello World\');

  end;

  调用存储过程:
  execute
  1、exec sayHelloWorld()
  2、在另外的PL/SQL程序中调用
    begin
      sayHelloWorld();
      sayHelloWorld();
    end;

--给指定员工涨100元工资,并打印涨前和涨后的薪水
--带参数的存储过程
create or replace procedure raiseSalary(eno in number)
is
--定义变量
psal emp.sal%type;
begin
--得到涨前的薪水
select sal into psal from emp where empno=eno;

--涨100
update emp set sal=sal+100 where empno=eno;

--要不要commit?
--一般不在存储过程和存储函数中提交
--一般谁来调用,谁来提交 和 回滚

dbms_output.put_line(\'涨前的薪水\'||psal ||\' 涨后\'||(psal+100))

end raiseSalary;

使用in out 表示参数是输入或输出参数
存储函数
  为一个命名的存储程序,可以带参数,并返回一个计算值。函数和过程结构类似,但是
  必须要有一个RETURN子句,用于返回函数值
  CREATE OR REPLACE FUNCTION 函数名(参数列表)   RETURN 函数值类型   AS   PL/SQL子程序   --查询某个员工的年收入   CREATE OR REPLACE FUNCTION queryEmpIncome(eno in number){   return number   is   --定义变量保存月薪和奖金   psal number emp.sal%type;   pcomm emp.comm%type;   begin     --得到月薪和奖金     select sal,comm into psal,pcomm from emp where empno=eno;     --返回年收入     return psal*12+nvl(pcomm,0);   end queryEmpIncome; } 过程和函数都可以通过OUT指定一个或者多个输出参数,可以利用OUT参数,在过程和函数中实现返回多个值 --如果只有一个返回值,用存储函数;否者,就用存储过程 --查询某个员工的姓名、薪水、职位 create or replace procedure queryEmpInformation(enp in number, pename out varchar2, psal out varchar2, pjob out varchar2) is begin   select ename,sal,job into pename,psal,pjob from emp where empno=eno;   end queryEmpInformation;   =>也是赋值的语法 有了OUT后存储函数就多余了,因为存储过程可以有返回值,可以没有返回值 为什么ORACLE依然保留存储函数? 历史问题 ORACLE最早的版本中存储过程与存储函数是有区别的,为了做到新版本需要向下兼容,所以要保留存储函数

  --问题 查询某个员工的所有信息
  --out参数太多了,怎么解决参数过多
  --问题 查询某个部门中的所有员工信息 返回的应该是一个集合

在Out参数中使用游标

申明包结构
CREATE OR REPLACE PACKAGE MYPACKAGE 
 AS
 type empcursor is ref cursor
 procedure queryEmpList(dno in number,empList out empcursor)
END MYPACKAGE;
创建包体
CREATE OR REPLACE PACKAGE BODY MYPACKAGE 
AS
 procedure queryEmpList (dno in number,empList out empcursor)
 AS
 BEGIN
  open empList for select * from emp where deptno=dnp;
 END queryEmpList;
 
END MYPACKAGE;

-------
--查询某个部门中的所有员工信息
create or replace package mypackage is
 type empcursor is ref cursor;
 procedure queryEmpList(dno in number,empList out empcursor);
end mypackage;
create or replace package body mypackage is procedure queryEmpList(dno in number,empList out empcursor) as begin open empList for select * from emp where deptno=dno; end queryEmpList; end mypackage;
desc mypackage;//查看包结构

触发器

触发器
 数据库触发器是一个与表相关联的、存储的PL/SQL程序。
每当一个特定的数据操作语句在指定的表上发出时,ORcale自动地执行触发器中定义的语句序列 第一个触发器 每当成功插入新员工后自动插入一句话:成功插入新员工 create trigger firsttriger after insert on emp declare begin dbms_output.put_line(\'成功插入新员工\') end; 触发器可用于   1、数据确认   2、实施复杂的安全性检查   3、做审计,跟踪表上所做的数据操作等(审计:记录日志,跟踪表上的数据操作等)      基于值的审计     强制审计     标准审计     细粒度审计     管理员审计 4、数据的备份和同步   CREATE OR REPLACE TRIGGER   {BEFORE|AFTER}   {DELETE|INSERT|UPDATE|OF 列名}   ON 表名   [FOR EACH ROW [WHEN 条件]]   PL/SQL块 触发器类型(针对的表):   语句级触发器:在指定的操作语句操作之前或之后执行一次,不管这条语句影响了多少行   行级触发器[FOR EACH ROW](针对的行):   触发语句作用的每一条记录都被出发。在行级触发器中使用:old 和:new 伪记录变量,识别值的状态 例子: 禁止在非工作时间插入数据 1、周末 :to_char(sysdate,\'day\') in (\'星期六\',\'星期日\') 2、上班前和下班后:to_number(to_char(sysdate,\'hh24\')) not between 9 and 17 (not between 在什么之外) --语句级触发器 create or replace trigger securityemp before insert on emp begin if to_char(sysdate,\'day\') in (\'星期六\',\'星期日\') or to_number(to_char(sysdate,\'hh24\')) not between 9 and 17 --禁止insert raise_application_error(-20001,\'不能在工作时间插入数据\') end if; end securityemp;
--检查涨工资不能越涨越少(涨后不能少于涨前) --数据确认/行级触发器 create or replace trigger checksalary before update on emp for each row
begin -- 如果涨后薪水小于涨前薪水 then 抛出异常 if :new.sal<:old.sal then raise_application_error(\'-2002\',\'涨后薪水不能少于涨前薪水\'); end; end checksalary;
:old 操作这一行之前这一行的值 :new 之后 wm_contact 使用逗号在分组中将同一个分组的内容连接成字符串

 

分类:

技术点:

相关文章: