2016年2月11日 星期四

游標一(隱式游標、顯式游標的查詢) (PL/SQL 九)

游標效能很低,所以要使用時要先想一下是否有必要使用,分成隱式游標(Implicit)和顯式游標(Explicit),可參考官網的 隱式游標 和 顯式游標


※隱式游標

就是不用打出「CURSOR」關鍵字,有四種,參考官網


※%ROWCOUNT

DECLARE 
    xxx_count NUMBER;
BEGIN
    SELECT COUNT(*) INTO xxx_count FROM EMP;
    DBMS_OUTPUT.PUT_LINE(SQL%ROWCOUNT);
    
    INSERT INTO DEPT VALUES(50, '業務部', '舊金山');
    DBMS_OUTPUT.PUT_LINE(SQL%ROWCOUNT);
    
    UPDATE DEPT SET DNAME='採購部' WHERE DEPTNO = 50;
    DBMS_OUTPUT.PUT_LINE(SQL%ROWCOUNT);
    
    DELETE FROM DEPT WHERE DEPTNO = 50;
    DBMS_OUTPUT.PUT_LINE(SQL%ROWCOUNT);
END;

※使用%ROWCOUNT可得知操作的行數,此例的結果是4個1



※%FOUND、%NOTFOUND

DECLARE 
BEGIN
    UPDATE EMP SET SAL = SAL * 1.2;
    
    IF SQL%FOUND THEN
        DBMS_OUTPUT.PUT_LINE('修改了' || SQL%ROWCOUNT || '筆');
    ELSE
        DBMS_OUTPUT.PUT_LINE('沒有資料被修改');
    END IF;
END;



※顯式游標的查詢

就是要明確的打出「CURSOR」關鍵字


※和LOOP、WHILE迴圈配合使用

DECLARE 
    CURSOR xxx_cur IS SELECT * FROM DEPT;
    xxx_dept DEPT%ROWTYPE;
BEGIN
    IF NOT xxx_cur%ISOPEN THEN
        OPEN xxx_cur;
    END IF;
    
    /*
    FETCH xxx_cur INTO xxx_dept;
    WHILE xxx_cur%FOUND LOOP
        DBMS_OUTPUT.PUT(xxx_cur%ROWCOUNT);
        DBMS_OUTPUT.PUT_LINE('=' || xxx_dept.dname);
        FETCH xxx_cur INTO xxx_dept;
    END LOOP;
    */
    
    LOOP
        FETCH xxx_cur INTO xxx_dept;
        EXIT WHEN xxx_cur%NOTFOUND;
        DBMS_OUTPUT.PUT(xxx_cur%ROWCOUNT);
        DBMS_OUTPUT.PUT_LINE('=' || xxx_dept.dname);
    END LOOP;
    
    CLOSE xxx_cur;
END;

※以上是兩種迴圈和游標的使用

※CURSOR xxx_cur IS SELECT * FROM DEPT 是省略寫法,全部是
CURSOR xxx_cur RETURN DEPT%ROWTYPE IS SELECT * FROM DEPT,也就是多了RETURN DEPT%ROWTYPE,因為查的是DEPT,所以預設也回傳DEPT的ROWTYPE,所以可以省略



※和FOR迴圈配合使用

DECLARE 
    CURSOR xxx_cur IS SELECT * FROM DEPT;
    xxx_dept DEPT%ROWTYPE;
BEGIN
    DBMS_OUTPUT.PUT_LINE('方法一');
    FOR xxx_dept IN xxx_cur LOOP
        DBMS_OUTPUT.PUT(xxx_cur%ROWCOUNT);
        DBMS_OUTPUT.PUT_LINE('=' || xxx_dept.dname);
    END LOOP;
    
    DBMS_OUTPUT.PUT_LINE(CHR(10) || '方法二');
    FOR xxx_dept IN (SELECT * FROM DEPT) LOOP
        DBMS_OUTPUT.PUT_LINE(xxx_dept.dname);
    END LOOP;
END;

※結果:
方法一
1=ACCOUNTING
2=RESEARCH
3=SALES
4=OPERATIONS

方法二
ACCOUNTING
RESEARCH
SALES
OPERATIONS

※和FOR迴圈配合時,不需要打開和關閉游標,也不用判斷哪時跳離迴圈,一切都交給系統控制,所以大部分的人都使用這一種

※方法一和方法二的差別就在方法二直接將SQL寫進去了,所以也無法使用%ROWCOUNT



※有參數的游標

DECLARE 
    CURSOR xxx_cur(ooo DEPT.DEPTNO%TYPE) IS SELECT * FROM DEPT WHERE DEPTNO = ooo;
BEGIN
    FOR xxx_dept IN xxx_cur(&pk) LOOP
        DBMS_OUTPUT.PUT(xxx_cur%ROWCOUNT || ' ');
        DBMS_OUTPUT.PUT(xxx_dept.deptno || ' ');
        DBMS_OUTPUT.PUT(xxx_dept.dname || ' ');
        DBMS_OUTPUT.PUT_LINE(xxx_dept.loc);
    END LOOP;
END;

※結果:
1 10 ACCOUNTING NEW YORK

※&pk為使用者輸入的參數值

 ※LOOP和WHILE要寫在OPEN xxx_cur(&pk)

※注意傳進去的參數不能用來判斷,如果是in的話,網路有用一種子查詢的方式,但我沒試出來,我用的是第26篇的動態SQL解決in的問題



※和陣列配合使用

DECLARE 
    CURSOR xxx_cur IS SELECT * FROM DEPT;
    TYPE xxx_index IS TABLE OF DEPT%ROWTYPE INDEX BY PLS_INTEGER;
    xxx xxx_index;
BEGIN
    FOR xxx_dept IN xxx_cur LOOP
        xxx(xxx_dept.deptno) := xxx_dept;
        DBMS_OUTPUT.PUT_LINE(xxx(xxx_dept.deptno).dname);
    END LOOP;
END;

※結果:
ACCOUNTING
RESEARCH
SALES
OPERATIONS



※和巢狀表配合使用

DECLARE 
    CURSOR xxx_cur IS SELECT * FROM DEPT;
    TYPE xxx_nested IS TABLE OF DEPT%ROWTYPE;
    xxx xxx_nested;
BEGIN
    IF NOT xxx_cur%ISOPEN THEN
        OPEN xxx_cur;
    END IF;
    
    FETCH xxx_cur BULK COLLECT INTO xxx;
    CLOSE xxx_cur;
    
    FOR i IN xxx.FIRST..xxx.LAST LOOP
        DBMS_OUTPUT.PUT_LINE(xxx(i).dname);
    END LOOP;
END;

※結果:
ACCOUNTING
RESEARCH
SALES
OPERATIONS

※使用BULK COLLECT一次取出,所以可以馬上關閉游標



※和VARRAY配合使用

DECLARE 
    CURSOR xxx_cur IS SELECT * FROM DEPT;
    TYPE xxx_varray IS VARRAY(3) OF DEPT%ROWTYPE;
    xxx xxx_varray;
BEGIN
    IF NOT xxx_cur%ISOPEN THEN
        OPEN xxx_cur;
    END IF;
    
    FETCH xxx_cur BULK COLLECT INTO xxx LIMIT 3;
    CLOSE xxx_cur;
    
    FOR i IN xxx.FIRST..xxx.LAST LOOP
        DBMS_OUTPUT.PUT_LINE(xxx(i).dname);
    END LOOP;
END;

※結果:
ACCOUNTING
RESEARCH
SALES

※BULK COLLECT INTO ~~~ LIMIT 可以限制資料量

2016年2月10日 星期三

集合的例外、FORALL、BULK COLLECT (PL/SQL 八)

※處理集合例外

※COLLECTION_IS_NULL

DECLARE 
    TYPE xxx_testnested IS VARRAY(3) OF VARCHAR2(10);
    xxx xxx_testnested;
BEGIN
    xxx(0) := '10';
    
EXCEPTION
    WHEN COLLECTION_IS_NULL THEN
        DBMS_OUTPUT.PUT_LINE('集合未初始化!');
END;



※SUBSCRIPT_BEYOND_COUNT

DECLARE 
    TYPE xxx_testnested IS VARRAY(3) OF VARCHAR2(10);
    xxx xxx_testnested := xxx_testnested('a', 'b');
BEGIN
    xxx(3) := '10';
    
EXCEPTION
    WHEN SUBSCRIPT_BEYOND_COUNT THEN
        DBMS_OUTPUT.PUT_LINE('超過index的個數!');
END;



※SUBSCRIPT_OUTSIDE_LIMIT

DECLARE 
    TYPE xxx_testnested IS VARRAY(3) OF VARCHAR2(10);
    xxx xxx_testnested := xxx_testnested('a', 'b');
BEGIN
    xxx(4) := '10';
    
EXCEPTION
    WHEN SUBSCRIPT_OUTSIDE_LIMIT THEN
        DBMS_OUTPUT.PUT_LINE('超過最大極限!');
END;



※VALUE_ERROR

DECLARE 
    TYPE xxx_testnested IS VARRAY(3) OF VARCHAR2(10);
    xxx xxx_testnested := xxx_testnested('a', 'b');
BEGIN
    xxx('2') := '1';
    xxx('x') := '2';
EXCEPTION
    WHEN VALUE_ERROR THEN
    DBMS_OUTPUT.PUT_LINE('index類型錯誤!');
END;



※NO_DATA_FOUND

DECLARE 
    TYPE xxx_testnested IS TABLE OF VARCHAR2(10);
    xxx xxx_testnested := xxx_testnested('a', 'b');
BEGIN
    xxx.DELETE(1);
    DBMS_OUTPUT.PUT_LINE(xxx(1));
EXCEPTION
    WHEN NO_DATA_FOUND THEN
        DBMS_OUTPUT.PUT_LINE('找不到資料!');
END;



※FORALL

FORALL是將SQL一次性的發送到資料庫執行


※一般的修改

DECLARE 
    TYPE emp_empno IS VARRAY(8) OF EMP.EMPNO%TYPE;
    xxx emp_empno := emp_empno(7369, 7902);
BEGIN
    FOR i IN xxx.FIRST..xxx.LAST LOOP
        UPDATE EMP SET sal = sal * 1.2 WHERE empno = xxx(i); 
    END LOOP;
    
    DBMS_OUTPUT.PUT_LINE('修改成功!');
END;

※這樣子修改比較粍效能



※使用FORALL

DECLARE 
    TYPE emp_empno IS VARRAY(8) OF EMP.EMPNO%TYPE;
    xxx emp_empno := emp_empno(7369, 7902);
    cou number := 0;
BEGIN
    FORALL i IN xxx.FIRST..xxx.LAST
    UPDATE EMP SET sal = sal * 1.2 WHERE empno = xxx(i); 
    
    FOR i IN xxx.FIRST..xxx.LAST LOOP
        cou := cou + SQL%BULK_ROWCOUNT(i);
    END LOOP;
    
    DBMS_OUTPUT.PUT_LINE('修改了'|| cou || '筆!');
END;

※使用SQL%BULK_ROWCOUNT(i)可取得修改的筆數



※BULK COLLECT

BULK COLLECT是從資料庫一次性的取出多條資料


※取得一個欄位的集合

DECLARE 
    TYPE emp_ename IS VARRAY(8) OF EMP.ENAME%TYPE;
    xxx emp_ename;
BEGIN
    SELECT ename BULK COLLECT INTO xxx FROM EMP WHERE DEPTNO = 10;
    
    FOR i IN xxx.FIRST..xxx.LAST LOOP
        DBMS_OUTPUT.PUT_LINE(xxx(i));
    END LOOP;
END;



※取得多個欄位的集合

DECLARE 
    TYPE emp_all IS TABLE OF EMP%ROWTYPE;
    xxx emp_all;
BEGIN
    SELECT * BULK COLLECT INTO xxx FROM EMP;
    
    FOR i IN xxx.FIRST..xxx.LAST LOOP
        DBMS_OUTPUT.PUT_LINE(xxx(i).empno);
        DBMS_OUTPUT.PUT_LINE(xxx(i).ename);
        DBMS_OUTPUT.PUT_LINE(xxx(i).deptno || CHR(10));
    END LOOP;
END;

集合的方法 (PL/SQL 七)

※這一篇雖然是集合的方法,但不見得所有的集合都能用

※CARDINALITY、MEMBER OF、SET

CARDINALITY:取得長度
MEMBER OF:判斷是否是某集合的成員
SET:可去除重覆,也可判斷是否是集合; 集合裡面有重覆不算集合(IS [NOT] A SET)

DECLARE 
    TYPE xxx_nested IS TABLE OF VARCHAR2(10); 
    xxx xxx_nested := xxx_nested('duck', 'tiger', 'duck');
    ooo xxx_nested := xxx_nested();
    str VARCHAR2(10) := 'tiger';
BEGIN
    DBMS_OUTPUT.PUT_LINE(CARDINALITY(xxx));
    DBMS_OUTPUT.PUT_LINE(CARDINALITY(SET(xxx)));
    
    IF xxx IS NOT EMPTY THEN
        DBMS_OUTPUT.PUT_LINE('xxx not empty!');
    END IF;
    
    IF str MEMBER OF xxx THEN
        DBMS_OUTPUT.PUT_LINE('str是xxx的成員!');
    END IF;
    
    IF xxx IS A SET THEN
        DBMS_OUTPUT.PUT_LINE('xxx是集合!');
    END IF;
    
    IF ooo IS A SET THEN
        DBMS_OUTPUT.PUT_LINE('ooo是集合!');
    END IF;
    
    /*
    IF str IS A SET THEN
        DBMS_OUTPUT.PUT_LINE('str是集合!');
    END IF;
    */
END;

※結果:
3
2
xxx not empty!
str是xxx的成員!
ooo是集合!

※xxx之所以不是集合,是因為內容有重覆

※不是集合使用SET判斷會直接錯誤,無法執行



※MULTISET EXCEPT/INTERSECT/UNION、SUBMULTISET


MULTISET EXCEPT:將重覆的元素排除,只留自己(左邊)的
MULTISET INTERSECT:只留重覆的元素
MULTISET UNION:將全部的集合全部合併為一個新集合,包括重覆
SUBMULTISET:判斷左邊的集合是否為右邊的集合,元素的內容必需和比對者的集合<或=才算是比對者的子集合

DECLARE 
    TYPE xxx_nested IS TABLE OF VARCHAR2(10); 
    xxx xxx_nested := xxx_nested('duck', 'tiger');
    ooo xxx_nested := xxx_nested('duck', 'lion');
    judge xxx_nested;
    
    ccc xxx_nested := xxx_nested('duck', 'tiger');
    yyy xxx_nested := xxx_nested('duck', 'tiger', 'elephant');
    zzz xxx_nested := xxx_nested('duck');
BEGIN
    judge := xxx MULTISET EXCEPT ooo;
    FOR i IN judge.FIRST..judge.LAST LOOP
        DBMS_OUTPUT.PUT_LINE('EXCEPT=' || judge(i));
    END LOOP;
    
    judge := xxx MULTISET INTERSECT ooo;
    FOR i IN judge.FIRST..judge.LAST LOOP
        DBMS_OUTPUT.PUT_LINE('INTERSECT=' || judge(i));
    END LOOP;
    
    judge := xxx MULTISET UNION ooo;
    FOR i IN judge.FIRST..judge.LAST LOOP
        DBMS_OUTPUT.PUT_LINE('UNION=' || judge(i));
    END LOOP;
    
    IF ooo SUBMULTISET xxx THEN
        DBMS_OUTPUT.PUT_LINE('ooo是xxx的子集合!');
    END IF;
    
    IF ccc SUBMULTISET xxx THEN
        DBMS_OUTPUT.PUT_LINE('ccc是xxx的子集合!');
    END IF;
    
    IF yyy SUBMULTISET xxx THEN
        DBMS_OUTPUT.PUT_LINE('yyy是xxx的子集合!');
    END IF;
    
    IF zzz SUBMULTISET xxx THEN
        DBMS_OUTPUT.PUT_LINE('zzz是xxx的子集合!');
    END IF;
END;

※結果:
EXCEPT=tiger
INTERSECT=duck
UNION=duck
UNION=tiger
UNION=duck
UNION=lion
ccc是xxx的子集合!
zzz是xxx的子集合!



※DELETE(一個參數)

這裡的刪除,要把COUNT想成沒變動才容易理解
DECLARE 
    TYPE xxx_nested IS TABLE OF VARCHAR2(10); 
    xxx xxx_nested := xxx_nested('a', 'b', 'c', 'd', 'e');
BEGIN
    DBMS_OUTPUT.PUT_LINE('COUNT1=' || xxx.COUNT);
    FOR i IN xxx.FIRST..xxx.LAST LOOP
        DBMS_OUTPUT.PUT_LINE(xxx(i));
    END LOOP;
    
    xxx.DELETE(1);
    
    DBMS_OUTPUT.PUT_LINE(CHR(10) || 'COUNT2=' || xxx.COUNT);
    FOR i IN xxx.FIRST..xxx.LAST LOOP
        DBMS_OUTPUT.PUT_LINE(xxx(i));
    END LOOP;
    
    xxx.DELETE(3);
    
    DBMS_OUTPUT.PUT_LINE(CHR(10) || 'COUNT3=' || xxx.COUNT);
    FOR i IN xxx.FIRST..xxx.LAST LOOP
        IF i = 3 THEN
            CONTINUE;
        END IF;
        DBMS_OUTPUT.PUT_LINE(xxx(i));
    END LOOP;
END;

※結果:
COUNT1=5
a
b
c
d
e

COUNT2=4
b
c
d
e

COUNT3=3
b
d
e

※刪除第一個不需要判斷,但刪其他的不判斷會出錯,因為迴圈一直加1,到刪除的那一筆就會出錯,所以才說要把它想成COUNT不變,才比較好理解



※DELETE(兩個參數)

刪除連續的元素
DECLARE 
    TYPE xxx_nested IS TABLE OF VARCHAR2(10); 
    xxx xxx_nested := xxx_nested('a', 'b', 'c', 'd', 'e');
BEGIN
    DBMS_OUTPUT.PUT_LINE('COUNT1=' || xxx.COUNT);
    FOR i IN xxx.FIRST..xxx.LAST LOOP
        DBMS_OUTPUT.PUT_LINE(xxx(i));
    END LOOP;
    
    xxx.DELETE(2, 4);
    
    DBMS_OUTPUT.PUT_LINE(CHR(10) || 'COUNT2=' || xxx.COUNT);
    FOR i IN xxx.FIRST..xxx.LAST LOOP
        IF i <> 2 AND i != 3 AND i <> 4 THEN
        -- IF i NOT IN(2,3,4) THEN
            DBMS_OUTPUT.PUT_LINE(xxx(i));
        END IF;
    END LOOP;
END;

※結果:
COUNT1=5
a
b
c
d
e

COUNT2=2
a
e

※和刪除(一個參數)一樣,如果是從第一個刪除,不需要判斷


※EXTEND

如果不使用EXTEND,直接給值會出錯,使用了EXTEND可以不給值
DECLARE 
    TYPE xxx_nested IS VARRAY(10) OF VARCHAR2(10); 
    xxx xxx_nested := xxx_nested('a', 'b', 'c');
BEGIN
    DBMS_OUTPUT.PUT_LINE('COUNT1=' || xxx.COUNT);
    xxx.EXTEND(2);
    xxx(5) := 'd';
    DBMS_OUTPUT.PUT_LINE('COUNT2=' || xxx.COUNT);
    
    FOR i IN xxx.FIRST..xxx.LAST LOOP
        DBMS_OUTPUT.PUT_LINE(xxx(i));
    END LOOP;
    
    xxx.EXTEND(2, 2);
    xxx(3) := 'x';
    DBMS_OUTPUT.PUT_LINE('COUNT3=' || xxx.COUNT);
    
    FOR i IN xxx.FIRST..xxx.LAST LOOP
        DBMS_OUTPUT.PUT_LINE(xxx(i));
    END LOOP;
END;

※結果:
COUNT1=3
COUNT2=5
a
b
c

d
COUNT3=7
a
b
x

d
b
b

※不管一或兩個參數,一律都是往下延伸,如果只給一個參數,那延伸的內容是空的,如果給第二個參數,則延伸的內容要看對應的index是什麼值,就全部都是那個值,如上面的index是給2,而2就是「b」,所以最後的兩個延伸出來的內容就全部是「b」


※EXISTS、LIMIT、COUNT

DECLARE 
    TYPE xxx_nested IS VARRAY(5) OF VARCHAR2(10); 
    xxx xxx_nested := xxx_nested('a', 'b', 'c');
BEGIN
    IF xxx.EXISTS(2) THEN
        DBMS_OUTPUT.PUT_LINE('index2存在!');
    END IF;
    
    IF NOT xxx.EXISTS(10) THEN
        DBMS_OUTPUT.PUT_LINE('index10不存在!');
    END IF;
    
    DBMS_OUTPUT.PUT_LINE('集合最大長度=' || xxx.LIMIT);
    DBMS_OUTPUT.PUT_LINE('集合目前長度=' || xxx.COUNT);
END;

※結果:
index2存在!
index10不存在!
集合最大長度=5
集合目前長度=3

※如果是用巢狀表,則集合最大長度是空的



※NEXT、PRIOR

NEXT為取下一個元素; PRIOR為取上一個元素,取不到就是空的
DECLARE 
    TYPE xxx_nested IS TABLE OF VARCHAR2(10) INDEX BY PLS_INTEGER; 
    xxx xxx_nested;
    top NUMBER;
BEGIN
    xxx(10) := 'a';
    xxx(-2) := 'b';
    xxx(1) := 'c';
    xxx(0) := 'd';
    xxx(-10) := 'e';
    
    top := xxx.FIRST;
    /*
    WHILE(xxx.EXISTS(top)) LOOP
        DBMS_OUTPUT.PUT_LINE(top || '=' || xxx(top));
        top := xxx.NEXT(top);
    END LOOP;
    */
    
    FOR i IN xxx.FIRST..xxx.LAST LOOP
        IF i <> top THEN
            CONTINUE;  
        END IF;
    
        IF xxx.EXISTS(top) THEN
            DBMS_OUTPUT.PUT_LINE(i || '=' || xxx(i));
            top := xxx.NEXT(top);
        END IF;
    END LOOP;
    
    DBMS_OUTPUT.PUT_LINE(xxx.NEXT(10));
    DBMS_OUTPUT.PUT_LINE(xxx.NEXT(9));
    DBMS_OUTPUT.PUT_LINE(xxx.PRIOR(10));
    DBMS_OUTPUT.PUT_LINE(xxx.PRIOR(9));
END;

※結果:
-10=e
-2=b
0=d
1=c
10=a

10
1
1



※TRIM

這裡的TRIM和SQL的LTRIM和RTRIM,還有java的trim很不一樣,是刪掉最右邊的意思

DECLARE 
    TYPE xxx_nested IS VARRAY(8) OF VARCHAR2(10); 
    xxx xxx_nested := xxx_nested('a', 'b', 'c', 'd', 'e');
BEGIN
    DBMS_OUTPUT.PUT_LINE('COUNT1=' || xxx.COUNT);
    xxx.TRIM;
    
    DBMS_OUTPUT.PUT_LINE('COUNT2=' || xxx.COUNT);
    FOR i IN xxx.FIRST..xxx.LAST LOOP
        DBMS_OUTPUT.PUT_LINE(xxx(i));
    END LOOP;
    
    xxx.TRIM(2);
    DBMS_OUTPUT.PUT_LINE('COUNT3=' || xxx.COUNT);
    FOR i IN xxx.FIRST..xxx.LAST LOOP
        DBMS_OUTPUT.PUT_LINE(xxx(i));
    END LOOP;
END;

※結果:
COUNT1=5
COUNT2=4
a
b
c
d
COUNT3=2
a
b

2016年2月9日 星期二

集合-巢狀表和VARRAY (PL/SQL 六)

※巢狀表

在DML的巢狀表和VARRAY看這篇,這裡是在PL/SQL裡操作巢狀表

※使用迴圈取值

DECLARE
    TYPE xxx_nested IS TABLE OF VARCHAR2(10) NOT NULL;
    xxx xxx_nested := xxx_nested('banana', 'apple', 'blueberry');
BEGIN
    -- FOR x IN 1..xxx.COUNT LOOP
    -- FOR x IN xxx.FIRST..xxx.COUNT LOOP
    FOR i IN xxx.FIRST..xxx.LAST LOOP
        DBMS_OUTPUT.PUT_LINE(xxx(i));
    END LOOP;
END;

※結果:
banana
apple
blueberry

※可以使用FIRST開始,LAST結束,而結束的數量也等於COUNT數



※增加到資料庫

CREATE OR REPLACE TYPE xxx_nested IS TABLE OF VARCHAR2(10) NOT NULL;
    
DECLARE
    xxx xxx_nested := xxx_nested('banana', 'apple', 'blueberry');
BEGIN
    FOR x IN xxx.FIRST..xxx.LAST LOOP
        DBMS_OUTPUT.PUT_LINE(xxx(x));
    END LOOP;
END;

※上面的declare版是測試用,正式版就要變成CREATE或CREATE OR REPLACE才會增加到資料庫裡,多OR REPLACE差在如果資料庫已有會覆蓋,軟體的畫面要在如下的畫面看:

如果要從資料庫刪除,可下「DROP TYPE type名稱」,或按右鍵DROP就可,但要確保Table裡和其他Type等,都沒用到才不會出錯誤訊息



※巢狀表的增刪改查

DROP TABLE testnested PURGE;
CREATE TABLE testnested(
    PK NUMBER,
    NNAME VARCHAR2(10) NOT NULL,
    NEST xxx_nested,
    CONSTRAINT TESTNESTED_PK PRIMARY KEY(PK)
) NESTED TABLE NEST STORE AS PROJECTS_NESTED_TABLE;
    
    
DECLARE
    ooo testnested%ROWTYPE;
BEGIN
    ooo.pk := 10;
    ooo.nname := 'fruit';
    ooo.nest := xxx_nested('banana', 'apple', 'blueberry');
    
    INSERT INTO testnested VALUES ooo;
    DBMS_OUTPUT.PUT_LINE('新增成功!');
END;
    
    
--查詢
SELECT * FROM TABLE (
    SELECT nest FROM testnested WHERE pk = 10
);
    
    
DECLARE
    ooo xxx_nested := xxx_nested('strawberry', 'apple');
BEGIN
    UPDATE testnested SET nest = ooo WHERE pk = 10;
    DBMS_OUTPUT.PUT_LINE('修改成功!');
END;
    
    
DECLARE
    ooo xxx_nested := xxx_nested('strawberry', 'apple');
BEGIN
    DELETE FROM testnested where pk = 10;
    DBMS_OUTPUT.PUT_LINE('刪除成功!');
END;

※修改和DML不一樣,可以針對想修改的部分操作即可



※巢狀表自定型態

CREATE OR REPLACE TYPE many_nested AS OBJECT(
    mid number,
    mname varchar2(5),
    mdate date
);
    
DECLARE
    TYPE xxx_nested IS TABLE OF many_nested NOT NULL;
    ooo xxx_nested := xxx_nested(
        many_nested(30, 'duck', TO_DATE('1980-01-01', 'YYYY-MM-DD')),
        many_nested(40, 'tiger', TO_DATE('1990-12-31', 'YYYY-MM-DD'))
    );
BEGIN
    FOR i IN ooo.FIRST..ooo.LAST LOOP
        DBMS_OUTPUT.PUT_LINE('mid=' || ooo(i).mid);
        DBMS_OUTPUT.PUT_LINE('mname=' || ooo(i).mname);
        DBMS_OUTPUT.PUT_LINE('mdate=' || ooo(i).mdate || CHR(10));
    END LOOP;
END;

※自定一個many_nested,然後使用


※增加到資料庫

DROP TABLE testnested PURGE;
CREATE TABLE testnested(
    PK NUMBER,
    NNAME VARCHAR2(10) NOT NULL,
    NEST xxx_nested,
    CONSTRAINT TESTNESTED_PK PRIMARY KEY(PK)
) NESTED TABLE NEST STORE AS PROJECTS_NESTED_TABLE;
    
CREATE OR REPLACE TYPE xxx_nested IS TABLE OF many_nested NOT NULL;

※先建一張表,然後增加到資料庫



※巢狀表自定型態的增刪改查

DECLARE
    xxx testnested%ROWTYPE;
BEGIN
    xxx.pk := 22;
    xxx.nname := 'animal';
    xxx.nest := xxx_nested(
        many_nested(30, 'duck', TO_DATE('1980-01-01', 'YYYY-MM-DD')),
        many_nested(40, 'tiger', TO_DATE('1990-12-31', 'YYYY-MM-DD'))
    );
    
    INSERT INTO testnested VALUES xxx;
    DBMS_OUTPUT.PUT_LINE('新增成功!');
END;
    
    
--查詢
SELECT * FROM TABLE (
    SELECT nest FROM testnested WHERE pk = 22
);
    
    
DECLARE
    ooo xxx_nested := xxx_nested(
        many_nested(30, 'lion', TO_DATE('2000-10-10', 'YYYY-MM-DD')),
        many_nested(40, 'tiger', TO_DATE('1990-12-31', 'YYYY-MM-DD'))
    );
BEGIN
    UPDATE testnested SET nname = 'zoo', nest = ooo WHERE PK = 22;
    DBMS_OUTPUT.PUT_LINE('修改成功!');
END;
    
    
DECLARE
BEGIN
    DELETE FROM testnested WHERE PK = 22;
    DBMS_OUTPUT.PUT_LINE('刪除成功!');
END;



※VARRAY

※宣告VARRAY

DECLARE 
    TYPE xxx_varray IS VARRAY(2) OF VARCHAR2(10); 
    xxx xxx_varray := xxx_varray('cat', 'dog');
BEGIN
    xxx(1) := 'elephant';
    
    FOR i IN xxx.FIRST..xxx.LAST LOOP
        DBMS_OUTPUT.PUT_LINE(xxx(i));
    END LOOP;
END;

※結果:
elephant
dog


※VARRAY的增刪改查

DECLARE 
    TYPE xxx_varray IS VARRAY(2) OF many_nested; 
    xxx xxx_varray := xxx_varray(
        many_nested(1, 'fruit', to_date('1990-10-10', 'yyyy-mm-dd')),
        many_nested(2, 'zoo', to_date('1999-05-07', 'yyyy-mm-dd'))
    );
BEGIN
    FOR i IN xxx.FIRST..xxx.LAST LOOP
        DBMS_OUTPUT.PUT_LINE(xxx(i).mid);
        DBMS_OUTPUT.PUT_LINE(xxx(i).mname);
        DBMS_OUTPUT.PUT_LINE(xxx(i).mdate || CHR(10));
    END LOOP;
END;

※結果:
1
fruit
10-OCT-90

2
zoo
07-MAY-99


巢狀表和VARRAY (DML 十五)

※巢狀表

巢狀表就是欄位模擬成table,所以不用join,可以提升效能,Oracle8以上才有,但其他資料庫沒有,所以斟酌使用



※宣告一個巢狀表並使用

CREATE OR REPLACE TYPE xxx_nested IS TABLE OF VARCHAR2(10) NOT NULL;
    
DROP TABLE testnested PURGE;
CREATE TABLE testnested(
    PK NUMBER,
    NNAME VARCHAR2(10) NOT NULL,
    NEST xxx_nested,
    CONSTRAINT TESTNESTED_PK PRIMARY KEY(PK)
) NESTED TABLE NEST STORE AS PROJECTS_NESTED_TABLE;
    
SELECT * FROM testnested;

※IS TABLE OF或AS都可以,但有些地方不能使用AS



※巢狀表的增刪改查

INSERT INTO testnested(PK, NNAME, NEST)VALUES(
    1, 'fruit', xxx_nested('banana', 'apple', 'blueberry')
);
    
INSERT INTO testnested VALUES(
    2, 'animal', xxx_nested('tiger', 'duck', 'elephant')
);
    
SELECT * FROM TABLE (
    SELECT nest FROM testnested WHERE pk = 2
);
    
UPDATE TABLE (
    SELECT nest FROM testnested WHERE pk = 2
) tn 
SET VALUE(tn) = 'cat'
WHERE tn.COLUMN_VALUE = 'tiger';
    
DELETE FROM TABLE(
    SELECT nest FROM testnested WHERE pk = 2
) tn 
WHERE tn.COLUMN_VALUE = 'cat';

※SELECT nest FROM testnested WHERE pk = 2沒辦法一個欄位一個值
Oracle SQL Developer看到的畫面是集合的值,不是一個欄位一個值

PL/SQL Developer看到的畫面是這樣
必須點「…」,才會跑出上面的畫面,所以使用上面的程式碼,可以直接顯示上面的畫面



※巢狀表自定型態

CREATE OR REPLACE TYPE many_nested AS OBJECT(
    mid number,
    mname varchar2(5),
    mdate date
);
    
CREATE OR REPLACE TYPE xxx_nested IS TABLE OF many_nested NOT NULL;
    
DROP TABLE testnested PURGE;
CREATE TABLE testnested(
    PK NUMBER,
    NNAME VARCHAR2(10) NOT NULL,
    NEST xxx_nested,
    CONSTRAINT TESTNESTED_PK PRIMARY KEY(PK)
) NESTED TABLE NEST STORE AS PROJECTS_NESTED_TABLE;

※自定一個many_nested,然後使用,create table 都和上面一樣



※巢狀表自定型態的增刪改查

INSERT INTO testnested(PK, NNAME, NEST)VALUES(
    1, 'fruit',
    xxx_nested(
        many_nested(10, 'A1', TO_DATE('1950-01-01', 'YYYY-MM-DD')),
        many_nested(20, 'A2', TO_DATE('1960-12-31', 'YYYY-MM-DD'))
    )
);
    
INSERT INTO testnested VALUES(
    2, 'animal',
    xxx_nested(
        many_nested(30, 'duck', TO_DATE('1980-01-01', 'YYYY-MM-DD')),
        many_nested(40, 'tiger', TO_DATE('1990-12-31', 'YYYY-MM-DD'))
    )
);
    
UPDATE TABLE (
    SELECT nest FROM testnested WHERE pk = 2
) tn
SET VALUE(tn) = many_nested(40, 'cat', TO_DATE('1990-12-31', 'YYYY-MM-DD'))
WHERE tn.MNAME = 'tiger';
    
DELETE FROM TABLE(
    SELECT nest FROM testnested WHERE pk = 2
) tn
WHERE tn.MNAME = 'cat';

※修改其中一個值還是要全部都打出來,比較麻煩,如果用如下的語法只有MNAME有值
UPDATE TABLE (
    SELECT nest FROM testnested WHERE pk = 2
) tn
SET VALUE(tn) = many_nested(
    (
        SELECT mid FROM TABLE (
            SELECT nest FROM testnested WHERE pk = 2
        ) where mname = 'cat'
    ),
    'cat',
    (
        SELECT mdate FROM TABLE (
            SELECT nest FROM testnested WHERE pk = 2
        ) where mname = 'cat'
    )
)
WHERE tn.MNAME = 'tiger';

※MID和MDATE莫名的變成空的

※在PL/SQL操作巢狀表和VARRAY看這篇



※VARRAY

和巢狀表差不多,但可以限制長度



※VARRAY的宣告

CREATE OR REPLACE TYPE xxx_varray IS VARRAY(2) OF VARCHAR2(10);
    
CREATE TABLE TEST_VARRAY(
    pk NUMBER,
    vname VARCHAR(10),
    xxx xxx_varray,
    CONSTRAINT VARRAY_PK PRIMARY KEY (pk)
);

※宣告一個長度為2的VARRAY



※VARRAY的增刪改查

INSERT INTO test_varray VALUES (1, 'animal', xxx_varray('duck', 'tiger'));
INSERT INTO test_varray VALUES (2, 'fruit', xxx_varray('apple', 'banana'));
    
SELECT * FROM TABLE(
    SELECT xxx FROM test_varray WHERE pk = 2
);
    
UPDATE test_varray SET vname = 'car', xxx = xxx_varray('benz') WHERE pk = 2;
    
DELETE FROM test_varray WHERE pk = 2;

2016年2月8日 星期一

集合-陣列、物件 (PL/SQL 五)

※物件

CREATE TYPE xxx_object AS OBJECT (
    p1 varchar2(7),
    p2 number
);
--------------------
DECLARE 
    p xxx_object := xxx_object('default', 0);
BEGIN
    p.p1 := 'ppp';
    DBMS_OUTPUT.PUT_LINE(p.p1);
    DBMS_OUTPUT.PUT_LINE(p.p2);
END;

※物件裡放什麼類型都可以



※陣列


陣列就是定義一組相同的類型,如字串陣列(裡面都是字串)、數字陣列(裡面都是數字)

※宣告陣列

DECLARE 
    TYPE xxx_arrays IS TABLE OF VARCHAR(5) INDEX BY PLS_INTEGER;
    xxx xxx_arrays;
BEGIN
    xxx(-5) := 'a';
    xxx(0) := 'b';
    xxx(3) := 'c';
    
    DBMS_OUTPUT.PUT_LINE('xxx(-5)=' || xxx(-5));
    DBMS_OUTPUT.PUT_LINE('xxx(0)=' || xxx(0));
    DBMS_OUTPUT.PUT_LINE('xxx(3)=' || xxx(3));
    
    --ORA-01403: no data found
    --DBMS_OUTPUT.PUT_LINE('xxx(4)=' || xxx(4));
END;

※TABLE OF後面接類型,而INDEX BY後面接的是索引裡面的類型,要用數字不能用NUMBER(TABLE OF後還是可寫NUMBER),只能用PLS_INTEGER;而字串就寫STRING,官網的5-1表有寫

※如果沒有給index,又想印出來會出錯



※陣列預設值

DECLARE 
    TYPE xxx_arrays IS TABLE OF VARCHAR(5);
    xxx xxx_arrays := xxx_arrays('a','b','c');
BEGIN
    -- DBMS_OUTPUT.PUT_LINE('xxx(1)=' || xxx(1));
    -- DBMS_OUTPUT.PUT_LINE('xxx(2)=' || xxx(2));
    -- DBMS_OUTPUT.PUT_LINE('xxx(3)=' || xxx(3));
     
    FOR i IN xxx.FIRST..xxx.LAST LOOP
        DBMS_OUTPUT.PUT_LINE('xxx(' || i || ')=' || xxx(i));
    END LOOP;
END;

※不能寫「INDEX BY XXX」,預設是從1開始跑,所以可以跑迴圈


※EXISTS

DECLARE 
    TYPE xxx_arrays IS TABLE OF VARCHAR(5) INDEX BY PLS_INTEGER;
    xxx xxx_arrays;
BEGIN
    xxx(0) := 'a';
    xxx(1) := 'b';
    xxx(2) := 'c';
    
    FOR XXX IN 0..2 LOOP
        DBMS_OUTPUT.PUT_LINE(xxx);
    END LOOP;
    
    IF xxx.EXISTS(1) THEN
        DBMS_OUTPUT.PUT_LINE('存在=' || xxx(1));
    ELSE
        DBMS_OUTPUT.PUT_LINE('不存在!');
    END IF;
    
    FOR i IN 0..xxx.COUNT LOOP
        IF xxx.EXISTS(i) THEN
            DBMS_OUTPUT.PUT_LINE(xxx(i));
        END IF;
    END LOOP;
    
    LOOP
        EXIT WHEN NOT xxx.EXISTS(i);
        DBMS_OUTPUT.PUT_LINE(xxx(i));
        i := i + 1;
    END LOOP;
    
    WHILE xxx.EXISTS(i) LOOP
        DBMS_OUTPUT.PUT_LINE(xxx(i));
        i := i + 1;
    END LOOP;
END;

※結果:
0
1
2
存在!b

※使用FOR迴圈取出內容時,要配合EXITS,否則會報「ORA-01403:找不到資料」的錯

※其他方法可在官網找到



※%ROWTYPE

DECLARE 
    TYPE xxx_arrays IS TABLE OF DEPT%ROWTYPE INDEX BY VARCHAR2(5);
    xxx xxx_arrays;
BEGIN
    xxx('dept').dname := 'a';
    xxx('dept').loc := 'b';
    
    IF xxx.EXISTS('dept') THEN
        DBMS_OUTPUT.PUT_LINE('xxx(''dept'').dname=' || xxx('dept').dname);
        DBMS_OUTPUT.PUT_LINE('xxx(''dept'').loc=' || xxx('dept').loc);
        DBMS_OUTPUT.PUT_LINE('xxx(''dept'').deptno=' || xxx('dept').deptno);
    END IF;
END;

※結果:
xxx('dept').dname=a
xxx('dept').loc=b
xxx('dept').deptno=

※陣列裡可以是個%ROWTYPE,如果沒給值印出,不會報錯,是空的



※配合記錄類型一起使用

DECLARE 
    TYPE aaa is record (
        x dept.deptno%type,
        y dept.dname%type,
        z dept.loc%type
    );
    TYPE xxx_arrays IS TABLE OF aaa INDEX BY PLS_INTEGER;
    xxx xxx_arrays;
BEGIN
    xxx(0).y := 'aaa';
    xxx(0).z := 'bbb';
    
    IF xxx.EXISTS(0) THEN
        DBMS_OUTPUT.PUT_LINE('xxx(0).y=' || xxx(0).y);
        DBMS_OUTPUT.PUT_LINE('xxx(0).z=' || xxx(0).z);
        DBMS_OUTPUT.PUT_LINE('xxx(0).x=' || xxx(0).x);
    END IF;
END;

※結果:
xxx(0).y=aaa
xxx(0).z=bbb
xxx(0).x=

※記錄類型必須先宣告,不然陣列抓不到



※配合物件使用

CREATE TYPE xxx_object AS OBJECT (
    p1 varchar2(7),
    p2 number
);
--------------------
DECLARE 
    p xxx_object := xxx_object('default', 0);
    
    TYPE arrs IS TABLE OF xxx_object INDEX BY PLS_INTEGER;
    arr arrs;
BEGIN
    p.p1 := 'ppp';
    DBMS_OUTPUT.PUT_LINE(p.p1);
    DBMS_OUTPUT.PUT_LINE(p.p2);
    
    arr(0) := p;
    FOR i IN arr.first..arr.last LOOP
        DBMS_OUTPUT.PUT_LINE(arr(i).p1);
        DBMS_OUTPUT.PUT_LINE(arr(i).p2);
    END LOOP;
END;




※刪除

declare 
    TYPE xxx_arrays IS TABLE OF VARCHAR(5);
    xxx xxx_arrays := xxx_arrays('a','b','b','c');
begin
    DBMS_OUTPUT.PUT_LINE('xxx=' || xxx.count);
    FOR i IN xxx.FIRST..xxx.LAST LOOP
        IF xxx(i) = 'b' THEN
            DBMS_OUTPUT.PUT_LINE('i=' || i);
            xxx.delete(i);
        END IF;
    END LOOP;
    DBMS_OUTPUT.PUT_LINE('xxx=' || xxx.count);
end;


java 集合的 remove(),還有 javascript 陣列的 splice 在跑循環刪除時,如果有兩個以上連續的、資料一樣的元素,如上面的 b,此時判斷是 b 刪除會有問題,但在 PL/SQL 沒有這個問題

2016年2月7日 星期日

Hibernate 4.x 整合 Spring 3.x

Spring3.x和Hibernate4的整合只支援到 4.2(第4版的最後一版為4.3),整合方式和Hibernate3.x一樣,但Hibernate4不支援HibernateTemplate了,getCurrentSession也差不多,我只改兩個地方就可以run了

<bean id="sf"
    class="org.springframework.orm.hibernate4.LocalSessionFactoryBean">
    <!-- ... -->
</bean>
    
<bean id="txManagerHibernate"
    class="org.springframework.orm.hibernate4.HibernateTransactionManager">
    <!-- ... -->
</bean>

※其實就是將Hibernate3改成Hibernate4而已

※如果transactionManager使用JDBC的org.springframework.jdbc.datasource.DataSourceTransactionManager,Hibernate3.x整合Spring3.x可以使用getCurrentSession,而Hibernate4.2之前整合Spring3.x會出「No Session found for current thread」的錯,網路上說加什麼thread,我試沒有用,而JTA的我沒試過

雖然只是改成4,我還是將這次的pom檔列出來好了

pom.xml

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-core</artifactId>
    <version>3.2.13.RELEASE</version>
</dependency>
    
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
    <version>3.2.13.RELEASE</version>
</dependency>
    
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-orm</artifactId>
    <version>3.2.13.RELEASE</version>
</dependency>
    
<dependency>
    <groupId>commons-dbcp</groupId>
    <artifactId>commons-dbcp</artifactId>
    <version>1.4</version>
</dependency>
    
<dependency>
    <groupId>org.hibernate</groupId>
    <artifactId>hibernate-core</artifactId>
    <version>4.2.21.Final</version>
</dependency>



※applicationContext.xml和hibernate.cfg.xml都有的設定


applicationContext.xml

<context:annotation-config />
<context:component-scan base-package="dao.impl" />
    
<bean id="ds" class="org.apache.commons.dbcp.BasicDataSource"
    destroy-method="close">
    <property name="driverClassName" value="oracle.jdbc.driver.OracleDriver" />
    <property name="url" value="jdbc:oracle:thin:@127.0.0.1:1521:orcl" />
    <property name="username" value="username" />
    <property name="password" value="password" />
</bean>
    
<bean id="sf"
    class="org.springframework.orm.hibernate4.LocalSessionFactoryBean">
    <property name="dataSource" ref="ds" />
    <property name="configLocations" value="classpath:hibernate.cfg.xml" />
</bean>
    
<tx:annotation-driven transaction-manager="txManagerHibernate" />
    
<bean id="txManagerHibernate"
    class="org.springframework.orm.hibernate4.HibernateTransactionManager">
    <property name="sessionFactory" ref="sf" />
</bean>
    
<tx:advice id="xxx" transaction-manager="txManagerHibernate">
    <tx:attributes>
        <tx:method name="update*" propagation="REQUIRED" />
        <tx:method name="insert*" propagation="REQUIRED" />
        <tx:method name="edit*" propagation="REQUIRED" />
        <tx:method name="save*" propagation="REQUIRED" />
        <tx:method name="add*" propagation="REQUIRED" />
        <tx:method name="remove*" propagation="REQUIRED" />
        <tx:method name="delete*" propagation="REQUIRED" />
        <tx:method name="get*" propagation="REQUIRED" read-only="true" />
        <tx:method name="find*" propagation="REQUIRED" read-only="true" />
        <tx:method name="load*" propagation="REQUIRED" read-only="true" />
    </tx:attributes>
</tx:advice>
    
<aop:config>
    <aop:pointcut id="ooo" expression="execution(* dao.impl*.*(..))" />
    <aop:advisor advice-ref="xxx" pointcut-ref="ooo" />
</aop:config>

※如果還想針對特定的方法才執行transaction就要加tx:advice和aop:config,一般都是針對service,而我自己測試用,懶的寫service,所以直接跳到dao,可參考官網



pom.xml

<dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjweaver</artifactId>
    <version>1.8.4</version>
</dependency>

※加這個是因為aop會用到,而我沒有,只好下載了,spring3.x整合hibernate3.x和4.2之前的我都試過,都是OK的



hibernate.cfg.xml

<hibernate-configuration>
    <session-factory>
        <property name="hibernate.dialect">org.hibernate.dialect.OracleDialect</property>
        <property name="hibernate.show_sql">true</property>
        <property name="hibernate.format_sql">true</property>
        <property name="hibernate.use_sql_comments">true</property>
        <mapping resource="hbm/Dept.hbm.xml" />
    </session-factory>
</hibernate-configuration>

※hbm.xml原本是寫在LocalSessionFactoryBean裡面的mappingResources屬性,也可寫在這裡,兩者選其一即可