Oracle(五)

一、什么是PL/SQL?

        PL/SQL全名Procedure Language/SQL,PL/SQL是Oraclesql语言过程化扩展语言,指在SQL命令语言中增加了过程处理语句(逻辑--如分支、循环等),使SQL语言具有过程处理能力。

        特点:把SQL语言的数据操纵能力与过程语言的数据处理能力结合起来

二、PL/SQL的结构

DECLARE   --声明块:理解为初始化(可选的)

--定义变量、常量、类型、游标、异常、复杂数据类型(记录和表)、以及局部的存储过程和函数 

BEGIN     --执行块:程序体的开始端口(必须有)

--要执行的pl/sql语句和sql语句(程序的主要部分)

EXCEPTION --异常处理块(可以有)

--处理各种运行时的错误

END;      --程序体的结束声明

三、PL/SQL的使用

需求1:在控制台打印一行数据

DECLARE 

BEGIN 

DBMS_OUTPUT.put_line('hello pl/sql');
-- 说明:dbms_output叫程序包,相当于Java种的类,put_line()相当于Java中的方法,这里是存储过程或者函数
END;

说明1:了解块编程,理解程序包

注意:在黑窗口中执行pl/sql语句,必须加上"/"才能执行整个(pl/sql)过程,并且通过set serveroutput on; --开启屏幕输出开关(设置显示PLSQL程序的执行结果),默认情况下不显示PLSQL程序的执行结果;原因:dbms_output是Oracle中独有的一个输出对象,put_line是上述对象的一个方法,用于输出一个字符串自动换行。

小技巧:F8来执行pl/sql语句;desc dbms_output; 来查看某个程序包的详细的文档说明

需求2:按职工的职称涨工资,总裁涨1000元、经理涨800元、其他人员涨400元

说明:无法使用一条SQL来实现,需要借助其他程序来帮助完成,优先使用pl/sql

需求3:查询并打印员工号是7839员工的姓名和薪水

DECLARE 
-- 自定义变量(一般形式)和引用类型的变量
pname varchar(20);
psalary emp.sal%type; -- 我们称之为引用型变量:表示引用sal字段的类型
BEGIN 
select ename,sal into pname,psalary from emp where empno=7839;
dbms_output.put_line(pname||'的薪水为'||psalary);
-- DBMS_OUTPUT.put_line('hello pl/sql');
END;

说明:将查询的数据传递给自定义变量(into--注意位置)、字符串的拼接(||)

注意:自定义变量一定要注意类型,在实际开发中常用的是引用类型的变量(注意书写),不容易出错。

需求4:查询并打印员工号是7839员工的所有信息

-- 需求:在控制台打印所有的数据
DECLARE 
-- 自定义变量
marry emp%rowtype;-- 记录类型的变量,接受一条数据(所有字段的值),相当于一个容器
BEGIN 
select * into marry from emp where empno=7839;              -- 查询所有的信息保存到容器中
dbms_output.put_line(marry.ename||'的薪水是'||marry.sal);    -- 注意取数据的方式
dbms_output.put_line(marry.ename||'的岗位是'||marry.job);    -- 注意取数据的方式
END;

说明:理解记录型变量含义,会定义记录型变量(针对某个表中的数据),取数据的方式(通过emp中的字段名来获取数据)

需求5:判断用户从键盘输入的数字,并打印到屏幕上(if else 语句 进行逻辑判断)

分析:既然我们要接收用户从键盘上输入的数字,我们得使用键盘录入,那么程序中必须接受键盘录入的数据,用户需要输入数据时,给用户提示

set serveroutput ON; --语句1,环境配置,可以使用函数dbms_output.put_line()可以输出参数的值
-- 键盘录入时,用户提示
accept my prompt'请输入一个数字:';-- 语句2
-- 说明1:accept表示接受键盘录入的数据、
-- 说明2:mynumin是定义的一个变量(内存中的地址值)来保存数据
-- 说明3:prompt是文字提示
DECLARE 
-- 接受(键盘录入的数据),并保存到自定义的变量中
mynum number:=&my;
-- 说明2:":="是一个赋值号,&是一个地址运算符
-- 表示获取此地址的值(键盘录入的数据)
BEGIN 
   if mynum=1 then dbms_output.put_line('今天是星期一');
   elsif mynum=2 then dbms_output.put_line('今天是星期二');
   elsif mynum=3 then dbms_output.put_line('今天是星期三');
   elsif mynum=4 then dbms_output.put_line('今天是星期四');
   elsif mynum=5 then dbms_output.put_line('今天是星期五');
   elsif mynum=6 then dbms_output.put_line('今天是星期六');
   elsif mynum=7 then dbms_output.put_line('今天是星期日');
   else dbms_output.put_line('您输入的数字有误,请重新检查');
   end if;-- 一定要加分号
END;

掌握:if else的逻辑判断结构

注意elsif千万不要写成elseif(确实是少了一个e)!!!

补充:if else常用有三种形式(if、if else、if elsif else),但是都要与end if配对

执行过程1:在Oracle自带的sqlplus中先执行语句1、2然后输入数字,最后再输入结构体,最后输入斜杠才能执行

执行过程2:在pl/sql的Command Window里执行即可

问题描述:在Oracle自带的sqlplus里能运行,但在pl/sql的测试窗口里不能运行。报错:无效SQL语句

简单说明:点击打开链接点击打开链接

set serveroutput on:点击打开链接

需求6:定义一个常量

c_mynum constant number:=100;-- 注意命名规范

说明:循环语句  while、for、loop的用法

语法1:
WHILE  条件  
LOOP
.. .
total : = total + salary;
END  LOOP; 

需求7:输出1到10的数字--方式1

SQL> declare
  2    step number := 1;   --定义一个起始变量
  3  begin
  4    while step <= 10    --注意条件后面不要写分号
  5    loop
  6      dbms_output.put_line(step);
  7      step := step + 1; -- pl/sql中没有加等的说法
  8    end loop;
  9  end;
 10  /

需求8:同上--方式2

declare 	
     num number :=1; --注意赋值号是 :=
 begin
 loop
   exit when num>10;  --循环结束条件(有分号)
     dbms_output.put_line(num);
     num:=num+1;  --控制条件语句,注意oracle中没有 ++ 和 -- 这两个运算符
 end loop; --结束循环 注意后面要加分号
end;

说明:注意上面两种方式的细节实现,以及特点。方式1是首先判断while是否为ture,才决定执行不执行,而方式2会首先进入loop循环体中,至少要执行一次(依据exit_when的位置)。

需求9:同上--方式3

declare 
-- (1)定义变量默认是1
  v_num NUMBER DEFAULT 1;
BEGIN
  -- 表明:如果V_name是在[1,10]的范围则继续循环,否则退出
  -- 注意:for in 的这种循环形式
  FOR v_num IN 1..10 --传递变量,也是没有分号
    LOOP
      dbms_output.put_line(v_num);
      -- 注意:v_num不能作为赋值目标(具体原因未知)
      -- v_num:=v_num+1;
    END LOOP;
end;

需求10:使用游标方式输出emp表中的员工编号和姓名打印到屏幕上

概念:Oracle中的游标其实就是类似JDBC中的resultSet,就是一个指针的概念。既然是类似与resultSet,那么游标仅仅是在查询的时候有效的。cursor理解为多条记录所构成的集合(是一个结构体),游标可以存储查询返回的多条数据。

--定义游标的语法:cusor  游标名  [ (参数名  数据类型,参数名 数据类型,...)]  is select   语句;

使用步骤

        --打开游标:       open c1;    (打开游标执行查询)
	--取一行游标的值:  fetch c1 into v_job; (取一行数据到变量中)
	--关闭游标:       close  c1;(关闭游标释放资源)
	--游标的结束方式    exit when c1%notfound --数据结束的标志

相关代码(注意逻辑):

declare 
  -- 定义游标(保存数据集)
  CURSOR all_cusor IS SELECT ename,sal FROM emp;
  -- 定义两个变量,保存姓名和工资
  -- 为了避免数据类型的错误,采用引用类型的变量
  v_ename emp.ename%TYPE;
  v_sal emp.sal%TYPE;
begin
  --(1)打开游标中的光标
  OPEN all_cusor;
  --(2)循环的从游标中获取数据
  LOOP 
  --(3)表示取出当前行的记录,赋值给我们定义的变量,并把光标下移一行,光标默认的位置就在第一行
     FETCH all_cusor INTO v_ename,v_sal;
  --(4)啥时候将数据取完,总得有一个结束退出的标志吧!!!
  EXIT WHEN all_cusor%NOTFOUND; -- 光标没有取到记录的时候,结束循环 %notfound是光标的一个属性
  dbms_output.put_line(v_ename||'的薪水是'||v_sal);
  END LOOP;
  --(5)数据取出完毕,则关闭光标
  CLOSE all_cusor;
end;

说明:也可以在外层通过while all_cusor%found来定义结束条件

补充:光标的常用属性有--%isopen(光标是否打开)、%rowcount(影响的行数)、%found、%notfound

需求10:给不同职位的员工涨工资,总裁涨工1000、经理800、其他员工400

思路:查询出各个员工的职位和部门封装到游标中存储,然后定义两个变量接受员工的职位和部门,if else条件根据职位对薪水进行调整

代码:有点复杂

declare 
   --  定义游标
   cursor temp_cursor is select empno,job from emp;
   -- 定义两个变量存储游标取出来的值
   v_empno emp.empno%type;
   v_job emp.job%type;
begin
   open temp_cursor; --打开光标
    loop
      fetch temp_cursor into v_empno,v_job; --从游标中取出值 赋值给变量
      exit when temp_cursor%notfound;       --循环结束条件,当游标中没有找到值时结束循环(带分号)
          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+800 where empno=v_empno;-- empno是独一无二的
           else update emp set sal=sal+400 where empno=v_empno;
          end if; --结束if语句
    end loop;     --结束循环语句
    close temp_cursor;   --关闭光标
    commit;   
--注意:这里要提交一下事务,不然我们在sqlplus 里面查询的时候 发现数据没改变  
--因为:我们的Oracle 数据库的隔离级别是读未提交,我们在软件里面不提交,黑窗口会读不到
    dbms_output.put_line('完成');
end;

需求11:查询某个部门的员工姓名,我们可以使用带有参数的游标来做

语法

CURSOR  光标名  [ (参数名  数据类型[,参数名 数据类型]...)] IS  SELECT   语句;

理解上:游标理解为带有行号(索引)的临时表

代码:

declare 
 -- (1)定义带参数的游标(引用类型的变量)
 CURSOR temp_cursor(v_cusor ename.deptno%type) IS SELECT ename FROM emp WHERE deptno=v_cusor;
 -- (2)定义一个变量,接受从光标中查询出来的数据
 v_ename emp.ename%TYPE;
begin
 -- (3)打开一个光标--在开启光标的时候,我们就传入具体的参数 比如查10号部门,我们就传入10
 OPEN temp_cursor(10);--注意:这时光标位于第一条记录之前,也可以外部传入,此时:参数是&v_cusor,不需要再accept了
LOOP
 -- (4)获取光标中的数据赋给变量
 FETCH temp_cursor INTO v_ename;
 -- (5)循环结束条件
 EXIT WHEN temp_cursor%NOTFOUND;-- 让光标下移
 -- (6)展示数据
 dbms_output.put_line(v_ename);
 END LOOP;
  -- (7)关闭光标
 -- CLOSE temp_cursor;
end;
/

需求12:当除数为0时 处理异常

概念:异常也叫做例外,异常是程序设计语言提供的一种功能,用来增强程序的健壮性和容错性。

1、系统定义好的常见异常

系统定义好的异常 
no_data_found        (没有找到数据)
too_many_rows        (select …into语句匹配多个行) 
zero_divide          (被零除)
value_error          (算术或转换错误)
timeout_on_resource  (在等待资源时发生超时)

代码

declare 
-- 定义一个变量接受运算结果
v_result NUMBER;
begin
 v_result:=1/0;--计算出现异常
 EXCEPTION     --出现异常要处理异常
   --判断是哪种异常
   WHEN zero_divide THEN
        dbms_output.put_line('被除数不能为0');
     WHEN value_error THEN
        dbms_output.put_line('数值转换错误');
     WHEN others THEN   --others 代表其他异常 其他异常也需要捕获
        dbms_output.put_line('其他错误');
end;

2、自定义异常

需求13:在数据库中查询50号部门员工的姓名(其实我们emp表中没有50号部门),这时候就会查不到,就可以定义为发生异常

常见的处理位置

--(1)在declare节中"定义"例外   
out_of   exception ;
--(2)在begin节中可行语句中"抛出"例外  
raise out_of ;
--(3)在exception节"处理"例外
when out_of then …
代码:
DECLARE 
  -- 定义变量接受数据
  v_ename emp.ename%TYPE;
  -- 定义游标接受数据带参数
  CURSOR v_cursor(v_deptno NUMBER)IS SELECT ename FROM emp WHERE deptno=v_deptno;
  -- 定义一个异常
  notfound_exception EXCEPTION;
BEGIN
  --打开光标,同时输入数据
  -- &v_deptno
  OPEN v_cursor(&my);-- 注意:不要与定义的变量与v_deptno相同
  --循环获取数据,判断是否有这个数据
  LOOP
    FETCH v_cursor INTO v_ename;
    --如果读到最后依然没有数据,说明没有50号的部门:则抛出异常
    IF v_cursor%NOTFOUND THEN
      RAISE notfound_exception;-- raise是抛出异常的
      ELSE
        --打印输出
        dbms_output.put_line(v_ename);-- 注意:千万别写错
  --结束每次循环的判断
    END IF; 
  --结束循环
  END LOOP;
  --结束游标
  CLOSE v_cursor;
 -- 处理异常 
EXCEPTION
    WHEN notfound_exception THEN
      dbms_output.put_line('暂无该部门的员工');
END;

说明:有一点小bug(查询出来也会执行异常,回头处理)

需求14:统计每年的入职人数

代码(注意设计思路)

-- 分析 
--(1)首先我得拿到日期
--(2)我每个年份定义一个变量,然后判断时那个年份的就累加
declare 
--(3)定义光标查出年份--并将其转换成字符的形式(好好体会!!!)
  cursor cemp is select to_char(hiredate,'yyyy') from emp;
--(4)定义接收年份的变量
  pdate varchar(4);
--(4)定义四个年份变量(已知)
  count80 number :=0;
  count81 number :=0;
  count82 number :=0;
  count87 number :=0;
begin
    open cemp;
      loop
         fetch cemp into pdate; --取出年份
        exit when cemp%notfound;--结束条件
        if pdate='1980' then count80:=count80+1;
        elsif pdate='1981' then count81:=count81+1;
        elsif pdate='1982' then count82:=count82+1;
        else count87:=count87+1;
        end if;
      end loop;   
    close cemp;
    -- 输出展示
    -- 注意这个几个变量相加要用括号括起来(表示是一个整体)
    dbms_output.put_line('总人数'||(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;

知识点:存储过程

      存储过程(Stored Procedure)是在大型数据库系统中,一组为了完成特定功能的SQL语句集,经编译后存储在数据库中,用户通过指定存储过程的名字并给出参数(如果该存储过程带有参数)来执行它。存储过程是数据库中的一个重要对象,任何一个设计良好的数据库应用程序都应该用到存储过程(复用性)。

语法:

create [or replace] PROCEDURE 过程名[(参数名 in/out 数据类型--多个参数类型)]  
AS 
begin
        PLSQL子程序体;
end;

需求15:创建一个存储过程

CREATE OR REPLACE PROCEDURE hello
AS
  BEGIN
    dbms_output.put_line('hello world');
  END;

调用过程的三种方式

(1)exec过程名---Oracle自带的SQLPLUS中使用
(2)PL/SQL程序调用--第三方使用
(3)Java调用

方式1:

-- exec hello();--注意:即使没有参数也要带上()

方式2:

 BEGIN
   -- 说明存储过程是可以多次执行的
   hello();
   hello();
 END;

需求16:带有参数的存储过程(in、out)  

in参数

需求7369号员工涨100工资

create or replace procedure test(myeno in number) is  --注意in的位置
-- 定义一个变量用来接收涨前的工资  主要变量定义在begin前面,不要定义到begin 里面去了
 oldsal emp.sal%type;
begin
        --根据传过来的员工号查出原来的工资 赋值给变量
        select sal into oldsal from emp where empno=myeno;
        --给工资加100
        update emp set sal=sal+100 where empno=myeno;
        --打印输出
        dbms_output.put_line('涨前工资'||oldsal||'涨后是'||(oldsal+100));
end test;

注意:in大小写不敏感,如果只有一个输入参数默认可以不写in。

需求:查询7788号员工的的姓名、职位、月薪,返回多个值

out参数

CREATE OR REPLACE PROCEDURE find(pempno IN NUMBER, psal OUT VARCHAR2,
 pename OUT VARCHAR2, pjob OUT VARCHAR2) -- 注意:我们是直接使用的这些变量,还尚未定义
AS
  BEGIN
    SELECT ename,sal,job
    INTO pename, psal, pjob
    FROM emp
    WHERE empno = pempno;
  END;

说明:在调用的时候使用到的psal、pname、pjob是没有定义的,因此调用的时候我们需要先定义变量后使用!!!

调用存储函数的代码:

DECLARE
  psal   emp.sal%TYPE;
  pename emp.ename%TYPE;
  pjob   emp.job%TYPE;
BEGIN
  find(7369, psal, pename, pjob);-- 调用函数后自定义变量接受获取的数据
  dbms_output.put_line(psal || pename || pjob);
END;/

--------------------------------------------

知识点:存储函数

语法:

CREATE [OR REPLACE] FUNCTION 函数名(Name in type, Name out type, ...)
 RETURN  返回值类型
  AS
PLSQL子程序体;

BEGIN
  
END;

说明:无论是过程还是函数,as关键字代替declare关键字

注意:函数也需要in、out的关键字

需求:查询7788号员工的的姓名(return)、职位(out)、月薪(out),返回多个值

CREATE OR REPLACE FUNCTION findEmpNameAndJobAndSal(pempno IN NUMBER, pjob OUT VARCHAR2, income OUT NUMBER)
  --这里指定的是返回值类型
  RETURN VARCHAR
AS
  /*查询出来的字段与列名相同,就使用列名相同的类型就行了。*/-- 多行注释
  pename emp.ename%TYPE; --认真体会此处的作用(与存储过程比较下)
  BEGIN
    SELECT sal,ename,job
    INTO income, pename, pjob
    FROM emp
    WHERE empno = pempno;
    RETURN pename;
  END;

调用:

DECLARE
  /*输出的字段与列名的类型是相同的。*/
  -- 接受函数中带出来(非非return)的数据
  income emp.sal%TYPE;
  pjob   emp.job%TYPE;
  pename emp.ename%TYPE;
BEGIN
  -- pename是存储函数的返回值; pjob、income是存储函数带回来的值
  -- 注意:与存储过程的比、
  -- pl/sql特有的方式  :=
  pename := findEmpNameAndJobAndSal(7369, pjob, income);
  dbms_output.put_line(pename || pjob || income);
END;/

存储过程和存储函数的区别?

(1)一般来讲,过程和函数的区别在于函数可以有一个返回值;而过程没有返回值。 
(2)但过程和函数都可以通过out指定一个或多个输出参数。我们可以利用out参数,在过程和函数中实现返回多个值。
(3)那也就是说,存储过程,没有返回值,但是可以通过out 输出参数来实现,那我们到底选用存储过程还是存储函数
(4)一般来说,如果有一个返回值,我们选存储函数,如果没有返回值,或者多个返回值,我们可以选存储过程.

补充:

SQL与过程函数使用场景
/*
【适合使用】过程函数:
(1)需要长期保存在数据库中
(2)需要被多个用户重复调用
(3)业务逻辑相同,只是参数不一样
(4)批操作大量数据,例如:批量插入很多数据
【适合使用】SQL:
(1)凡是上述反面,都可使用SQL
(2)对表,视图,序列,索引,等这些还是要用SQL */

额外补充

begin
  -- Call the procedure
  :result :=findEmpNameAndJobAndSal(myenum =>7369, -- 传入一个具体的员工号
         pename => :pename, --pename不需要声明
         psal => :psal,     --psal不需要声明(接受传回来的数据)
         pjob => :pjob);    --pjob不需要声明
end;

说明:=> 也是一种赋值方式,:result 是一个绑定变量(不需要声明),调用完函数后,函数返回的结果赋值给了:result。

注意:打印绑定变量的方式

 dbms_output.put_line(:result);-- 形如此

-----------------------------------

四、聊一聊变量命名规范

可以遵循如下的方式


未完待续。。。


猜你喜欢

转载自blog.csdn.net/wzj_110/article/details/80549193