2016年3月27日 星期日

分析函數一 (OVER) (DML 六)

官網
彙總函數OVER(xxx)的語法,彙總函數之前有介紹到,這裡主要說明OVER後面的語法,彙總函數OVER(xxx)是個整體,xxx一改,彙總函數也會不一樣

※彙總函數和欄位一起顯示

SELECT sum(sal) s, e.* FROM emp e;
SELECT sum(sal) OVER() s, e.* FROM emp e;

※結果:

※第一個SELECT會發生錯誤,因為彙總函數不能和欄位一起顯示

※使用上一篇的 GROUP BY 不能顯示其他的欄位,所以用分析函數

※GROUP BY 因為其他的欄位都不一樣,所以不知道要顯示什麼?這裡用分析函數最前面是sum,所以它會加總,然後每一行都一樣



※分區

SELECT deptno, sum(sal) FROM emp GROUP BY deptno ORDER BY deptno;
SELECT distinct sum(sal) OVER(PARTITION BY deptno) s, e.deptno FROM emp e;
SELECT sum(sal) OVER(PARTITION BY deptno) s, e.sal, e.deptno FROM emp e;

※結果:

※第一和第二個SELECT結果一樣,但如果想顯示其他的欄位,就只好用分析函數了



※分區多個欄位

SELECT sum(sal) OVER(PARTITION BY deptno, job) s, e.sal, e.job, e.deptno FROM EMP e;

※結果:

※會以deptno分區完後,再分區job,注意順序

※注意改成兩個分區後,會以兩個分區加總,看S欄位的變化



※分區排序

SELECT sum(sal) OVER(PARTITION BY deptno ORDER BY ename) s, e.sal, e.ename, e.deptno FROM EMP e;

※結果:


※可看出ename由小到大排序了(ASC),也可以 ORDER BY 多個欄位

※因為此範例 ENAME 沒有重覆值,所以S欄位,每一個都會累加

※以 DEPTNO 排序會有重覆值,如下:
SELECT sum(sal) OVER(ORDER BY deptno) s, e.sal, e.deptno FROM emp e;

※結果:

※這時會以DEPTNO為單位加總,和上面的分區結果圖對照(第二張圖),會發現這裡的語法沒有PARTITION BY,所以不會分區,而S欄位一開始會以10部門加總,這時看起來一樣,但第二次會將20部門加總再加上10部門的總和,這時就和第二張圖不一樣了,然後依此類推



※NULL顯示在前/後

SELECT sum(sal) OVER(ORDER BY comm NULLS FIRST) s, e.sal, e.comm, e.deptno FROM emp e;

※結果:

※加上NULLS FIRST會將有null的放在最前面,而NULLS LAST會放在最後面,如果都不打,等同NULLS LAST



※向上範圍

關鍵字是PRECEDING


SELECT sum(sal) OVER(PARTITION BY deptno ORDER BY sal RANGE 300 PRECEDING) s, e.sal, e.deptno FROM emp e;

※結果:

※sal RANGE 300 PRECEDING,表示薪水欄位往上在300之內(含)才會加總,如果往上在300之內會一直往上判斷,直到沒有或300之外才結束判斷

※以20部門為例,看中間的薪水欄
.第一個800,往上沒有值,所以左欄還是800
.第二個1100,減第一個800是300,所以加總(中間欄往上是300之內都會加)
左欄變成1100 + 800 = 1900
.第三個2975,減第二個1100並沒有在300之內,所以左欄還是2975
注意因為是以sal排序,所以一樣的會是一組
而因為第四、五個的薪水都是3000,所以是一組
.第四、五個3000,減第三個2975在300之內,所以會加總(中間欄往上是300之內都會加),而且一次將第四、五個加起來,變成 3000 + 3000 + 2975 = 8975

※再練習一題,如果將RANGE改成350,會變成如下的狀況

※以30部門為例,看中間的薪水欄
.第一個950,往上沒有值,所以左欄還是950
.第二、三個是1250,減第一個950在350之內,所以加總(中間欄往上是350之內都會加)
左欄變成1250 + 1250 + 950 = 3450
.第四個1500,減第三個1250在350之內,所以加總(中間欄往上是350之內都會加),
左欄變成1500 + 1250 + 1250 = 4000
.第五個1600,減第四個1500在350之內,所以加總(中間欄往上是350之內都會加),
左欄變成1600 + 1500 + 1250 + 1250 = 5600(這個是上個範例沒有的,之所以會加1250是因為1600 - 350 = 1250,所以1250以上(含)都會加)
.第六個2850,減第五個1600,並沒有在350之內,所以左欄還是2850



※向下範圍

關鍵字是FOLLOWING
但並不是sum(sal) OVER(PARTITION BY deptno ORDER BY sal RANGE 350 FOLLOWING)

SELECT sum(sal) OVER(PARTITION BY deptno ORDER BY sal RANGE BETWEEN 0 PRECEDING AND 350 FOLLOWING) s, e.sal, e.deptno FROM emp e;
※結果:

※以30部門為例,看中間的薪水欄
.第一個950,看第二、三個是1250,在350之內,所以加總(中間欄往下是350之內都會加)
左欄變成950 + 1250 + 1250 = 3450
.第二、三個是1250,看第四個是1500,在350之內,所以加總(中間欄往下是350之內都會加)
左欄變成1250 + 1250 + 1500 + 1600 = 5600
.第四個1500,看第五個是1600,在350之內,所以加總(中間欄往上是350之內都會加),
左欄變成1500 + 1600 = 3100
.第五個1600,看第六個是2850,不在350之內,所以左欄還是1600
.第六個2850,往下沒有值,所以左欄還是2850



※向上加向下範圍

也就是向上和向下的合併

SELECT sum(sal) OVER(PARTITION BY deptno ORDER BY sal RANGE BETWEEN 300 PRECEDING AND 350 FOLLOWING) s, e.sal, e.deptno FROM emp e;

※結果:

※以30部門為例,看中間的薪水欄,此範例是往上300、往下350
.第一個950,往上沒有值,看第二、三個是1250,在350之內
左欄變成 950 + 1250 + 1250 = 3450

.第二、三個是1250,減第一個950在350之內,往上要加
看第四個是1500,在350之內,往下也要加
左欄變成 950 + 1250 + 1250 + 1500 + 1600 = 6550

.第四個1500,減第二、三個在300之內,往上要加
看第五個是1600,在350之內,往下也要加
左欄變成 1250 + 1250 + 1500 + 1600 = 5600

.第五個1600,減第四個在300之內,往上要加
看第六個是2850,不在350之內,往下不加
左欄變成 1500 + 1600 = 3100

.第六個2850,減第五個不在300之內,往上不加
往下沒有值,往下也不加
左欄還是2850


※當前行
SELECT sum(sal) OVER(PARTITION BY deptno ORDER BY sal RANGE CURRENT ROW) s, e.sal, e.deptno FROM emp e;
SELECT sum(sal) OVER(PARTITION BY deptno ORDER BY sal RANGE BETWEEN 0 PRECEDING AND CURRENT ROW) s, e.sal, e.deptno FROM emp e;
SELECT sum(sal) OVER(PARTITION BY deptno ORDER BY sal RANGE BETWEEN 300 PRECEDING AND CURRENT ROW) s, e.sal, e.deptno FROM emp e;

※結果:

※前二條SQL的執行結果都和上面一樣,只有相同的薪水才會加總

※第三條SQL的執行結果和向上範圍一樣



※無範圍


SELECT sum(sal) OVER(PARTITION BY deptno ORDER BY sal RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) s, e.sal, e.deptno FROM emp e;
SELECT sum(sal) OVER(PARTITION BY deptno ORDER BY sal) s, e.sal, e.deptno FROM emp e;

※結果:

※因為向上無範圍,所以和第二條SQL(分區排序)一樣

※如果向下也無範圍,如下:

SELECT sum(sal) OVER(PARTITION BY deptno ORDER BY sal RANGE BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING) s, e.sal, e.deptno FROM emp e;
SELECT sum(sal) OVER(PARTITION BY deptno) s, e.sal, e.deptno FROM emp e;

※那結果就和第二條SQL差不多(分區),只差在沒排序



※以行當範圍


SELECT sum(sal) OVER(PARTITION BY deptno ORDER BY sal ROWS 2 PRECEDING) s, e.sal, e.deptno FROM emp e;

※結果:

※此範例是當前行和往上2行薪水的加總,這時並沒有薪水一樣會一起加的問題

2016年3月25日 星期五

分組、子查詢、WITH、自定排序、串接 (DML 五)

※分組(GROUP BY)

分組以emp這張table最好講,就是以部門為單位,看每個部門的最高、最低、平均、加總…等


※彙總函數(Aggregate Functions)

SELECT
    count(sal),
    max(sal),
    min(sal),
    sum(sal),
    round(avg(sal)),
    median(sal)
FROM emp;

※彙總函數就是將同一個欄位的所有資料加起來,然後做最高、最低、平均、加總…等,上面的round不是彙總函數,只是因為小數位太長了,還可以配合DISTINCT使用(median除外)

※如上的範例,彙總函數可以不用分組

※median是取中間數,如有15列,會取第7列;如有16列,會取第8和第9列再除2

※還有很多彙總函數,看官網(最下面)



※分組

SELECT
    -- deptno,
    count(sal),
    max(sal),
    min(sal),
    sum(sal),
    round(avg(sal)),
    median(sal)
FROM emp
-- WHERE sal > 500
GROUP BY deptno
-- HAVING deptno > 10
-- ORDER BY deptno DESC;

※分組就是 GROUP BY,注意 WHERE、GROUP BY、ORDER BY 的順序

※HAVING 是針對 GROUP BY的過濾條件,就像WHERE是針對表的過濾一樣

※註解的部分都可以用,但要注意要顯示的欄位和排序(ORDER BY),只能顯示GROUP BY的欄位

因為彙總函數是好幾個欄位加起來的,如果打其他的欄位,它不知道要怎麼顯示和排序
而GROUP BY的欄位比較例外,因為以它分組,那它的值一定都一樣

但是HAVING、ORDER BY 如果配合彙總函數就可以使用其他欄位,如下:
HAVING SUM(sal) > 500 --本來只能配合GROUP BY的欄位使用
ORDER BY SUM(mgr) DESC--本來只能配合GROUP BY的欄位使用
因為這時所有欄位合在一起,當然只剩一個值,所以就都OK了

※GROUP BY還可以很多欄位,而且也可以配合join使用



※彙總函數裡的彙總函數

SELECT
    max(round(avg(sal)))
FROM emp
-- WHERE sal > 500
GROUP BY deptno
ORDER BY sal DESC;

※彙總函數裡放彙總函數,叫嵌套,但要使用嵌套,只能顯示一個欄位,不能再打其他的欄位和彙總函數,而且一定要GROUP BY(沒有嵌套可以不用)

※ORDER BY這時又可以使用其他欄位了(分組不行)



※rollup、cube 的使用

SELECT d.deptno, sum(sal), ROUND(avg(sal), 2), min(sal), max(sal), count(sal)
from emp e join dept d on(e.deptno = d.deptno)
group by rollup(d.deptno)
order by d.deptno;
    
SELECT d.deptno, sum(sal), ROUND(avg(sal), 2), min(sal), max(sal), count(sal)
from emp e join dept d on(e.deptno = d.deptno)
group by cube(d.deptno)
order by d.deptno;

※結果:

※兩支SQL就只差在 group by 的不同而已

※會增加一行,配合彙總函數而有所不同

※尤於group by只有一個欄位,所以此時 rollup、cube 沒什麼不同



※rollup、cube 的差異

SELECT d.deptno, job, sum(sal), ROUND(avg(sal), 2), min(sal), max(sal), count(sal)
from emp e join dept d on(e.deptno = d.deptno)
group by rollup(d.deptno, job)
order by d.deptno;
    
SELECT d.deptno, job, sum(sal), ROUND(avg(sal), 2), min(sal), max(sal), count(sal)
from emp e join dept d on(e.deptno = d.deptno)
group by cube(d.deptno, job)
order by d.deptno;

※結果:

※此時會發現 cube 還會多出綠框的部分
因為group多一個job,所以 job 也會做進一步的分析,但要注意此時的 job 並沒有按照部門分類

※圖少打了一個字,是cube,不是cub


※Rollup、Cube 的意思

rollup是捲起來的意思,這裡指的是從右到左捲起來,為n+1
cube是立方的意思,這裡指的是2的x次方
假設rollup(c1, c2)=>2+1,所以要union all 3次
也就是c1, c2 union all c1 union all null

假設cube(c1, c2)=>2的2次方,所以要union all 4次
也就是c1 union all c2 union all c1, c2 union all null

※rollup 和 union all

SELECT d.deptno, job, sum(sal)
from emp e join dept d on(e.deptno = d.deptno)
group by rollup(d.deptno, job)
order by d.deptno;
    
Select d.deptno, job, sum(sal) from emp e join dept d on(e.deptno = d.deptno) group by d.deptno, job
    union all
Select d.deptno, null,sum(sal) from emp e join dept d on(e.deptno = d.deptno) group by d.deptno
    union all
Select null, null,sum(sal) from emp e join dept d on(e.deptno = d.deptno);

※這兩條SQL是一樣的,只是union all 排序不好排

※cube 和 union all

SELECT d.deptno, job, sum(sal)
from emp e join dept d on(e.deptno = d.deptno)
group by cube(d.deptno, job)
order by d.deptno;
    
Select d.deptno, null, sum(sal) from emp e join dept d on(e.deptno = d.deptno) group by d.deptno
    union all
Select null, job, sum(sal) from emp e join dept d on(e.deptno = d.deptno) group by job
    union all
Select d.deptno, job, sum(sal) from emp e join dept d on(e.deptno = d.deptno) group by d.deptno, job
    union all
Select null, null,sum(sal) from emp e join dept d on(e.deptno = d.deptno)
order by deptno;

※這兩條SQL也是一樣的,只是union all 排序不好排



※GROUPING SETS

Group by 可以說是Grouping sets 的其中之一
grouping sets 裡面可以接收好多欄位,而 group by 等同 grouping sets 的一個參數的方法
只是一個參數而且有多個欄位時,要用圓括號包起來

多個參數表示第一個參數做完後,會在接上第二個參數的group by,
然後再接第三個參數,一直接到最後一個參數
參數裡也可以加上小計的功能,就是 rollup、cube

一個SQL只能有一個GROUP BY ,但GROUPING SETS可以多個
所以還可以用group by xxx, grouping sets(...), grouping sets(...)


SELECT d.deptno, job, sum(sal)
from emp e join dept d on(e.deptno = d.deptno)
group by d.deptno, job
order by d.deptno;
    
SELECT d.deptno, job, sum(sal)
from emp e join dept d on(e.deptno = d.deptno)
group by grouping sets((d.deptno, job))
order by d.deptno;
    
SELECT d.deptno, job, sum(sal)
from emp e join dept d on(e.deptno = d.deptno)
group by grouping sets(d.deptno, job)
order by d.deptno;

※第一和第二個SQL完全一樣

※結果:

※上圖是第一和第二個SQL的結果

※第三個SQL會以第一個參數的欄位進行加總,然後再用第二個參數的欄位進行加總

※grouping sets(c1, c2, c3) 會對c1, c2, c3 group by,等同 group by c1, c2, c3

※grouping sets((c1, c2), c3) 會對(c1, c2) 做 group by,做完再加上 c3 的 group by





※GROUPING

rollup、cube因為是小計,所以沒有彙總的部分會變成空白,使用這個可以寫一些資訊,但也因為這樣,所以使用Grouping時,一定要有 rollup 或 cube

SELECT 
    d.deptno, 
    GROUPING(d.deptno) xxx, 
    decode(GROUPING(d.deptno), 1, '小計', d.deptno) deptno,
    sum(sal)
from emp e join dept d on(e.deptno = d.deptno)
group by cube(d.deptno)
order by d.deptno;

※結果:
※可以發現grouping回傳的是1和0,1表示有空白

※最左邊的deptno到第三列的deptno最下面有變化了



※使用 Grouping + cube 有兩個欄位時

SELECT 
    d.deptno,
    job,
    GROUPING(d.deptno) d,
    GROUPING(job) j, 
    decode(GROUPING(d.deptno), 1, '工作分類小計') xxx,
    decode(GROUPING(job), 1, '部門分類小計') ooo,
    decode(GROUPING(d.deptno), 
        0, decode(GROUPING(job), 1, '部門分類小計', d.deptno),
        1, decode(GROUPING(job), 1, '總小計', '工作分類小計')
    ) zzz,
    sum(sal)
from emp e join dept d on(e.deptno = d.deptno)
group by cube(d.deptno, job)
order by d.deptno;

※結果:

※GROUPING()裡只能放一個欄位

※D對應XXX;J對應OOO
但比較奇怪的是我的J寫的是GROUPING(job),OOO也是這麼寫,但如上圖,算的是部門的加總;D也是一樣,所以我才將J寫成部門分類小計;而D寫成工作分類小計

※ZZZ是兩個合併的寫法,因為兩個都是1,由D和J可以知道



※GROUPING_ID

SELECT 
    d.deptno,
    job,
    GROUPING_ID(d.deptno) AS dno,
    GROUPING_ID(job) AS job,
    GROUPING_ID(d.deptno, job) AS dno_and_job,
    GROUPING_ID(job, d.deptno) AS job_and_dno,
    sum(sal)
from emp e join dept d on(e.deptno = d.deptno)
group by cube(d.deptno, job)
order by d.deptno;

※結果:

※GROUPING_ID()裡面可放多個欄位

※數字怎麼來的和二進位有關,如下:

※如果是空的還是0,不是0才會做上圖的次方問題,而任何數的零次方都是1,然後全加起來,順序是有差的,所以dno_and_job 和 job_and_dno有點不太一樣



※GROUP_ID()

無參數
一筆紀錄的結果如有重覆會一直累加,從0開始


SELECT d.deptno, sum(sal), group_id()
from emp e join dept d on(e.deptno = d.deptno)
group by grouping sets(d.deptno, d.deptno)
-- having group_id() = 0
order by d.deptno;

※結果:

※加上group_id() = 0 可將重覆行去除



※子查詢

也就是查詢裡還有查詢
顯示的結果有一個欄位,但有好筆記錄,簡稱單欄多行


※SELECT 裡的子查詢

SELECT hiredate, (SELECT loc FROM dept WHERE deptno = 20) FROM emp;
    
SELECT 
    empno,
    ename,
    (
        SELECT grade FROM salgrade 
        WHERE e.sal BETWEEN losal AND hisal
    ) grade
FROM emp e
ORDER BY grade;

※在欄位裡的子查詢,結果只能是單欄單行



※FROM 裡的子查詢

SELECT * FROM (
    SELECT * FROM emp
);
    
SELECT * FROM dept d,(
    SELECT * FROM emp
) e
WHERE d.deptno = e.deptno;
------------------------------
------------------------------
SELECT * FROM dept d, emp e
WHERE d.deptno = e.deptno;
    
SELECT * FROM dept d /*INNER*/ JOIN emp e
ON(d.deptno = e.deptno);
    
SELECT * FROM dept d /*INNER*/ JOIN emp e
USING(deptno);
    
SELECT * FROM dept NATURAL JOIN emp;

※第二個SELECT,也可以是虛線下的四個SELECT任何一組,不懂join的可看第四篇

※這應該是子查詢裡最難理解的了,實際上在一般select的結果都是多欄多行(所以FROM裡的子查詢可以是單/多欄單/多行),而放在裡面的子查詢也是多欄多行,所以可以放在裡面,但一般不會像我的範例這樣用,因為已經有表的名稱了,就直接用就好了,大部分都是裡面的子查詢沒有表名稱或者因為效能問題才會使用,例如子查詢是join出來的,所以沒有表名稱,只好寫在裡面,欄位的名稱就是select的名稱,如果要自己取就用別名(as),可以說是個匿名table,如下:
SELECT grade,losal, hisal, ed.xxx, ed.dname
FROM salgrade s,(
    SELECT e.ename xxx, d.dname, e.sal s 
    FROM emp e, dept d
    WHERE d.deptno = e.deptno
) ed
WHERE
ed.s BETWEEN losal AND hisal;


※效能問題如下:
SELECT d.deptno, d.dname, count(*)
FROM dept d, emp e
WHERE d.deptno = e.deptno
GROUP BY d.deptno,d.dname;
    
SELECT d.deptno, d.dname, e.c
FROM dept d,(
    SELECT deptno,count(*) c
    FROM emp
    GROUP BY deptno
    ) e
WHERE d.deptno = e.deptno;

※EMP表有14條記錄,DEPT表有4條記錄

※第一個SELECT總共要查14*4=56次

※第二個SELECT總共要查dept表的4次*GROUP BY的結果3次+count的14次,所以總共是26次

※有工作經驗的都知道這資料量太少了,給它*100也不過分,
所以第一個SELECT:1400*400=560000
第二個SELECT:400*300+1400=121400
通過結果可以發現資料量越大,效能差的越多



※WHERE裡的子查詢

SELECT * FROM emp
where sal > (
    SELECT max(sal) FROM emp WHERE job = 'SALESMAN'
);
    
SELECT * FROM emp
where (job,sal,deptno,nvl(comm,0)) = (
    SELECT job,sal,deptno,nvl(comm,0) FROM emp where ename = 'FORD'
);

※第一個SELECT的子查詢結果是單欄單行,這應該最多人熟悉的使用方式了

※第二個SELECT的子查詢結果是多欄單行,要注意如果有null,它不會顯示任何結果,所以我加了NVL

※如果SELECT的子查詢結果是單欄多行,看第一篇的最下面有ANY/SOME/ALL



※HAVING裡的子查詢

SELECT
    deptno,
    count(sal),
    max(sal),
    min(sal),
    sum(sal),
    round(avg(sal)),
    median(sal)
FROM emp
WHERE sal > 500
GROUP BY deptno
HAVING max(sal) > (
    SELECT avg(sal) FROM emp
)
ORDER BY deptno DESC;



※WITH

子查詢寫在FROM是沒有名字的,所以叫匿名表,而WITH子句可以有名字,所以是虛擬表,如下使用:

WITH  V AS (
    SELECT * FROM EMP
)
SELECT * FROM  V, DEPT D WHERE V.DEPTNO = D.DEPTNO;

也可以用多張虛擬表格
WITH  V1 AS (
    SELECT * FROM EMP
),
V2 AS (
    SELECT * FROM DEPT
)
SELECT * FROM  V1, V2 WHERE V1.DEPTNO = V2.DEPTNO;

※注意AS一定要打,且「)」後面不能加「;」

※WITH裡面不能再放WITH



※自定排序

select * from (
    select 'a' as n from dual union all
    select 'b' as n from dual union all
    select 'c' as n from dual
)
order by decode(n, 'b', 1, 'a', 2, 'c', 3);
    
with xxx as(
    select 'a' as n from dual union all
    select 'b' as n from dual union all
    select 'c' as n from dual
) select n from xxx
ORDER BY n desc;

※可用子查詢和 WITH 的方式達成



※串接


listagg 可以實現串接的功能
語法如下:

SELECT LISTAGG(xxx, ':')
WITHIN GROUP (ORDER BY xxx) result
FROM (
    SELECT TO_CHAR(DATE '2017-01-01' + (LEVEL-1) , 'YYYY/MM/DD') xxx
    FROM DUAL
    CONNECT BY DATE '2017-01-01' + (LEVEL-1) <= DATE '2017-06-30'
);

※其中LISTAGG 的第二個參數不是必要的,如果不寫就會將全部的字連在一起

※WITHIN GROUP 本身,和後面的 ORDER BY 都是一定要的

※SELECT 只能放彙總函數

※後面還可加 OVER (PARTITION BY ...),我沒試過,是不是加了之後,就不一定要使用彙總函數了,有待驗證

2016年3月24日 星期四

log4j2 簡易教學

首先到官網的download下載,或者用 maven、gradle 也行


然後到 Configuration 來複製,也可以照裡面的教學,但我修改成如下:
public class App {
    private static final Logger logger = LogManager.getLogger(App.class);
    public static void main(final String... args) {
        logger.trace("a");
        logger.debug("b");
        logger.info("c");
        logger.warn("d");
        logger.error("e");
        logger.fatal("f");
    }
}

※注意 Logger 要 import 的包為 org.apache.logging.log4j.Logger

※排的順序是有原因的,logger有階層關係,看  architecture 中間偏下一點點有一張叫 LoggerConfig Level 的表,看直的

※執行後,console印出來如下:
ERROR StatusLogger No log4j2 configuration file found. Using default configuration: logging only errors to the console.
最後只有印出 e 和 f

※因為還沒設定,所以有警告,但已有預設值了,所以還是能輸出,但如果想用自己設定的,可在 classpath 放一個 xml 檔,叫 log4j2.xml,上面的 Configuration 連結有說明 Automatic Configuration,沒設時是走第10條規則,現在是用第9條的方式,內容可到 Configuration 複製(JSON、YAML、properties 也有範例),如下:

※log4j2.xml

<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="WARN">
    <Appenders>
        <Console name="Console" target="SYSTEM_OUT">
            <PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n" />
        </Console>
    </Appenders>
    <Loggers>
        <Root level="error">
            <AppenderRef ref="Console" />
        </Root>
    </Loggers>
</Configuration>

※Configuration 下有 Appenders 和 Loggers,因為 Root level 設成 error,所以 error 以下的才會印出來,Configuration 的 status 也有類似的東西,但是針對 log4j 內部的輸出,打 trace 就會印出很多,其他屬性可參考官網

※AppenderRef 的 ref 要對應 name 名稱,否則會報錯

※Appenders 有 20 多個可以用,官網都有貼範例,如 AsyncAppender,可以在 Appenders 下,增加 Async (不用打Appender) ,依此類推,至於屬性,每個表也都有

※假設想在 D 槽輸出日誌,可增加如下的設定
<Configuration status="WARN">
    <Appenders>
        <Console name="Console" target="SYSTEM_OUT">
            <PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n" />
        </Console>
    
        <File name="MyFile" fileName="D:/xxx.log">
            <PatternLayout>
                <Pattern>%d %p %c{1.} [%t] %m%n</Pattern>
            </PatternLayout>
        </File>
    </Appenders>
    
    <Loggers>
        <Root level="error">
            <AppenderRef ref="Console" />
            <AppenderRef ref="MyFile" />
        </Root>
    </Loggers>
</Configuration>

※AppenderRef 的 ref,可視情況增加或減少,如不想輸出到控制台,就註解

※會用附加的方式到檔案裡

※PatternLayout 的 pattern 可參考 Layouts 的 PatternLayout



※PatternLayout 格式

%d{HH:mm:ss,SSS}:時間,還有很多,看官網

%-5p:p 也可用 level 表示,格式有5格,加「-」是左邊對齊,不加是右邊,如右邊對齊如下:
DEBUG
 INFO

%t:執行的是哪一隻thread

%F:file name,檔案名稱

%L:line,行數

%m:訊息,就是我打的a~f,也可用 %msg、%message

%n:換行

%C:包+類別名稱,像我是「test.log4j2.App」,花括號表示從右邊以「.」為一個單位,
所以2表示log4j2.App,如下:

我將第一行改成如下:private static final Logger logger = LogManager.getLogger("www.google.com.tw");
然後pattern改成「%c{3} %C %d{yyyy-MM-dd} %L%n」
以下是結果:
google.com.tw test.log4j2.App 2016-03-24 14
google.com.tw test.log4j2.App 2016-03-24 15
google.com.tw test.log4j2.App 2016-03-24 16
google.com.tw test.log4j2.App 2016-03-24 17
google.com.tw test.log4j2.App 2016-03-24 18
google.com.tw test.log4j2.App 2016-03-24 19



※在 Web 環境使用 log4j2

官方文件
下 log4j-core 時,會依賴 log4j-api,但 log4j-web 並沒有,所以還得去下載
注意 container 的版本

※web.xml

<context-param>
    <param-name>log4jConfiguration</param-name>
    <param-value>/WEB-INF/log4j2.xml</param-value>
</context-param>
    
<listener>
    <listener-class>org.apache.logging.log4j.web.Log4jServletContextListener</listener-class>
</listener>
    
<filter>
    <filter-name>log4jServletFilter</filter-name>
    <filter-class>org.apache.logging.log4j.web.Log4jServletFilter</filter-class>
</filter>
    
<filter-mapping>
    <filter-name>log4jServletFilter</filter-name>
    <url-pattern>/*</url-pattern>
    <dispatcher>REQUEST</dispatcher>
    <dispatcher>FORWARD</dispatcher>
    <dispatcher>INCLUDE</dispatcher>
    <dispatcher>ERROR</dispatcher>
    <dispatcher>ASYNC</dispatcher><!-- Servlet 3.0 w/ disabled auto-initialization 
        only; not supported in 2.5 -->
</filter-mapping>

※如果 container 的版本夠高 (Tomcat 7(含) 以上,但7.0.43有bug),只要寫 context-param 就可以了,又如果設定檔放在 classpath 裡,而且叫 log4j2.xml,那連 context-param 也不用寫(最上面的 Configuration 連結的 10 條規則其中之一)

※都是官方複製的,唯一改的地方就是放配置檔的路徑

※如果設定沒成功,和沒 Web 時很像,只會印出 error 以下層級,但不會有警告

※官方還提供一個開關,要關閉自動初始化 log 嗎?如下:

<context-param>
    <param-name>isLog4jAutoInitializationDisabled</param-name>
    <param-value>true</param-value>
</context-param>

※true 代表關閉,此時不會去讀取設定檔,所以還是會有預設的 error 以下的層級



※API

官網連結


※代替參數、lambda

public class TestLog4j2 {
    private static final Logger logger = LogManager.getLogger(TestLog4j2.class);
    
    @Test
    public void bruceTest() {
        logger.info("replace1= {} {}", "a", "b");
        logger.info("replace2= {} {}", "a");
        logger.info("replace3= {}", "a", "b");
        
        logger.info("lambda1= {}", () -> array());
        logger.info("lambda2= {}", () -> System.getProperties());
        logger.info("lambda3= {}", System::getProperties);
    }
    
    public String[] array() {
        return new String[] { "xxxx", "oooo" };
    }
}

※使用 {} 代替

※lambda 只會佔用1個 {},但 lambda 必需 log4j 2.4以上才有支援

※注意 Supplier 已被改寫

※官網說使用 lambda 不用顯式判斷層級,我無法理解
層級有開就印,沒開就不印,不管加不加判斷式,結果都一樣,不知道它在說什麼?


※格式化、混合

public class TestLog4j2 {
    public static final Logger logger = LogManager.getFormatterLogger(TestLog4j2.class);
    
    @Test
    public void bruceTest() {
        logger.info("format1= %1$s born in %2$tm %2$te,%2$tY", "bruce", new Date());
        logger.info("format2= %,d", Integer.MAX_VALUE);
        logger.printf(Level.INFO, "mixing= %1$s born in %2$tm %2$te,%2$tY", "bruce", new Date());
    }
}

※混合的意思表示使用 getFormatterLogger 和 getLogger 都可以使用 $1 這種東西,但如果使用getFormatterLogger,那 {} 就失效了

2016年3月21日 星期一

Listener (Servlet 七)

※HttpSessionBindingListener

Servlet的內鍵觸發程式,也就是做了什麼事後,會自動做某件事
可以想成javascript的onXXX,譬如onClick,當使用者按一下滑鼠後,會自動做某件事
這裡的Binding是挷setAttribute(還要呼叫建構子)和removeAttribute


public class TestListener implements HttpSessionBindingListener {
    PrintWriter out;
    
    public TestListener(PrintWriter out){
        this.out = out;
    }
    
    @Override
    public void valueBound(HttpSessionBindingEvent e) {
        out.println("valueBound");
        out.println(e.getName());
        out.println(e.getValue());
    }
    
    @Override
    public void valueUnbound(HttpSessionBindingEvent e) {
        out.println("valueUnbound");
        out.println(e.getName());
        out.println(e.getValue());
    }
}

※寫一支class,實作HttpSessionBindingListener讓session觸發



resp.setContentType("text/plain");
req.getSession().setAttribute("out", new TestListener(resp.getWriter()));

※在doXXX加入這兩行就會觸發

※第一行不打也可以,只是不會換行,會變成空格,所以不寫的話也可以改成在listener那一支加<br />

※觸發的是valueBound,如果想觸發valueUnbound,就按一下重整或上一頁再下一頁,就會看到了,所以valueUnbound在reload、重整、呼叫removeAttribute、contain的預設30分session失效都會觸發



※ServletContextListener

container啟動和關閉時會觸發的listener


@WebListener
@WebInitParam(name = "p1", value = "ppp")
public class TestListener implements ServletContextListener {
// PrintWriter out;
//
// public TestListener(PrintWriter out) {
// this.out = out;
// }
    
    @Override
    public void contextInitialized(ServletContextEvent e) {
        System.out.println("contextInitialized");
        ServletContext sc = e.getServletContext();
        System.out.println(sc.getContextPath());
        System.out.println(sc.getInitParameter("p1"));
        System.out.println(sc.getInitParameter("abc"));
        System.out.println(sc.getInitParameter("def"));
    }
    
    @Override
    public void contextDestroyed(ServletContextEvent e) {
        System.out.println("contextDestroyed");
    }
}

※如果將out註解打開,程式無法啟動,會出現兩個錯誤
InstantiationException: listener.TestListener
NoSuchMethodException: listener.TestListener.<init>()
程式才剛啟動,當然是抓不到了

※這是annotation寫法,@WebListener有一個參數,是描述,很少人在寫

※@WebInitParam沒有作用,要設定初始參數只能寫在web.xml了



<listener>
    <listener-class>listener.TestListener</listener-class>
</listener>
    
<context-param>
    <param-name>abc</param-name>
    <param-value>111</param-value>
</context-param>
    
<context-param>
    <param-name>def</param-name>
    <param-value>222</param-value>
</context-param>

※listener包住listener-class,等同@WebListener,擇一使用

※在reload和關閉container時,會觸發contextDestroyed方法,注意和之前一樣,要按Servers的紅鈕,按Console的紅鈕和按右上角的X是不會執行到contextDestroyed方法的



※ServletContextAttributeListener

ServletContext在新增、取代、移除屬性時觸發


@WebListener
public class TestListener implements ServletContextAttributeListener {
    @Override
    public void attributeAdded(ServletContextAttributeEvent e) {
        System.out.println("attributeAdded");
        System.out.println(e.getName());
        System.out.println(e.getValue());
        System.out.println(e.getServletContext());
    }
    
    @Override
    public void attributeRemoved(ServletContextAttributeEvent e) {
        System.out.println("attributeRemoved");
        System.out.println(e.getName());
        System.out.println(e.getValue());
        System.out.println(e.getServletContext());
    }
    
    @Override
    public void attributeReplaced(ServletContextAttributeEvent e) {
        System.out.println("attributeReplaced");
        System.out.println(e.getName());
        System.out.println(e.getValue());
        System.out.println(e.getServletContext());
    }
}

※比較奇怪的是,我一run到jsp頁面,就會觸發added了,如下訊息:
attributeAdded
org.apache.jasper.runtime.JspApplicationContextImpl
org.apache.jasper.runtime.JspApplicationContextImpl@6f1943a6
org.apache.catalina.core.ApplicationContextFacade@4373821f


※測試

resp.setContentType("text/plain");
ServletContext sc = req.getServletContext();
sc.setAttribute("k", "v");
sc.setAttribute("k", "v");
sc.removeAttribute("k");

※取代時,就算value一樣也能觸發;移除時,一定要有key被移除才會觸發



※HttpSessionAttributeListener

HttpSession在新增、取代、移除屬性時觸發


@WebListener
public class TestListener implements HttpSessionAttributeListener {
    @Override
    public void attributeAdded(HttpSessionBindingEvent e) {
        System.out.println("attributeAdded");
        System.out.println(e.getName());
        System.out.println(e.getValue());
        System.out.println(e.getSession().getId());
    }
    
    @Override
    public void attributeRemoved(HttpSessionBindingEvent e) {
        System.out.println("attributeRemoved");
        System.out.println(e.getName());
        System.out.println(e.getValue());
        System.out.println(e.getSession().getId());
    }
    
    @Override
    public void attributeReplaced(HttpSessionBindingEvent e) {
        System.out.println("attributeReplaced");
        System.out.println(e.getName());
        System.out.println(e.getValue());
        System.out.println(e.getSession().getId());
    }
}

※因為是HttpSession,所以沒有getServletContext方法了



※測試

resp.setContentType("text/plain");
HttpSession session = req.getSession();
session.setAttribute("k", "v");
session.setAttribute("k", "v");
session.removeAttribute("x");

※和ServletContextAttributeListener一樣,但執行頁面時不會觸發



※HttpSessionListener


@WebListener
public class TestListener implements HttpSessionListener {
    @Override
    public void sessionCreated(HttpSessionEvent e) {
        System.out.println("sessionCreated");
        System.out.println(e.getSession().getId());
    }
    
    @Override
    public void sessionDestroyed(HttpSessionEvent e) {
        System.out.println("sessionDestroyed");
        System.out.println(e.getSession().getId());
    }
}



※測試
HttpSession session = req.getSession();
session.invalidate();



HttpSessionActivationListener參考良葛格的網站,總之就是JVM搬到另一個JVM的觸發程式



※ServletRequestListener、ServletRequestAttributeListener

@WebListener
public class TestListener implements ServletRequestListener, ServletRequestAttributeListener {
    @Override
    public void requestDestroyed(ServletRequestEvent e) {
        System.out.println("requestDestroyed");
        System.out.println(e.getServletContext().getServletContextName());
        System.out.println(e.getServletRequest().getLocalName());
    }
    
    @Override
    public void requestInitialized(ServletRequestEvent e) {
        System.out.println("requestInitialized");
        System.out.println(e.getServletContext().getServletContextName());
        System.out.println(e.getServletRequest().getLocalName());
    }
    
    @Override
    public void attributeAdded(ServletRequestAttributeEvent e) {
        System.out.println("attributeAdded");
        System.out.println(e.getName());
        System.out.println(e.getValue());
    }
    
    @Override
    public void attributeRemoved(ServletRequestAttributeEvent e) {
        System.out.println("attributeRemoved");
        System.out.println(e.getName());
        System.out.println(e.getValue());
    }
    
    @Override
    public void attributeReplaced(ServletRequestAttributeEvent e) {
        System.out.println("attributeReplaced");
        System.out.println(e.getName());
        System.out.println(e.getValue());
    }
}



※測試

resp.setContentType("text/plain");
req.setAttribute("k", "v");
req.setAttribute("k", "v");
req.removeAttribute("k");

※和之前的大同小異

2016年3月20日 星期日

Java SE 官方文件

首先到Oracle官方網站

1.選擇Download再選Java SE


2.選擇Documentation活頁標籤,然後按那個大框框


3.選擇右邊的Java VM文件


4.目前有Java6~8,可看HTML和PDF

常用function 二(日期、NULL判斷、ROWID、ROWNUM) (DML 三)

※五種日期

sessiontimezone:Asia/Taipei
current_date:20-3月 -16
sysdate:20-3月 -16
current_timestamp:20-3月 -16 05.43.14.965000000 下午 ASIA/TAIPEI
localtimestamp:20-3月 -16 05.43.14.965000000 下午
systimestamp:20-3月 -16 05.43.14.965000000 下午 +08:00

※可以使用「+」和「-」來加減天數

※如果想改變顯示的效果,可用(DD、MON…等格式,下面的to_char(日期)會說明)
ALTER SESSION SET NLS_DATE_FORMAT = 'DD-MON-YYYY HH24:MI:SS';
ALTER SESSION SET TIME_ZONE = '-5:0';



※日期相減

SELECT
    CURRENT_DATE-hiredate, -- 12877.7442592592592592592592592592592593
    SYSDATE-hiredate, -- 12877.7442592592592592592592592592592593
    CURRENT_TIMESTAMP-hiredate, -- 12877 17:51:44.996
    LOCALTIMESTAMP-hiredate, -- 12877 17:51:44.996
    SYSTIMESTAMP-hiredate -- 12877 17:51:44.996
FROM emp where empno = 7369;

※只能相減,不能相加,左邊減右邊可得到天數



※日期相減 INTERVAL

SELECT sysdate - 30                  FROM dual;
SELECT sysdate - INTERVAL '30' day   FROM dual;
SELECT sysdate - INTERVAL '30' month FROM dual;
SELECT sysdate - INTERVAL '30' year  FROM dual;

※直接減其是就是 INTERVAL '天數' DAY,但如果是要減年月就要完整的寫出來

※時分秒也可以,分別是 HOUR、MINUTE、SECOND



※trunc

trunc(CURRENT_DATE):20-3月 -16
trunc(SYSDATE):20-3月 -16
trunc(CURRENT_TIMESTAMP):20-3月 -16
trunc(LOCALTIMESTAMP):20-3月 -16
trunc(SYSTIMESTAMP):20-3月 -16
    
trunc(CURRENT_DATE-hiredate):12877
trunc(SYSDATE-hiredate):12877

※trunc可以只留年月日,所以五種日期都一樣

※注意:假設有個欄位叫 xxx_date,格式是年月日時分秒
下 xxx_date BETWEEN to_date('2016-03-15', 'yyyy-mm-dd') AND to_date('2016-03-20', 'yyyy-mm-dd')
此時會搜尋315~319的資料如上剛好是320零點零分零秒的資料,此時將 xxx_date,加上 trunc 就可以搜尋到320的資料

※日期相減必需注意格式要一樣,像我的hiredate只有年月日,所以有時分秒的timestamp相減就會出錯



※增加月份、兩個月份相差的天數

add_months(sysdate, 3):左邊日期增加3個月
SELECT months_between(sysdate, hiredate) FROM emp where empno=7369;

※左邊減右邊的月數(會有小數點),所以可以用trunc去除(上一篇的trunc也能處理數字)

※這個方法會幫我們解決潤年的事



※轉日期和數字、求每月最後一天

to_number('101.25')
to_date('2013-05-25','yyyy-mm-dd')
to_timestamp('2013-05-25 20:20:20','yyyy-mm-dd hh24:mi:ss')
last_day(sysdate)--每月最後一天

※hh24…等格式,後面的to_char(日期)會說明



※to_char(數字)

to_char(1234.5678, '9999.9999'),--  1234.5678
to_char(1234.5678, '0000.0000'),--  1234.5678
to_char(0, '9999.9999'),--     .0000
to_char(0, '0000.0000'),--  0000.0000
to_char(0, '9'),-- 0
to_char(0, '0'),-- 0
to_char(1234.5678, '9990.9999'),--  1234.5678
to_char(10, '0')-- ##

※右邊是給格式,只要給的不夠,就會出現「#」,至於給幾個#要看第一個參數



※to_char(日期) 

官網格式說明

to_char(SYSTIMESTAMP, 'yyyy'),-- 年
to_char(SYSTIMESTAMP, 'mm'),-- 月01~12
to_char(SYSTIMESTAMP, 'dd'),-- 日01~31
to_char(SYSTIMESTAMP, 'hh'),-- 時(12小時制)等同「hh12」
to_char(SYSTIMESTAMP, 'hh24'),--時(24小時制)
to_char(SYSTIMESTAMP, 'mi'),-- 分0~59
to_char(SYSTIMESTAMP, 'ss'),-- 秒0~59
to_char(SYSTIMESTAMP, 'sssss'),-- 0-86399
to_char(SYSTIMESTAMP, 'ww'),-- 一年當中的第幾個星期
to_char(SYSTIMESTAMP, 'w'),-- 一個月份當中的第幾個星期
to_char(SYSTIMESTAMP, 'ddd'),-- 001~366 一年當中的第幾天
    
to_char(SYSTIMESTAMP, 'd'),-- 回傳1~7,星期日是1,星期六是7
to_char(SYSTIMESTAMP, 'month'),-- 顯示幾月,如「3月」
to_char(SYSTIMESTAMP, 'day')-- 顯示星期幾,如「星期日」

※大小寫沒差

※有七個符號可以直接在格式裡面使用(第二個參數)
「-」「/」「,」「.」「;」「:」「"text"」
其中text表示隨便打,因為前面六種不夠用就要使用「"」包起來,然後隨個人發揮



※取得下一個星期的日期

to_char(sysdate,'day')
next_day(sysdate, '星期日')

※範例的「星期日」也有可能是要打「sunday」,所以可用第一行的to_char看一下

※此範例表示依今天的日期,顯示出下一個星期日是哪一天,回傳的是date



※Extract

EXTRACT(year FROM systimestamp)-- 截取年
EXTRACT(month FROM systimestamp)
EXTRACT(day FROM systimestamp)
EXTRACT(hour FROM systimestamp)
EXTRACT(minute FROM systimestamp)
EXTRACT(second FROM systimestamp)--秒+3位的毫秒
    
EXTRACT(year FROM date '2001-03-05'),
EXTRACT(month FROM date '2001-03-05'),
EXTRACT(day FROM date '2001-03-05'),
    
EXTRACT(hour FROM timestamp '2001-03-05 12:34:56'),
EXTRACT(minute FROM timestamp '2001-03-05 12:34:56'),
EXTRACT(second FROM timestamp '2001-03-05 12:34:56')

※用to_char時,大部份格式不到10的,譬如8好了,會顯示「08」,而extract不會出現0

※要注意systimestamp是有時分秒的,所以可以截取,但sysdate沒有時分秒,截取就會出錯

※如果傳進來的日期是個變數,就用date或timestamp

※date截取方式和sysdate一樣;timestamp截取方式和systimestamp一樣



※NULL判斷

nvl(null,0),-- 0
nvl(23,0),-- 23
    
nvl2(23,0,1),-- 0
nvl2(null,0,1),-- 1
    
nullif(23,23),-- null
nullif(23,null)-- 23
-- nullif(null,23)編譯錯誤

※nvl第一個參數是null就回傳第二個參數,否則就回傳第一個參數

※nvl2第一個參數是null就回傳第二個參數,否則就回傳第三個參數

※nullif第一和第二個參數一樣就回傳null,否則就回傳第一個參數,第一個參數不能是null



※LNNVL

SELECT * FROM emp WHERE comm < 500;
SELECT * FROM emp WHERE lnnvl(comm < 500);

※使用在where條件

※LNNVL會將條件變成相反,以上面的例子會變成「>=」,並且將NULL顯示



※NANVL

用法和NVL一樣,但用在型態是BINARY_FLOAT 或 BINARY_DOUBLE,如果不是這個型態也不會報錯,結果是有沒有用NANVL都一樣,這個我還沒試過,因為我在工作時還沒遇到這些型態


※COALESCE

coalesce('a','1','2','3'),-- a
coalesce(null,1,2,3),-- 1
coalesce(null,null,2,3),-- 2
coalesce(null,null,null,3),-- 3
coalesce(null,null,null,null)-- null

※除了null外,全部的參數型態都要一樣,如第一行後面的123,如果不用「'」包起來會出錯

※可以有很多參數,第一個參數是null,就判斷第二個參數是不是null,如果是,再繼續判斷第三個參數是不是null,以此類推,如果到最後都是null,就回傳null

官方一直到12c,判斷null的就這6個,而還有兩個常用的方法也能判斷null,decode和case when then else end,看這篇


※ROWID

是每一條紀錄的唯一地址,如果刪除一條記錄又新增一樣的記錄也是會不一樣


※刪除多餘的記錄

-- 1.
CREATE TABLE xxx AS SELECT * FROM dept;
    
-- 2.
SELECT x.*, x.ROWID FROM xxx x;
    
-- 3.
INSERT INTO xxx VALUES (10, 'ACCOUNTING', 'NEW YORK');
INSERT INTO xxx VALUES (20, 'RESEARCH', 'DALLAS');
INSERT INTO xxx VALUES (30, 'SALES', 'CHICAGO');
INSERT INTO xxx VALUES (40, 'OPERATIONS', 'BOSTON');
    
-- 4.
SELECT MIN(ROWID) FROM xxx x GROUP BY deptno;
    
-- 5.
DELETE FROM xxx
WHERE ROWID NOT IN(
    SELECT MIN(ROWID) FROM xxx x GROUP BY deptno
);

※以下的測試是認為新增的資料,ROWID一定會比較大測試的

※1.複製dept來練習 ,之前有講過複製不會複製constraint

※2.看看目前的ROWID長什麼樣子

※3.複製一樣的資料到xxx,因為xxx沒有複製到constraint,所以可以新增多筆一樣的資料,複製個兩、三次以後,再回第2步看目前的ROWID長什麼樣子

※4.這一步是看一下最小的ROWID是和第2步是相同的,因為dept只有四筆而且每個欄位都不一樣,所以group by 哪個欄位都可以

※5.這一步就可以將大於原本的ROWID刪除了



※ROWNUM

可以顯示行號,本身和記錄沒有關係,可以把它想成資料全部都搜完後,再加上的行號
所以想要有和記錄挷在一起的錯覺,就要看下SQL的功力了


SELECT ROWNUM, e.* FROM emp e WHERE ROWNUM = 1;
    
SELECT ROWNUM, e.* FROM emp e WHERE ROWNUM <= 3;
    
SELECT * FROM(
    SELECT ROWNUM r, e.* FROM emp e WHERE ROWNUM <= 10
) t
WHERE t.r >= 6;

※第一條SQL是取第一筆記錄的,但要注意只能取到第一筆和第一筆之後的記錄

※第二條SQL是取前三條記錄,ROWNUM一定要包含第一筆的記錄,和第一條SQL的道理是一樣的,所以取不到非第一筆之後的記錄,如第6到第10筆

※如果想取得第一筆之後的記錄,如第6到第10筆可以用第三條SQL的下法
先取出前10筆,然後用SELECT包起來,這樣子就可以取得到了,算是一種運用吧!

常用function 一(數字、字串) (DML 二)

官網連結


※數字function

SELECT abs(-212),power(2,10),sqrt(25),sign(6) FROM dual;--1
    
--2
ceil(67.84):68
ceil(-67.84):-67
    
floor(67.84):67
floor(-67.84) :-68
    
--3
round(15.193,1):15.2
round(15.193,2):15.19
round(15.193,0):15
round(15.193):15
round(15.193,-1):20
round(15.193,-2):0
    
--4
trunc(15.193,1):15.1
trunc(15.193,2):15.19
trunc(15.193,0):15
trunc(15.193):15
trunc(15.193,-1):10
trunc(15.193,-2):0
    
--5
mod(11,4):3
mod(-11,-4):-3
mod(-11,4):-3
mod(11,-4):3

※1:abs是絕對值;power是次方,範例是2的10次方;sqrt是根號,5*5=25,所以是5.
sign裡面放數字,正數回傳1,負數回傳-1,0回傳0

※2:天花板與地板,都是以整數為主(不管小數),往上或往下拉

※3:四捨五入,第二個參數是小數第幾位,0可以省略

※4:取到小數第幾位,0可省略

※5:取餘數,正負數以第一個參數為主



※一串數字,取最大和最小

select greatest(1,2,3,4,4.5) max, least(1,2,3,4,0.5) min from dual;

※max 為4.5;min 為0.5



※字串function

--6
chr(48):0
chr(65):A
chr(97):a
chr(65+256):A
    
--7
ascii('0'):48
ascii('a'):97
ascii('A'):65
ascii('05'):48
ascii(01):49
    
--8
SELECT
     concat('xxx', 'ooo') -- xxxooo
    ,concat(dname,'ooo') -- RESEARCHooo
    ,concat('ooo',dname) -- oooRESEARCH
    ,dname||'ooo' -- RESEARCHooo
FROM dept where deptno=20;
    
--9
replace('back','b','h'):hack
initcap('monkey'):Monkey
upper('moNkey'):MONKEY
lower('moNkey'):monkey
translate(56338, '123456789', 'avlihemoqr'):hello
    
--10
trim('  a  '):a
ltrim('  a  '):a  
rtrim('  a  '):  a
length('ab'):2
length(ltrim('  a  ')):3
    
--11
lpad('a',5,'#'):####a
rpad('a',5,'#'):a####
    
--12
substr('ABCDEFG',3,4):CDEF
substr('ABCDEFG',-5,4):CDEF
substr('ABCDEFG',3,4.2):CDEF
substr('ABCDEFG',3,4.6):CDEF
    
--13
instr('corporate floor','or'):2
instr('corporate floor','or',1,1):2
instr('corporate floor','or',1,3):14
instr('corporate floor','or', 1, 2):5
instr('corporate floor','or', 2, 2):5
instr('corporate floor','or', 3, 2):14
instr('corporate floor','or', -1, 3):2

※6:裡面的數字是ASCII code,45、65、97是一定要背的,超過2的8次方(也就是256),會一直減256,減到256之內

※7:和chr相反,如果裡面放字串,以第一個為主;如果是數字,如範例是01,那就是1
如果想知道「'」的ascii,可以用跳脫字元「'」,所以是ascii('''');為39

※8:concat和「||」一樣,都是串接,java也有,可搭配欄位,以下的function都一樣

※9:取代、首字大寫、全大寫、全小寫
translate第二和第三個參數是比對用的,如1=a,2=v,3=l…等,不一定要用數字,如以下的寫法,結果也是一樣「translate('efcch', 'abcdefghi', 'avlihemoqr')」

※10:left、right的trim,看要往那邊去除空白或兩邊都去掉,可搭配length確定有沒有成功

※11:範例的意思是,a有5個空格,往左或右增加「#」,要小心中文問題

※12:最左邊是1,範例的意思是從第3個字元取4個字(中文也算一個字)
最右邊是-1,所以第二個的結果也是一樣,第三和第四個不管小數點,所以也和第一個一樣

※13:第3和第4個參數不寫,就都是1
第3個參數表示從第幾個字元開始尋找;第4個參數表示出現第幾次
如果第3個參數是負的,表示從右往左尋找


2016年3月19日 星期六

查詢 (DML 一)

這篇的SQL當練習資料
V$VERSION 這張表可以得知Oracle的版本


※基本查詢、排序

SELECT DEPTNO, DNAME, LOC FROM dept;-- 1
SELECT * FROM dept;-- 2
SELECT d.DEPTNO,d.LOC FROM dept as d;-- 3
SELECT DISTINCT FROM dept;-- 4
SELECT DISTINCT job, deptno FROM emp;-- 5
SELECT sal + 100, sal * 1.2 FROM emp;-- 6
SELECT (sal + 100) bb, sal as salary FROM emp;-- 7
SELECT 'a'||ename, ('xxx'||'ooo') xo  FROM emp;-- 8
SELECT COUNT(*) FROM emp;-- 9
SELECT ename FROM emp ORDER BY ename ASC;-- 10

※1:搜尋欄位
※2:搜尋全部欄位時,可以用「*」代替,可以少打點字
※3:表可以取別名,「as」可省略
※4:DISTINCT可以去掉重覆的欄位
※5:DISTINCT可以用多個欄位,如job,deptno兩個加起來如果重覆才會去掉
※6:還可以加減乘除
※7:欄位一樣可取別名,「as」一樣可省略
※8:可以用「||」將欄位或文字、數字…等加起來
※9:COUNT(*)表示整個資料有幾筆;
COUNT('a')、COUNT(5454),裡面是數字、文字執行的結果都和COUNT(*)一樣

COUNT(欄位)不會加NULL的筆數
假設只查詢一個欄位,有查到9筆。
如果有2筆不是null,那結果就是7;再如果全部是null,那結果就是0

※10:排序,預設是小到大,所以「ASC」可省略;倒序用「DESC」
「ORDER BY sal, deptno DESC」這句是以sal正序,如果值一樣,就以deptno倒序

不一定要使用欄位名稱,也可以用數字,例如 ORDER BY 1 ASC, 3 DESC
ORACLE第一個不管在哪裡都是1(不是0),所以表示以第一個欄位正序,如果一樣再以第3個欄位倒序



※自定排序

SELECT * FROM table
order by decode(column, 'column value2', 1, 'column value1', 2, 'column value3', 3)

※使用 decode 或 case when 方法,可以做到自定排序

※此例的結果為column value2-->column value1-->column value3-->其他欄位



※條件

-- 11
SELECT empno, e.sal FROM emp e
WHERE sal > 500;
    
-- 12
SELECT empno, e.sal FROM emp e
WHERE sal >= 500
AND sal <= 1000;
    
-- 13
SELECT empno, e.sal FROM emp e
WHERE sal BETWEEN 500 AND 1000;

※11:可以用「>」「<」等符號判斷,可看官方文件,注意不等於有三種寫法,11g我下載的文件還有第四種,但那個字根本打不出來,我用複製的也沒用

※12:有很多條件時,用「AND」隔開

※13:12的寫法也可以用這種



※AND/OR/NOT

-- 14
SELECT empno, e.sal FROM emp e
where sal >= 1300
OR sal <= 1000;
    
-- 15
SELECT empno, e.sal FROM emp e
WHERE (deptno=10 AND job='MANAGER')
OR (deptno=20 AND job='CLERK');
    
-- 16
SELECT empno, e.sal FROM emp e
WHERE NOT(deptno=10 OR job='MANAGER');

※14、15:OR的用法
※16:NOT就是相反囉!



※NULL

-- 17
SELECT empno, e.sal FROM emp e
WHERE comm = null;
    
-- 18
SELECT empno, e.sal FROM emp e
WHERE comm IS NULL;

※17:這是錯誤的判斷,但也不會錯,只是搜尋不到而已(包括使用字串null的方式,如果出現錯誤,是因為型態問題)

※18:要判斷NULL,就只能用IS NULL或IS NOT NULL
假設查詢的結果有Y、N和NULL,用WHERE column != 'Y',只能查到N而已,所以還要加上AND column IS NULL或IS NOT NULL



※IN

-- 19
SELECT empno, e.sal FROM emp e
WHERE sal = 1600
OR sal = 1250
OR sal = 800;
    
-- 20
SELECT empno, e.sal FROM emp e
WHERE e.sal IN(1600,1250,800);

※20:19的寫法也可以,但太長了,想少打點字,可用IN或NOT IN



※LIKE

-- 21
SELECT ename FROM emp
WHERE ename LIKE 'A%';

※要注意有分大小寫

※「%」表示0~多;「_」表示1,類似windows的「*」和「?」

※「%A%」表示有A就搜出來; 「_A_」表示什麼A什麼的,如bAq、1A)…等,反正前後都剛好有一個字就可以了
中文我試的結果也算一個,但不知編碼有沒有差

※如果想搜尋「%」和「_」字串要用跳脫字元的寫法,如下:
LIKE '%\%' ESCAPE '\';
LIKE '%&%' ESCAPE '&';
LIKE 'A\_' ESCAPE '\';

※ESCAPE後面接的就是跳脫字元,可以隨自己的想法用,只要和前面的一樣就可以了
第一條SQL表示要找「XXX%」,XXX是0~多個字元
第二條SQL和第一條一樣,只差在跳脫字元不同而已
第三條SQL表示要找「A_」,所以也等同= 'A_'
跳脫字元一般習慣用「\」


ANY/SOME/ALL

-- 22
SELECT ename,sal
FROM emp
WHERE sal > ANY(SELECT sal FROM emp WHERE deptno = 10);
    
-- 23
SELECT ename,sal
FROM emp
WHERE sal > ALL(SELECT sal FROM emp WHERE deptno = 20);

※22:ANY等同SOME(但SOME大部分喜歡用在「=」的時候),上面的官網連結有寫,這語法如果只執行ANY後面的SQL,會發現有多筆,因為any(some)是指任何一筆,所以會以sal最小的為主,所以只要薪水比得出來的條件最小的還大,就搜尋出來

※23:這語法如果只執行ALL後面的SQL,會發現有多筆,因為all是全部,所以會以sal最大的為主

※注意事項(綠色的沒意義):

> ANY:顯示最的結果還的(最的結果要顯示,要寫 >=)
< ANY:顯示最的結果還的(最的結果要顯示,要寫 <=)
= ANY:和IN一樣
^= ANY、<> ANY、!= ANY:全部顯示,並不是NOT IN

> ALL:顯示最的結果還的(最的結果要顯示,要寫 >=)
< ALL:顯示最的結果還的(最的結果要顯示,要寫 <=)
= ALL:全部不顯示
^= ALL、<> ALL、!= ALL:和NOT IN一樣

2016年3月18日 星期五

物件導向六(物件表二) (PL/SQL 三十三)

延續上一張的內容


※新增第二個兒子

CREATE OR REPLACE TYPE emp_obj2 UNDER emp_papa(
    attr_sal NUMBER(7,2),
    attr_comm NUMBER(7,2),
    attr_deptno REF dept_obj,
    
    OVERRIDING MEMBER FUNCTION to_string RETURN VARCHAR2,
    OVERRIDING MAP MEMBER FUNCTION compare RETURN NUMBER
) NOT FINAL;

※多個REF

※BODY內容和第一個兒子一樣



※增加DEPT_OBJ測試資料

CREATE TABLE t_dept OF DEPT_OBJ;
    
INSERT INTO t_dept VALUES (10,'ACCOUNTING','NEW YORK');
INSERT INTO t_dept VALUES (20,'RESEARCH','DALLAS');
INSERT INTO t_dept VALUES (30,'SALES','CHICAGO');
INSERT INTO t_dept VALUES (40,'OPERATIONS','BOSTON');



※增加EMP_OBJ2測試資料

CREATE TABLE t_emp OF EMP_OBJ2;
    
INSERT INTO t_emp VALUES (7369,'SMITH','CLERK',800,NULL,
    (SELECT REF(d) FROM t_dept d WHERE ATTR_DEPTNO = 20 )
);
INSERT INTO t_emp VALUES (7499,'ALLEN','SALESMAN',1600,300,
    (SELECT REF(d) FROM t_dept d WHERE ATTR_DEPTNO = 30 )
);
INSERT INTO t_emp VALUES (7521,'WARD','SALESMAN',1250,500,
    (SELECT REF(d) FROM t_dept d WHERE ATTR_DEPTNO = 30 )
);
INSERT INTO t_emp VALUES (7566,'JONES','MANAGER',2975,NULL,
    (SELECT REF(d) FROM t_dept d WHERE ATTR_DEPTNO = 20 )
);
INSERT INTO t_emp VALUES (7654,'MARTIN','SALESMAN',1250,1400,
    (SELECT REF(d) FROM t_dept d WHERE ATTR_DEPTNO = 30 )
);
INSERT INTO t_emp VALUES (7698,'BLAKE','MANAGER',2850,NULL,
    (SELECT REF(d) FROM t_dept d WHERE ATTR_DEPTNO = 30 )
);
INSERT INTO t_emp VALUES (7782,'CLARK','MANAGER',2450,NULL,
    (SELECT REF(d) FROM t_dept d WHERE ATTR_DEPTNO = 10 )
);
INSERT INTO t_emp VALUES (7788,'SCOTT','ANALYST',3000,NULL,
    (SELECT REF(d) FROM t_dept d WHERE ATTR_DEPTNO = 20 )
);
INSERT INTO t_emp VALUES (7839,'KING','PRESIDENT',5000,NULL,
    (SELECT REF(d) FROM t_dept d WHERE ATTR_DEPTNO = 10 )
);
INSERT INTO t_emp VALUES (7844,'TURNER','SALESMAN',1500,0,
    (SELECT REF(d) FROM t_dept d WHERE ATTR_DEPTNO = 30 )
);
INSERT INTO t_emp VALUES (7876,'ADAMS','CLERK',1100,NULL,
    (SELECT REF(d) FROM t_dept d WHERE ATTR_DEPTNO = 20 )
);
INSERT INTO t_emp VALUES (7900,'JAMES','CLERK',950,NULL,
    (SELECT REF(d) FROM t_dept d WHERE ATTR_DEPTNO = 30 )
);
INSERT INTO t_emp VALUES (7902,'FORD','ANALYST',3000,NULL,
    (SELECT REF(d) FROM t_dept d WHERE ATTR_DEPTNO = 20 )
);
INSERT INTO t_emp VALUES (7934,'MILLER','CLERK',1300,NULL,
    (SELECT REF(d) FROM t_dept d WHERE ATTR_DEPTNO = 10 )
);

※注意是使用REF()



※查詢

SELECT * FROM T_DEPT;
SELECT attr_empno, DEREF(attr_deptno) FROM T_EMP;

※原本的sql下法在sqlplus看到的畫面

※使用DEREF看到的畫面

※在SQL Developer看到的都一樣,滑鼠按下去能看到裡面的內容



※修改

UPDATE T_EMP SET 
    ATTR_SAL = ATTR_SAL * 1.2,
    ATTR_DEPTNO = dept_obj(30,'RESEARCH','芝加哥')
WHERE ATTR_JOB = UPPER('clerk');


※要修改dept_obj的內容用一般的寫法會出「ORA-00932: 不一致的資料類型: 應該是 REF ooo.DEPT_OBJ」的錯,一定要使用REF的寫法:
UPDATE T_EMP SET 
    ATTR_SAL = ATTR_SAL * 1.2,
    ATTR_DEPTNO = (SELECT REF(d) FROM t_dept d WHERE ATTR_DEPTNO = 10 )
WHERE ATTR_JOB = UPPER('clerk');



※刪除

DELETE FROM T_EMP
WHERE ATTR_DEPTNO = (
    SELECT REF(d) FROM t_dept d WHERE ATTR_DEPTNO = 10
);

※如果where條件是DEPT_OBJ還是要用REF()

物件導向七(物件view) (PL/SQL 三十四)

※新增類別

CREATE OR REPLACE TYPE v_emp IS OBJECT(
    v_empno NUMBER(4),
    v_ename VARCHAR2(10),
    v_job VARCHAR2(9),
    v_mgr NUMBER(4),
    v_hiredate DATE,
    v_sal NUMBER(7,2),
    v_comm NUMBER(7,2),
    v_deptno NUMBER(2),
    
    MEMBER FUNCTION to_string RETURN VARCHAR2
) NOT FINAL;

※屬性的類型和順序必需和EMP表的類型和順序一模一樣



※BODY

CREATE OR REPLACE TYPE BODY v_emp IS
    MEMBER FUNCTION to_string RETURN VARCHAR2 IS
    BEGIN
        RETURN SELF.v_empno || ':' || SELF.v_ename;
    END;
END;

※隨便寫一個測試



※物件view語法

CREATE OR REPLACE VIEW view_test OF v_emp
    WITH OBJECT IDENTIFIER(v_empno) AS
SELECT * FROM emp;

※如果權限不夠要用有權限的登入,下「GRANT CREATE VIEW TO 使用者;」



※測試

DECLARE
    v v_emp;
BEGIN
    SELECT VALUE(vt) INTO v FROM view_test vt WHERE v_empno=7499;
    DBMS_OUTPUT.PUT_LINE(v.to_string);
END;

Cache、錯誤頁面、Cookie、HttpSession方法 (Servlet 六)

※Cache

也就是回應給瀏覽器的cache,如果不想讓瀏覽器cache,可以如下設定
resp.setHeader("Cache-Control", "no-store");
.no-cache此文件不應該被cache;
.no-store此文件不應該被cache,也不可以儲存在proxy伺服器裡
有七個選項,看5.2.1

resp.setHeader("Expires", "0");//文件失效時間,0表示馬上失效,看5.3



※錯誤頁面

在web.xml的web-app下增加如下的設定
<error-page>
    <error-code>405</error-code>
    <location>/WEB-INF/405.jsp</location>
</error-page>
    
<error-page>
    <exception-type>java.lang.NullPointerException</exception-type>
    <location>/xxx.jsp</location>
</error-page>

※location可以是另外一支servlet,所以「/」的觀念還是一樣

※405的錯是get或post沒有對應的方法就會拋出來,所以我在jsp給post,但我servlet沒寫post,這時就會跳出405.jsp的畫面

※比較奇怪的是NullPointer,我在servlet寫Integer i;(沒初始化) i++,這樣子一定是NullPointer,可是不會跳xxx.jsp的頁面



※Cookie

Cookie cookie = new Cookie("bruce", "chen");
cookie.setMaxAge(60*60*24);
resp.addCookie(cookie);
    
Cookie[] cookieArray = req.getCookies();
for(Cookie c:cookieArray){
    if(c.getName().equals("bruce")){
        System.out.println(c.getValue());
    }
}

※setMaxAge設定有效期限,單位是秒

每個瀏覽器都有相關的設定,下面是IE的
勾結束時刪除瀏覽瀏覽記錄,會刪除一個叫刪除按鈕裡面的設定


這個設定可以調整Cookie的存取,如果調到最高就是完全不用Cookie



HttpSession方法

resp.setContentType("text/html; charset=UTF-8");
PrintWriter out = resp.getWriter();
HttpSession session = req.getSession();
    
out.print("sessionId=" + session.getId());
out.print("<br/>session是新的嗎?" + session.isNew());
out.print("<br/>session有效時間=" + session.getMaxInactiveInterval());
out.print("<br/>session創建時間=" + session.getCreationTime());
out.print("<br/>session上次存取時間=" + session.getLastAccessedTime());
out.print("<br/>從Cookie取得session嗎?" + req.isRequestedSessionIdFromCookie());
out.print("<br/>從URL取得session嗎?" + req.isRequestedSessionIdFromURL());
out.print("<br/>sessionId有效嗎?" + req.isRequestedSessionIdValid());
out.print("<br/><a href='");
out.print(resp.encodeURL(req.getRequestURI()));
out.print("'>注意狀態列和網址</a>");

※req.getSession()等同req.getSession(true),false表示不新增session,所以只能在session之後

※invalidate()是登出

※setMaxInactiveInterval(int)設定session有效時間,單位是秒

※如果要重導就用這一組resp.sendRedirect(res.encodeRedirectURL(""));

※isRequestedSessionIdFromURL想變成true。Session會先到Cookie找資料,如果發現Cookie被關掉,就會用URL Rewriting的方式,但測試時,localhost要改成ip才行,否則還是有辦法抓到Cookie

※會在網址後加上「;jsessionid=16進位的數字」,這個數字如果被有心人拿到,也是會有安全的漏洞,所以只要寫req.getSession()就會有一條session,如果瀏覽器關掉再開就是兩條,因為jsessionid不同了

2016年3月15日 星期二

ServletContext的初始化參數、HttpServletResponse方法 (Servlet 五)

初始化參數前面給有介紹到,但之前的初始化參數是ServletConfig的,由GenericServlet實作,是針對這一支Servlet使用,其他Servlet取不到,而ServletContext的初始化參數每支Servlet都取得到
web.xml
<web-app>
    <context-param>
        <param-name>abc</param-name>
        <param-value>111</param-value>
    </context-param>
    <context-param>
        <param-name>def</param-name>
        <param-value>222</param-value>
    </context-param>
</web-app>


@WebServlet(
    urlPatterns = { "/xxx" },
    loadOnStartup = 1,
    initParams = {
        @WebInitParam(name = "p1", value = "xxx"),
        @WebInitParam(name = "p2", value = "20")
    }
)
// @WebInitParam(name = "bbb", value = "bbb")
public class TestServletContextParam extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        resp.setContentType("text/plain; charset=Big5");
        PrintWriter out = resp.getWriter();
    
        out.println("Context Params");
        ServletContext sc = getServletContext();
        Enumeration<String> contextParams = sc.getInitParameterNames();
        while (contextParams.hasMoreElements()) {
            String key = contextParams.nextElement();
            out.println("key=" + key);
            out.println("value=" + sc.getInitParameter(key));
        }
    
        out.println("this Servlet Params");
        Enumeration<String> thisServletParams = getInitParameterNames();
        while (thisServletParams.hasMoreElements()) {
            String key = thisServletParams.nextElement();
            out.println("key=" + key);
            out.println("value=" + getInitParameter(key));
        }
    }
}

※結果:
Context Params
key=abc
value=111
key=def
value=222
this Servlet Params
key=p1
value=xxx key=p2 value=20

※上面是ServletContext的,下面是本身自己的

※再寫一支Servlet寫法一樣,但不要用annotation,web.xml也不要設定(只要設定路徑即可),這時會發現取得到ServletContext,而TestServletContextParam的初始化參數取不到

※註解那行寫了不會錯,但沒作用,API有寫說要配合@WebFilter或@WebServlet的

※annotation好像沒有設定ServletContext初始化參數的寫法



※HttpServletResponse方法

.父介面是ServletResponse


※注意事項

BufferedReader in1 = req.getReader();
ServletInputStream in2 = req.getInputStream();
PrintWriter out1 = resp.getWriter();
ServletOutputStream out2 = resp.getOutputStream();

※會出例外,如:IllegalStateException: getReader() has already been called for this request

※in1和in2只能出現一個; out1和out2只能出現一個



※ContentType

resp.setContentType("text/html");
PrintWriter out1 = resp.getWriter();
resp.setContentType("image/gif");
ServletOutputStream out2 = resp.getOutputStream();
    
PrintWriter out = resp.getWriter();
ServletContext sc = req.getServletContext();
out.println(sc.getMimeType("*.jpg") + "<br />");
out.println(sc.getMimeType("abc.txt")); 

※ContentType不知道要打什麼,可以用ServletContext的MimeType,給副檔名就會印出來了

※也可以打開Servers的web.xml,裡面可以複製

※而最好的方式是在Servers點兩下就會跳出如下的畫面,可以看出副檔是body、htm、html的mimeType都是text/html



※BufferSize

System.out.println("預設的BufferSize=" + resp.getBufferSize());
resp.setBufferSize(16 * 1024);
System.out.println("設定後的BufferSize=" + resp.getBufferSize());

※單位是Byte,所以預設的會印出8192,最後印出16384

※主要是連網站有個緩衝,緩衝滿了會重新連線,所以如果感覺太慢了,可以適當的加大這個數字



※reset

PrintWriter out = resp.getWriter();
     
System.out.println("xxxA");
out.println("xxxB");
resp.reset();
System.out.println("oooA");
out.println("oooB");
resp.reset();
out.println("oooC");

※reset了兩次,所以只會印出oooC,是指out,控制台都能印出



※ContentLength

resp.setContentType("text/plain; charset=UTF-8");
resp.setContentLength(3);
PrintWriter out = resp.getWriter();
out.println("一");

※回傳給網頁的大小,單位為B,而我印了一個中文字,且編碼是UTF-8,所以印的出「一」,但如果小於3,什麼都不會印出,如果打的是abcdef,那只會印出「abc」,有些人不知道為什麼,認為UTF-8是2個Byte,我這一篇可以證明



※sendError

resp.sendError(HttpServletResponse.SC_BAD_REQUEST);
resp.sendError(HttpServletResponse.SC_BAD_REQUEST, "xxx");

※sendError是個overloading,差在有沒有說明而已,
第一行的標頭會印出「HTTP Status 400 - 」,第二行會印出「HTTP Status 400 - xxx」




※resp.setStatus

1xx:Informational
2xx:Successful
3xx:Redirection
4xx:Client Error
5xx:Server Error

維基百科有說明,我將它貼到這來,裡面有連結,可以連到這裡,才有上面那段

HTTP/1.1 200 OK

Date: Mon, 23 May 2005 22:38:34 GMT
Content-Type: text/html; charset=UTF-8
Content-Encoding: UTF-8
Content-Length: 138
Last-Modified: Wed, 08 Jan 2003 23:11:55 GMT
Server: Apache/1.3.3.7 (Unix) (Red-Hat/Linux)
ETag: "3f80f-1b6-3e1cb03b"
Accept-Ranges: bytes
Connection: close

<html>
<head>
<title>An Example Page</title>
</head>
<body>
Hello World, this is a very simple HTML document.
</body>
</html>

※最上面是Status Line
再來是Response Header
最後是Response Content,也就是HTML

Header還有很多可看另一頁,注意有分request和response



※setXXXHeader

setHeader(String, String);
setIntHeader(String, int);
setDateHeader(String, long);
   &nbsp;
addHeader(String, String);
addIntHeader(String, int);
addDateHeader(String, long);
    
containsHeader(String);

※設定標頭,可以設字串、數字、日期,使用set會蓋掉前面的;使用add會增加到原來的之後,containsHeader看名字叫知道了

※setHeader("text/html", "charset=utf-8");有沒有charset我忘了,因為很麻煩,所以有setContentType



※Refresh

<head>
    <meta http-equiv="Refresh" content="3">
</head>
    
res.setHeader("Refresh", "3");
res.setHeader("Refresh", "3; URL=http://www.xxx.ooo");

※上面是HTML的寫法,下面是Servlet的寫法

※過程如下圖的上面,下面是待會要說的sendRedirect
※如果沒有URL,兩個都一樣,都是Servlet直接回傳



※sendRedirect

res.sendRedirect(String)是
res.setStatus(resp.SC_MOVED_TEMPORARILY); // 302
+
res.setHeader("Location", location);
因為太麻煩,所以才有sendRedirect

sendRedirect(String):絕對、相對路徑(「/」的用意和form action的觀念一樣)都可以
setHeader:只能用絕對路徑

Object account = session.getAttribute("account");
if(account == null){
    session.setAttribute("location", req.getRequestURI());
    resp.sendRedirect(req.getContextPath() + "/login.jsp");
    return;
}

※取不到登入者的帳號,可以這樣運用

2016年3月14日 星期一

物件導向五(物件表一)(PL/SQL 三十二)

※新增以下五個TYPE來練習


※dept_obj宣告與實作 

CREATE OR REPLACE TYPE dept_obj IS OBJECT(
    attr_deptno NUMBER(2),
    attr_dname VARCHAR2(14),
    attr_loc VARCHAR2(13),
    
    MEMBER FUNCTION to_string RETURN VARCHAR2
) NOT FINAL;
    
CREATE OR REPLACE TYPE BODY dept_obj IS
    MEMBER FUNCTION to_string RETURN VARCHAR2 IS
    BEGIN
        RETURN SELF.attr_deptno || ':' || SELF.attr_dname || ':' || SELF.attr_loc;
    END;
END;

※emp_obj的抽象父類別emp_papa宣告

CREATE OR REPLACE TYPE emp_papa IS OBJECT(
    attr_empno NUMBER(4),
    attr_ename VARCHAR2(10),
    attr_job VARCHAR2(9),
    
    NOT INSTANTIABLE MEMBER FUNCTION to_string RETURN VARCHAR2,
    NOT INSTANTIABLE MAP MEMBER FUNCTION compare RETURN NUMBER
) NOT FINAL NOT INSTANTIABLE;

※emp_obj宣告與實作

CREATE OR REPLACE TYPE emp_obj UNDER emp_papa(
    attr_sal NUMBER(7,2),
    attr_comm NUMBER(7,2),
    attr_deptno dept_obj,
    
    OVERRIDING MEMBER FUNCTION to_string RETURN VARCHAR2,
    OVERRIDING MAP MEMBER FUNCTION compare RETURN NUMBER
) NOT FINAL;
    
CREATE OR REPLACE TYPE BODY emp_obj IS
    OVERRIDING MEMBER FUNCTION to_string RETURN VARCHAR2 IS
    BEGIN
        RETURN SELF.attr_empno || ':' || SELF.attr_ename || ':' || SELF.attr_sal || ':' || SELF.attr_comm;
    END;
    
    OVERRIDING MAP MEMBER FUNCTION compare RETURN NUMBER IS
    BEGIN
        RETURN SELF.attr_sal + NVL(SELF.attr_comm, 0);
    END;
END;

※emp_obj和emp_papa為父子,而dept_obj為emp_obj裡的屬性型態



※新增測試資料練習

CREATE TABLE object_table OF EMP_OBJ;
    
INSERT INTO object_table VALUES (7369,'SMITH','CLERK',800,NULL,dept_obj(20,'RESEARCH','DALLAS'));
INSERT INTO object_table VALUES (7499,'ALLEN','SALESMAN',1600,300,dept_obj(30,'SALES','CHICAGO'));
INSERT INTO object_table VALUES (7521,'WARD','SALESMAN',1250,500,dept_obj(30,'SALES','CHICAGO'));
INSERT INTO object_table VALUES (7566,'JONES','MANAGER',2975,NULL,dept_obj(20,'RESEARCH','DALLAS'));
INSERT INTO object_table VALUES (7654,'MARTIN','SALESMAN',1250,1400,dept_obj(30,'SALES','CHICAGO'));
INSERT INTO object_table VALUES (7698,'BLAKE','MANAGER',2850,NULL,dept_obj(30,'SALES','CHICAGO'));
INSERT INTO object_table VALUES (7782,'CLARK','MANAGER',2450,NULL,dept_obj(10,'ACCOUNTING','NEW YORK'));
INSERT INTO object_table VALUES (7788,'SCOTT','ANALYST',3000,NULL,dept_obj(20,'RESEARCH','DALLAS'));
INSERT INTO object_table VALUES (7839,'KING','PRESIDENT',5000,NULL,dept_obj(10,'ACCOUNTING','NEW YORK'));
INSERT INTO object_table VALUES (7844,'TURNER','SALESMAN',1500,0,dept_obj(30,'SALES','CHICAGO'));
INSERT INTO object_table VALUES (7876,'ADAMS','CLERK',1100,NULL,dept_obj(20,'RESEARCH','DALLAS'));
INSERT INTO object_table VALUES (7900,'JAMES','CLERK',950,NULL,dept_obj(30,'SALES','CHICAGO'));
INSERT INTO object_table VALUES (7902,'FORD','ANALYST',3000,NULL,dept_obj(20,'RESEARCH','DALLAS'));
INSERT INTO object_table VALUES (7934,'MILLER','CLERK',1300,NULL,dept_obj(10,'ACCOUNTING','NEW YORK'));



※排序

SELECT VALUE(ot) v, attr_empno, attr_sal, attr_comm, attr_sal + attr_comm sal_comm 
FROM object_table ot
ORDER BY v DESC;

※之前的物件導向二(排序)(PL/SQL 二十九)講到這裡



※還是可以使用INTO賦值

DECLARE
    xxx emp_obj;
BEGIN
    SELECT VALUE(ot) INTO xxx FROM object_table ot WHERE ot.ATTR_EMPNO = 7521;
    DBMS_OUTPUT.PUT_LINE(xxx.to_string());
    DBMS_OUTPUT.PUT_LINE(xxx.attr_deptno.to_string());
END;



※和游標配合使用

DECLARE
    xxx emp_obj;
    CURSOR xxx_cursor IS SELECT VALUE(ot) v FROM object_table ot;
BEGIN
    FOR i IN xxx_cursor LOOP
        xxx := i.v;
        DBMS_OUTPUT.PUT_LINE(xxx.to_string());
        DBMS_OUTPUT.PUT_LINE(xxx.attr_deptno.to_string() || CHR(10));
    END LOOP;
END;