PL/SQL 程式設計(一)基礎,變數,分支,迴圈,異常
SQL和PL/SQL:
SQL 結構化查詢語言(Structural Query Language),是用來訪問和操作關係型資料庫的一種標準通用語言,屬於第四代語言(4GL)。可以方便的呼叫相應語句來去的結果,特點是非過程化,使用的時候不用指明執行的具體方法,不用關注實現的細節,但是某些情況下滿足不了複雜業務流程的需求。
PL/SQL是 Procedure Language & Structured Query Language 的縮寫。屬於第三代語言(3GL),是一種過程化語言。PL/SQL是對SQL語言儲存過程語言的擴充套件,是一種高階資料庫程式設計語言,該語言專門用於在各種環境下對Oracle資料庫進行訪問。除此之外,可以在Oracle資料庫的某些客戶端工具中,使用PL/SQL語言也是該語言的一個特點。PL/SQL可以向Java一樣實現邏輯判斷。條件迴圈和異常處理等。
同傳統的SQL相比PL/SQL有以下優點:
1.可以提高程式的執行效能。
2.可以使程式模組化。
3.可以採用邏輯控制語句來控制程式結構。
4.利用處理執行時的錯誤資訊。
5.良好的可移植性。
PL/SQL塊
pl/sql的基本單位是塊。分為三部分,宣告部分,執行部分,異常處理部分。其中執行部分時必須存在的,宣告和異常處理可以沒有。
--PL/SQL塊的結構如下: DECLARE --宣告部分: 在此宣告PL/SQL用到的變數,型別及遊標,以及區域性的儲存過程和函式 BEGIN -- 執行部分: 過程及SQL 語句 , 即程式的主要部分 EXCEPTION -- 執行異常部分: 錯誤處理 END;
變數 常量
變量表示的值是可以變化的,常量初始化後,其值不可改變。
需要注意:pl/sql是一種強型別語言。
如果表示常量,必須用CONSTANT關鍵字。
標量型別變數:
最簡單型別的變數,它本身是單一的值,不包含任何的型別組合,標量型別主要包含數值型別,字元型別,布林型別,日期型別。還有一種特殊的宣告變數型別的方式: %type
引用型變數:
使用%TYPE,利用已存在的資料型別定義新變數的資料型別。最常見的就是把表中欄位型別作為變數或常量的資料型別。
使用%TYPE特性的優點在於:
l 所引用的資料庫列的資料型別可以不必知道;
l 所引用的資料庫列的資料型別可以實時改變,容易保持一致,也不用修改PL/SQL程式。
DECLARE
-- 用%TYPE 型別定義與表相配的欄位
TYPE T_Record IS RECORD(
T_no emp.empno%TYPE,
T_name emp.ename%TYPE,
T_sal emp.sal%TYPE );
-- 宣告接收資料的變數
v_emp T_Record;
BEGIN
SELECT empno, ename, sal INTO v_emp FROM emp WHERE empno=7788;
DBMS_OUTPUT.PUT_LINE
(TO_CHAR(v_emp.t_no)||' '||v_emp.t_name||' ' || TO_CHAR(v_emp.t_sal));
END;
DECLARE
v_empno emp.empno%TYPE :=&no;
Type t_record is record (
v_name emp.ename%TYPE,
v_sal emp.sal%TYPE,
v_date emp.hiredate%TYPE);
Rec t_record;
BEGIN
SELECT ename, sal, hiredate INTO Rec FROM emp WHERE empno=v_empno;
DBMS_OUTPUT.PUT_LINE(Rec.v_name||'---'||Rec.v_sal||'--'||Rec.v_date);
END;
記錄型變數:
它把邏輯相關的、分離的、基本資料型別的變數組成一個整體儲存起來,它必須包括至少一個標量型或RECORD 資料型別的成員,稱作PL/SQL RECORD 的域(FIELD),其作用是存放互不相同但邏輯相關的資訊。在使用記錄資料型別變數時,需要先在宣告部分先定義記錄的組成、記錄的變數,然後在執行部分引用該記錄變數本身或其中的成員。
該型別可以包含一個或多個成員,每個成員型別可以不同。成員可以是標量型別,也可以是引用型別。記錄型別適合處理查詢語句中有多個列的情況,比如呼叫某個表的一行記錄時用記錄型別變數儲存這行記錄。
--可以用 SELECT語句對記錄變數進行賦值,只要保證記錄欄位與查詢結果列表中的欄位相配即可。
DECLARE
TYPE test_rec IS RECORD(
Name VARCHAR2(30) NOT NULL := '胡勇',
Info VARCHAR2(100));
rec_book test_rec;
BEGIN
rec_book.Name :='胡勇';
rec_book.Info :='談PL/SQL程式設計;';
DBMS_OUTPUT.PUT_LINE(rec_book.Name||' ' ||rec_book.Info);
END;
--一個記錄型別的變數只能儲存從資料庫中查詢出的一行記錄,若查詢出了多行記錄,就會出現錯誤。
DECLARE
--定義與hr.employees表中的這幾個列相同的記錄資料型別
TYPE RECORD_TYPE_EMPLOYEES IS RECORD(
f_name hr.employees.first_name%TYPE,
h_date hr.employees.hire_date%TYPE,
j_id hr.employees.job_id%TYPE);
--宣告一個該記錄資料型別的記錄變數
v_emp_record RECORD_TYPE_EMPLOYEES;
BEGIN
SELECT first_name, hire_date, job_id INTO v_emp_record
FROM employees
WHERE employee_id = &emp_id;
DBMS_OUTPUT.PUT_LINE('僱員名稱:'||v_emp_record.f_name
||' 僱傭日期:'||v_emp_record.h_date
||' 崗位:'||v_emp_record.j_id);
END;
使用%ROWTYPE宣告記錄型別資料
這種宣告方式可以直接引用表中的行作為變數型別,同 %type 相似。
使用%ROWTYPE特性的優點在於:
l 所引用的資料庫中列的個數和資料型別可以不必知道;
l 所引用的資料庫中列的個數和資料型別可以實時改變,容易保持一致,也不用修改PL/SQL程式。
DECLARE
v_empno emp.empno%TYPE :=&no;
rec emp%ROWTYPE;
BEGIN
SELECT * INTO rec FROM emp WHERE empno=v_empno;
DBMS_OUTPUT.PUT_LINE('姓名:'||rec.ename||'工資:'||rec.sal||'工作時間:'||rec.hiredate);
END;
陣列型別
是具有相同資料型別的一組成員的集合。每個成員都有一個唯一的下標,它取決於成員在陣列中的位置。在PL/SQL中,陣列資料型別是VARRAY。
DECLARE
--定義一個最多儲存5個VARCHAR(25)資料型別成員的VARRAY資料型別
TYPE reg_varray_type IS VARRAY(5) OF VARCHAR(25);
--宣告一個該VARRAY資料型別的變數
v_reg_varray REG_VARRAY_TYPE;
BEGIN
--用建構函式語法賦予初值
v_reg_varray := reg_varray_type
('中國', '美國', '英國', '日本', '法國');
DBMS_OUTPUT.PUT_LINE('地區名稱:'||v_reg_varray(1)||'、'
||v_reg_varray(2)||'、'
||v_reg_varray(3)||'、'
||v_reg_varray(4));
DBMS_OUTPUT.PUT_LINE('賦予初值NULL的第5個成員的值:'||v_reg_varray(5));
--用建構函式語法賦予初值後就可以這樣對成員賦值
v_reg_varray(5) := '法國';
DBMS_OUTPUT.PUT_LINE('第5個成員的值:'||v_reg_varray(5));
END;
賦值:賦值要用 := 。
結構控制:
if條件控制:
和Java基本相同。
declare
a varchar(10);
b number(10);
c number(10);
begin
a := '明';
dbms_output.put_line(a);
b := 2;
c := 3;
--分支
if b > c then
dbms_output.put_line('b大於c');
elsif b < c then
dbms_output.put_line('b小於c');
else
dbms_output.put_line('b等於c');
end if;
end;
DECLARE
v_empno employees.employee_id%TYPE :=&empno;
V_salary employees.salary%TYPE;
V_comment VARCHAR2(35);
BEGIN
SELECT salary INTO v_salary FROM employees
WHERE employee_id = v_empno;
IF v_salary < 1500 THEN
V_comment:= '太少了,加點吧~!';
ELSIF v_salary <3000 THEN
V_comment:= '多了點,少點吧~!';
ELSE
V_comment:= '沒有薪水~!';
END IF;
DBMS_OUTPUT.PUT_LINE(V_comment);
exception
when no_data_found then
DBMS_OUTPUT.PUT_LINE('沒有資料~!');
when others then
DBMS_OUTPUT.PUT_LINE(sqlcode || '---' || sqlerrm);
END;
DECLARE
v_first_name VARCHAR2(20);
v_salary NUMBER(7,2);
BEGIN
SELECT first_name, salary INTO v_first_name, v_salary FROM employees
WHERE employee_id = &emp_id;
DBMS_OUTPUT.PUT_LINE(v_first_name||'僱員的工資是'||v_salary);
IF v_salary < 10000 THEN
DBMS_OUTPUT.PUT_LINE('工資低於10000');
ELSE
IF 10000 <= v_salary AND v_salary < 20000 THEN
DBMS_OUTPUT.PUT_LINE('工資在10000到20000之間');
ELSE
DBMS_OUTPUT.PUT_LINE('工資高於20000');
END IF;
END IF;
END;
DECLARE
v_first_name VARCHAR2(20);
v_hire_date DATE;
v_bonus NUMBER(6,2);
BEGIN
SELECT first_name, hire_date INTO v_first_name, v_hire_date FROM employees
WHERE employee_id = &emp_id;
IF v_hire_date > TO_DATE('01-1月-90') THEN
v_bonus := 800;
ELSIF v_hire_date > TO_DATE('01-1月-88') THEN
v_bonus := 1600;
ELSE
v_bonus := 2400;
END IF;
DBMS_OUTPUT.PUT_LINE(v_first_name||'僱員的僱傭日期是'||v_hire_date
||'、獎金是'||v_bonus);
END;
case語句
DECLARE
V_grade char(1) := UPPER('&p_grade');
V_appraisal VARCHAR2(20);
BEGIN
V_appraisal :=
CASE v_grade
WHEN 'A' THEN 'Excellent'
WHEN 'B' THEN 'Very Good'
WHEN 'C' THEN 'Good'
ELSE 'No such grade'
END;
DBMS_OUTPUT.PUT_LINE('Grade:'||v_grade||' Appraisal: '|| v_appraisal);
END;
DECLARE
v_first_name employees.first_name%TYPE;
v_job_id employees.job_id%TYPE;
v_salary employees.salary%TYPE;
v_sal_raise NUMBER(3,2);
BEGIN
SELECT first_name, job_id, salary INTO
v_first_name, v_job_id, v_salary
FROM employees WHERE employee_id = &emp_id;
CASE
WHEN v_job_id = 'PU_CLERK' THEN
IF v_salary < 3000 THEN v_sal_raise := .08;
ELSE v_sal_raise := .07;
END IF;
WHEN v_job_id = 'SH_CLERK' THEN
IF v_salary < 4000 THEN v_sal_raise := .06;
ELSE v_sal_raise := .05;
END IF;
WHEN v_job_id = 'ST_CLERK' THEN
IF v_salary < 3500 THEN v_sal_raise := .04;
ELSE v_sal_raise := .03;
END IF;
ELSE
DBMS_OUTPUT.PUT_LINE('該崗位不漲工資: '||v_job_id);
END CASE;
DBMS_OUTPUT.PUT_LINE(v_first_name||'的崗位是'||v_job_id
||'、的工資是'||v_salary
||'、工資漲幅是'||v_sal_raise);
END;
迴圈:
declare
a varchar(10);
b number(10);
c number(10);
m number(10);
sname varchar2(10);
begin
a := '明';
dbms_output.put_line(a);
b := 2;
c := 3;
--分支
if b > c then
dbms_output.put_line('b大於c');
elsif b < c then
dbms_output.put_line('b小於c');
else
dbms_output.put_line('b等於c');
end if;
--迴圈 1
loop
exit when c < 0;
dbms_output.put_line('loop:' || c);
c := c - 1;
end loop;
--迴圈 2
while b > 0 loop
dbms_output.put_line('while:' || b);
b := b - 1;
end loop;
--迴圈 3
for n in 1 .. 3 loop
dbms_output.put_line('for:' || n);
end loop;
end;
異常
編譯時的錯誤不能稱為異常。
有三種類型的異常錯誤:
1. 預定義 ( Predefined )錯誤
ORACLE預定義的異常情況大約有24個。對這種異常情況的處理,無需在程式中定義,由ORACLE自動將其引發。
2. 非預定義 ( Predefined )錯誤
即其他標準的ORACLE錯誤。對這種異常情況的處理,需要使用者在程式中定義,然後由ORACLE自動將其引發。
3. 使用者定義(User_define) 錯誤
預定義異常一覽:
處理異常:
select s.name into sname from z_student s where s.id=m;
dbms_output.put_line('查詢結果:' || sname);
exception
when no_data_found then
dbms_output.put_line('無查詢結果');
--預定義異常
DECLARE
v_empno employees.employee_id%TYPE := &empno;
v_sal employees.salary%TYPE;
BEGIN
SELECT salary INTO v_sal FROM employees WHERE employee_id = v_empno;
IF v_sal<=1500 THEN
UPDATE employees SET salary = salary + 100 WHERE employee_id=v_empno;
DBMS_OUTPUT.PUT_LINE('編碼為'||v_empno||'員工工資已更新!');
ELSE
DBMS_OUTPUT.PUT_LINE('編碼為'||v_empno||'員工工資已經超過規定值!');
END IF;
EXCEPTION
WHEN NO_DATA_FOUND THEN
DBMS_OUTPUT.PUT_LINE('資料庫中沒有編碼為'||v_empno||'的員工');
WHEN TOO_MANY_ROWS THEN
DBMS_OUTPUT.PUT_LINE('程式執行錯誤!請使用遊標');
WHEN OTHERS THEN
DBMS_OUTPUT.PUT_LINE(SQLCODE||'---'||SQLERRM);
END;
非預定義異常
對於這類異常情況的處理,首先必須對非定義的ORACLE錯誤進行定義。步驟如下:
1. 在PL/SQL 塊的定義部分定義異常情況:
<異常情況> EXCEPTION;
2. 將其定義好的異常情況,與標準的ORACLE錯誤聯絡起來,使用EXCEPTION_INIT語句:
PRAGMA EXCEPTION_INIT(<異常情況>, <錯誤程式碼>);
3. 在PL/SQL 塊的異常情況處理部分對異常情況做出相應的處理。
--刪除指定部門的記錄資訊,以確保該部門沒有員工。
INSERT INTO departments VALUES(50, 'FINANCE', 'CHICAGO');
DECLARE
v_deptno departments.department_id%TYPE := &deptno;
deptno_remaining EXCEPTION;
PRAGMA EXCEPTION_INIT(deptno_remaining, -2292);
/* -2292 是違反一致性約束的錯誤程式碼 */
BEGIN
DELETE FROM departments WHERE department_id = v_deptno;
EXCEPTION
WHEN deptno_remaining THEN
DBMS_OUTPUT.PUT_LINE('違反資料完整性約束!');
WHEN OTHERS THEN
DBMS_OUTPUT.PUT_LINE(SQLCODE||'---'||SQLERRM);
END;
自定義異常
步驟如下:
1. 在PL/SQL 塊的定義部分定義異常情況:
<異常情況> EXCEPTION;
2. RAISE <異常情況>;
3. 在PL/SQL 塊的異常情況處理部分對異常情況做出相應的處理。
if m = 0 then
raise nozero;
end if;
exception
when nozero then
dbms_output.put_line('m不能為0');
--更新指定員工工資,增加100;
DECLARE
v_empno employees.employee_id%TYPE :=&empno;
no_result EXCEPTION;
BEGIN
UPDATE employees SET salary = salary+100 WHERE employee_id = v_empno;
IF SQL%NOTFOUND THEN
RAISE no_result;
END IF;
EXCEPTION
WHEN no_result THEN
DBMS_OUTPUT.PUT_LINE('你的資料更新語句失敗了!');
WHEN OTHERS THEN
DBMS_OUTPUT.PUT_LINE(SQLCODE||'---'||SQLERRM);
END;
--建立一個函式get_salary, 該函式檢索指定部門的工資總和,其中定義了-20991和-20992號錯誤,分別處理引數為空和非法部門程式碼兩種錯誤:
CREATE TABLE errlog(
Errcode NUMBER,
Errtext CHAR(40));
CREATE OR REPLACE FUNCTION get_salary(p_deptno NUMBER)
RETURN NUMBER
AS
v_sal NUMBER;
BEGIN
IF p_deptno IS NULL THEN
RAISE_APPLICATION_ERROR(-20991, ’部門程式碼為空’);
ELSIF p_deptno<0 THEN
RAISE_APPLICATION_ERROR(-20992, ’無效的部門程式碼’);
ELSE
SELECT SUM(employees.salary) INTO v_sal FROM employees
WHERE employees.department_id=p_deptno;
RETURN v_sal;
END IF;
END;
DECLARE
V_salary NUMBER(7,2);
V_sqlcode NUMBER;
V_sqlerr VARCHAR2(512);
Null_deptno EXCEPTION;
Invalid_deptno EXCEPTION;
PRAGMA EXCEPTION_INIT(null_deptno,-20991);
PRAGMA EXCEPTION_INIT(invalid_deptno, -20992);
BEGIN
V_salary :=get_salary(10);
DBMS_OUTPUT.PUT_LINE('10號部門工資:' || TO_CHAR(V_salary));
BEGIN
V_salary :=get_salary(-10);
EXCEPTION
WHEN invalid_deptno THEN
V_sqlcode :=SQLCODE;
V_sqlerr :=SQLERRM;
INSERT INTO errlog(errcode, errtext)
VALUES(v_sqlcode, v_sqlerr);
COMMIT;
END inner1;
V_salary :=get_salary(20);
DBMS_OUTPUT.PUT_LINE('部門號為20的工資為:'||TO_CHAR(V_salary));
BEGIN
V_salary :=get_salary(NULL);
END inner2;
V_salary := get_salary(30);
DBMS_OUTPUT.PUT_LINE('部門號為30的工資為:'||TO_CHAR(V_salary));
EXCEPTION
WHEN null_deptno THEN
V_sqlcode :=SQLCODE;
V_sqlerr :=SQLERRM;
INSERT INTO errlog(errcode, errtext) VALUES(v_sqlcode, v_sqlerr);
COMMIT;
WHEN OTHERS THEN
DBMS_OUTPUT.PUT_LINE(SQLCODE||'---'||SQLERRM);
END outer;