知識點概覽:
PLSQL 概覽、PLSQL 的塊概念、PLSQL變量|、PLSQL 中的SQL語句、
PLSQL 的控制語法、PLSQL 中的複雜自定義數據類型、
PLSQL 中的遊標、PLSQL 的例外處理、PLSQL 中的存儲過程和函數
知識點講解:
PLSQL 概覽:
PLSQL 是Oracle 公司在SQL 基礎上進行擴展而成的一種過程語言。PLSQL 提供了典型的高級語言特
性,包括封裝,例外處理機制,信息隱藏,面向對象等;並把最新的編程思想帶到了數據庫服務器和工具
集中。
與 與Java, C# 相比 , ,PLSQL 的優勢是:SQL 語言可以直接寫到PLSQL 的“塊”中或者是PLSQL 的過程、
向 函數中。沒有必要向java 那樣先創建Statement 對象來執行SQL; 這使得PLSQL 成爲很強大的事務處理語
用 言,即:使用SQL 來處理數據,使用控制結構來處理業務邏輯。
PLSQL 在Oracle 數據庫服務器(在存儲過程、函數、數據庫觸發器,Package 包中使用)和Oracle 開發
工具集(在; 開發工具組件的觸發器中使用);Form Developer ,Report Developer 還可以使用共享庫
(包含使用PLSQL 寫的過程和函數,擴展名爲PLL 的文件); SQL 數據類型也可以在PLSQL 中使用,結合
SQL 提供者的直接訪問,這些共享數據類型整合了PLSQL 和Oracle 的數據庫字典。PLSQL 消除了存取數據
庫的便利性與過程語言之間的障礙。
PLSQL 的另一個顯著好處在於它可以通過減少來回交互減輕網絡流量壓力、節省時間:
PLSQL塊概念
PLSQL 是一種類PASCAL 語言,每一段程序都是由Block 組成的:
DECLARE (Optional)
Variables, cursors, user-defined exceptions
BEGIN (Mandatory)
SQL statements
PL/SQL statements
EXCEPTION (Optional)
Actions to perform when errors occur
END; (Mandatory)
使用分號作爲一句SQL 或者PLSQL 語句的結束;
塊結構關鍵字(DECLARE, BEGIN,EXCEPTION 後面不跟分號;
END 後面需帶分號;
你可以把一句SQL 語句寫在一行上,但一般不建議這麼做,因爲代碼不夠漂亮!
PLSQL 的塊包括三種:匿名塊、存儲過程、函數;
PLSQL 的變量
PLSQL 的變量類型:
1 、系統內置的常規簡單變量類型: 比如大多數 數據庫表的字段類型都可以作爲變量類型;
2、用戶自定義複雜變量類型: 比如記錄類型;
3 、引用類型:保存了一個指針值;
4 、大對象類型(LOB ):保存了一個指向大對象的地址;
PLSQL 的變量 類型舉例:
布爾類型 數值類型 BFILE類型 日期類型 BLOB類型 LONG類型 字符串類型
SQLPLUS 變量
PLSQL 本身沒有輸入輸出功能,如果要想 像命令行運行C 程序那樣可以接收輸入值,那你必須依賴執行環境
把值傳給PLSQL 塊,比如iSQL Plus 執行環境或者PLSQL Developer 的Command Window 執行環境中,
種 有一種substitution 變量 可以用來接收輸入值;而另一種Host 變量可以把運行時的值傳出到執行環境中。
關 有關SQLPLUS 變量,我們會在以後詳細展開;
PLSQL的 的 變量聲明語法:
identifier [CONSTANT] datatype [NOT NULL]
[:= | DEFAULT expr];
說明:
1 、變量命名建議遵循通用規則,比如v_name 表示一個變量,c_name 表示一個常量;
2 、一般建議每一行聲明一個變量,這樣程序的可讀性比較好;
3 、如果聲明瞭變量,但未進行初始化,則在沒有賦值之前該變量的值爲NULL; 一個好的編程習慣是對所有聲明
的變量進行初始化賦值。
4 、在同一個塊中,避免命名與數據庫表中的字段名相同的變量;
PLSQL 特有的%TYPE 屬性來聲明與XX 類型一致的變量類型:
identifier Table.column_name%TYPE;
可綁定變量( (Bind Variable 也稱爲Host Variable , 非PLSQL 變量):
VARIABLE g_salary NUMBER
BEGIN
SELECT salary
INTO :g_salary
FROM employees
WHERE employee_id = 178;
END;
/
PRINT g_salary
可綁定變量是一種在縮主環境中定義的變量,所謂 縮主 環境一般指示SQLPLUS 執行環境或者是
PLSQL Developer 的Command Window 執行環境;可綁定變量可用於在運行時把值傳遞給PLSQL, 創建語法:
VARIABLE return_code NUMBER
VARIABLE return_msg VARCHAR2(30)
的 大家注意,在標準的PLSQL 中定義變量是不能用VARIABLE 關鍵字的,此關鍵字只在SQLPLUS 執行環境中有效,
可使用PRINT 語句輸出變量內容。
在PLSQL 中使用這種變量時,前面加”:”, 以示區分。
DBMS_OUTPUT.PUT_LINE() 介紹:
在接下來的實驗中,經常需要在調試程序時輸出中間變量的值,我們可使用Oracle 內置的Package 中的函數:
DECLARE
v_sal NUMBER(9,2) := &p_annual_sal;
BEGIN
v_sal := v_sal/12;
DBMS_OUTPUT.PUT_LINE ('The monthly salary is ' ||
TO_CHAR(v_sal));
END;
上述例子中,我們使用DBMS_OUTPUT.PUT_LINE () 輸出變量v_sal 的值;
解釋:&p_annual_sal 在Plsql Developer 的SQL window 執行環境中,可用於提示用戶輸入一個具體
的值。
注意:在SQLPLUS 中執行DBMS_OUTPUT.PUT_LINE () 前,必須先執行:
SET SERVEROUTPUT ON ,而在PLSQL Developer 的SQL Window 中則不需要這句話。
PLSQL 中的註釋語句:
1 、多行註釋類似於java 或者 C , , 用 使用 /* 和 和 */
2 、單行註釋是在語句後面使用–
SQL 函數在PLSQL 的過程語句中的使用:
哪些可以用? 哪些不可以用?
大多數SQL 函數都可以在PLSQL 的過程語句中使用,比如:
單行的數值和字符串函數、數據類型轉換函數、日期函數、時間函數、求最大、最小值的GREATEST, LEAST
函數等;
但有些函數在PLSQL 的過程語句中是不能使用的,比如:
Decode 函數、分組函數(AVG, MIN, MAX, COUNT, SUM, STDDEV, and VARIANCE )等;
塊嵌套和變量範圍:
PLSQL的塊是可以嵌套的,變量的作用範圍與其他語言類似
變量限定詞: 假設我們在塊嵌套的程序中,裏層和外層有相同的變量聲明,而裏層的程序要訪問外層的同名變量
該怎麼辦呢?
答案是:使用塊限定詞,請看例子:
<<outer>>
DECLARE
birthdate DATE;
BEGIN
DECLARE
birthdate DATE;
BEGIN
...
outer.birthdate :=
TO_DATE( ' 03-AUG-1976 ' ,
' DD-MON-YYYY ' );
END;
....
END;
這個例子中,birthdate 是同名變量,限定詞outer 表示外層,裏層要訪問外層的bithdate 時使用
outer.birthdate這種格式
PLSQL中的SQL語句:
SELECT INTO 語句: 用於把從數據庫查詢出內容存入變量
DECLARE
v_hire_date employees.hire_date%TYPE;
v_salary employees.salary%TYPE;
BEGIN
SELECT hire_date, salary
INTO v_hire_date, v_salary
FROM employees
WHERE employee_id = 100;
...
END;
/
注意點:該語句支持單行的查詢結果,果 如果Where 條件控制不好,導致多行查詢結果,則會引發Too_many_rows的例外
INSERT 、UPDATE 、DELETE 、MERGE 語句:
在 PLSQL 中執行這些SQL 語句和直接執行這些語句差不多,只不過可以在SQL 語句中使用PLSQL聲明的變量;
BEGIN
INSERT INTO employees
(employee_id, first_name, last_name, email,
hire_date, job_id, salary)
VALUES
(employees_seq.NEXTVAL, 'Ruth', 'Cores', 'RCORES',
sysdate, 'AD_ASST', 4000);
END;
/
DECLARE
v_sal_increase employees.salary%TYPE := 800;
BEGIN
UPDATE employees
SET salary = salary + v_sal_increase
WHERE job_id = 'ST_CLERK';
END;
/
DECLARE
v_deptno employees.department_id%TYPE := 10;
BEGIN
DELETE FROM employees
WHERE department_id = v_deptno;
END;
/
DECLARE
v_empno employees.employee_id%TYPE := 100;
BEGIN
MERGE INTO copy_emp c
USING employees e
ON (e.employee_id = v_empno)
WHEN MATCHED THEN
UPDATE SET
c.first_name = e.first_name,
c.last_name = e.last_name,
c.email = e.email,
. . .
WHEN NOT MATCHED THEN
INSERT VALUES(e.employee_id, e.first_name, e.last_name,
. . .,e.department_id);
END;
PLSQL中的控制語句
和其他語言一樣,控制主要包括判斷和循環;
判斷語句的語法與其他語言類似:
IF condition THEN
statements;
[ELSIF condition THEN
statements;]
[ELSE
statements;]
END IF;
CASE selector
WHEN expression1 THEN result1
WHEN expression2 THEN result2
...
WHEN expressionN THEN resultN
[ELSE resultN+1;]
END;
需要注意的是對NULL的判斷處理:一般人容易犯錯誤或者不容易記住
循環語句的語法與其他語言類似:有基本循環、For 循環、Wihle循環三種
LOOP
statement1;
. . .
EXIT [WHEN condition];
END LOOP;
WHILE condition LOOP
statement1;
statement2;
. . .
END LOOP;
FOR counter IN [REVERSE]
lower_bound..upper_bound LOOP
statement1;
statement2;
. . .
END LOOP;
嵌套循環和Label
...
BEGIN
<<Outer_loop>>
LOOP
v_counter := v_counter+1;
EXIT WHEN v_counter>10;
<<Inner_loop>>
LOOP
...
EXIT Outer_loop WHEN total_done = 'YES';
-- Leave both loops
EXIT WHEN inner_done = 'YES';
-- Leave inner loop only
...
END LOOP Inner_loop;
...
END LOOP Outer_loop;
END;
Label 一般用不着,只有在使用goto 語句,或者內部循環需要訪問外部的同名變量的時候才需要,而一般這
種做法也是不被提倡的。
PLSQL中的複雜自定義數據類型(類似Java構造器感覺)
概述:PLSQL 中常用的自定義類型就兩種:記錄類型、PLSQL 內存表類型(根據表中的數據字段的簡單和複雜
程度又可分別實現類似於簡單數組和記錄數組的功能)
記錄類型的定義語法:
TYPE type_name IS RECORD
(field_declaration[, field_declaration]…);
identifier type_name;
這裏的field_declaration 的具體格式可以是:
field_name {field_type | variable%TYPE
| table.column%TYPE | table%ROWTYPE}
[[NOT NULL] {:= | DEFAULT} expr]
%ROWTYPE 屬性:在PLSQL 中 %ROWTYPE 表示某張表的記錄類型 或者是用戶指定以的記錄類型,使用此
屬性可以很方便的定義一個變量,其類型與某張表的記錄或者自定義的記錄類型保持一致。極大的方便了
Select * into ….的語句實用
舉例:
DECLARE
emp_rec employees%ROWTYPE;
BEGIN
SELECT * INTO emp_rec
FROM employees
WHERE employee_id = &employee_number;
INSERT INTO retired_emps(empno, ename, job, mgr, hiredate,
leavedate, sal, comm, deptno)
VALUES (emp_rec.employee_id, emp_rec.last_name, emp_rec.job_id,
emp_rec.manager_id, emp_rec.hire_date, SYSDATE, emp_rec.salary,
emp_rec.commission_pct, emp_rec.department_id);
COMMIT;
END;
/
PLSQL 內存表即Index By Table , 這種結構類似於數組,使用主鍵提供類似於數組那樣的元
素訪問。: 這種類型必須包括兩部分:1 、使用BINARY_INTEGER ; 類型構成的索引主鍵; 2 、另外一個簡單類型
。 或者用戶自定義類型的字段作爲具體的數組元素。 這種類型可以自動增長,所以也類似於可變長數組。
語法:
TYPE type_name IS TABLE OF
{column_type | variable%TYPE
| table.column%TYPE} [NOT NULL]
| table.%ROWTYPE
[INDEX BY BINARY_INTEGER];
identifier type_name;
PLSQL 內存表應用舉例:
下面定義的兩個內存表中的元素都是簡單數據類型,所以相當於定義了兩個簡單數組;
DECLARE
TYPE ename_table_type IS TABLE OF
employees.last_name%TYPE
INDEX BY BINARY_INTEGER;
TYPE hiredate_table_type IS TABLE OF DATE
INDEX BY BINARY_INTEGER;
ename_table ename_table_type;
hiredate_table hiredate_table_type;
BEGIN
ename_table(1) := 'CAMERON';
hiredate_table(8) := SYSDATE + 7;
IF ename_table.EXISTS(1) THEN
INSERT INTO ...
...
END;
/
備註:對PLSQL 內存表中某個元素的訪問類似於數組,可以使用下表,因爲BINARY_INTEGER 這種數據類型
在 的值在-2147483647 ... 2147483647 範圍內,所以下表也可以在這個範圍內。
PLSQL中的遊標
遊標概論:遊標是一個私有的SQL 工作區域,Oracle 數據庫中有兩種遊標,分別是隱式遊標和顯式遊標,
隱 式 遊標不易被用戶和程序員察覺和意識到,實際上Oracle 服務器使用隱式遊標來解析和執行我們提交的SQL
語句;而顯式遊標是程序員在程序中顯式聲明的;通常我們說的遊標均指顯式遊標。
隱式遊標的幾個有用屬性:
顯式遊標:對於返回多行結果的SQL 語句的返回結果,可使用顯式遊標獨立的處理器中每一行的數據。
顯式遊標的相關函數可以做到:
1 、一行一行的處理返回的數據。
2 、保持當前處理行的一個跟蹤,像一個指針一樣指示當前的處理的記錄。
3 、允許程序員在PLSQL 塊中人爲的控制遊標的開啓、關閉、上下移動;
在程序中對顯式遊標控制的一般過程:
如果你覺得像前面那個例子那樣對一個遊標的遍歷很麻煩的話,可以考慮使用For 循環,For 循環省去了遊標的
聲明、打開、提取、測試、關閉等語句,對程序員來說很方便,語法如下:
FOR record_name IN cursor_name LOOP
statement1;
statement2;
. . .
END LOOP;
遊標能否帶有參數?答案是肯定的:
CURSOR cursor_name
[(parameter_name datatype, ...)]
IS
select_statement;
FOR UPDATE NOWAIT 語句:
有的時候我們打開一個遊標是爲了更新或者刪除一些記錄,這種情況下我們希望
用 在打開遊標的時候即鎖定相關記錄,應該使用for update nowait 語句,倘若鎖定失敗我們就停止不再繼續,以免
出現長時間等待資源的死鎖情況。
SELECT ...
FROM ...
FOR UPDATE [OF column_reference][NOWAIT];
WHERE CURRENT OF cursor :
我們經常要逐條處理遊標中的每一條記錄,在循環體內做Update 或者 Delete
有 時需要有Where 指向遊標的當前記錄,的 有沒有簡單一點的的Where 條件寫法呢?
答案是肯定的,就是。。。
DECLARE
CURSOR sal_cursor IS
SELECT e.department_id, employee_id, last_name, salary
FROM employees e, departments d
WHERE d.department_id = e.department_id
and d.department_id = 60
FOR UPDATE OF salary NOWAIT;
BEGIN
FOR emp_record IN sal_cursor
LOOP
IF emp_record.salary < 5000 THEN
UPDATE employees
SET salary = emp_record.salary * 1.10
WHERE CURRENT OF sal_cursor;
END IF;
END LOOP;
END;
/
PLSQL中的例外處理
PLSQL 中的例外一般有兩種:
1 、Oracle 內部錯誤拋出的例外:這又分爲預定義例外(有錯誤號+ 常量定義)和 和 非預定義例外
(僅有錯誤號,無常量定義)
2 、程序員顯式的拋出的例外
PLSQL 中的 例外捕獲和傳遞:與其他語言類似,如果例外在當前塊中被處理,則到此爲止,否則會被傳遞到
外層(外層BLOCK 或者 外層調用者函數)
PLSQL 中的例外處理一般語法:
處理預定義的例外:有些常見例外,Oracle 都已經預定義好了,使用時無需預先聲明,比如:
NO_DATA_FOUND 和 和 TOO_MANY_ROWS 是最常見的例外,大多數Block 中都建議對這兩種例外
有處理; 完整的預定義例外的列表,
請參考:PL/SQL User’s Guide and Reference, “Error Handling.” (百度搜索,下載點很多)
OTHERS 的處理:
Others 表明我們程序員未能預計到這種錯誤,所以全部歸入到others 裏面去了,單發生這種的情況是,我們還是希望瞭解當時發生的Oracle 錯誤號和相關描述信息,怎樣才能取到呢?Oracle 提供了兩個內置函數SQLCODE 和 和 SQLERRM 分別用來返回Oracle 錯誤號和錯誤描述
處理非預定義的Oracle 錯誤:
此類錯誤屬於Oracle 錯誤,有編號,但無錯誤名稱定義,使用時需要先聲明,並進行錯誤初始化:
Oracle 內部錯誤號很多,想了解全部的Ora 錯誤號,請參考:http://www.ora-code.com/
處理用戶自定義的錯誤:
這種錯誤一般是程序員根據具體的業務邏輯定義的應用類錯誤,需要先聲明後使用:
定義和處理過程如下:
RAISE_APPLICATION_ERROR() 函數:對於用戶自定義的業務錯誤,如果覺得先定義再使用很麻煩,那麼也可以簡單的使用raise_application_error() 來簡化處理。它可以無需預先定義錯誤,而在需要拋出錯誤的地方直接使用此函數拋出例外,例外可以包含用戶自定義的錯誤嗎和錯誤描述;
PLSQL中的存儲過程和函數:
語法:
CREATE [OR REPLACE] PROCEDURE procedure_name
[(parameter1 [mode1] datatype1,
parameter2 [mode2] datatype2,
. . .)]
IS|AS
PL/SQL Block;
自己去深入瞭解吧,這裏是重點!!!說不清
今日作業:重點存儲過程!!
-- 學生表:學號、姓名、性別、年齡、班級
select * from t2_student;
create table t2_student(
sno varchar2(11) primary key,
sname varchar2(20) not null,
sex varchar2(3),
age number(3),
classid varchar2(5),
classname varchar2(20),
constraint c2_1 check(sex in ('男','女'))
);
-- 成績表:學號、課程、成績
select * from t2_grade;
create table t2_grade(
sno varchar2(11),
cno varchar2(5),
cname varchar2(20),
grade number,
constraint c2_2 check(grade >=0 and grade <=100)
);
insert into t2_student values('s001','張三','男',15,'c001','18班');
insert into t2_student values('s002','李華','女',15,'c001','18班');
insert into t2_student values('s003','李斯','男',16,'c002','19班');
insert into t2_student values('s004','王武','男',16,'c002','19班');
insert into t2_student values('s005','趙柳','女',16,'c002','19班');
insert into t2_student values('s006','孫琪','女',17,'c002','19班');
insert into t2_grade values('s001','c01','語文',90);
insert into t2_grade values('s001','c02','數學',80);
insert into t2_grade values('s002','c01','語文',70);
insert into t2_grade values('s002','c02','數學',90);
insert into t2_grade values('s003','c02','數學',80);
insert into t2_grade values('s004','c02','數學',91);
insert into t2_grade values('s005','c02','數學',92);
insert into t2_grade values('s006','c02','數學',93);
-- 以下都是存儲過程的練習,要求使用存儲過程操作
-- 1、插入3條數據到學生表
select * from t2_student;
create or replace procedure proc_insert_student
(v_age in t2_student.age%TYPE)
is
begin
insert into t2_student values('s007','老八','男',v_age,'c003','20班');
insert into t2_student values('s008','老九','女',v_age,'c003','20班');
insert into t2_student values('s009','老十','男',v_age,'c003','20班');
commit;
end proc_insert_student;
begin
cheirmin.proc_insert_student(18);
end;
-- 2、打印所有18歲的學生
--使用存儲過程加遊標
create or replace procedure proc_select_age_student
(v_age t2_student.age%TYPE)
is
cursor stu_cursor1 is
select sname,age from t2_student where age = v_age;
c_sname t2_student.sname%TYPE :='';
c_age t2_student.age%TYPE :=0;
begin
open stu_cursor1;
loop
fetch stu_cursor1 into c_sname,c_age;
exit when stu_cursor1%notfound;
dbms_output.put_line(c_sname||' - '||c_age);
end loop;
close stu_cursor1;
end proc_select_age_student;
--調用存儲過程
begin
cheirmin.proc_select_age_student(18);
end;
--只使用遊標
declare
v_age t2_student.age%TYPE := 0;
v_sname t2_student.sname%TYPE :='';
cursor stu_cursor1 is
select sname,age from t2_student where age = 18;
begin
open stu_cursor1;
loop
fetch stu_cursor1 into v_sname,v_age;
exit when stu_cursor1%rowcount > 10 or stu_cursor1%notfound;
dbms_output.put_line(v_sname||' - '||v_age);
end loop;
close stu_cursor1;
end;
-- 3、更新“張三”的年齡爲20歲,返回更新後的數據
select * from t2_student where sname = '張三';
create or replace procedure proc_update_age
(v_age in t2_student.age%TYPE,
c_name out t2_student.sname%TYPE,
c_age out t2_student.age%TYPE)
is
begin
update t2_student set age = v_age where sname = '張三';
commit;
select sname,age into c_name,c_age from t2_student where sname = '張三';
end proc_update_age;
--執行代碼塊測試上述存儲過程;成立
declare
v_name varchar2(20);
v_age number(3);
begin
cheirmin.proc_update_age(20,v_name,v_age);
dbms_output.put_line(v_name||' - '||v_age);
end;
-- 4、將“張三”的年齡、性別、班級數據賦值給“李斯”
select * from t2_student where sname = '張三';
select * from t2_student where sname = '李斯';
create or replace procedure proc_select_and_update
as
v_age t2_student.age%TYPE;
v_sex t2_student.sex%TYPE;
v_classid t2_student.classid%TYPE;
v_classname t2_student.classname%TYPE;
begin
select age,sex,classid,classname
into v_age,v_sex,v_classid,v_classname
from t2_student where sname = '張三';--查
update t2_student
set age = v_age,sex = v_sex,classid = v_classid,classname = v_classname
where sname = '李斯';--賦值
end proc_select_and_update;
--執行代碼塊測試上述存儲過程;成立
begin
cheirmin.proc_select_and_update;
end;
-- 5、將“19班”學生的所有成績全部在原有基礎上加10分
select * from t2_student;
select * from t2_grade;
update t2_grade set grade = grade+10 where sno in(
select sno from t2_student where classname = '19班');
create or replace procedure proc_update_grade
is
cursor p_cursor2 is
select sno from t2_student where classname = '19班';
c_sno t2_student.sno%TYPE :=' ';
begin
open p_cursor2;
loop
fetch p_cursor2 into c_sno;
exit when p_cursor2%notfound;
update t2_grade set
grade = (case when grade <90 then (grade +10) else 100 end)
where sno = c_sno;
end loop;
close p_cursor2;
end proc_update_grade;
--執行代碼塊測試上述存儲過程;成立
begin
cheirmin.proc_update_grade;
end;
-- 6、查詢“張三”的語文課成績(一個入參)
select * from t2_student;
select * from t2_grade;
create or replace procedure proc_select_grade
(v_cname in t2_grade.cname%TYPE,
c_grade out t2_grade.grade%TYPE)
is
begin
select grade into c_grade
from t2_grade where sno =
(select sno from t2_student where sname = '張三')
and cname = v_cname;
end proc_select_grade;
--執行代碼塊測試上述存儲過程;成立
declare
v_grade t2_grade.cname%TYPE;
begin
cheirmin.proc_select_grade('語文',v_grade);
dbms_output.put_line(v_grade);
end;
-- 7、刪除“張三”的數學成績
create or replace procedure proc_delete_grade
is
begin
delete from t2_grade where sno =
(select sno from t2_student where sname = '張三')
and cname = '數學';
end proc_delete_grade;
begin
cheirmin.proc_delete_grade;
end;