Oracle 第4章 同義詞、序列、視圖、索引
Oracle 第6章 遊標
Oracle 第5章 使用PL/SQL
1、技術目標
- 理解 PL/SQL 功能和特點
- 瞭解數據類型及其用法
- 理解邏輯比較
- 理解控制結構
- 掌握錯誤處理
2、什麼是PL/SQL?
- PL/SQL是過程語言(Procedural Language)與結構化查詢語言(SQL)結合而成的編程語言
- PL/SQL是對SQL的擴展
- 支持多種數據類型,如大對象和集合類型,可使用條件和循環等控制結構
- 可用於創建存儲過程、觸發器和程序包,給SQL語句的執行添加程序邏輯
- 與 Oracle服務器和Oracle 工具緊密集成,具備可移植性、靈活性和安全性
3、PL/SQL特點
-
支持 SQL,在 PL/SQL 中可以使用:
數據操縱命令
事務控制命令
遊標控制 - 支持動態SQL,可以在程序運行過程中動態構造和運行各種SQL命令
- SQL函數和SQL運算符
- 支持面向對象編程 (OOP)
- 可移植性,可運行在任何操作系統和平臺上的Oralce 數據庫
- 更佳的性能,PL/SQL 經過編譯執行
-
與 SQL 緊密集成,簡化數據處理,如下:
支持所有SQL數據類型
支持NULL值
支持%TYPE和%ROWTYPE屬性類型 - 安全性,可以通過存儲過程限制用戶對數據的訪問
4、PL/SQL體系結構
PL/SQL引擎用來編譯和執行PL/SQL塊或子程序,該引擎駐留在Oracle
服務器中,引擎僅執行過程語句,對於SQL語句則發送給Oracle服務器
上的SQL語句執行器執行,如圖:
5、什麼是PL/SQL塊?
- PL/SQL是一種塊結構語言,將一組語句放在一個塊中
- 構成PL/SQL程序的基本單元是邏輯塊(過程、函數或匿名塊),邏輯塊可以包含任何數量的嵌套子塊,每個邏輯塊對應要解決的問題或子問題,匿名塊是未在數據庫中命名的PL/SQL塊,運行時傳遞到PL/SQL引擎執行
(本文只涉及匿名塊,命名塊見後續文章) - 將邏輯上相關的聲明和語句組合在一起
PL/SQL分爲三個部分:
- 聲明部分,聲明塊中使用的變量、遊標和自定義異常,其作用域僅限所在的塊,另外,局部子程序也可在聲明部分中定義
- 可執行部分,執行命令並操作聲明部分的變量、遊標,可嵌套子塊,該部分是必選
- 異常處理部分,處理執行塊時引發的異常,該部分也可嵌套子塊
定義PL/SQL塊,語法如下:
[DECLARE
declarations]
BEGIN
executable statements
[EXCEPTION
handlers]
END;
語法說明:
declarations,爲聲明部分(可不要)
executable statements,爲可執行部分
exception handlers,爲錯誤處理代碼塊(可不要)
使用: 定義PL/SQL語句塊
--聲明部分
DECLARE
--定義變量
qty_on_hand number(5);
--可執行部分
BEGIN
--PL/SQL語句
SELECT quantity INTO qty_on_hand
FROM Products
WHERE product = '芭比娃娃'
FOR UPDATE OF quantity;
IF qty_on_hand > 0 THEN
UPDATE Products SET quantity = quantity + 1
WHERE product = '芭比娃娃';
INSERT INTO purchase_record
VALUES ('已購買芭比娃娃', SYSDATE);
END IF;
COMMIT;
--異常處理部分
EXCEPTION /* 異常處理語句 */
WHEN OTHERS THEN
DBMS_OUTPUT.PUT_LINE('出錯:'|| SQLERRM);
END;
/
注意:默認情況下,SQL*Plus工具不會顯示DBMS_OUTPUT程序包
的輸出,要啓用DBMS_OUTPUT,須執行命令set serveroutput on ,
語句結束後的"/",表示執行程序塊
6、PL/SQL語言特性
PL/SQL對大小寫不敏感
PL/SQL中的複合符號:
:= 賦值操作符
|| 連接操作符
-- 單行註釋
/* */ 多行註釋
<<, >> 標籤分隔符
.. 範圍操作符
** 求冥操作符
可執行部分所使用的變量和常量,必須在聲明部分先聲明
聲明變量時必須指定變量的數據類型,可以在聲明變量時初始化,如下:
語法: 變量名 數據類型[(範圍)] [:= 初始值];
使用:
DECLARE
vcode varchar2(5);
vname varchar2(25) := 'rose'
BEGIN
...
END;
注意:一條語句只能聲明一個變量
變量賦值時可使用select ... into語句將查詢出的數據賦值給變量,
使用:
DECLARE
icode varchar2(6);
pCatg varchar2(20);
pRate number;
--定義常量
cRate CONSTANT number := 0.10;
BEGIN
...
icode := 'i205'; --給變量icode賦值
select pCategory, itemRate * cRate
into pCatg, pRate --給變量pCatg、pRate賦值
from ItemFile where itemCode = icode;
...
END;
聲明常量的語法爲: 常量名 CONSTANT 數據類型 := 常量值
注意:初始化變量和常量時,可用保留字"DEFAULT"替換賦值操作符":=",例如,
flag boolean DEFAULT true; --聲明布爾型變量flag,初始值爲true
score number DEFAULT 50; --聲明數值型常量score,值爲50
7、PL/SQL 數據類型
PL/SQL所使用的數據類型如下圖:
數值類型:
字符類型:
日期時間類型 :
- DATE
- TIMESTAMP
布爾數據類型,BOOLEAN:
- 用於存儲邏輯值(TRUE、FALSE和NULL)
- 不能向數據庫中插入BOOLEAN數據
- 不能將列值保存到BOOLEAN變量中
- 只能對BOOLEAN變量執行邏輯操作
LOB數據類型:
- 用於存儲大文本、圖像、視頻剪輯和聲音剪輯等非結構化數據。
- LOB 數據類型可存儲最大 4GB的數據。
- LOB 類型包括:
BLOB 將大型二進制對象存儲在數據庫中
CLOB 將大型字符數據存儲在數據庫中,
使用:
SET SERVEROUTPUT ON
DECLARE
clob_var CLOB;
amount INTEGER;
offset INTEGER;
output_var VARCHAR2(100);
BEGIN
--從表中選擇CLOB字段值到clob_var變量中
SELECT chapter_text INTO clob_var
FROM my_book_text
WHERE chapter_id=5;
amount := 24; -- 要讀取的字符數
offset := 1; -- 起始位置
--從clob數據中讀取24個字符存到output_var變量中
DBMS_LOB.READ(clob_var, amount, offset, output_var);
--顯示讀取的信息
DBMS_OUTPUT.PUT_LINE(output_var);
END;
/
NCLOB 存儲大型UNICODE字符數據
BFILE 將大型二進制對象存儲在操作系統文件中,
使用:
insert into myBook values ('Oracle第5章',
BFILENAME('oracle_book', 'oracle05.txt'));
oracle_book爲目錄別名(目錄可以使用create directory語句創建),
oracle05.txt爲目錄下的文件名
屬性類型:
用於引用數據庫表中列的數據類型,以及表示表中一行的記錄
類型,有兩種:
- %TYPE,引用變量和數據庫列的數據類型
- %ROWTYPE,提供表示表中一行的記錄類型
使用1: 聲明變量icode,其類型爲表ItemFile中的列itemCode的類型,
icode ItemFile.itemCode%TYPE;
使用2: 聲明變量empRec,可存儲表Emp表中的一行記錄,
empRec Emp%ROWTYPE;
屬性類型的優點:
- 不需要知道被引用的表列的具體類型
- 如果被引用對象的數據類型發生改變,PL/SQL 變量的數據類型也隨之改變
8、關係運算符
關係運算符 說明
-------------------------------------------------------------------
= 比較兩個變量是否相等,如果值相當,則返回 True
<>, != 比較兩個變量,如果不相等,則返回 True
< 比較兩個變量,檢查值 1 是否小於值 2
> 比較兩個變量,檢查值 1 是否大於 值 2
<= 比較兩個變量,檢查變量 1 是否小於等於變量 2
>= 比較兩個變量,檢查變量 1 是否大於等於變量 2
9、布爾表達式
布爾表達式的結果爲TRUE、FALSE或NULL,通常由邏輯運算符
AND、OR和NOT連接,有3中類型的布爾表達式:
表達式 說明 示例
------------------------------------------------------------------------------------
數字布爾型 用於比較數字,爲定量比較 num1 := 25, num2 := 68,
num1 <= num2 爲 true
字符布爾型 比較字符串每個字節的二進制值 char1 := 'Mickey',
char2 := 'Michael',
char1 > char2 爲 true
日期布爾型 按年月順序進行比較 date1 := '15-9月-08', date2 := '16-10-08',
date1 < date2 爲 true
10、PL/SQL程序控制
三種程序控制方式:
- 分支(條件)控制
- 循環控制
- 順序控制
11)分支(條件)控制
11.1)分支控制if語句
if語句有3種形式:if-then、if-then-else、if-then-elsif,
if-then語句 :先檢測條件,條件爲true時執行then部分的語句,
語法爲,
IF condition THEN
語句;
END IF
if-then-else語句:不符合條件,執行else部分語句,
語法爲,
IF condition THEN
語句;
ELSE
語句;
END IF;
使用: 從ItemFile表中查詢編號爲I188的商品價格,存變量iRate中,如果
價格大於200,將價格減少200,價格小於200,將價格減少50,
set serveroutput on
DECLARE
iCode varchar2(4);--商品編號
iRate number;--商品價格
BEGIN
--查詢編號爲I188的商品,將商品編號賦給iCode,
--將價格賦給變量iRate
select itemCode, itemRate into iCode ,iRate
from ItemFile
where itemCode = 'I188';
--根據價格範圍進行更新操作
IF iRate > 200 THEN
update ItemFile set itemRate = itemRate - 200
where itemCode = iCode;
ELSE
update ItemFile set itemRate = itemRate - 50
where itemCode = iCode;
END IF;
--輸出信息
DBMS_OUTPUT.PUT_LINE('編號:' || iCode || ' 價格:' || iRate);
END;
/
if-then-elsif語句:
將附加條件語句與if-then-else語句相結合的一種形式,語法爲,
IF condition1 THEN
語句1;
ELSIF condition2 THEN
語句2;
ELSIF condition3 THEN
語句3;
...
ELSE
語句4;
END IF;
11.2)分支控制,case語句
將單個變量或表達式與多個值進行比較,執行 CASE 語句前先計算
選擇器(selector)的值,當selector的值與WHEN中的表達式相等時,
執行THEN部分的語句,語法:
CASE selector
WHEN 表達式1 THEN 語句1;
WHEN 表達式1 THEN 語句1;
...
WHEN 表達式N THEN 語句N;
[ELSE 語句;]--當以上條件都不滿足時執行ELSE中語句
END CASE;
使用: 提示用戶輸入成績值,根據成績輸出相應的等級,
set serveroutput on
BEGIN
CASE '&score' --接收輸入的成績
WHEN 'A' THEN DBMS_OUTPUT.PUT_LINE('優異');
WHEN 'B' THEN DBMS_OUTPUT.PUT_LINE('優秀');
WHEN 'C' THEN DBMS_OUTPUT.PUT_LINE('良好');
WHEN 'D' THEN DBMS_OUTPUT.PUT_LINE('一般');
WHEN 'E' THEN DBMS_OUTPUT.PUT_LINE('較差');
ELSE DBMS_OUTPUT.PUT_LINE('沒有此成績!');
END CASE;
END;
/
12、循環控制
循環控制包括:loop、exit語句,exit可退出循環,
exit when語句可按條件退出循環,共三種類型:
loop-end loop 循環
while 循環
for 循環
12.1)LOOP循環(無條件循環)
語法:
LOOP
語句;
END LOOP;
循環執行LOOP塊中的語句,爲了避免死循環,LOOP的語句中必須
加入EXIT或EXIT WHEN語句退出循環
使用:使用學員分數,大於60分的顯示"通過",
set serveroutput on
BEGIN
LOOP
--&score變量接收用戶的輸入
IF &score > 60 THEN
DBMS_OUTPUT.PUT_LINE('通過');
EXIT;
END IF;
END LOOP;
END;
/
12.2)WHILE循環(條件循環)
語法:
WHILE 條件表達式 LOOP
語句;
END LOOP;
使用: 每日銷量循環累加,直至月銷量大於等於4000爲止,
語法:
set serveroutput on
DECLARE
monthlyValue number := 0;--聲明月銷量
dailyValue number := 0;--聲明日銷量
BEGIN
WHILE monthlyValue <= 4000 LOOP
--根據日銷量計算月銷量
monthlyValue := dailyValue * 31;
--日銷量累加
dailyValue := dailyValue + 10;
--輸出日銷量信息
DBMS_OUTPUT.PUT_LINE('日銷量:' || dailyValue);
END LOOP;
--輸出月銷量信息
DBMS_OUTPUT.PUT_LINE('月銷量:' || monthlyValue);
END;
/
12.3)FOR循環(帶計數器的循環)
語法:
FOR 計數器變量 IN [REVERSE] 值1...值N
LOOP
語句;
END LOOP;
說明:reverse可使值由大到小執行循環
使用: 循環25次,顯示25個偶數,
set serveroutput on
BEGIN
FOR counter IN 1..25
LOOP
DBMS_OUTPUT.PUT_LINE(counter * 2);
END LOOP;
END;
/
13、順序控制
可控制程序的執行順序,可使用如下語句,
- GOTO語句
- 無條件地跳到標籤指定的語句,標籤使用雙尖括號(<<標籤名>>)括起來
- 2在語句塊內必須是唯一的名稱,標籤後跟待執行語句或PL/SQL塊,GOTO
- 3無法跳轉到IF、CASE、LOOP語句或子快中
- NULL語句
- NULL是一條可執行語句,什麼也不做,用在IF或其他語句的語法要求至少
- 需要一條可執行語句但又不需要執行任何操作的情況下
使用: 檢查itemCode爲'I188'的產品的庫存,如現有庫存小於訂購量則更新
現有庫存
DECLARE
inventory ItemFile.inventory%type;--庫存,類型同字段inventory
relevel ItemFile.relevel%type;--訂購數量
BEGIN
--查詢商品編號爲'I188'的商品庫存和訂購量並賦值給變量
select inventory, relevel into inventory, relevel
from ItemFile
where itemCode = 'I188';
--判斷庫存是否小於訂購量
IF inventory < relevel THEN
--跳轉到updation標籤處
GOTO updation;
ELSE
--跳轉到quit標籤處
GOTO quit;
END IF;
--設置updation標籤
<<updation>>
update ItemFile set inventory = inventory + relevel
where itemCode = 'I188';
--設置quit標籤
<<quit>>
NULL;
END;
/
14、動態SQL
在PL/SQL程序中,可使用DML、TCL語句,但是DDL語句和會話控制
語句不能在PL/SQL中直接使用,要想使用可以通過動態SQL實現
動態SQL指在PL/SQL塊編譯時SQL語句不確定,比如可以根據用戶輸入
參數的不同而執行不同的操作,編譯程序不處理動態語句部分,在程序
運行時可動態創建語句、對語句進行語法分析並執行
動態SQL執行方式:
- 通過本地SQL命令執行
- 通過DBMS_SQL程序包來執行
本地動態SQL語法:
EXECUTE IMMEDIATE dynamic_sql_string
[into define_variable_list]
[using bind_argument_list];
說明:
dynamic_sql_string,爲動態SQL語句字符串
into子句,用於接受select語句查詢出的值
using子句,用於綁定輸入參數變量
注意:EXECUTE IMMEDIATE語句只能處理返回單行或沒有返回的SQL語句,
要處理返回多行的動態SQL可使用REF遊標的OPEN...FOR語句(見後續文章)
使用: 先執行創建表的動態SQL,再執行帶參select語句,
DECLARE
sqlStmt varchar2(200); --保存SQL語句的字符串
empId number(4) := 9527; --員工編號
empRec emp%ROWTYPE; --保存行記錄
BEGIN
--執行建表的動態SQL語句
EXECUTE IMMEDIATE
'create table TempTable (id number, amt number)';
--查詢員工表的SQL語句,帶一個輸入參數id
sqlStmt := 'select * from Emp where empNo = :id';
--執行查詢員工表的動態SQL語句,同時設置輸入參數empId,
--查詢結果(1條記錄)保存到變量empRec
EXECUTE IMMEDIATE sqlStmt INTO empRec USING empId;
END;
/
15、異常
在運行程序時出現的錯誤叫做異常,發生異常後,語句將停止執行,
控制權轉移到 PL/SQL 塊的異常處理部分
異常有兩種類型:
- 預定義異常,當PL/SQL程序違反 Oracle 規則或超越系統限制時隱式引發
- 用戶定義異常,用戶可以在PL/SQL塊的聲明部分定義異常,自定義的異常通過RAISE語句顯式引發
16、預定義異常
- Oracle把一些常見錯誤預先定義爲異常
- 每個預定義異常都有一個錯誤號
- 必須通過名字來捕獲異常,比如除數爲零會引發"ZERO_DIVIDE"異常
PL/SQL支持的預定義異常如下:
異常名 說明
--------------------------------------------------------------------------------
ACCESS_INTO_NULL 未初始化對象時出現
CASE_NOT_FOUND 在CASE語句中的選項與輸入的數據不匹配時出現
COLLECTION_IS_NULL 給尚未初始化的表或數組賦值時出現
CURSOR_ALREADY_OPEN 試圖重新打開已打開的遊標時出現,重新打開遊標
前必須先關閉
DUP_VAL_ON_INDEX 試圖將重複的值存儲在使用唯一索引的列中時出現
INVALID_CURSOR 執行非法遊標運算時出現,比如打開一個尚未打開
的遊標
INVALID_NUMBER 將字符串轉換爲數字時出現
LOGIN_DENIED 輸入的用戶名或密碼無效時出現
NO_DATA_FOUND 表中不存在請求的行時出現或者引用已刪除的元素時
STORAGE_ERROR 內存損壞或PL/SQL耗盡內存時出現
TOO_MANY_ROWS 執行select into語句後返回多行時出現
VALUE_ERROR 產生大小限制錯誤時出現
ZERO_DIVIDE 除數爲零時出現
注意:預定義異常在STANDARD程序包中聲明
異常處理程序語法爲:
BEGIN
...
EXCEPTION
WHEN 異常名 THEN
語句;
WHEN OTHERS THEN
語句;
END;
說明:
- OTHERS 處理程序確保不會遺漏任何異常,PL/SQL塊只能有一個
- OTHERS異常處理程序,函數SQLCODE和SQLERRM可返回錯誤代碼和錯誤文本信息
使用: 檢測代碼中的預定義錯誤,
set serveroutput on
DECLARE
orderNum varchar2(5); --訂單號
BEGIN
--查詢訂單號
select orderNo into orderNum from orderMaster;
--異常處理
EXCEPTION
WHEN TOO_MANY_ROWS THEN
--輸出提示信息
DBMS_OUTPUT.PUT_LINE('返回多行');
END;
/
注意:select into只允許返回一行,所以會引發TOO_MANY_ROWS預定義異常
17、用戶定義異常
用戶可自行定義代碼中使用的異常,這種處理方式與PL/SQL引擎處理錯誤的方式相同,可以作爲一種良好的編程習慣,獲得直觀而好管理的代碼,用戶定義異常在PL/SQL塊或子程序的聲明部分定義,語法如下:
DECLARE
異常名 EXCEPTION;
自定義異常可使用RAISE語句顯示引發,語法爲:
RAISE 異常名;
使用: 用自定義異常處理錯誤,如用戶輸入'附件', '頂蓋', '備件',
屬於錯誤的類別,引發自定義異常,
set serveroutput on
DECLARE
--自定義異常(無效類別)
InvalidCategory EXCEPTION;
category varchar2(10);
BEGIN
--接收輸入的數據
category := '&Category';
--判斷輸入數據
IF category NOT IN ('附件', '頂蓋', '備件') THEN
--引發自定義異常
RAISE InvalidCategory;
ELSE
--輸出信息
DBMS_OUTPUT.PUT_LINE('你輸入的類別是:' || category);
END IF;
--異常處理塊
EXCEPTION
--捕獲InvalidCategory異常
WHEN InvalidCategory THEN
DBMS_OUTPUT.PUT_LINE('無法識別該類別');
END;
/
18、引發應用程序錯誤
使用過程RAISE_APPLICATION_ERROR創建用戶定義的錯誤信息,用戶
定義的錯誤信息可以比指定的異常描述得更詳細,語法如下:
RAISE_APPLICATION_ERROR (error_number, error_message);
說明:
error_number,是爲異常指定的編號,編號必須是-20000 ~ -20999之間
的負整數
error_message,爲用戶異常指定的消息文本,消息長度可到2048字節,
錯誤消息是與error_number表示關聯的文本
RAISE_APPLICATION_ERROR即可在PL/SQL程序可執行部分使用,還
可在其異常處理部分使用,調用此過程可同時顯示錯誤編號和消息
使用: 如果費率是未知的,將顯示"未指定費率",
set serveroutput on
DECLARE
rate ItemFile.itemRate%TYPE;
--自定義異常
RateException EXCEPTION;
BEGIN
--查詢編號爲'I188'的商品的費率,賦給變量rate,費率爲NULL就賦0
select NVL(itemRate, 0) into rate from ItemFile
where itemCode = 'I188';
--判斷費率
IF rate = 0 THEN
--費率爲0,引發異常
RAISE RateException;
ELSE
--費率不爲0,顯示費率
DBMS_OUTPUT.PUT_LINE('費率爲:' || rate);
END IF;
--異常處理塊
EXCEPTION
WHEN RateException THEN
--顯示指定的異常編號和異常信息
RAISE_APPLICATION_ERROR(-20008, '該商品未指定費率');
END;
/
19、總結
- PL/SQL 是一種可移植的高性能事務處理語言
- PL/SQL 引擎駐留在 Oracle 服務器中
- PL/SQL 塊由聲明部分、可執行部分和異常處理部分組成
- PL/SQL 數據類型包括標量數據類型、LOB 數據類型和屬性類型
- 控制結構包括條件控制、循環控制和順序控制
- PL/SQL 支持動態 SQL
- 運行時出現的錯誤叫做異常
- 異常可以分爲預定義異常和用戶定義的異常