Oracle學習10:PLSQL學習
1. PLSQL概述
PLSQL是Oracle內部的一種程式語言。
PLSQL是一門語言。叫做過程化SQL語言(Procedural Language SQL)
PLSQL是一種過程化語言,屬於第三代語言,它與C、C++、Java等語言一樣關注於處理細節,可以用來實現比較複雜的業務邏輯。
PL/SQL是對結構化查詢語言(SQL)的過程語言擴充套件。
PL/SQL的基本單位叫做一個區段,由三個部分組成:一個宣告部分,一個可執行部分,和排除-構建部分。
PL/SQL區段只被編譯一次並且以可執行的形式儲存,以降低響應時間。
實際工作中,PLSQL多用於寫觸發器、儲存過程、函式等。
2. PLSQL基本語法
PL/SQL的基本單位叫做一個區段,由三個部分組成:一個宣告部分,一個可執行部分,和排除-構建部分。
這裡宣告只有執行部分是必須的,其餘均是可選,如下:
declare --(可選,宣告變數用)
begin --(must)
null;
exception --(可選)
end; --(must)
2.1 簡單的PSQL語句塊
2.1.1 null語句塊
如下我們建立一個最簡單的PLSQL語句塊
begin
null;
end;
/
在命令列執行,結果如下:
需要指出的是,這裡的 null 不可以省略,PLSQL語句塊中必須包含一條語句
2.1.2 Hello World語句
下面我們寫一個最簡單並且列印Hello World的PLSQL。
set serveroutput on; --用於開啟控制檯輸出服務,預設是off,則不列印
begin
dbms_output.put_line('HelloWorld'); --類似於java的System.out.print
end;
/
在命令列執行,結果:
2.2 含有宣告(declare)語句的PLSQL
declare用於宣告變數。
變數名稱與變數型別不可省略,預設值通過 :=
賦值,預設值可以省略。
格式如下:
declare
v_variable1 variable_type [ := default_value];
v_variable2 variable_type [ := default_value];
2.2.1 PLSQL變數型別
2.2.1.1 變數命名
變數宣告
規則:
- 變數不能使用保留字。如from select;
- 第一個字元必須是字母,一般以
v_
開頭; - 變數名最多包含30個字元;
- 不要與資料庫的表或者列同名;
- 每一行只能宣告一個變數。
2.2.1.2 基本變數型別7個
PLSQL的基本變數型別有7個。如下:
- binary_integer:整數,主要用來計數而不是用來表示欄位型別。
- number:數字型別
- char:定長字串
- varchar2:變長字串
- date:日期
- long:長字串,最長2GB
- boolean:布林型別,可以取值true、false和null
根據以上型別,我們寫一個含有變數宣告的PLSQL。
declare
v_temp number(1);
v_count binary_integer := 0;
v_sal number(7,2) :=4000.00;
v_date date := sysdate;
v_pi constant number(3,2) := 3.14; --類似於Java的final關鍵字,表示常量
v_valid boolean := false;
v_name varchar2(20) not null := 'MyName';
begin
dbms_output.put_line('v_temp value: '||v_temp);
dbms_output.put_line('v_count value: '||v_count);
dbms_output.put_line('v_sal value: '||v_sal);
dbms_output.put_line('v_date value: '||v_date);
dbms_output.put_line('v_pi value: '||v_pi);
-- dbms_output.put_line('v_valid value: '||v_valid); --不能列印boolean值
dbms_output.put_line('v_name value: '||v_name);
end;
命令列結果如下:
這裡需要指出的是,PLSQL的dbms_output.put_line()
函式不能直接列印boolean型別。
2.2.1.3 關聯變數型別(關聯別的欄位、變數的變數型別)
在工作中,變數的作用更多的是儲存某個欄位中的值,因而保證變數資料型別與欄位資料型別一致是很有必要的。
PLSQL中,在變數宣告時,可以通過%type
獲得指定欄位(變數)的資料型別。這種方式可以確保當源表的欄位型別改變時,變數的型別自動改變。
如下:
declare
v_empno number(4);
v_empno2 emp.empno%type; --v_empno2的型別是表emp的empno欄位的型別
v_empno3 v_empno2%type; --v_empno3的型別是變數v_empno2的型別
2.2.1.4 複合變數
PLSQL提供了兩種複合變數。
- Table 類似於Java的陣列Array
- record 類似於Java的類Class
如下宣告一個Table變數
declare
type type_table_emp_empno is table of emp.empno%type index by binary_integer; --宣告一個數組型別
v_empnos type_table_emp_empno; --利用型別宣告變數
begin
v_empno(0) := 7369;
v_empno(2) := 7839;
v_empno(-1) := 9999; --Table型別的下標可以是負數
dbms_output.put_line(v_empno(-1));
end;
宣告一個Record型別
--RECORD型別
declare
type type_record_dept is record
(
deptno dept.deptno%type,
dname dept.dname%type,
loc dept.loc%type
);
v_temp type_record_dept;
begin
v_temp.deptno := 50;
v_temp.dname := 'aaa';
v_temp.loc := 'bj';
dbms_output.put_line(v_temp.deptno||v_temp.dname||v_temp.loc);
end;
這裡如果表的結構發生了變化,我們如何保證宣告的複合變數和表中的變數相同呢,可以通過rowtype實現。
%rowtype:定義一個表示表中一行記錄的變數
如下:
declare
v_temp dept%rowtype;
begin
v_temp.deptno := 50;
v_temp.dname := 'aaa';
v_temp.loc := 'bj';
dbms_output.put_line(v_temp.deptno||v_temp.dname||v_temp.loc);
end;
這裡關於 %type
和 %rowtype
做一個簡單的對比說明:
如下:
%TYPE
為了使一個變數的資料型別與另一個已經定義了的變數(尤其是表的某一列)的資料型別相一致,當被參照的那個變數的資料型別改變了之後,這個新定義的變數的資料型別會自動跟隨其改變,無需修改PL/SQL程式。當不能確切地知道被參照的那個變數的資料型別時,就只能採用這種方法定義變數的資料型別。
%ROWTYPE
如果一個表有較多的列,使用%ROWTYPE來定義一個表示表中一行記錄的變數,比分別使用%TYPE來定義表示表中各個列的變數要簡潔得多,並且不容易遺漏、出錯。這樣會增加程式的可維護性。當表的某些列的資料型別改變了之後,這個新定義的變數的資料型別會自動跟隨其改變,無需修改PL/SQL程式。當不能確切地知道被參照的那個表的結構及其資料型別時,就只能採用這種方法定義變數的資料型別。
2.3 含有異常處理的PLSQL
異常塊通過exception關鍵字宣告,如下:
declare
v_num number := 0;
begin
v_num := 2/v_num;
dbms_output.put_line(v_num);
exception
when others then
dbms_output.put_line('error');
end;
/
3.PLSQL中的SQL語法
3.1 DML語句(資料操作語言,select,update,insert,delete from)
PLSQL中可以寫SQL語句,和平時的SQL語法基本一致,需要注意的是以下幾點:
- select語句必須返回一條記錄,並且只能返回一條記錄;
- select必須和into一塊用(除非使用遊標);
- 操作的資料可以是變數。
- insert、update、delete等語句與普通SQL完成一致,只是可以傳入引數而已。
這裡需要解釋的是,select語句不返回值則無意義,返回許多則變數裝不了。
下面我們有一張測試表,並且根據這張表,實驗理解PLSQL的SQL語句。
set serveroutput on; --該服務開啟一次即可,如果之前開啟過無需再次開啟。
declare
v_dep ljb_test.dep%type; --type
v_name ljb_test.name%type; --type
v_test ljb_test%rowtype; --rowtype
--測試insert&update的資料
v_new_dep ljb_test.dep%type := 10;
v_new_name ljb_test.name%type := '小明';
v_new_salary ljb_test.salary%type := 5000;
v_anoter_name ljb_test.name%type := '大明';
begin
--select語句必須返回一條記錄,並且只能返回一條記錄
select dep, name into v_dep, v_name from ljb_test where salary = 6000; --限定條件一定要保證返回一條記錄
dbms_output.put_line(v_dep||v_name);
select * into v_test from ljb_test where salary = 6000; --限定條件一定要保證返回一條記錄
dbms_output.put_line(v_test.dep||v_test.name||v_test.salary);
--insert,與普通SQL語句一致,只是可以插入變數
insert into ljb_test values(v_new_dep, v_new_name, v_new_salary);
commit; --執行完插入語句記得提交事務
--update,與普通SQL語句一致,只是可以插入變數
update ljb_test t set name = v_anoter_name where t.name = v_new_name;
--delete,與普通SQL語句一致,只是可以插入變數
delete from ljb_test t where t.dep = '1'; --刪除部門1的記錄
end;
/
輸出結果:
同時我們看下更新後的測試表:
可以看到資料已經更新。
3.2 DDL(資料定義語言)
DDL語句不能直接執行,必須使用 execute immediate ''
進行包裹;
如下,我們通過PLSQL建立一張表:
begin
execute immediate 'create table tb(aaa varchar2(255) default ''asd'')'; --兩個單引號代表一個單引號
end;
/
需要指出的兩點:
- DDL必須被
execute immediate ''
包裹,否則報錯ORA-06550; - 平時的語句中的單引號必須通過兩個單引號進行表示,如以上指令碼的預設值。
- delete語句可以不使用execute immediate包裹。
我們在命令列執行,並檢視該表
同樣的,truncate和drop寫法如下:
begin
execute immediate 'truncate table tb'; --刪除記錄,釋放表空間
execute immediate 'drop table tb'; --刪除記錄及定義,釋放表空間
end;
/
但是有個例外:就是delete。
delete既可以使用execute也可以不使用,如下兩種寫法均是正確的。
begin
execute immediate'delete tb'; --delete刪除記錄,但不釋放表空間
commit;
delete tb;
commit;
end;
/
這裡可以這樣理解,實際上delete table_name
等價於 delete from table_name
,是一種DML語言,因而可以直接執行。
這裡針對drop、truncate、delete在複習下三者的區別
- TRUNCATE TABLE:刪除內容、釋放空間但不刪除定義。
- DELETE TABLE:刪除內容不刪除定義,不釋放空間。
- DROP TABLE:刪除內容和定義,釋放空間。
4.PLSQL的判斷迴圈語句
判斷迴圈語句是PLSQL的重要語句。
4.1 判斷(分支)語句
判斷語句也叫做分支語句,通過if實現。如下通過一個簡單例項進行理解:
對於如下表:
我們寫一個薪水等級判斷語句:
<3000 low
3000~5000 middle
其餘 high
--if語句
declare
v_sal ljb_test.salary%type;
begin
select salary into v_sal from ljb_test where name = 'ri';
if(v_sal < 3000) then
dbms_output.put_line('low');
elsif(v_sal < 5000) then --elsif
dbms_output.put_line('middle');
else --else後面沒有then
dbms_output.put_line('high');
end if;
end;
/
set serveroutput on; --也可以放在下面,但是需要在之前增加一個 /
/
和Java等語句的if語句極其相似,不同的是具體的語法,需要注意其特點。
輸出結果如下:
4.2 迴圈語句
與Java中的迴圈語句類似,PLSQL也要擁有類似的三種迴圈語句。為了方便理解,我使用Java迴圈的區分方式來區分PLSQL的三種迴圈。
即:
do while迴圈
while迴圈
for迴圈
對於三種迴圈,均具有以下特點:
PLSQL的迴圈一定是以loop開頭,以end loop結束。
4.2.1 do while迴圈
先列印,然後在判斷條件,如下:
--類似於do while 迴圈
set serveroutput on;
declare
i binary_integer := 1; --宣告一個計數變數
begin
loop --迴圈總是loop開頭
dbms_output.put_line(i);
i := i + 1;
exit when(i>=11); --迴圈退出語句
end loop; --迴圈總是end loop結束
end;
/
命令列列印結果如下:
4.2.2 while迴圈
先判斷,在迴圈列印
--while迴圈
declare
j binary_integer := 1; --宣告一個計數變數
begin
while j<11 loop --迴圈退出語句,迴圈總是loop開頭
dbms_output.put_line(j);
j := j+1;
end loop; --迴圈總是end loop結束
end;
/
結果如下:
4.2.3 for迴圈
for迴圈無需declare塊宣告變數。
1..10表示1-10,注意是兩個點。
--for迴圈
begin --for迴圈無需declare宣告變數
for k in 1..10 loop --1..10表示1-10,注意是兩個點
dbms_output.put_line(k);
end loop;
for k in reverse 1..10 loop --reverse表示逆序
dbms_output.put_line(k);
end loop;
end;
/
結果如下:
5. PLSQL異常處理
前面說過,PLSQL是一門過程語言,所以也支援類似於Java的異常處理機制。
需要指出的是,PLSQL的異常處理實際工作中並不常用,原因很簡單,為了提高程式的可移植性(用於多個數據庫),我們通常把異常處理放在Java等語言中處理。
異常處理的核心應用一般是日誌表。
所以,下文我們僅作簡單的介紹。
5.1異常處理的是執行時異常,而非編譯時異常
首先,明確一點,異常處理只能捕獲執行時的異常,編譯時的異常無法捕獲,因而如果存在編譯異常,則程式無法執行,更無法呼叫異常處理機制。
begin
insert into dual values('',''); --插入欄位數明顯不符合
exception
when others then
dbms_output.put_line('ERROR!');
end;
/
這時,程式無法編譯通過,會直接報錯:
5.2 常見的異常型別及處理
下面,我們就之前PLSQL中select語句返回一條記錄的要求,來寫幾個簡單的異常捕獲程式:
5.2.1 too_many_rows
set serveroutput on;
declare
v_name ljb_test.name%type;
begin
select name into v_name from ljb_test;
exception
when too_many_rows then --too_many_rows,返回值超過一條
dbms_output.put_line('返回值太多');
when others then
dbms_output.put_line('error');
end;
/
執行程式,結果如下:
5.2.2 no_data_found
declare
v_name ljb_test.name%type;
begin
select name into v_name from ljb_test where 1 = 0;
exception
when no_data_found then --no_data_found,無資料異常
dbms_output.put_line('無資料');
when others then
dbms_output.put_line('error');
end;
/
結果如下:
5.3 日誌表建立
異常處理最核心的應用應該就是日誌表,而日誌表多用於儲存過程等。
- 首先建立一個日誌表:
create table err_log(
err_id number primary key,
err_code number,
err_msg varchar2(1024),
err_date date
);
- 建立sequence
create sequence seq_err_log start with 1 increment by 1;
- 新增異常塊
declare
v_errcode number;
v_errmsg varchar2(1024);
v_name ljb_test.name%type;
begin
--insert into dual values('0','2'); --編譯時錯誤是無法通過異常捕獲處理的
select name into v_name from ljb_test;
--commit;
exception
when others then
rollback; --回滾,取消錯誤操作的影響
v_errcode := SQLCODE; --SQLCODE,關鍵字,代表出錯程式碼,Oracle的錯誤程式碼全部是負數
v_errmsg := SQLERRM; --SQLERRM,關鍵字,代表出錯資訊
insert into err_log values(seq_err_log.nextval,v_errcode,v_errmsg,sysdate);
commit; --不要忘記commit
end;
/
我們查詢下對應的日誌表內容:
如果是儲存過程中的日誌表,可以新增一個pro_name欄位。
插入的時候,直接插入該儲存過程的名字即可。
6.寫在最後
至此,PLSQL的基本語法已經學習完成,有關儲存過程、遊標等的介紹,將會另起一文,本文僅對基本語法等做簡單學習總結。