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中