# PL/SQL
### 常見分隔符
+, - , * , / 加, 減, 乘, 除
** 次方(如10 ** 3 為1000)
= , >= , <= , > , < 等同於, 大於等於, 小於等於, 大於, 小於
<>, '=, ~=, ^=, != 皆為不等於
:= 賦值
% 屬性索引
### 比較運算符
**LIKE**: 符合LIKE條件回傳true,否則false
**BETWEEN**: 當值有在BETWEEN X AND Y裡面,回傳true,否則false
**IN**: 當值有在IN(set)裡面,回傳true,否則false
**IS NULL**: 當值非空時會回傳true,否則false
### 邏輯運算符
**AND**: (A and B) 兩者皆true時才為true,否則為false
**OR**: (A OR B) 其中一者為true即為true,兩者皆false才為false
**NOT**: 如條件為false時反轉為true,反之亦然
### 印出資料
dbms_output.put_line('欲印出值'); PL/SQL的印出值
## PL/SQL語法結構
```sql
DECLARE
宣告區域;
BEGIN
執行操作區域;
EXCEPTION --不一定會有EXCEPTION區
捕捉例外區域;
END;
```
初始化賦值: 可以用: =或DEFAULT設預設值
```sql
counter binary_integer := 0;
greetings varchar2(20) DEFAULT 'Have a Good Day';
```
常數賦值: 用:=賦值,賦值後不可變更其值
```sql
PI CONSTANT NUMBER := 3.141592654;
```
### %TYPE V.S %ROWTYPE
%TYPE: 通常用來指定與甚麼column型別相同,當被參照變量的型別改變時,此新定義的變量的型別也會隨之改變
%ROWTYPE: 通常用來指定與甚麼table型別相同,當被參照變量的型別改變時,此新定義的變量的型別也會隨之改變
ex.
```sql
DECLARE
V_ORG_NAME ORG.NAME%TYPE --代表與ORG裡的NAME欄位相同
V_ORG ORG%ROWTYPE --代表與ORG裡的全欄位相同
BEGIN
SELECT NAME INTO V_ORG_NAME FROM ORG;
dbms_output.put_line(‘部門名稱:’ || V_ORG_NAME)
SELECT * INTO V_ORG FROM ORG;
dbms_output.put_line(‘部門名稱:’ || V_ORG.NAME)
END
```
**Into**: 將某欄位查出結果指定給某變數
```sql
declare
value_xxx tablexxx.columnxx%type;
begin
select tx.columnxx into value_xxx from tablexxx tx where tx.columnyy = 123123;
dbms_output.put_line( value_xxx );
end;
```
**Dynamic SQL**: 將值以變數動態傳入SQL的方法
```sql
declare
value_xxx t_table_xxx.column_xxx%type;
v_sql varchar2(2000) := 'select tx.column_xxx from t_table_xxx tx where tx.columnxxx = :1 and tx.columnyyy = :2' ; --宣告:xxx做為預帶入參數
begin
execute immediate v_sql into value_xxx using 'Y' ,1; --用execute immediate執行,並實際帶入參數
dbms_output.put_line( value_xxx );
end;
```
Exception
**Exception語法**: when 某exception then 執行程式碼,others包括所有exception,注意: 包覆可能出現exception的區域要像java的try catch一樣另外用一個begin…end;夾起來
```sql
declare
value_xxx table_xxx.column_xxx%type;
begin
begin
select tx.column_xxx
into value_xxx
from table_xxx tx
where tx.column_yyy in (20600152, 20600153);
dbms_output.put_line(value_xxx);
exception
when others then
dbms_output.put_line('all others exception!');
end;
end;
```
**自訂Exception**:
```sql
DECLARE
v_error exception;
v_name varchar2(20) :='cat';
BEGIN
dbms_output.put_line('I’m a ' || v_name);
if v_name = 'cat' then -- 如果滿足條件則返回v_error
raise v_error;
end if;
EXCEPTION
when v_error then
dbms_output.put_line('I m a cat exception');
END;
```
## 條件控制
**IF-THEN條件控制**
```sql
IF (a <= 20) THEN
dbms_output.put_line(‘value a<=20’);
END IF;
```
**IF-THEN-ELSE條件控制**
```sql
IF (a <= 20) THEN
dbms_output.put_line(‘value a<=20’);
ELSE
dbms_output.put_line(‘value a >20’);
END IF;
```
**IF-THEN-ELSIF條件控制**
```sql
IF ( a = 10 ) THEN
dbms_output.put_line('Value of a is 10' );
ELSIF ( a = 20 ) THEN
dbms_output.put_line('Value of a is 20' );
ELSE
dbms_output.put_line('None of the values is matching');
END IF;
```
**CASE條件控制**
```sql
CASE valueXX
WHEN 'value1' THEN S1; --當valueXX的值為'value1'時
WHEN 'value2' THEN S2;
ELSE Sn;
END CASE;
```
## 迴圈
**For Loop基礎寫法**: for xxx in(SQL) loop 執行程式碼 end loop;
```sql
declare
begin
for data in (select tx.columnxxx from table_xxx tx
where tx.columnyyy in (12312, 12313)) loop
dbms_output.put_line(data.columnxxx);
end loop;
end;
```
**帶入值的For Loop寫法**: for idx in 起始..結束 loop;
```sql
DECLARE
a number(2);
BEGIN
FOR a in 10..20 LOOP
dbms_output.put_line('value of a: ' || a);
END LOOP;
END;
/
```
**While Loop基礎寫法**: WHILE 條件 LOOP 執行程式碼 END LOOP;
```sql
DECLARE
a number(2) := 10;
BEGIN
WHILE a < 20 LOOP
dbms_output.put_line('value of a: ' || a);
a := a + 1;
END LOOP;
END;
/
```
**Continue用法**: 跳出本次迴圈
```sql
declare
begin
for v_idx in 1 .. 5 loop
if v_idx = 4 then
continue;
end if;
dbms_output.put_line( v_idx || '沒被continue跳過');
end loop;
end;
```
**GOTO用法**: 直接跳到預定義好的段落<<>>,非迴圈也可使用
```sql
Declare
begin
for v_idx in 1 .. 10 loop
dbms_output.put_line('now the idx is: '||v_idx);
if v_idx = 5 then
goto stopline;
end if;
end loop;
<<stopline>>
dbms_output.put_line('stop it');
end;
```
## 集合
**Nested Table宣告集合**的語法:
```sql
type xxxType is table of 型別;
v_xxxs xxxType := xxxType (); --集合初始化
```
注意一定要**初始化型別**,集合無資料時迴圈會爆
```sql
declare
type v_name_type is table of varchar2(100); --定義一個型別
v_names v_name_type := v_name_type(); --集合初始化
begin
v_names.extend; --增加集合的size,一次增加一個
v_names.extend;
v_names(1) := 'jack'; --PL/SQL索引值起始為1,不是0!
v_names(2) := 'howard';
for idx in v_names.first .. v_names.last loop
dbms_output.put_line(v_names(idx));
end loop;
end;
```
**Associative array宣告集合**的語法:
```sql
type xxxType is table of 型別 index by 索引型別;
```
Associative array ( index-by table): **迭代取集合**方法
```sql
declare
type tcp_type is table of t_contract_product%rowtype index by binary_integer;
v_tcp tcp_type;
v_idx integer;
begin
v_tcp(3).product_id := 1001;
v_tcp(3).amount := 10000;
v_tcp(5).product_id := 1002;
v_tcp(5).amount := 20000;
v_idx := v_tcp.first; --指向索引值到第一個
loop
exit when v_idx is null; --結束條件:索引值null
dbms_output.put_line(v_tcp(v_idx).product_id || ': ' || v_tcp(v_idx).amount);
v_idx := v_tcp.next(v_idx); --像迭代器一樣持續取出下一個
end loop;
end;
```
## Cursor游標
分為**隱式游標**與**顯式游標**
隱式游標: 無法控制隱式遊標其中的信息。
針對SQL有%FOUND,%ISOPEN,%NOTFOUND,和%ROWCOUNT屬性
顯式游標: 可以定義遊標獲得更多的控製權的上下文的區域
總共有4步驟:
聲明游標
```sql
cursor xxxcursor is SQL
```
打開游標
```sql
OPEN xxxcursor;
```
獲取游標
```sql
FETCH xxxcursor INTO c_id, c_name, c_addr;
```
關閉游標
```sql
CLOSE xxxcursor;
```
**For Loop Cursor寫法**: 先宣告cursor xxxcursor is SQL指令,再for xxx in xxxcursor loop 執行程式碼 end loop;
```sql
declare
cursor xxxcursor is
select txx.columnxxx from table_xxx txx
where txx.columnyy in (1, 2);
begin
for e in xxxcursor loop
dbms_output.put_line(e.columnxxx);
end loop;
end;
```
**For loop cursor寫法 + 塞入集合裡**
```sql
declare
cursor cursorxxx is select tcp.column_xxx from table_xxx tcp where rownum <=50;
type t_value_list is table of table_xxx.column_xxx%type;
valuexxx_list t_value_list := t_value_list();
counter integer := 1;
begin
for data in cursorxxx loop
valuexxx_list.extend; --增加list大小1
valuexxx_list(counter) := data.column_xxx;
dbms_output.put_line(valuexxx_list(counter));
counter := counter +1;
end loop;
end;
```
**Loop Cursor open寫法(顯式游標寫法)**: 一樣先宣告cursor,但cursor需要open與close
```sql
declare
value_xxx table_xxx.policy_code%type;
value_yyy table_xxx.policy_id%type;
cursor p_cursor is
select txx.column_xxx,column_yyy
from table_xxx txx
where txx.column_xxx in (206, 207);
begin
open p_cursor;
loop
fetch p_cursor into value_xxx,value_yyy;
exit when p_cursor%notfound;
dbms_output.put('value_xxx='||value_xxx);
dbms_output.put_line(' value_yyy='||value_yyy);
end loop;
close p_cursor;
end;
```
## PROCEDURES V.S FUNCTION
兩者使用方法很接近,主要差別在於FUNCTION有回傳值(RETURN),而PROCEDURE則沒有,但FUNCTION回傳時為回傳單一值,直接從JAVA call回時方便,但如果需要回傳多個值時,則建議使用PROCEDURE,OUT多個參數一次拿回。
PROCEDURE的IN/OUT/INOUT: 用在傳入參數時,語法: 參數名 [IN/OUT/INOUT] 型別,不寫時為**預設為IN**,IN代表**可傳入不可傳出**,表示該值將被從外部傳遞,OUT代表**可傳出不可傳入**,表示該參數將被用於PROCEDURE外返回一個值,INOUT則代表**可傳入也可傳出**
-建立FUNCTION或PROCEDURES時,記得切到command window,可以清楚看到建立是否成功及相關訊息
**最基礎的PROCEDURE創建語法結構**
```sql
CREATE PROCEDURE Procedure名稱(傳入參數,如果有)
IS(或AS)
宣告區域;
BEGIN
執行操作區域;
END;
/
```
範例
```sql
CREATE PROCEDURE p_get_xxx(a_in number,b_out varchar2(100),c_out varchar2(100))
AS
BEGIN
執行程式碼...
END;
```
呼叫
```sql
declare
v_in1 number;
v_out1 varchar2(100);
v_out2 varchar2(100);
begin
a_in:=17771661;
p_get_xxx(v_in1,v_out1,v_out2);
dbms_output.put_line(v_out1);
dbms_output.put_line(v_out2);
end;
```
或使用pl/sql developer的test測試(直接在方法上右鍵->test)
**最基礎的FUNCTION創建語法結構**
```sql
CREATE FUNCTION Procedure名稱(傳入參數,如果有)
IS(或AS) RETURN回傳型別;
宣告區域;
BEGIN
執行操作區域;
RETURN 回傳值;
END;
/
```
範例
```sql
CREATE FUNCTION f_get_xxx(i_value number) RETURN tablexxx.columnxxx%type
AS
valuexxx tablexxx.columnxxx%type;
BEGIN
執行程式碼...
RETURN valuexxx;
END;
```
在SQL裡呼叫(等同多一個利用function查出來的欄位)
```sql
select f_get_xxx(74470), tcp.* from tablexxx tcp where tcp.columnxx = 74470;
```
直接呼叫
```sql
begin
dbms_output.put_line( f_get_xxx(74470));
end;
```
查詢PROCEDURE或FUNCTION創建狀態(需用**大寫**):
如果STATUS欄位顯示VALID代表編譯成功,INVALID則代表失敗
```sql
begin
select * from all_objects al where al.OBJECT_NAME = 'PROCEDURE或FUNCTION名稱';
end;
```
刪除FUNCTION或PROCEDURE:
```sql
Drop FUNCTION FUNCTION名稱;
Drop PROCEDURE PROCEDURE名稱;
```
## OTHERS
**RECORD**: 功用是做為一個自訂的型別(類似class),語法:
```
Type名稱 is record (
變數1 型別1,
變數2 型別2
);
```
**PACKAGE**: 用來將RECORD及FUNCTION、PROCEDURE包裝,分為兩大部分,Specification及Body,Specification部分主要用來做宣告/定義,Body部分則主要用來實作,最基本的Specification及Body結構如以下包覆
```sql
CREATE OR REPLACE PACKAGE 包裝名稱 [IS/AS]
END 包裝名稱;
/
CREATE OR REPLACE PACKAGE BODY 包裝名稱 [IS/AS]
END 包裝名稱;
/
```
使用:直接呼叫該package名.方法即可使用
```sql
v_info := pkg_getxxx_new.p_get_xxx(123);
```
**TRIGGER**: 觸發器,讓SQL能在特定時機時觸發對應行為,語法:
```sql
CREATE OR REPLACE TRIGGER 觸發器名稱
BEFORE [INSERT|UPDATE|DELETE]
ON 資料表
FOR EACH ROW
DECLARE
BEGIN
執行行為
END 觸發器名稱;
```
範例:在UPDATE時自動填入系統時間
```sql
create or replace trigger emp_trg
before update
on emp
for each row
declare
begin
:new.update_time :=sysdate; --新的值會自動放入sysdate
end emp_trg;
```