day44_Oracle學習筆記_03

十3、PL/SQL程序設計

PL/SQL(Procedure Language/Structured Query Language)
一、PL/SQL是一種高級數據庫程序設計語言,專門用於在各類環境下對Oracle數據庫進行訪問。該語言集成於數據庫服務器中,因此PL/SQL代碼能夠對數據進行快速高效的處理。

二、PL/SQL是對SQL語言存儲過程語言的擴展,是Oracle系統的核心語言。

三、PL/SQL程序由三個塊組成:聲明部分、執行部分、異常處理部分。

13.一、sqldeveloper工具的使用

先去Oracle官網去下載最新版本的sqldeveloper,下載地址:https://www.oracle.com/technetwork/developer-tools/sql-developer/downloads/index.html
獲得2個zip壓縮包,以下圖所示:html


解壓縮後,找到sqldeveloper.exe點擊打開便可。
新建數據庫鏈接

就可使用了。
若是想要鏈接Mysql數據庫,須要進行配置:工具 --> 首選項 --> 數據庫 --> 第三方 JDBC 驅動包 --> 添加條目,添加所須要的jar包。以下圖所示:

13.二、小案例

小案例-回顧條件表達式:java

給員工漲工資:總裁漲1000元 經理漲800元 其餘漲400
寫一段java的JDBC程序,咱們這裏寫的是僞代碼,僞代碼不可以執行,可是能夠幫助咱們分析程序執行的過程和結構。

ResultSet rs = "select empno,job from emp";
while(rs.next()) {
    int eno = rs.getInt("empno");
    String job = rs.getString("job");
    if("PRESIDENT".eauals(job)) {
        update emp sal=sal+1000 where empno=eno;
    } else if ("MANAGER".eauals(job)) {
        update emp sal=sal+800 where empno=eno;
    } else {
        update emp sal=sal+400 where empno=eno;
}

  PL/SQL = Procedure Language/SQL = 過程語言/SQL
  PL/SQL程序從功能上來說,與上面JDBC的程序想要完成的功能是同樣的。
學習PL/SQL程序的目的:
  一、PL/SQL是Oracle對SQL語言的過程化擴展,操做效率更高。
  二、PL/SQL在SQL命令語言中增長了過程處理語句(分支、循環等),使SQL語言具備過程處理能力。
咱們把SQL語言的數據操縱能力與過程語言的數據處理能力結合起來,使得PL/SQL面向過程但比過程語言簡單、高效、靈活和實用。
  Oracle中對SQL語言的擴展叫作PL/SQL。
  SQL Server中對SQL語言的擴展叫作Transact-sql。sql

13.三、PL/SQL程序--打印輸出Hello World

示例代碼以下:shell

SQL> --聲明部分
SQL> declare
  2  --說明部分
  3  begin
  4  --程序部分
  5     dbms_output.put_line('Hello World');
  6  end;
  7  --退出編輯環境,並執行PL/SQL程序
  8  /

PL/SQL 過程已成功完成。

SQL>
 --默認狀況下,Oracle的輸出開關是關閉的。
SQL> --若是要在屏幕上輸出信息,須要將 serveroutput開關打開 set serveroutput on
SQL> set serveroutput on
SQL> /
Hello World

PL/SQL 過程已成功完成。

SQL>

13.四、變量和常量說明

PL/SQL程序結構截圖以下:數據庫


PL/SQL程序結構完整截圖以下:

變量和常量說明:

引用型變量示例代碼:
--查詢員工編號爲7839的姓名和薪水
set serveroutput on

declare
  --定義變量保存姓名和薪水
  --pename varchar2(20);
  --psal   number;
  --定義引用型變量保存姓名和薪水
  pename emp.ename%type;
  psal   emp.sal%type;
begin
  --獲得姓名和薪水
  --在PL/SQL中,賦值方式有兩種方式,一種是 :=  一種是 使用關鍵字into
  select ename,sal into pename,psal from emp where empno=7839;

  dbms_output.put_line(pename||'的薪水是'||psal);
end;
/

記錄型變量示例代碼:安全

--查詢員工編號爲7839的姓名和薪水
set serveroutput on

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;
/

如何定義常量呢?性能優化

  pename emp.ename%type;
  psal   emp.sal%type;
  加一個constant,就變成常量了。
  pename constant emp.ename%type;
  psal constant emp.sal%type;

13.五、分支


if語句示例代碼:
set serveroutput on
--判斷用戶從鍵盤輸入的數字

--接收鍵盤輸入
--num: 地址值,在該地址上保存了輸入的值。
accept num prompt '請輸入一個數字';

declare
  --定義變量保存輸入的數字
  pnum number := #
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('其餘數字');
  end if;
end;
/

13.六、循環


循環語句示例代碼:
--打印1~10
set serveroutput on

declare
  pnum number := 1;
begin
  loop
    --退出條件
    exit when pnum > 10;

    dbms_output.put_line(pnum);
    --加一
    pnum := pnum + 1;
  end loop;
end;
/

13.七、光標Cursor(遊標)== ResultSet

示例:按員工的工種長工資,總裁漲1000元,經理漲800元,其餘員工漲400元。
示例代碼截圖:bash


光標Cursor(遊標)詳解以下圖所示:

示例代碼:
--查詢並打印員工的姓名和薪水
/*
1. 光標的屬性:
    %isopen(光標是否打開)    
    %rowcount(光標影響的行數)
    %found(光標找到內容)      
    %notfound(光標沒有找到內容)

2. Oracle中默認,一個會話中只能打開300個光標
SQL> --修改光標個數須要管理員權限
SQL> show user
USER 爲 "SCOTT"
SQL> conn sys/password@192.168.56.101:1521/orcl as sysdba
已鏈接。
SQL> show user
USER 爲 "USER"
SQL> show parameter cursor

NAME                                 TYPE                             VALUE
------------------------------------ -------------------------------- -----------
cursor_sharing                       string                           FORCE
cursor_space_for_time                boolean                          FALSE
open_cursors                         integer                          300
session_cached_cursors               integer                          20

修改: alter system set open_cursors=400;

3. (思考):上面參數 cursor_sharing 什麼做用? --> 對於數據庫性能優化很是有用。
      EXACT(默認值), FORCE(應急使用), SIMILAR
*/


--示例:使用光標查詢員工姓名和工資,並打印
set serveroutput on

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 沒有取到記錄;
    exit when cemp%notfound;

    --打印
    dbms_output.put_line(pename||'的薪水是'||psal);
  end loop;

  --關閉光標
  close cemp;
end;
/

再來給員工漲工資代碼:服務器

--示例:按員工的工種長工資,總裁漲1000元,經理漲800元,其餘員工漲400元。
set serveroutput on

declare 
  --alter table "SCOTT"."EMP" rename column "JOB" to empjob
  cursor cemp is select empno,empjob from emp;
  --爲這個光標定義所須要用到的對應的變量
  pempno emp.empno%type;
  pjob   emp.empjob%type;
begin

  open cemp;
  loop
    --取一條記錄到變量中
    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;

  --Oracle是自動開啓事務的
  --Oracle默認的隔離級別是:read committed
  --why? --> ACID
  commit;

  dbms_output.put_line('漲工資完成');
end;
/

帶參數的光標
示例代碼以下:session

--查詢某個部門的員工姓名
set serveroutput on

declare
  cursor cemp(dno numberis select ename from emp where deptno=dno; --不同的地方
  pename emp.ename%type;
begin
  open cemp(20); --不同的地方
  loop
    fetch cemp into pename;
    exit when cemp%notfound;

    dbms_output.put_line(pename);

  end loop;
  close cemp;
end;
/

13.八、例外

例外:是程序設計語言提供的一種功能,用來加強程序的健壯性和容錯性。
Oracle中對異常的處理
  一、系統定義的例外
     No_data_found (沒有找到數據)
     Too_many_rows (select … into 語句中匹配多個行)
     Zero_Divide (被零除)
     Value_error (算術或轉換錯誤)
     Timeout_on_resource (在等待資源時發生超時)
  二、用戶定義的例外
    
演示:系統定義的例外(被0除)

--系統例外:被0
set serveroutput on

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('算術或轉換錯誤');
  when others then dbms_output.put_line('其餘例外');
end;
/

演示:用戶定義的例外以及處理例外

--查詢50號部門的員工姓名
set serveroutput on

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 no_emp_found;
  end if;

  --回顧
  --Java中是經過IO流來操做硬盤中的文件,
  --Java中IO最終是經過什麼方式操做硬盤上的文件呢?答:經過操做系統的進程。

  --Oracle中經過內存中的實例操做硬盤中的文件,
  --而內存中實例最終是怎麼操做硬盤上的文件呢?答:也是經過操做系統的進程。

  --這句執行不到,Oracle中怎麼辦呢?答:經過進程監視器
  --pmon: process monitor 進程監視器
  close cemp;

exception
  when no_emp_found then dbms_output.put_line('沒有找到員工');
  when others then dbms_output.put_line('其餘例外'); 
end;
/

13.九、實例

瀑布模型圖解:


實例1:統計每一年入職的員工人數
/*
SQL語句:
select to_char(hiredate,'yyyy') from emp;
--> 集合 --> 光標 --> 循環 --> 退出條件:notfound

變量:
    1. 初始值  
    2. 最終怎麼獲得

每一年入職的員工人數:
count80 number := 0;
count81 number := 0;
count82 number := 0;
count87 number := 0;
*/
set serveroutput on

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;
    --退出條件:notfound
    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 if;
  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;
/

實例2:爲員工漲工資,從最低工資調起每人漲10%,但工資總額不能超過5萬元,請計算漲工資的人數和漲工資後的工資總額,並輸出漲工資人數及工資總額。

/*
SQL語句:
select empno,sal from emp order by sal;
--> 光標 --> 退出條件:1. 工資總額 > 5w   2. notfound

變量:
    1. 初始值  
    2. 最終獲得

漲工資的人數: countEmp number := 0;
漲後的工資總額: salTotal number;

    方式1. select sum(sal) into salTotal from emp;
    方式2. 漲後=漲前 + sal * 0.1

    寫程序的原則:能不操做數據庫就不要操做數據庫。

練習:人數:7   總額:50205.325
*/
set serveroutput on

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

    --取一個員工出來到變量中
    fetch cemp into pempno,psal;
    --1. 工資總額 > 5w
    exit when salTotal > 50000;
    --2. notfound
    exit when cemp%notfound;

    --漲工資操做
    update emp set sal=sal*1.1 where empno=pempno;
    --人數+1
    countEmp := countEmp + 1;
    --2. 漲後工資總額=漲前工資總額 + sal * 0.1
    salTotal := salTotal + psal * 0.1;

  end loop;
  close cemp;

  commit;
  dbms_output.put_line('人數:'||countEmp||'   總額:'||salTotal);
end;
/

實例3:用PL/SQL語言編寫一程序,實現按部門分段(6000以上、(6000,3000)、3000元如下)統計各工資段的職工人數、以及各部門的工資總額(工資總額中不包括獎金)

/*
SQL語句:
部門: select deptno from dept;
部門中員工的薪水:select sal from emp where deptno=???;    問號是部門編號

變量:
    1. 初始值  
    2. 最終獲得

每一個段的人數:
    count1 number; 
    count2 number; 
    count3 number;
部門的工資總額: 
    salTotal number := 0;

獲得部門的工資總額的方式:
    1.select sum(sal) into salTotal from emp where deptno=???;
    2.累加
*/
set serveroutput on
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 number := 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;

      --判斷
      if psal < 3000 then count1:=count1+1;
        elsif psal>=3000 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;

  dbms_output.put_line('完成');
end;
/

13.十、筆試題2道

筆試1腳本.txt

create table test1
(id int primary key,
 name varchar(20),
 money int);

insert into test1 values(1,'Tom',1000);
insert into test1 values(2,'Mary',2000);
insert into test1 values(3,'Mike',3000);
insert into test1 values(4,'Jeff',4000);
commit;

示例代碼以下:

SQL> select * from test1;

        ID NAME                      MONEY
---------- -------------------- ----------
         1 Tom                        1000
         2 Mary                       2000
         3 Mike                       3000
         4 Jeff                       4000

SQL> select id,name,money,(select money from test1 where id=t.id-1) money1 from test1 t;

        ID NAME                      MONEY     MONEY1
---------- -------------------- ---------- ----------
         1 Tom                        1000
         2 Mary                       2000       1000
         3 Mike                       3000       2000
         4 Jeff                       4000       3000

SQL>

筆試2腳本.txt

create table pm_ci
(ci_id varchar(20) primary key,
 stu_ids varchar(100));

insert into pm_ci values('1','1,2,3,4');
insert into pm_ci values('2','1,4');

create table pm_stu
(stu_id varchar(20) primary key,
 stu_name varchar(20));

insert into pm_stu values('1','張三');
insert into pm_stu values('2','李四');
insert into pm_stu values('3','王五');
insert into pm_stu values('4','趙六');
commit;

示例代碼以下:

SQL> select * from pm_ci;

CI_ID                STU_IDS
-------------------- ----------------------------------------------------------------------------------------------------
1                    1,2,3,4
2                    1,4

SQL> select * from pm_stu;

STU_ID               STU_NAME
-------------------- --------------------
1                    張三
2                    李四
3                    王五
4                    趙六

SQL> select c.ci_id,s.stu_name
  2  from pm_ci c,pm_stu s
  3  where instr(c.stu_ids,s.stu_id)>0;

CI_ID                STU_NAME
-------------------- --------------------
1                    張三
1                    李四
1                    王五
1                    趙六
2                    張三
2                    趙六

已選擇 6 行。

SQL> select ci_id,wm_concat(stu_name) namelist
  2  from(select c.ci_id,s.stu_name
  3       from pm_ci c,pm_stu s
  4       where instr(c.stu_ids,s.stu_id)>0)
  5  group by ci_id
;

CI_ID
--------------------
NAMELIST
------------------------------------------------------------------------------------------------------------------------------------------------------
1
張三,李四,王五,趙六

2
張三,趙六


SQL> --設置列的寬度
SQL> col namelist for a50
SQL> select ci_id,wm_concat(stu_name) namelist
  2  from(select c.ci_id,s.stu_name
  3       from pm_ci c,pm_stu s
  4       where instr(c.stu_ids,s.stu_id)>0)
  5  group by ci_id
;

CI_ID                NAMELIST
-------------------- --------------------------------------------------
1                    張三,李四,王五,趙六
2                    張三,趙六

SQL>

十4、存儲過程和存儲函數

14.一、存儲過程

詳解以下:

存儲在數據庫中供全部用戶程序調用的子程序(用PL/SQL寫的)叫存儲過程、存儲函數。

建立存儲過程的語法:
create [or replace] PROCEDURE 過程名(參數列表) 
as PL/SQL子程序體;

示例代碼1:

--打印Hello World,不傳遞參數
/*
調用存儲過程的方式:
1. exec sayHelloWorld();
2. begin
    sayHelloWorld();
    sayHelloWorld();
    sayHelloWorld();
   end;
   /
*/

create or replace procedure sayHelloWorld    --注意Oracle中的命名規範,可是這裏爲了簡便,咱們使用java的命名規範
as
  --說明部分
begin
   dbms_output.put_line('Hello World');
end;
/

示例代碼2:

--給指定的員工漲100,而且打印漲前和漲後的工資,傳遞單個參數
create or replace procedure raisesalary(eno in number)    --注意:須要指明參數是輸入參數,仍是輸出參數
as
   --定義變量保存漲前的薪水
   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呢?答:不要。
    --原則:通常狀況下,咱們不在存儲過程和存儲函數中commit和rollback數據,應該交由調用者去作。

   dbms_output.put_line('漲前:'||psal||'   漲後:'||(psal+100));
end;
/

示例代碼3:

--給指定的員工漲指定額度的工資,傳遞多個參數
create or replace procedure raiseSalary(eno in number,rate in number)
as
    psal emp.sal%type
;
begin
    --獲得漲前的薪水
    select sal into psal from emp where empno=eno;
    --漲指定額度的工資
    update emp set sal=sal*rate where empno=eno;

    dbms_output.put_line('漲前:'||psal||'  漲後:'||(psal*rate));
end;

14.二、存儲函數

詳解以下:

存儲函數和存儲過程的結構相似,但必須有一個return子句,用於返回函數值。
函數說明要指定函數名、結果值的類型,以及參數類型等。

建立存儲函數的語法:
create [or replaceFUNCTION 函數名(參數列表) 
return 函數返回值類型
as PL/SQL子程序體;

示例代碼1:

--查詢某個員工的年收入
create or replace function queryempincome(eno in number)
return number
as
   --定義變量保存月薪和獎金
   psal 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;
/

14.三、存儲過程和存儲函數中的in和out參數

詳解以下:

通常來說,存儲過程和存儲函數區別在於存儲函數能夠有一個返回值,而存儲過程沒有返回值。

但存儲過程和存儲函數均可以經過out指定一個或多個輸出參數。咱們能夠利用out參數,在存儲過程和存儲函數中實現返回多個值。
這時存儲函數的功能就被存儲過程取代了,那爲何還要保留存儲函數呢?答:爲了版本的向下兼容。

何時使用存儲過程/存儲函數呢?
原則:
    通常而言,若是隻有一個返回值,就用存儲函數;不然,就用存儲過程。

示例代碼以下:

--查詢某個員工的姓名 月薪 職位
create or replace procedure queryempinfo(eno in number,
                                         pename out varchar2,
                                         psal   out number,
                                         pjob   out varchar2)
as
begin
  select ename,sal,empjob into pename,psal,pjob from emp where empno=eno;
end;
/

思考:
  1. 查詢某個員工的全部信息 --> 問題:out參數太多
  2. 查詢某個部門中的全部員工信息 --> 問題:返回的是集合

14.四、在Java中調用存儲過程和存儲函數

  • 在java中想要訪問數據庫,首先要獲得Connection對象,經過該對象獲得Statement對象(接口),咱們使用Statement的子接口CallableStatement。

在Java中調用存儲過程和存儲函數 的示例代碼:

    /*
    create or replace procedure queryempinfo(eno in number,
                                             pename out varchar2,
                                             psal   out number,
                                             pjob   out varchar2)
    as
    begin
      select ename,sal,empjob into pename,psal,pjob from emp where empno=eno;
    end;
    */

    @Test
    public void testProcedure() 
{
        // {call <procedure-name>[(<arg1>,<arg2>, ...)]}
        String sql = "{call queryempinfo(?,?,?,?)}";
        Connection conn = null;
        CallableStatement call = null;
        try {
            conn = JDBCUtils.getConnection();
            call = conn.prepareCall(sql);

            // 對於in參數,須要賦值
            call.setInt(17839);

            // 對於out參數,須要聲明
            call.registerOutParameter(2, OracleTypes.VARCHAR);
            call.registerOutParameter(3, OracleTypes.NUMBER);
            call.registerOutParameter(4, OracleTypes.VARCHAR);

            // 執行存儲過程
            call.execute();

            // 取出結果
            String name = call.getString(2);
            double sal = call.getDouble(3);
            String job = call.getString(4);
            System.out.println(name + "\t" + sal + "\t" + job);
        } catch (Exception e) {
            e.printStackTrace();
        }finally{
            JDBCUtils.release(conn, call, null);
        }
    }

    /*
    create or replace function queryempincome(eno in number)
    return number
    as
       --定義變量保存月薪和獎金
       psal 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;
    */

    @Test
    public void testFunction()
{
        // {?= call <procedure-name>[(<arg1>,<arg2>, ...)]}
        String sql = "{?=call queryempincome(?)}";

        Connection conn = null;
        CallableStatement call = null;
        try {
            conn = JDBCUtils.getConnection();
            call = conn.prepareCall(sql);

            // 第一個是out參數,須要聲明
            call.registerOutParameter(1, OracleTypes.NUMBER);
            // 第二個是in參數,須要賦值
            call.setInt(27839);

            // 執行存儲函數
            call.execute();

            // 取出年收入
            double income = call.getDouble(1);
            System.out.println(income);
        } catch (Exception e) {
            e.printStackTrace();
        }finally{
            JDBCUtils.release(conn, call, null);
        }       
    }

14.五、在out參數中使用光標

查詢某個部門中全部員工的全部信息,返回的是集合。
咱們須要聲明包結構和建立包體,其中包和包體也是數據庫的對象。
示例代碼以下:

    /* 
        1. 查詢某個員工的全部信息 --> 問題:out參數太多
        2. 查詢某個部門中的全部員工信息 --> 問題:返回的是集合
     */
    // 在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=dno;
        end queryEmpList;
    end myPackage;
     */
    @Test
    public void testCursor(){
        String sql = "{call myPackage.queryEmpList(?,?)}";

        Connection conn = null;
        CallableStatement call = null;
        ResultSet rs = null;
        try {
            conn = JDBCUtils.getConnection();
            call = conn.prepareCall(sql);

            // 對於in參數,須要賦值
            call.setInt(120);
            // 對於out參數 ,須要聲明
            call.registerOutParameter(2, OracleTypes.CURSOR);

            // 執行存儲函數
            call.execute();

            // 取出結果
            rs = ((OracleCallableStatement)call).getCursor(2);
            while (rs.next()) {
                // 取出一個員工,示例只取出了兩列
                String name = rs.getString("ename");
                double sal = rs.getDouble("sal");
                System.out.println(name + "\t" + sal);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }finally{
            JDBCUtils.release(conn, call, rs);
        }
    }

十5、觸發器

詳解以下:

數據庫觸發器是一個與表相關聯的、存儲的PL/SQL程序。
每當一個特定的數據操做語句(insertupdatedelete)在指定的表上發出時,Oracle自動地執行觸發器中定義的語句序列。

觸發器的類型:
    語句級(表級)觸發器:在指定的操做語句操做以前或以後執行一次,無論這條語句影響了多上行。
    行級觸發器(for each row):觸發語句做用的每一條記錄都被觸發。在行級觸發器中使用 :old 和 :new 僞記錄變量來識別值的狀態。

建立觸發器的語法:
create or replace trigger 觸發器名
before | after
insert | update | delete [of 列名]
on 表名
[for each row [when(條件)]]     --觸發器的類型
declare
begin
    ......
end;

觸發器的用途:
    1. 數據確認
    2. 實施複雜的安全性檢查
    3. 作審計,跟蹤表上所作的數據操做等(想要作什麼事,不被查到,須要關閉數據庫的審計功能)
    4. 數據的備份和同步

示例1:

--每當成功插入新員工後,自動打印「成功插入了新員工」
create or replace trigger abcd
after insert
on emp
declare
begin
    dbms_output.put_line('成功插入了新員工');
end;

15.一、觸發器應用一:實施複雜的安全性檢查

禁止在非工做時間向數據庫中插入數據

週末:to_char(sysdate,'day'in ('星期六','星期日')
上班前 下班後:to_number(tochar(sysdate,'hh24')) not between 9 and 17
------------------------------------------------------------------
create or replace trigger securityemp
before insert
on emp
declare
begin
    if to_char(sysdate,'day'in ('星期六','星期日'or
        to_number(to_char(sysdate,'hh24')) not between 9 and 17 then
        --禁止insert
        raise_application_error(-20002,'禁止在非工做時間向數據庫中插入數據');   -- -20000到-20999之間
    end if;
end;
------------------------------------------------------------------
SQL> insert into emp(empno,ename,sal,deptno) values(1001'tom',300020);
insert into emp(empno,ename,sal,deptno) values(1001'tom',300020)
            *
第 1 行出現錯誤:
ORA-20002: 禁止在非工做時間向數據庫中插入數據
ORA-06512: 在 "SCOTT.SECURITYEMP", line 6
ORA-04088: 觸發器 'SCOTT.SECURITYEMP' 執行過程當中出錯

SQL>

15.二、觸發器應用二:數據確認

檢查emp表中的sal的修改值不低於原值
------------------------------------------------------------------
create or replace trigger checksalary
before update 
on emp
for each row
declare
begin
    if :new.sal<:old.sal then
        raise_application_error(-20001,'漲後的工資不能少於漲前的工資。漲前:'||:old.sal||'  漲後:'||:new.sal);   -- -20000到-20999之間
    end if;
end;
------------------------------------------------------------------
測試代碼:
SQL> update emp set sal=sal+1 where empno=7839;

已更新 1 行。

SQL> update emp set sal=sal-1 where empno=7839;
update emp set sal=sal-1 where empno=7839
       *
第 1 行出現錯誤:
ORA-20001: 漲後的工資不能少於漲前的工資。漲前:7987  漲後:7986
ORA-06512: 在 "SCOTT.CHECKSALARY", line 4
ORA-04088: 觸發器 'SCOTT.CHECKSALARY' 執行過程當中出錯

SQL>

15.三、練習:限制每一個部門只招聘10名員工,超過計劃則報出錯誤信息

限制每一個部門只招聘10名員工,超過計劃則報出錯誤信息
------------------------------------------------------------------
create or replace trigger limitEmpCount
before insert
on emp
declare
    count10 number := 0;
    count20 number := 0;
    count30 number := 0;
begin
    select count(*into count10 from emp where deptno
=10;
    select count(*into count20 from emp where deptno=20;
    select count(*into count30 from emp where deptno=30;

    if count10>=10 then raise_application_error(-20005,'部門:10,員工已有'||count10||'人')
        elsif count20>=10 then raise_application_error(-20005,'部門:20,員工已有'||count20||'人')
        elsif count30>=10 then raise_application_error(-20005,'部門:30,員工已有'||count30||'人')
    end if;
end;
------------------------------------------------------------------
測試代碼:
SQL> insert into emp(empno,ename,sal,deptnovalues(1030,'tom',300030);
insert into emp(empno,ename,sal,deptnovalues(1030,'tom',300030)            *第 1 行出現錯誤:ORA-20005: 部門:30,員工已有10人ORA-06512: 在 "SCOTT.LIMITEMPCOUNT", line 12ORA-04088: 觸發器 'SCOTT.LIMITEMPCOUNT' 執行過程當中出錯SQL>
相關文章
相關標籤/搜索