PL/SQL基础语法

教程资料:http://www.w3ii.com/zh-CN/plsql/plsql_basic_syntax.html

以下示例都是基于安装Oracle时系统创建的orcl数据库emp表

Hello World

    set serveroutput on -- 打开输出开关
    -- pl/sql程序
    declare
       -- 声明部分(变量声明,光标声明,异常声明)
    begin -- 程序开始
       ....语句序列(DML语句)
       dbms_output.put_line('Hello World'); -- 打印Hello World
    exception
       -- 异常处理语句
    end; -- 程序结束
    / -- 表示退出当前程序编辑并执行程序,在SQL Developer中要加,但是在PL/SQL Developer中不要加,set serveroutput on也是这样。   
 * 如果程序中有insert/update/delete语句,SQL Developer中一定要加commit, PL/SQL Developer中不需要(它是自动管理事务)

变量和常量

  • 常用变量类型
    char,varchar2,date,number,boolean,long
    例如:
    varl      char(15);
    pi constant number := 3.141592654;
    married   boolean := true;
    psal      number(7,2);
    my_name   emp.ename%type;
    emp_rec   emp%rowtype;
    
    char(15):表示字符长度限定在15个以内。
    constant : 定义常量。
    boolean := true : 表示赋值为true。
    number(p,s): precision也叫精度,是指数中的总数字个数,默认情况下,精度为38 位,取值范围是1~38 之间。  
                     scale是小数位数,即数中小数点右边的数字个数。其范围从-84到127,能够决定舍入规则。
             如果我们不指定scale的值,默认就为0,表示该变量是个整数。
    emp.ename%type: 表示引用emp表ename字段,ename是什么my_name就是什么。
    emp%rowtype: 表示记录型变量,它记录了表中一行数据所有的字段,所以它的结果是一个数组。  
             取出方式:emp_rec.ename(表示取ename这个字段),emp_rec.xxx(任意字段)  
                     取出并赋值:emp_rec.ename := 'gordon'
引用型变量:  
    declare
         -- 定义变量保存姓名和薪水
         --pename   varchar2(20);
         --psal    number; p,s都不指定表示 p,s都是默认值
         pename emp.ename%type;
         psal   emp.sal%type;
    begin
        -- 得到7839的姓名和薪水,并使用 into 关键字赋值给pename,psal
        select ename,sal into pename,psal from emp where empno = 7839;
        -- 打印
        dbms_output.put_line(pename||'的薪水是'||psal);
    end;
    /
记录型变量:
    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;
    /

条件语句

  • 三种方式:
IF 条件 THEN 语句1;
    语句2;
END IF;
IF 条件 THEN 语句1;
ELSE 语句2;
END IF;
IF 条件 THEN 语句1;
ELSIF 条件 THEN 语句2; -- 注意这里的 ELSIF 写法,不是ELSEIF
ELSE 语句3;
END IF;

示例:

-- 接收键盘输入
accept nums prompt '请输入一个数字';
-- nums : 地址值,在该地址上保存了输入的值
declare
   -- 定义变量保存输入的数字
   -- 隐式转换(键盘输入的都是字符串),& 符号表示取出地址值
   pnum number := &nums;-- 将输入的数字赋值给pnum这个变量
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');
   else
     dbms_output.put_line('你输入的数字是:' || pnum);
   end if;
end;  

循环语句

  • 三种方式:
WHILE total <= 20000 -- 当条件成立时执行循环
LOOP
....语句序列
total := total + salary;
END LOOP;
LOOP
EXIT when 条件;-- 当条件成立时退出循环,否则继续循环
....语句序列
END LOOP;
FOR I IN 1 .. 3 
-- 1..3 : 表示可以循环3次分别是 I = 1,I = 2,I = 3。又如:5..100(表示从5到100之间的循环)
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;

Cursor(游标) == ResultSet(结果集)

  • 游标语法
    CURSOR 游标名 [(参数名 数据类型,参数名 数据类型....)] IS SELECT 语句

  • 用于存储一个查询返回的多行数据
    cursor cs is select ename from emp;

  • 打开游标:   open  cs;(打开游标执行查询)

  • 取一行游标的值:  fetch cs into pename;(取一行值并赋值给pename变量)

  • 关闭游标:   close  cs;(关闭游标释放资源)

  • 游标属性

    • %isopen : 是否打开
    • %rowcount : 有效的行数,比如游标中有100条记录但是只返回10条记录,那么rowcount就是10.
    • %notfound : 没有找到
    • %found : 找到了
    • Oracle每个会话,一次只能打开300个光标。当然也可以修改此默认值,以管理员身份进入Oracle,执行alter  system  set  open_cursors=400;
  • cursor游标和ResultSet游标的区别 : ResultSet游标的初始位置是第一行数据的前一个位置,而cursor游标的初始位置就是第一行数据

    示例:

    /*
    涨工资 总裁1000 经理800 其他400
    */
    declare
        -- 取出表中所有员工编号和职位
        cursor cemp is select empno, empjob from emp;
        -- 定义接收的变量
        pempno emp.empno%type;
        pjob   emp.empjob%type;
    begin
       open cemp;-- 打开游标
        loop
          -- 取出一条记录并赋值给pempno,pjob
          fetch cemp into pempno, pjob;
          exit when cemp%notfound; -- 当游标取不到时退出循环
          -- 判断
          if pjob = 'PRESIDENT' 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 emp set sal = sal + 400 where empno = pempno;
          end if;
        end loop;
        close cemp;-- 关闭游标
    end;
    带参数的cursor:
    /*
    查询某个部门的员工姓名
    */
    declare
        -- 定义游标参数dno,查询deptno = dno时此部门的员工姓名
        cursor cemp(dno number) is select ename from emp where deptno = dno;
        pename emp.ename%type;
    begin
        open cemp(10);-- 按Java的理解,上面定义的是形参,此处就是实参.表示查询10号部门
        loop
          fetch cemp into pename;
          exit when cemp%notfound;
          dbms_output.put_line(pename);
        end loop;
        close cemp;  
    end;

异常处理

  • 系统定义的异常
    • NO_data_found    没有找到数据
    • Too_many_rows    select...into 语句匹配多行(多行数据赋值给单一变量)
        declare
        pename emp.ename%type;
        begin
        select ename into pename from emp;
        end;
+ Zero\_Divide&nbsp;&nbsp;&nbsp;&nbsp;被零除,如 1/0
+ Value\_error&nbsp;&nbsp;&nbsp;&nbsp;算术或转换错误
+ Timeout\_on_resource&nbsp;&nbsp;&nbsp;&nbsp;连接资源超时  
        /*
        异常处理
        */
        declare
           pnum number;
        begin
           pnum := 1/0;
        exception
           -- 被零除
           when zero_divide then dbms_output.put_line('1:0不能做被除数');
                                 dbms_output.put_line('2:0不能做被除数');
           -- 算术或转换错误                      
           when value_error then dbms_output.put_line('算术或转换错误');
           -- 其他错误 others :表示其他所有类型的异常
           when others then dbms_output.put_line('其他异常');     
           -- 在PL/SQL程序中应该捕获所有的异常,否则异常会给抛给数据库,可能会导致数据库异常而影响其他方面的正常使用                 
        end;
  • 自定义异常
/*
自定义异常
查询50号部门的员工姓名(数据库中没有50号部门)
*/
declare
  cursor cemp is select ename from emp where deptno = 50;
  pename emp.ename%type;
  -- 自定义异常
  no_emp_found exception;
begin
  open cemp;
     -- 取一条记录
     fetch cemp into pename;
     if cemp%notfound then
       -- 抛出自定义异常,关键字 raise
       raise no_emp_found;
     end if;
  -- 其实在抛出异常后close cemp是不执行的,
  -- 但是Oracle有一个Process monitor进程跟java虚拟机一样会定时清理闲置或垃圾资源
  close cemp;
exception
  when no_emp_found then dbms_output.put_line('自定义异常');
  when others then dbms_output.put_line('其他异常');
end;

实例

  • 实例1
 /*
统计每年入职的员工人数,如:
total    1980     1981     1982     1987
   15      1       10        2        2
 */
declare
   -- to\_char:按一定规则转成字符串类型,这里把入职时间转成字符串并只取年份
   cursor ctemp is select to\_char(hiredate, 'yyyy') from emp;
   pyear   varchar2(4);
   pcount0 number := 0;
   pcount1 number := 0;
   pcount2 number := 0;
   pcount7 number := 0;
begin
   open ctemp;
   loop
      fetch ctemp into pyear;
      exit when ctemp%notfound;
      if pyear = '1980' then
         pcount0 := pcount0 + 1;
      elsif pyear = '1981' then
         pcount1 := pcount1 + 1;
      elsif pyear = '1982' then
         pcount2 := pcount2 + 2;
      else
         pcount7 := pcount7 + 1;
      end if;
   end loop;
   close ctemp;
   dbms\_output.put_line('total:' ||
                        (pcount0 + pcount1 + pcount2 + pcount7) ||
                        '  1980:' || pcount0 || '  1981:' || pcount1 ||
                        '  1982:' || pcount2 || '  1987:' || pcount7);
end;
  • 实例2
/*
为员工涨工资。从最低工资调起每人长10%,但工资总额不能超过5万元,
请计算长工资的人数和长工资后的工资总额
*/
declare
  -- order by 默认是升序,题目要求从最低开始
  cursor cemp is select empno,sal from emp order by sal;
  pempno emp.empno%type;
  psal emp.sal%type;
  pcount number := 0;
  ptotal number;
begin
  -- 得到涨前工资总额
  select sum(sal) into ptotal from emp;
  dbms_output.put_line('涨前工资总额:'||ptotal);
  open cemp;
         loop
            exit when ptotal > 50000;-- 当总额大于5000时退出
            fetch cemp into pempno,psal;
            exit when cemp%notfound;-- 当cemp找不到时退出
            -- 涨工资
            update emp set sal = sal * 1.1 where empno = pempno;
            -- 涨工资的人数
            pcount := pcount + 1;
            -- 涨后的工资总额
            ptotal := ptotal + psal \* 0.1;
         end loop;
      close cemp;
      commit;
      dbms_output.put_line('涨工资的人数:'||pcount||'  涨后总额: '||ptotal);
end;
注意:程序还是有问题的,多执行几次此程序每次结果都不一样。
问题原因:第一次执行是对的,但是当第二次执行时又把已加过工资的人重新取出,
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;而且最低的那个人涨10%还是比较少的并没有超过限定条件。第三次第四次都是这样。
解决方式:应该保证程序在一定时间段内只执行一次,前3秒涨了工资现在又涨明显不合理。
  • 实例3
实现按部门分段(3000元以下,3000 ~ 6000,6000以上),统计各工资段的职工人数,
以及各部门的工资总额(工资总额中不包括奖金),参考如下:
部门&nbsp;&nbsp;&nbsp;&nbsp;<3000&nbsp;&nbsp;&nbsp;&nbsp;3000~6000&nbsp;&nbsp;&nbsp;&nbsp;>6000&nbsp;&nbsp;&nbsp;&nbsp;工资总额
10&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;2&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;1&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;0&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;8750
20&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;3&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;2&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;0&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;10875
30&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;6&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;0&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;0&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;9400
40&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;0&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;0&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;0&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;0
-- 创建一张表记录查出的结果
drop table msg;
create table msg(deptId number,count3 number,count6 number,count9 number,saltotal number);
declare
   -- 找出所有部门 根据部门号找出该部门所有人工资
   cursor cdept is select deptno from dept;
   pdeptno   dept.deptno%type;
   cursor csal(tno number) is select sal from emp where deptno = tno;
   psal      emp.sal%type;
   pcount3   number;
   pcount6   number;
   pcount9   number;
   ptotalSal number;
begin
   open cdept;
   loop
          fetch cdept into pdeptno;
          exit when cdept%notfound;
          pcount3   := 0;
          pcount6   := 0;
          pcount9   := 0;
          ptotalSal := 0;
          
          open csal(pdeptno);
          loop
             fetch csal into psal;
             exit when csal%notfound;
             if psal < 3000 then
                pcount3 := pcount3 + 1;
             elsif psal > 3000 and psal < 6000 then
                pcount6 := pcount6 + 1;
             elsif psal > 6000 then
                pcount9 := pcount9 + 1;
             else
                dbms_output.put_line('不在考虑范围内');
             end if;
             ptotalSal := ptotalSal + psal;
             -- 还可以直接以pdeptno为条件查出该部门的总工资
             -- select sum(sal) into ptotalSal from emp where deptno = pdeptno;
          end loop;
          close csal;
          -- 插入表中保存查询记录 ,nvl(p1,p2):相当于三元运算符,如果p1是null则返回p2,否则返回p1
          insert into msg values(pdeptno,pcount3,pcount6,pcount9,ptotalSal);
          dbms_output.put_line(pdeptno || '部门' || '  3k: ' || pcount3 ||
                               '  3-6k: ' || pcount6 || '  >6k: ' || pcount9 ||
                               '   总额 : ' || ptotalSal);
      end loop;
      close cdept;
      commit;
end;
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,761评论 5 460
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,953评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,998评论 0 320
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,248评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,130评论 4 356
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,145评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,550评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,236评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,510评论 1 291
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,601评论 2 310
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,376评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,247评论 3 313
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,613评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,911评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,191评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,532评论 2 342
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,739评论 2 335

推荐阅读更多精彩内容