1. 程式人生 > >PLSQL語法&&遊標&&儲存過程/儲存函式&&異常&&觸發器

PLSQL語法&&遊標&&儲存過程/儲存函式&&異常&&觸發器

什麼是PL/SQL?

結構化查詢語言(Structured Query Language,簡稱SQL)是用來訪問關係型資料庫一種通用語言,屬於第四代語言(4GL),其執行特點是非過程化,即不用指明執行的具體方法和途徑,而是簡單地呼叫相應語句來直接取得結果即可。顯然,這種不關注任何實現細節的語言對於開發者來說有著極大的便利。然而,有些複雜的業務流程要求相應的程式來描述,這種情況下4GL就有些無能為力了。PL/SQL的出現正是為了解決這一問題,PL/SQL是一種過程化語言,屬於第三代語言,它與C、 C++、Java等語言一樣關注於處理細節,可以用來實現比較複雜的業務邏輯。

1. 變數命名規則

這裡寫圖片描述

2. helloworld

declare
    --宣告的變數,型別,遊標
begin
    --程式處理部分(類似於java的main()方法)
    dbms_output.put_line('helloworld');
exception
    --針對於begin塊中處理的異常,提供處理機制
    -- when ... then ...
    -- when ... then ...
end;

輸出:

helloworld

3. 簡單的查詢操作

declare
    --宣告變數
v_sal number(10,2);
-- v_sal employees.salary%type;
v_email varcgar2(20); -- v_email employees.email%type; V_hire_date date; -- v_hire_date employees.hire_date%type; begin -- sql語句的操作 select salary,email,hire_date, into v_sal,v_email,v_hire_date from employees where employee_id=100; dbms_output.put_line(v_sal||','||v_email||','||v_hire_date);

3.1 建立一個物件,存放需要查詢的值作為成員變數

type [物件名] is record(
v_xxx [型別]
)

declare
    --宣告變數
    type emp_record is record(
    v_sal number(10,2),
    v_email varcgar2(20),
    v_hire_date date
    );
    --定義 一個記錄型別的成員變數
    v_emp_record emp_record;

begin
    -- sql語句的操作
    select salary,email,hire_date, into v_emp_record
    from employees where employee_id=100;
    dbms_output.put_line(v_emp_record.v_sal||','||v_emp_record.v_email||','||v_emp_record.v_hire_date);

3.2 使用 %rowtype

declare
--宣告一個記錄型別的變數
  v_emp_record employees%rowtype;
begin
  --通過 select ... into ... 語句為變數賦值
 select * into v_emp_record
 from employees
 where employee_id = 186;

 -- 列印變數的值
 dbms_output.put_line(v_emp_record.last_name || ', ' || v_emp_record.email || ', ' ||  
                                        v_emp_record.salary || ', ' ||  v_emp_record.job_id  || ', ' ||  
                                        v_emp_record.hire_date);
end;

3.3 賦值語句:通過變數實現查詢語句

declare
  v_emp_record employees%rowtype;
  v_employee_id employees.employee_id%type;
begin
  --使用賦值符號位變數進行賦值
  v_employee_id := 186;

  --通過 select ... into ... 語句為變數賦值
 select * into v_emp_record
 from employees
 where employee_id = v_employee_id;

 -- 列印變數的值
 dbms_output.put_line(v_emp_record.last_name || ', ' || v_emp_record.email || ', ' ||  
                                        v_emp_record.salary || ', ' ||  v_emp_record.job_id  || ', ' ||  
                                        v_emp_record.hire_date);
end;

3.4 通過變數實現DELETE、INSERT、UPDATE等操作

declare
  v_emp_id employees.employee_id%type;

begin
  v_emp_id := 109;
  delete from employees
  where employee_id = v_emp_id;
  --commit;
end; 

4.流程控制

4.1 條件判斷(兩種)

方式一:if … then elsif then … else … end if;

要求: 查詢出 150號 員工的工資, 若其工資大於或等於 10000 則列印 ‘salary >= 10000’;
若在 5000 到 10000 之間, 則列印 ‘5000<= salary < 10000’; 否則列印 ‘salary < 5000’

declare
  v_salary employees.salary%type;
begin
  --通過 select ... into ... 語句為變數賦值
 select salary into v_salary
 from employees
 where employee_id = 150;

 dbms_output.put_line('salary: ' || v_salary);
  -- 列印變數的值
 if v_salary >= 10000 then
    dbms_output.put_line('salary >= 10000');
 elsif v_salary >= 5000 then
    dbms_output.put_line('5000 <= salary < 10000');
 else
    dbms_output.put_line('salary < 5000');
 end if;

方式二:case … when … then … end;

declare
       v_sal employees.salary%type;
       v_msg varchar2(50);
begin     
       select salary into v_sal
       from employees
       where employee_id = 150;

       --case 不能向下面這樣用
       /*
       case v_sal when salary >= 10000 then v_msg := '>=10000' 
                  when salary >= 5000 then v_msg := '5000<= salary < 10000'
                  else v_msg := 'salary < 5000'
       end;
       */

       v_msg := 
             case trunc(v_sal / 5000)
                  when 0 then 'salary < 5000'
                  when 1 then '5000<= salary < 10000'
                  else 'salary >= 10000'
             end;

       dbms_output.put_line(v_sal ||','||v_msg);
end;

要求:

查詢出 122 號員工的 JOB_ID, 若其值為 ‘IT_PROG’, 則列印 ‘GRADE: A’;
‘AC_MGT’, 列印 ‘GRADE B’,
‘AC_ACCOUNT’, 列印 ‘GRADE C’;
否則列印 ‘GRADE D’

declare
       --宣告變數
       v_grade char(1);
       v_job_id employees.job_id%type;
begin
       select job_id into v_job_id
       from employees
       where employee_id = 122;

       dbms_output.put_line('job_id: ' || v_job_id);

       --根據 v_job_id 的取值, 利用 case 字句為 v_grade 賦值
       v_grade :=  
               case v_job_id when 'IT_PROG' then 'A'
                             when 'AC_MGT' then 'B'
                             when 'AC_ACCOUNT' then 'C'
                             else 'D'
                end;

       dbms_output.put_line('GRADE: ' || v_grade);
end; 

4.2 迴圈結構(三種)

使用迴圈語句列印 1 - 100.(三種方式

方式一:loop … exit when … end loop;

declare
       --初始化條件
       v_i number(3) := 1;
begin
       loop
       --迴圈體
        dbms_output.put_line(v_i);
    --迴圈條件
        exit when v_i = 100;
    --迭代條件
        v_i := v_i + 1;
       end loop;
end;

方式二:while … loop … end loop;

declare
    v_i number(3) :=1;
begin
    while v_i <=100 loop
      dbms_output.put_line(v_i);
      v_i := v_i+1;
      end loop;
end;

方式三for i in … loop … end loop;

begin
    for i in 1 .. 100 loop
      dbms_output.put_line(i);
      end loop;
end;

練習

輸出100以內的素數

declare
  v_i number(3) :=2;
  v_j number(2) :=2;
  --標記值, 若為 1 則是素數, 否則不是
  v_flg number(1):=1;
begin
  while(v_i <= 100) loop
         while(v_j < sqrt(v_i)) loop
                   if(mod(v_i,v_j)=0) then 
                      v_flg :=0;
                   end if;
               v_j := v_j+1; 
         end loop;
         if (v_flg=1) then 
           dbms_output.put_line(v_i);
         end if;
         v_i :=v_i+1;
         v_flg := 1;
         v_j:=2;
  end loop;
end;

輸出:

SQL> /
2
...........
97
PL/SQL procedure successfully completed

4.3goto、exit

goto

同樣拿素數來舉列子

declare
v_flg number(1):=0;
begin 
  for i in 2 .. 100 loop
    v_flg := 1;
    for j in 2 .. sqrt(i) loop
      if mod(i,j)=0 then 
        v_flg :=0;
        -- use lable
        goto label;
      end if;
    end loop;

    <<label>>
    if v_flg = 1 then
      dbms_output.put_line(i);
    end if;

  end loop;
end;

exit

相當於 Java中個break,跳出迴圈
舉例說明:
列印1——100的自然數,當列印到50時,跳出迴圈,輸出“列印結束”

begin
  for i in 1..100 loop
      dbms_output.put_line(i);
      if(i mod 50 = 0) then 
      dbms_output.put_line('列印結束');
      -- 跳出迴圈
      exit;
      end if;
  end loop;
end;

5. 遊標

類似於Java的Iterator

  • 定義遊標:cursor [遊標名] is select XXX
  • 開啟遊標:open [遊標名];
  • 提取遊標:fetch [遊標名] into [變數名];
  • 獲得遊標下一個:[遊標名]%found
  • 關閉遊標:close [遊標名]

練習1:

打印出 80 部門的所有的員工的工資:salary: xxx

declare
  --1. 定義遊標
  cursor salary_cursor is select salary from employees where department_id = 80;
  v_salary employees.salary%type;
begin
 --2. 開啟遊標
 open salary_cursor;

 --3. 提取遊標
 fetch salary_cursor into v_salary;

 --4. 對遊標進行迴圈操作: 判斷遊標中是否有下一條記錄
while salary_cursor%found loop
      dbms_output.put_line('salary: ' || v_salary);
      fetch salary_cursor into v_salary;
end loop;  

 --5. 關閉遊標
 close  salary_cursor;
end;

練習2:

打印出 manager_id 為 100 的員工的 employee_id,last_name, salary 資訊(使用遊標, 記錄型別)

declare
cursor emp_cursor is select employee_id,last_name,salary from employees where department_id = 80;
type emp_record is record(
     id employees.employee_id%type,
     name employees.last_name%type,
     salary employees.salary%type
);

v_emp_record emp_record;
begin
  open emp_cursor;
  fetch emp_cursor into v_emp_record;
  while(emp_cursor%found) loop
       dbms_output.put_line('id:'||v_emp_record.id||' name: ' || v_emp_record.name||' salary:'||v_emp_record.salary);
       fetch emp_cursor into v_emp_record;
  end loop;
end;

練習3

利用遊標, 調整公司中員工的工資:

工資範圍       調整基數
0 - 5000       5%
5000 - 10000   3%
10000 - 15000  2%
15000 -        1%
declare
    --定義遊標
    cursor emp_sal_cursor is select salary, employee_id from employees;

    --定義基數變數
    v_temp number(4, 2);

    --定義存放遊標值的變數
    v_sal employees.salary%type;
    v_id employees.employee_id%type;
begin
    --開啟遊標
    open emp_sal_cursor;

    --提取遊標
    fetch emp_sal_cursor into v_sal, v_id;

    --處理遊標的迴圈操作
    while emp_sal_cursor%found loop
          --判斷員工的工資, 執行 update 操作
          --dbms_output.put_line(v_id || ': ' || v_sal);

          if v_sal <= 5000 then
             v_temp := 0.05;
          elsif v_sal<= 10000 then
             v_temp := 0.03;   
          elsif v_sal <= 15000 then
             v_temp := 0.02;
          else
             v_temp := 0.01;
          end if;

          update employees set salary = salary * (1 + v_temp) where employee_id = v_id; 

          fetch emp_sal_cursor into v_sal, v_id;
    end loop;
    --關閉遊標
    close emp_sal_cursor;
end;

利用 for 迴圈遍歷 遊標

使用 for 遍歷遊標的話就不用再開啟遊標或者關閉遊標了,相關操作會自動進行

練習4

同樣用上述練習3的例子

declare
    --定義遊標
    cursor emp_sal_cursor is select salary, employee_id id from employees;

    --定義基數變數
    v_temp number(4, 2);
begin
    --處理遊標的迴圈操作
    for c in emp_sal_cursor loop
          --判斷員工的工資, 執行 update 操作

          if c.salary <= 5000 then
             v_temp := 0.05;
          elsif c.salary <= 10000 then
             v_temp := 0.03;   
          elsif c.salary <= 15000 then
             v_temp := 0.02;
          else
             v_temp := 0.01;
          end if;

          --dbms_output.put_line(v_id || ': ' || v_sal || ', ' || temp);
          update employees set salary = salary * (1 + v_temp) where employee_id = c.id;
    end loop;
end;

帶引數的遊標

同樣用上述例子說明:


declare
    --定義遊標
    cursor emp_sal_cursor(dept_id number, sal number) is 
           select salary + 1000 sal, employee_id id 
           from employees 
           where department_id = dept_id and salary > sal;

    --定義基數變數
    v_temp number(4, 2);
begin
    --處理遊標的迴圈操作sal => 4000表示4000賦值給sal,這個是形參變數的複製操作,不是比較運算
    for c in emp_sal_cursor(sal => 4000, dept_id => 80) loop
          --判斷員工的工資, 執行 update 操作
          --dbms_output.put_line(c.id || ': ' || c.sal);

          if c.sal <= 5000 then
             v_temp := 0.05;
          elsif c.sal <= 10000 then
             v_temp := 0.03;   
          elsif c.sal <= 15000 then
             v_temp := 0.02;
          else
             v_temp := 0.01;
          end if;

          update employees set salary = salary * (1 + v_temp) where employee_id = c.id;
    end loop;
end;

6.異常的處理(三種)

預定義異常

在預定義異常的表中能找到的異常

練習1

declare

  v_sal employees.salary%type;
begin
  select salary into v_sal
  from employees
  where employee_id >100;

  dbms_output.put_line(v_sal);

exception
  when Too_many_rows then dbms_output.put_line('輸出的行數太多了');
end;

非預定義異常

在預定義異常表中沒有的

練習2

declare

  v_sal employees.salary%type;
  --宣告一個異常
  delete_mgr_excep exception;
  --把自定義的異常和oracle的錯誤程式碼號關聯起來
  PRAGMA EXCEPTION_INIT(delete_mgr_excep,-2292);
begin
  delete from employees
  where employee_id = 100;

  select salary into v_sal
  from employees
  where employee_id >100;

  dbms_output.put_line(v_sal);

exception
  when Too_many_rows then dbms_output.put_line('輸出的行數太多了');
  when delete_mgr_excep then dbms_output.put_line('Manager不能直接被刪除');
end;

使用者自定義異常

declare

  v_sal employees.salary%type;
  --宣告一個異常
  delete_mgr_excep exception;
  --把自定義的異常和oracle的錯誤關聯起來
  PRAGMA EXCEPTION_INIT(delete_mgr_excep,-2292);

  --宣告一個異常
  too_high_sal exception;
begin

  select salary into v_sal
  from employees
  where employee_id =100;

  if v_sal > 1000 then
    -- 滿足條件時,丟擲異常
     raise too_high_sal;
  end if;

  delete from employees
  where employee_id = 100;

  dbms_output.put_line(v_sal);

exception
  when Too_many_rows then dbms_output.put_line('輸出的行數太多了');
  when delete_mgr_excep then dbms_output.put_line('Manager不能直接被刪除');
  --處理異常
  when too_high_sal then dbms_output.put_line('工資過高了');
end;

練習

更新指定員工工資,如工資小於300,則加100;對 NO_DATA_FOUND 異常, TOO_MANY_ROWS 進行處理.

declare
   v_sal employees.salary%type;
begin
   select salary into v_sal from employees where employee_id = 100;

   if(v_sal < 300) then update employees set salary = salary + 100 where employee_id = 100;
   else dbms_output.put_line('工資大於300');
   end if;
exception
   when no_data_found then dbms_output.put_line('未找到資料');
    when too_many_rows then dbms_output.put_line('輸出的資料行太多');
end;

7. 儲存函式(又返回值),儲存過程(無返回值)

  • 儲存函式:有返回值,建立完成後,通過select function() from dual;執行
  • 儲存過程:由於沒有返回值,建立完成後,不能使用select語句,只能使用pl/sql塊執行

儲存函式

[格式]

--函式的宣告(有引數的寫在小括號裡)
create or replace function func_name(v_param varchar2)
    --返回值型別
    return varchar2
is 
--PL/SQL塊變數、記錄型別、遊標的宣告(類似於前面的declare的部分)
begin
    --函式體(可以實現增刪改查等操作,返回值需要return)
       return 'helloworld'|| v_logo;
end;

練習1

寫一個返回hellorle字串的的函式

create or replace function fun1 
return varchar2
is
begin 
  return 'helloworld';
end;

練習2

傳入一個兩數字,然後返回他們的和

create or replace function fun2(a number,b number) 
return number
is 
v_sum number :=0;
begin
 v_sum := a + b; 
 return v_sum;
end;

呼叫函式:

SQL> select fun2(1,2) from dual;
 FUN2(1,2)
----------
         3

練習3

定義一個函式: 獲取給定部門的工資總和, 要求:部門號定義為引數, 工資總額定義為返回值.

create or replace function sum_sal(dept_id number )
return number
is
cursor sal_cursor is select salary from employees where department_id = dept_id;
v_sum number(8):=0;
begin
  for c in sal_cursor loop
    v_sum :=v_sum + c.salary; 
  end loop;
  return v_sum;

end;

輸出;

SQL> select sum_sal(80) from dual;
SUM_SAL(80)
-----------
     305300

OUT 型的引數

因為函式只能有一個返回值, PL/SQL 程式可以通過 OUT 型的引數實現有多個返回值
例子:
定義一個函式: 獲取給定部門的工資總和 和 該部門的員工總數(定義為 OUT 型別的引數).
要求: 部門號定義為引數, 工資總額定義為返回值.

create or replace function sum_sal2(dept_id number,total_count out number)
return number
is
cursor sal_cursor is select salary from employees where department_id = dept_id;
v_sum_sal number(8):=0;
begin
  total_count :=0;
  for c in sal_cursor loop
      v_sum_sal := v_sum_sal+c.salary;
      total_count :=total_count+1;
  end loop;
  return v_sum_sal;

end;

呼叫該函式

declare 
-- 該變數接受的值就是total_count
  v_total number(3) := 0;

begin
    dbms_output.put_line(sum_sal2(80, v_total));
    dbms_output.put_line(v_total);
end;

輸出:

SQL> /
305300
34
PL/SQL procedure successfully completed

儲存過程

定義一個儲存過程: 獲取給定部門的工資總和(通過 out 引數), 要求:部門號和工資總額定義為引數

create or replace procedure sum_sal_procedure(dept_id number, v_sum_sal out number)
       is

       cursor sal_cursor is select salary from employees where department_id = dept_id;
begin
       v_sum_sal := 0;

       for c in sal_cursor loop
           --dbms_output.put_line(c.salary);
           v_sum_sal := v_sum_sal + c.salary;
       end loop;       

       dbms_output.put_line('sum salary: ' || v_sum_sal);
end;

呼叫函式:

declare 
v_sum_sal number(8):=0;
begin
  sum_sal_procedure(80,v_sum_sal);
  dbms_output.put_line('sum salary: ' || v_sum_sal);

end;

輸出:

SQL> /
sum salary: 305300
PL/SQL procedure successfully completed

例子3

自定義一個儲存過程完成以下操作:
對給定部門(作為輸入引數)的員工進行加薪操作, 若其到公司的時間在 (? , 95) 期間, 為其加薪 %5
[95 , 98) %3
[98, ?) %1
得到以下返回結果: 為此次加薪公司每月需要額外付出多少成本(定義一個 OUT 型的輸出引數).

create or replace procedure add_sal_procedure(dept_id number, temp out number)

is

       cursor sal_cursor is select employee_id id, hire_date hd, salary sal from employees where department_id = dept_id;
       -- 加薪基數
  a number(4, 2) := 0;
begin
       temp := 0;       

       for c in sal_cursor loop
           a := 0;    

           if c.hd < to_date('1995-1-1', 'yyyy-mm-dd') then
              a := 0.05;
           elsif c.hd < to_date('1998-1-1', 'yyyy-mm-dd') then
              a := 0.03;
           else
              a := 0.01;
           end if;

           temp := temp + c.sal * a;
           update employees set salary = c.sal * (1 + a) where employee_id = c.id;
       end loop;       
end;

呼叫該函式:

declare 

v_cost number(8) :=0;
begin
  add_sal_procedure(80,v_cost);
  dbms_output.put_line('cost is :'||v_cost);
end;

測試結果:

cost is :6129
PL/SQL procedure successfully completed

8. 觸發器

觸發事件

觸發器的例項

首先建立一個表

create table emp1(
emp_id number(3),
emp_name varchar2(6),
emp_age number(3)
)

然後定義一個觸發器

create or replace trigger emp1_insert_trigger
-- after表示執行之後觸發
after
insert on emp1
begin
  dbms_output.put_line('emp1 insert new column!!!');
end;

執行insert操作,測試是否執行觸發器

insert into emp1 values(1,'bart',22);
emp1 insert new column!!!
1 row inserted

發現觸發器在插入資料之後,執行了

使用 :new, :old 修飾符

  • :new 表示舊 的值
  • :old 表示新的值
    例子說明
create or replace trigger emp1_update_trigger
-- after表示執行之後觸發
after
update on emp1
for each row
begin
  dbms_output.put_line('emp1 insert new column!!!');
  dbms_output.put_line('old age: ' || :old.emp_age || ', new age: ' || :new.emp_age);
end;

測試:

SQL> update emp1 set emp_name='lisa',emp_age=24 where emp_id=1;
emp1 insert new column!!!
old age: 23, new age: 24
1 row updated

利用觸發器備份表

編寫一個觸發器, 在對 my_emp 記錄進行刪除的時候, 在 my_emp_bak 表中備份對應的記錄

縣建立兩張表,一個查詢建立的 my_emp 另外一個空表 my_emp_bak

create table my_emp as select employee_id id, last_name name, salary sal from employees

create table my_emp_bak as select employee_id id, last_name name, salary sal from employees where 1 = 2

建立觸發器,實現備份操作

create or replace trigger bak_emp_trigger
before 
delete on my_emp
-- 操作每一行的時候都會執行
for each row
begin
 insert into my_emp_bak values(:old.id,:old.name,:old.sal);

end;

操作測試觸發器

select * from my_emp;
 ID NAME                             SAL
------- ------------------------- ----------
    174 Abel                        11330.00
    175 Hutton                       9064.00
   .....
    192 Bell                         4000.00
    193 Everett                      3900.00
    194 McCain                       3200.00
     ID NAME                             SAL
------- ------------------------- ----------
    195 Jones                        2800.00
SQL> delete from my_emp where id=194;
1 row deleted

SQL> select * from my_emp_bak;
     ID NAME                             SAL
------- ------------------------- ----------
    194 McCain                       3200.00

測試表明,刪除的資料確實備份在了表my_emp_bak中