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;
PL/SQL 編程(一)基礎,變量,分支,循環,異常