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閱讀 228,461評論 6 532
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,538評論 3 417
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,423評論 0 375
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,991評論 1 312
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,761評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,207評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,268評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,419評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,959評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,782評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,983評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,528評論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,222評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,653評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,901評論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,678評論 3 392
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,978評論 2 374

推薦閱讀更多精彩內容