2016年2月7日 星期日

集合-記錄類型 (PL/SQL 四)

不用記錄類型的例子

DECLARE
    xxx_deptno DEPT.DEPTNO%TYPE;
    xxx_dname DEPT.DNAME%TYPE;
    xxx_loc DEPT.LOC%TYPE;
BEGIN
    SELECT DEPTNO, DNAME, LOC INTO xxx_deptno, xxx_dname, xxx_loc
    FROM DEPT
    WHERE DEPTNO = 40;
    
    DBMS_OUTPUT.PUT_LINE('xxx_deptno=' || xxx_deptno);
    DBMS_OUTPUT.PUT_LINE('xxx_dname=' || xxx_dname);
    DBMS_OUTPUT.PUT_LINE('xxx_loc=' || xxx_loc);
EXCEPTION
    WHEN OTHERS THEN
        RAISE_APPLICATION_ERROR(-20111, '找不到資料!');
END;

※如果有20個以上的欄位就要宣告20個變數,比較麻煩

※本來執行錯誤會直接跳EXCEPTION,而EXCEPTION用RAISE_APPLICATION_ERROR一樣會跳,只不過訊息的key和value變成自己打的



※宣告記錄類型

DECLARE
    TYPE xxx_record IS RECORD(
        a DEPT.DEPTNO%TYPE,
        b DEPT.DNAME%TYPE,
        c DEPT.LOC%TYPE
    );
    xxx_ooo xxx_record;
BEGIN
    SELECT DEPTNO, DNAME, LOC INTO xxx_ooo
    FROM DEPT
    WHERE DEPTNO = 40;
    
    DBMS_OUTPUT.PUT_LINE('xxx_deptno=' || xxx_ooo.a);
    DBMS_OUTPUT.PUT_LINE('xxx_dname=' || xxx_ooo.b);
    DBMS_OUTPUT.PUT_LINE('xxx_loc=' || xxx_ooo.c);
EXCEPTION
    WHEN OTHERS THEN
        RAISE_APPLICATION_ERROR(-20111, '找不到資料!');
END;

※宣告記錄類型後,再宣告一個變數來接收記錄類型

※這個例子也可以用第一篇的%ROWTYPE做到,只不過這裡較靈活,假設有20個欄位,這裡可以只使用5個(看需求),%ROWTYPE不行



※給記錄類型賦值

DECLARE
    TYPE xxx_record IS RECORD(
        a DEPT.DEPTNO%TYPE,
        b DEPT.DNAME%TYPE,
        c DEPT.LOC%TYPE := 'ccc'
    );
    xxx_ooo xxx_record;
BEGIN
    xxx_ooo.a := 50;
    
    DBMS_OUTPUT.PUT_LINE('xxx_deptno=' || xxx_ooo.a);
    DBMS_OUTPUT.PUT_LINE('xxx_dname=' || xxx_ooo.b);
    DBMS_OUTPUT.PUT_LINE('xxx_loc=' || xxx_ooo.c);
EXCEPTION
    WHEN OTHERS THEN
        RAISE_APPLICATION_ERROR(-20111, '找不到資料!');
END;


※結果:
xxx_deptno=50
xxx_dname=
xxx_loc=ccc

※可以在record賦值,也可以在外面賦值,沒賦值到的就是空(length=0)



※記錄類型裡的記錄類型

DECLARE
    TYPE xxx_record IS RECORD(
        a DEPT.DEPTNO%TYPE,
        b DEPT.DNAME%TYPE,
        c DEPT.LOC%TYPE
    );
    
    TYPE ooo_record IS RECORD(
        d EMP.EMPNO%TYPE,
        e EMP.ENAME%TYPE,
        f xxx_record
    );
    xxx_ooo ooo_record;
    ooo_xxx xxx_record;
BEGIN
    SELECT e.EMPNO, d.DNAME
    INTO xxx_ooo.d, ooo_xxx.b
    FROM DEPT d, EMP e
    WHERE d.DEPTNO = 40 AND e.EMPNO = 7369;
    
    DBMS_OUTPUT.PUT_LINE('empno=' || xxx_ooo.d);
    DBMS_OUTPUT.PUT_LINE('dname=' || ooo_xxx.b);
EXCEPTION
    WHEN NO_DATA_FOUND THEN
        DBMS_OUTPUT.PUT_LINE('找不到資料!');
    WHEN TOO_MANY_ROWS THEN
        DBMS_OUTPUT.PUT_LINE('找到太多資料!');
END;

※雖然很複雜,但應該還是看得懂



※新增

DECLARE
    TYPE xxx_record IS RECORD(
        a DEPT.DEPTNO%TYPE,
        b DEPT.DNAME%TYPE,
        c DEPT.LOC%TYPE
    );
    xxx_ooo xxx_record;
BEGIN
    xxx_ooo.a := 50;
    xxx_ooo.b := '業務部';
    xxx_ooo.c := '舊金山';
    
    INSERT INTO DEPT VALUES xxx_ooo;
    COMMIT;
    DBMS_OUTPUT.PUT_LINE('新增成功');
END;

※要注意順序要和資料庫一樣或者INTO()括號裡的順序要和宣告的順序一樣



※修改

DECLARE
    TYPE xxx_record IS RECORD(
        a DEPT.DEPTNO%TYPE,
        b DEPT.DNAME%TYPE,
        c DEPT.LOC%TYPE
    );
    xxx_ooo xxx_record;
BEGIN
    xxx_ooo.a := 70;
    xxx_ooo.b := '業務部';
    xxx_ooo.c := '舊金山';
    
    UPDATE DEPT SET ROW = xxx_ooo WHERE DEPTNO = 50;
    COMMIT;
    DBMS_OUTPUT.PUT_LINE('修改成功');
END;

※修改和SQL不太一樣,有個ROW可以用

例外 (PL/SQL 三)

※攔截例外


先看一個一定會死的例子
DECLARE 
    i INTEGER;
BEGIN
    i := 10/0;
END;

※結果:
※根據框起來的訊息,去官網找,01476會對應 Error Code ,並把前面的0拿掉,了解後增加如下的程式碼:

DECLARE 
    i INTEGER;
BEGIN
    i := 10/0;
    DBMS_OUTPUT.PUT_LINE('這行印不到');
EXCEPTION
    WHEN ZERO_DIVIDE THEN
        DBMS_OUTPUT.PUT_LINE('被除數不能是0');
        DBMS_OUTPUT.PUT_LINE('SQLCODE=' || SQLCODE);
        DBMS_OUTPUT.PUT_LINE('SQLERRM=' || SQLERRM);
END;

※結果:
被除數不能是0 SQLCODE=-1476 SQLERRM=ORA-01476: divisor is equal to zero

※ZERO_DIVEDE是針對這個程式使用的,如果不是這個例外,也就等於沒攔截到,那就是和沒寫是一樣的



DECLARE 
    i INTEGER;
BEGIN
    i := 'S';
    DBMS_OUTPUT.PUT_LINE('這行印不到');
EXCEPTION
    WHEN VALUE_ERROR THEN
        DBMS_OUTPUT.PUT_LINE('給值的型態或長度不正確');
        DBMS_OUTPUT.PUT_LINE('SQLCODE=' || SQLCODE);
        DBMS_OUTPUT.PUT_LINE('SQLERRM=' || SQLERRM);
END;

※結果:
給值的型態或長度不正確 SQLCODE=-6502 SQLERRM=ORA-06502: PL/SQL: numeric or value error: character to number conversion error



DECLARE 
    xxx_dname DEPT.DNAME%TYPE;
BEGIN
    SELECT DNAME INTO xxx_dname
    FROM DEPT
    WHERE DEPTNO = 90;
    
    DBMS_OUTPUT.PUT_LINE('這行印不到');
EXCEPTION
    WHEN NO_DATA_FOUND THEN
        DBMS_OUTPUT.PUT_LINE('找不到資料');
        DBMS_OUTPUT.PUT_LINE('SQLCODE=' || SQLCODE);
        DBMS_OUTPUT.PUT_LINE('SQLERRM=' || SQLERRM);
END;

※結果:
找不到資料 SQLCODE=100 SQLERRM=ORA-01403: no data found

※這個比較奇怪,錯誤的訊息不是100,是「no data found」,剛好裡面也有,所以我照打,還真的攔到了



DECLARE 
    xxx_dname DEPT.DNAME%TYPE;
BEGIN
    SELECT DNAME INTO xxx_dname
    FROM DEPT;
    
    DBMS_OUTPUT.PUT_LINE('這行印不到');
EXCEPTION
    WHEN TOO_MANY_ROWS THEN
        DBMS_OUTPUT.PUT_LINE('找到太多資料');
        DBMS_OUTPUT.PUT_LINE('SQLCODE=' || SQLCODE);
        DBMS_OUTPUT.PUT_LINE('SQLERRM=' || SQLERRM);
END;

※結果:
找到太多資料
SQLCODE=-1422
SQLERRM=ORA-01422: exact fetch returns more than requested number of rows

※以上的例子一個一個去查太累了,所以可以用others就一切掉定了,WHEN OTHERS THEN




※自定義例外

※沒有例外的訊息

DECLARE
    i NUMBER;
BEGIN
    DBMS_OUTPUT.PUT_LINE('SQLCODE=' || SQLCODE);
    DBMS_OUTPUT.PUT_LINE('SQLERRM=' || SQLERRM);
END;

※結果:
SQLCODE=0
SQLERRM=ORA-0000: normal, successful completion



※宣告例外類型

DECLARE
    i NUMBER := 6;
    xxx_exp EXCEPTION;
BEGIN
    IF i != 4 THEN
        RAISE xxx_exp;
    END IF;
EXCEPTION 
    WHEN xxx_exp THEN
        DBMS_OUTPUT.PUT_LINE('SQLCODE=' || SQLCODE);
        DBMS_OUTPUT.PUT_LINE('SQLERRM=' || SQLERRM);
END;

※結果:
SQLCODE=1
SQLERRM=User-Defined Exception

※類型就是EXCEPTION,RAISE和java的throw類似



※自定例外代號

尤於上面的訊息太差了,有和沒有一樣,因為它的代號永遠都是1

DECLARE
    i NUMBER := 6;
    xxx_exp EXCEPTION;
    PRAGMA EXCEPTION_INIT(xxx_exp, -20111);
BEGIN
    IF i != 4 THEN
        RAISE xxx_exp;
    END IF;
EXCEPTION 
    WHEN xxx_exp THEN
        DBMS_OUTPUT.PUT_LINE('SQLCODE=' || SQLCODE);
        DBMS_OUTPUT.PUT_LINE('SQLERRM=' || SQLERRM);
END;

※結果:
SQLCODE=-20111
SQLERRM=ORA-20111:

※一定要叫PRAGMA,而自定的錯誤代碼是-20000 ~ -20999,看官網

※如果不是-20000 ~ -20999,例如-01476除零的例外,PRAGMA EXCEPTION_INIT(xxx_exp, -01476);,就會出如下的訊息:
SQLCODE=-1476
SQLERRM=ORA-01476: divisor is equal to zero
就好像變成除零例外的子類



※自定例外訊息

上面的程式碼,雖然解決了例外代號,但訊息仍然有和沒有一樣

DECLARE
    i NUMBER := 6;
    xxx_exp EXCEPTION;
    PRAGMA EXCEPTION_INIT(xxx_exp, -20111);
BEGIN
    IF i != 4 THEN
        RAISE_APPLICATION_ERROR(-20111, '自定義例外');
    END IF;
EXCEPTION 
    WHEN xxx_exp THEN
        DBMS_OUTPUT.PUT_LINE('SQLCODE=' || SQLCODE);
        DBMS_OUTPUT.PUT_LINE('SQLERRM=' || SQLERRM);
END;

※結果:
SQLCODE=-20111
SQLERRM=ORA-20111: 自定義例外

※例用RAISE_APPLICATION_ERROR就可以了,代號要和PRAGMA一樣



※簡化自定義例外訊息

DECLARE
    i NUMBER := 6;
    xxx_exp EXCEPTION;
BEGIN
    IF i != 4 THEN
        RAISE_APPLICATION_ERROR(-20111, '自定義例外');
    END IF;
EXCEPTION 
    WHEN OTHERS THEN
        DBMS_OUTPUT.PUT_LINE('SQLCODE=' || SQLCODE);
        DBMS_OUTPUT.PUT_LINE('SQLERRM=' || SQLERRM);
END;

※也就是省略了PRAGMA EXCEPTION_INIT(xxx_exp, -20111);,而EXCEPTION一定要用OTHERS就可以了,結果和上面的程式碼一樣

2016年2月6日 星期六

基本語法二(IF、迴圈、內部程序) (PL/SQL 二)

※IF條件判斷

DECLARE
    xxx_count number;
BEGIN
    SELECT COUNT(*) INTO xxx_count FROM DEPT;
    
    IF xxx_count < 5 THEN
        DBMS_OUTPUT.PUT_LINE('<5');
    END IF;
END;

※這是單純的IF,和java比起來,還要多THEN和END IF,ELSE要用下面的用法

-- IF ELSE
IF xxx_count < 3 THEN
    DBMS_OUTPUT.PUT_LINE('<3');
ELSE
    DBMS_OUTPUT.PUT_LINE('>3');
END IF;
    
-- IF ELSIF ELSE
IF xxx_count < 5 THEN
    DBMS_OUTPUT.PUT_LINE('<5');
ELSIF xxx_count > 5 THEN
    DBMS_OUTPUT.PUT_LINE('>5');
ELSE
    DBMS_OUTPUT.PUT_LINE('=5');
END IF;
    
-- 正則表達式
IF REGEXP_LIKE(xxx_count, '^\d+$') THEN
    DBMS_OUTPUT.PUT_LINE('是數字');
ELSE
    DBMS_OUTPUT.PUT_LINE('不是數字');
END IF;

※注意是「ELSIF」,不是「ELSEIF」哦

※有支援正則表達式,當然IF裡面也可以再判斷IF ELSE

※布林值:如 b boolean :=false;,除了用 if b = true then ...外,還可用
if not b then ...


等於:=
不等於有三種:!=、<>、^=
大於:>
小於:<
大於等於:>=
小於等於:<=
and、or 可一次判斷多個條件


※LOOP迴圈

DECLARE
    xxx number := 1;
BEGIN
    LOOP
        DBMS_OUTPUT.PUT_LINE(xxx);
        EXIT WHEN xxx = 5;
        xxx := xxx + 1;
    END LOOP;
END;

※如果不設EXIT WHEN會無限迴圈



※WHILE迴圈

WHILE xxx <= 5 LOOP
    DBMS_OUTPUT.PUT_LINE(xxx);
    xxx := xxx + 1;
END LOOP;

※想提早結束要用待會要介紹的EXIT和CONTINUE

※是END LOOP哦!不是END WHILE

※沒有什麼「xxx++」和「+:=」的語法


※FOR迴圈

FOR xxx IN 1..5 LOOP
    DBMS_OUTPUT.PUT_LINE(xxx);
END LOOP;
    
FOR xxx IN REVERSE 1..5 LOOP

※加上 REVERSE是後面開始印出

※IN 後面可下 SQL,但要用圓括號包起來

※是END LOOP哦!不是END FOR



※EXIT和CONTINUE

FOR xxx IN 1..10 LOOP
    IF xxx = 5 THEN
        EXIT;
    END IF;
    
    DBMS_OUTPUT.PUT_LINE(xxx);
END LOOP;

※也就是java的break和continue



※GOTO

FOR xxx IN 1..10 LOOP
    IF xxx = 5 THEN
        GOTO ooo;
    END IF;
    
    DBMS_OUTPUT.PUT_LINE(xxx);
END LOOP;
    
<<ooo>>
DBMS_OUTPUT.PUT_LINE('escape');

※C++也有GOTO,聽說被人用的亂七八槽的,所以java將它變成關鍵字,而且不能使用,一用就編譯錯誤,PL/SQL可以用

※注意<<ooo>>如果寫在FOR迴圈上會無限迴圈



※內部程序

DECLARE
    xxx number := 1;
BEGIN
    DECLARE
        xxx number := 0;
    BEGIN
        DBMS_OUTPUT.PUT_LINE(xxx);
    END;
    
    DBMS_OUTPUT.PUT_LINE(xxx);
END;

※也就是java的內部類別

※和java一樣,內外部如果都宣告一樣的變數名稱,那內部就是找到內部的; 外部就是找到外部的

※java還可以內抓外,外抓內,非常亂,所以開發時,常被禁止內部類別的寫法

基本語法一(宣告變數、型態) (PL/SQL 一)

※匯入假資料練習

DROP TABLE EMP  PURGE ;
DROP TABLE DEPT PURGE ;
DROP TABLE BONUS PURGE ;
DROP TABLE SALGRADE PURGE ;
    
CREATE TABLE DEPT (
    DEPTNO    NUMBER(2) CONSTRAINT PK_DEPT PRIMARY KEY,
    DNAME    VARCHAR2(14),
    LOC    VARCHAR2(13) 
);
    
CREATE TABLE EMP (
    EMPNO    NUMBER(4) CONSTRAINT PK_EMP PRIMARY KEY,
    ENAME    VARCHAR2(10),
    JOB    VARCHAR2(9),
    MGR    NUMBER(4),
    HIREDATE    DATE,
    SAL    NUMBER(7,2),
    COMM    NUMBER(7,2),
    DEPTNO    NUMBER(2) CONSTRAINT FK_DEPTNO REFERENCES DEPT
);
    
CREATE TABLE BONUS (
    ENAME    VARCHAR2(10),
    JOB    VARCHAR2(9),
    SAL    NUMBER,
    COMM    NUMBER
);
    
CREATE TABLE SALGRADE ( 
    GRADE    NUMBER,
    LOSAL    NUMBER,
    HISAL    NUMBER
);
    
INSERT INTO DEPT VALUES    (10,'ACCOUNTING','NEW YORK');
INSERT INTO DEPT VALUES (20,'RESEARCH','DALLAS');
INSERT INTO DEPT VALUES    (30,'SALES','CHICAGO');
INSERT INTO DEPT VALUES    (40,'OPERATIONS','BOSTON');
    
INSERT INTO EMP VALUES (7369,'SMITH','CLERK',7902,TO_DATE('17-12-1980','DD-MM-YYYY'),800,NULL,20);
INSERT INTO EMP VALUES (7499,'ALLEN','SALESMAN',7698,TO_DATE('20-2-1981','DD-MM-YYYY'),1600,300,30);
INSERT INTO EMP VALUES (7521,'WARD','SALESMAN',7698,TO_DATE('22-2-1981','DD-MM-YYYY'),1250,500,30);
INSERT INTO EMP VALUES (7566,'JONES','MANAGER',7839,TO_DATE('2-4-1981','DD-MM-YYYY'),2975,NULL,20);
INSERT INTO EMP VALUES (7654,'MARTIN','SALESMAN',7698,TO_DATE('28-9-1981','DD-MM-YYYY'),1250,1400,30);
INSERT INTO EMP VALUES (7698,'BLAKE','MANAGER',7839,TO_DATE('1-5-1981','DD-MM-YYYY'),2850,NULL,30);
INSERT INTO EMP VALUES (7782,'CLARK','MANAGER',7839,TO_DATE('9-6-1981','DD-MM-YYYY'),2450,NULL,10);
INSERT INTO EMP VALUES (7788,'SCOTT','ANALYST',7566,TO_DATE('19-04-1987','DD-MM-YYYY')-85,3000,NULL,20);
INSERT INTO EMP VALUES (7839,'KING','PRESIDENT',NULL,TO_DATE('17-11-1981','DD-MM-YYYY'),5000,NULL,10);
INSERT INTO EMP VALUES (7844,'TURNER','SALESMAN',7698,TO_DATE('8-9-1981','DD-MM-YYYY'),1500,0,30);
INSERT INTO EMP VALUES (7876,'ADAMS','CLERK',7788,TO_DATE('23-05-1987','DD-MM-YYYY')-51,1100,NULL,20);
INSERT INTO EMP VALUES (7900,'JAMES','CLERK',7698,TO_DATE('3-12-1981','DD-MM-YYYY'),950,NULL,30);
INSERT INTO EMP VALUES (7902,'FORD','ANALYST',7566,TO_DATE('3-12-1981','DD-MM-YYYY'),3000,NULL,20);
INSERT INTO EMP VALUES (7934,'MILLER','CLERK',7782,TO_DATE('23-1-1982','DD-MM-YYYY'),1300,NULL,10);
    
INSERT INTO SALGRADE VALUES (1,700,1200);
INSERT INTO SALGRADE VALUES (2,1201,1400);
INSERT INTO SALGRADE VALUES (3,1401,2000);
INSERT INTO SALGRADE VALUES (4,2001,3000);
INSERT INTO SALGRADE VALUES (5,3001,9999);
    
COMMIT;



※最簡單的PL/SQL

DECLARE
    
BEGIN
    NULL;
END;
/

※大小寫沒差

※NULL一定要有,不然會出錯,所以才叫最簡單的PL/SQL

※在command(也就是dos)和PL/SQL Developer有些版本要加最後一行的「/」,而官方的Oracle SQL Developer不用

※單行註解用「--」; 多行註解用「/*」「*/」包起來

※我用PL/SQL Developer中文出現亂碼,我在環境變數加「NLS_LANG=AMERICAN_AMERICA.ZHT16MSWIN950」就解決了



※宣告變數

DECLARE
    i NUMBER;
BEGIN
    i := 3;
    DBMS_OUTPUT.PUT_LINE('i=' || i);
END;

※和java相反,要先宣告變數名稱,後面接型態

※給值用「:=」,可以在DECLARE裡,也可以在BEGIN裡

※相連用「||」,不是「+」哦!

※DBMS_OUTPUT.PUT_LINE的功能是在控制台輸出訊息,和java的System.out.println一樣

※而DBMS_OUTPUT.PUT和java的System.out.print類似,除了沒換行以外,它還要輸入NEW_LINE或PUT_LINE才會輸出,可參考第十六篇的DBMS_OUTPUT包



※NOT NULL 和 CONSTANT

i NUMBER NOT NULL := 3;
    
j CONSTANT NUMBER := 4;

※如果是 NOT NULL 後面一定要給值,寫在 BEGIN 不行,但如果都寫那就表示BEGIN會覆蓋DECLARE的值

※CONSTANT 和 NOT NULL 很像,後面一定要給值,但後面不能再覆蓋,而 CONSTANT 和javascript 的 const 一樣,第一次就要給值了
而 java 的 final,第一次可以不給值,給值只能給一次

※數字一般都是用NUMBER;字串用CHAR、VARCHAR2、NCHAR、NVARCHAR2; 日期用DATE、布林用BOOLEAN

※NUMBER 還可以寫 NUMBER(5, 2),表示小數點有兩位,全部有 5 位,所以整數位就是 3 位,「.」不算

※VARCHAR 和 VARCHAR2 目前一樣,大部分都習慣用 VARCHAR2

※CHAR(3)表示存3位,如果少於3位,後面會是空格,也就是長度永遠都是3,假設給java處理,又不想要空格,就要trim()掉,或用SQL的rtrim(); 但如果是這種需求,其實用VARCHAR2就好了,會自動幫我們去掉

※NCHAR或NVARCHAR2,多個N表示非ASCII和ASCII長度都一樣,假設在CHAR裡,「一」這個中文字,可能長度是3,「a」長度是1,用N的話就都是3,也就是不管什麼字都能統一它的長度,但缺點是會浪費空間,所以如果確定都會存ASCII,就用CHAR或VARCHAR2

※上面講的是常用的,還有很多類型,可參考官網



※%TYPE

DECLARE
    xxx_deptno DEPT.DEPTNO%TYPE;
    xxx_dname DEPT.DNAME%TYPE;
BEGIN
    xxx_deptno := &pk;  
    
    SELECT DNAME INTO xxx_dname 
    FROM DEPT 
    WHERE DEPTNO = xxx_deptno;
    
    DBMS_OUTPUT.PUT_LINE('pk是' || xxx_deptno || ',部門是' || xxx_dname);
END;

※也就是DEPT.DEPTNO的型態是什麼,xxx_deptno的型態就是什麼,xxx_dname一樣

※在PL/SQL裡要用「INTO」將資料庫的欄位給在這裡宣告的變數

※&pk的pk是隨便打的臨時變數,而「&」會在執行時跳出一個視窗讓我們打(想看更詳細的請看這篇),PL/SQL Developer不支援,要在官方的Oracle SQL Developer試,如以下的畫面:

※但預設在Oracle SQL Developer看不到執行的結果,要如下的設定


※執行完PL/SQL,在命令檔輸出,只會出現「匿名區塊已完成」,表示成功,而失敗也會在這裡出現錯誤訊息

※而一般的SQL的結果會出現在「查詢結果」的活頁標籤,沒有PL/SQL的結果,所以要如上設定

※叫出「DBMS輸出」後,選左上角的「+」,會出現上面的「選取連線」,然後選一位使用者,選完會在左下角顯示目前的使用者,表示成功了,而再次執行PL/SQL,就會在這裡看到訊息了



※%ROWTYPE

DECLARE
    xxx_dept DEPT%ROWTYPE;
BEGIN
    
    SELECT * INTO xxx_dept 
    FROM DEPT 
    WHERE DEPTNO = 40;
    
    DBMS_OUTPUT.PUT_LINE('pk是' || xxx_dept.deptno || ',部門是' || xxx_dept.dname);
END;

※DEPT%ROWTYPE就是DEPT表的一條記錄,變成一個型態給xxx_dept接收

※透過「*」給xxx_dept就是一條記錄,輸出時就「.」下去就可以了



※子類型

DECLARE
    SUBTYPE ooo IS NUMBER(3, 2);
    xxx ooo := 1.23;
BEGIN
    DBMS_OUTPUT.PUT_LINE('xxx是' || xxx);
END;

※就是是自己定義一個型態,如ooo是數字,而xxx是一個ooo的型態,也就是xxx就是數字,這個我不知道能做什麼,可看官網



※宣告型態


※宣告 integer 和 number 時可以不給長度;但 varchar 和 varchar2 一定要給

※宣告 integer,但賦值時給小數,會自動四捨五入到整數

※宣告 number(5,2),表示小數點 2 位,全部有 5 位(不包括「.」),所以整數位為 5 - 2 = 3

※宣告 number 時,若賦值時小數點超過會自動四捨五入;但整數超過會直接報錯

2016年2月5日 星期五

Oracle SQL和PL/SQL 官方文件

官方網址


1.照著做,沒什麼好說的



2.選擇活頁標籤的Documentation,然後看你要看哪一版的點進去



3.我點的是12.1的,不管哪一版,左邊都會有Application Development



4.中間會有個標題叫SQL and PL/SQL,一般SQL都是看SQL Language Reference,裡面的Function是大部分的人會看的; 而PL/SQL語法要看PL/SQL Language Reference,包的使用要看PL/SQL Packages and Types Reference

※左邊連結是在線上觀看,右邊是下載










2016年2月4日 星期四

MyBatis 3.x 整合 Spring 3.x (基本設定)

官方網站是多語系的

pom.xml

<dependency>
    <groupId>org.mybatis</groupId>
    <artifactId>mybatis</artifactId>
    <version>3.3.0</version>
</dependency>
    
<dependency>
    <groupId>org.mybatis</groupId>
    <artifactId>mybatis-spring</artifactId>
    <version>1.2.3</version>
</dependency>
    
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</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.springframework</groupId>
    <artifactId>spring-jdbc</artifactId>
    <version>3.2.13.RELEASE</version>
</dependency>

※下載spring-jdbc是因為我執行出現找不到類別的問題,所以下載這個包



※Annotation設定

applicationContext.xml

<context:component-scan base-package="\" />
    
<bean id="dataSource" 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="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
    <property name="dataSource" ref="dataSource" />
</bean>
    
<bean id="dao" class="org.mybatis.spring.mapper.MapperFactoryBean">
    <property name="mapperInterface" value="dao.IDeptDAO" />
    <property name="sqlSessionFactory" ref="sqlSessionFactory" />
</bean>



IDeptDAO.java

public interface IDeptDAO {
    @Select("SELECT * FROM DEPT")
    public List<Dept> findAll();
    
// @Select("SELECT * FROM DEPT WHERE DEPTNO = #{pk}")
// public Dept findByPK(@Param("pk") int deptno);
//     
// @Insert("INSERT INTO DEPT(DEPTNO, DNAME, LOC) VALUES (60, 'aaa', 'bbb')")
// public void insert();
//     
// @Update("UPDATE DEPT SET DNAME = 'AAA', LOC = 'BBB' WHERE DEPTNO = 60")
// public void update();
//     
// @Delete("DELETE FROM DEPT WHERE DEPTNO = 60")
// public void delete();
}



測試類

ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
    
for (String s : ctx.getBeanDefinitionNames()) {
    System.out.println(s);
}
    
IDeptDAO mfb = (IDeptDAO) ctx.getBean("dao");
List<Dept> list = mfb.findAll();
for (Dept dept : list) {
    System.out.println("deptNo=" + dept.getDeptNo());
    System.out.println("dName=" + dept.getdName());
    System.out.println("loc=" + dept.getLoc());
    System.out.println();
}
    
ctx.close();

※因為有sqlSessionFactory,所以dao可以直接強轉

※在 Service 寫 private IDeptDAO xxx; 然後 setter,最後上面放注入的 annotation 即可抓到


※XML設定

DeptDaoImpl.java

@Repository
public class DeptDaoImpl implements IDeptDAO {
    @Autowired
    public SqlSessionTemplate sqlSession;
    
    public List<Dept> findAll() {
        return sqlSession.selectList("xxx.getDept");
    }
}

※XML設定比較麻煩,要多個實作類別,還要有設定檔(mybatis-config.xml)或mapping檔(Dept.xml)


applicationContext.xml

<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
    <property name="dataSource" ref="dataSource" />
    <!-- <property name="configLocation" value="classpath:mybatis-config.xml" /> -->
    <property name="mapperLocations" value="classpath:Dept.xml" />
</bean>
    
<bean id="dao" class="org.mybatis.spring.SqlSessionTemplate">
    <constructor-arg index="0" ref="sqlSessionFactory" />
</bean>

※將dao換成SqlSessionTemplate,當然dataSource還是要,因為沒變就不寫了

※sqlSessionFactory增加屬性,configLocation和mapperLocations大部分的情況選其一即可,因為mybatis-config.xml可以如下設定:
<mappers>
    <mapper resource="Dept.xml" />
</mappers>

※因為目前的設定還不需要,所以我選擇用mapperLocations就夠了



Dept.xml

<mapper namespace="xxx">
    <select id="getDept" resultType="vo.Dept">
        SELECT * FROM DEPT
    </select>
</mapper>

※DeptDaoImpl.java的字串要namespace.id,和沒整合前一樣,沒什麼特別


測試類

IDeptDAO mfb = (IDeptDAO) ctx.getBean("deptDaoImpl");

※getBean就選新加的class就搞定了

2016年2月2日 星期二

log4j1 簡易教學

參考文件API,基本上只有API有幫助,我大部分還是在網路上找一些神人的文章試著用,也不知道為什麼那一些神人知道要這樣寫,反正可以run就是了

這是目前找到較完整的網站

pom.xml

<dependency>
    <groupId>log4j</groupId>
    <artifactId>log4j</artifactId>
    <version>1.2.17</version>
</dependency>



※語法

App.java

public class App {
    static Logger logger = Logger.getLogger("1234");
    
    public static void main(String[] args) {
        String p = 
        "c- %c %n" + 
        "C- %C %n" +
        "d- %d %n" + 
        "F- %F %n" + 
        "l- %l %n" + 
        "L- %L %n" + 
        "m- %m %n" +
        "M- %M %n" +
        "p- %p %n" +
        "r- %r %n" + 
        "t- %t %n" +
        "x- %x %n" +
        "X- %X %n" +
        "%%- %% %n" +
        "[%5c] %n" +
        "[%3c] %n" +
        "[%-5c] %n" +
        "[%-3c] %n" +
        "[%.3c] %n" +
        "[%5.3c] %n" +
        "[%5.4c] %n" +
        "[%-5.3c] %n" +
        "[%-5.4c] \n%n";
    
        PatternLayout pl = new PatternLayout(p);
        ConsoleAppender ca = new ConsoleAppender(pl);
        logger.addAppender(ca);
    
        logger.setLevel(Level.FATAL);
        // ALL(6) < TRACE(6) < DEBUG(5) < INFO(4) < WARN(3) < ERROR(2) < FATAL(1) < OFF(0)
    
        logger.trace("TRACE(6)");
        logger.debug("DEBUG(5)");
        logger.info("INFO(4)");
        logger.warn("WARN(3)");
        logger.error("ERROR(2)");
        logger.fatal("FATAL(1)");
    }
}

※結果:
c- 1234
C- SpringTest.log4j.App
d- 2016-02-02 15:45:44,219
F- App.java
l- SpringTest.log4j.App.main(App.java:53)
L- 53
m- FATAL(1)
M- main
p- FATAL
r- 0
t- main
x-
X- {}
%- %
[ 1234]
[1234]
[1234 ]
[1234]
[234]
[234]
[ 1234]
[234]
[1234 ]

※setLevel下面註解的圓括號的數字的意思是說,假設我設定WARN,那WARN層級以下(包括自己)會顯示,如最下面的六行,trace、debug、info雖然有寫,但不會顯示,如果setLevel不寫就等同OFF,不會顯示

※二個「%」等於一個%,如果少打會出類似「log4j:ERROR Unexpected char [ ] at position 107 in conversion patterrn.」的錯



※設定檔

要放在src下,但如果是maven,就要放在src/main/java下,有兩個檔案,二擇一即可


log4j.properties

# Set root logger level to DEBUG and its only appender to A1.
log4j.rootLogger=DEBUG, A1
    
# A1 is set to be a ConsoleAppender.
log4j.appender.A1=org.apache.log4j.ConsoleAppender
    
# A1 uses PatternLayout.
log4j.appender.A1.layout=org.apache.log4j.PatternLayout
log4j.appender.A1.layout.ConversionPattern=%-4r [%t] %-5p %c %x - %m%n



log4j.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE log4j:configuration SYSTEM "log4j.dtd">
<log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/">
    <appender name="console" class="org.apache.log4j.ConsoleAppender">
        <param name="Target" value="System.out" />
        <layout class="org.apache.log4j.PatternLayout">
            <param name="ConversionPattern" value="%-5p %c{1} - %m%n" />
        </layout>
    </appender>
    
    <root>
        <priority value="debug" />
        <appender-ref ref="console" />
    </root>
</log4j:configuration>



App.java

// PropertyConfigurator.configure("src/main/java/log4j.properties");
    
logger.trace("TRACE(6)");
logger.debug("DEBUG(5)");
logger.info("INFO(4)");
logger.warn("WARN(3)");
logger.error("ERROR(2)");
logger.fatal("FATAL(1)");

※如果設定檔不想放在預設地點,或者不想叫log4j,可以打開註解,但xml我試過沒用,還沒找到辦法



※輸出到指定地點

FileAppender fa = null;
try {
    fa = new FileAppender(new SimpleLayout(), "log4j1.txt", false);
} catch (IOException e) {
    e.printStackTrace();
}
    
logger.addAppender(fa);
logger.setLevel(Level.FATAL);
    
logger.trace("TRACE(6)");
logger.debug("DEBUG(5)");
logger.info("INFO(4)");
logger.warn("WARN(3)");
logger.error("ERROR(2)");
logger.fatal("FATAL(1)");

※log4j1會放在workspace\專案名稱下,或者直接打D:也是可以的,最後的boolean是說,如果已經有檔案存在,要不要覆寫