教程資料: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 被零除,如 1/0
+ Value\_error 算術或轉換錯誤
+ Timeout\_on_resource 連接資源超時
/*
異常處理
*/
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;
注意:程序還是有問題的,多執行幾次此程序每次結果都不一樣。
問題原因:第一次執行是對的,但是當第二次執行時又把已加過工資的人重新取出,
而且最低的那個人漲10%還是比較少的并沒有超過限定條件。第三次第四次都是這樣。
解決方式:應該保證程序在一定時間段內只執行一次,前3秒漲了工資現在又漲明顯不合理。
- 實例3
實現按部門分段(3000元以下,3000 ~ 6000,6000以上),統計各工資段的職工人數,
以及各部門的工資總額(工資總額中不包括獎金),參考如下:
部門 <3000 3000~6000 >6000 工資總額
10 2 1 0 8750
20 3 2 0 10875
30 6 0 0 9400
40 0 0 0 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;