2016年4月5日 星期二

增刪改、事務、鎖 (DML 十二)

※複製表

CREATE TABLE xxx AS SELECT * FROM emp;

※這是DDL的操作,再過幾篇會寫,先照做

※表的名稱叫xxx,內容和emp表一樣,但要注意CONSTRAINT 不複製,所以PK、FK都沒有

※複製這個表來練習,可以隨便亂搞,這裡的增刪改都是DML,所以增刪改完成後,只有目前的使用者才有用,或者重新登入就沒了,如果想讓其他使用者或者重新登入都有上次的資料,就要下COMMIT;,而如果在還沒COMMIT之前,目前的使用者感覺操作都有修改到,如果不想要這次的執行結果,可以下ROLLBACK;

※練習完可用「DROP TABLE xxx PURGE;」刪除表



※新增

INSERT INTO xxx(empno, ename, job, mgr, hiredate, sal, comm, deptno)
VALUES(9999, 'x', 'CLERK', 7902, sysdate, 1000, 0, 20);
    
INSERT INTO xxx VALUES(8888, 'o', 'MANAGER', 7839, sysdate, 3000, 0, 30);
    
INSERT INTO xxx SELECT * FROM emp WHERE deptno = 30;

※新增有三種
第一個SQL有打欄位,可以視需要,新增想增加的欄位即可,而值打在VALUES後面,不過PK、not null一定要打,否則編譯會失敗

第二個SQL不打欄位,所以值要按照資料庫的順序全部打出來

第三個SQL可以一次新增多筆,查詢一張表,欄位的型態要和想新增的表一樣即可



※修改

UPDATE xxx SET job = 'MANAGER', sal = 1500 WHERE empno = 7369;
    
UPDATE xxx SET (job,sal) = (
    SELECT job, sal FROM emp
    WHERE empno = 7499
)
WHERE empno = 7369;

※要注意不加 WHERE 會將所有表都修改,如果表裡有10筆,10筆都會修改

※多個欄位用逗號隔開

※第二個SQL是配合 SELECT 使用的



※刪除

DELETE FROM xxx WHERE empno = 7369;
    
DELETE FROM xxx WHERE job = (
    SELECT job FROM xxx WHERE empno = 7902
);

※要注意不加 WHERE 會將所有表都刪除,如果表裡有10筆,10筆都會被刪除,如果不小心刪除了,記得ROLLBACK

※DELETE沒有「*」

※第二個SQL是配合 SELECT 使用的



※MERGE INTO USING ON

有找到就修改;沒找到就新增


MERGE INTO emp e
USING (SELECT deptno FROM dept WHERE deptno = 40) d
ON(
    e.deptno = d.deptno
)
    
WHEN NOT MATCHED THEN
    INSERT (empno, ename) VALUES (EMP_SEQ.nextval, 'xxx')
    
WHEN MATCHED THEN
    UPDATE SET comm = comm * 1.2
    DELETE WHERE (ename = 'TURNER');

※USING 和 ON 都是一定要有的

※MERGE INTO 後面接要修改或新增的table

※注意USING裡的SQL,欄位值只能唯一,否則會報「ORA-30926:無法取得來源表格中可信的資料列集」,可用distinct

※ON裡的欄位也不能修改,否則會報『ORA-38104:無法更新「ON子句」中參照的資料欄:table.column』

※UPDATE可以加上DELETE,但where條件是update後的條件

※假設t1表的tid有1、2、3,而t2表有2、3、4,那結果就會修改2筆
又假設t2表變成4、5、6,那就會執行新增

※MATCHED和NOT MATCHED可擇一使用,順序也可以互換

※注意新增和修改的語法變的比較精簡了,insert如果要每個欄位都新增,insert後的圓括號可不打



※事務

※Transaction 翻譯成事務

※commit 可以讓其他的使用者可以看到修改的結果

※rollbak:在還沒commit前,可以回復到修改前的狀態

※資料庫要有四個特性才能叫資料庫,就是ACID
.Atomicity(原子性):整體一起commit或rollback

.Consistency(一致性):假設A轉帳給B1000元,
成功時,A少1000元,B也多1000元 --> 一致
失敗時,A和B的錢都不會增加或減少 --> 一致

.Isolation(隔離性):多個事務可以同時進行,而且彼此之間看不見

.Durability(持久性):事務提交成功後,即使資料庫馬上就壞了,也必須通過某種機制恢復資料

在12c預設不自動提交,SET AUTOCOMMIT ON|OFF;可以設定要不要自動提交


※SAVEPOINT

可以將當前的狀態存起來,以下一定要使用不自動提交
已經commit的就不能rollback了


※例1

SELECT * FROM xxx;-- 1.看一下目前資料
UPDATE xxx SET comm = 50 WHERE empno = 7369;-- 2.修改
SELECT * FROM xxx;-- 3.確認有修改到
SAVEPOINT s1;-- 4.將目前狀態存起來
ROLLBACK;-- 5.回到修改前,也就是第一和第二行之間
ROLLBACK TO s1;-- 6.想回到s1

※因為第5步已經回到1和2之間,那時並沒有存s1,這時就會出
「ORA-01086: 從未在此階段作業建立儲存點 'S1' 或此儲存點無效」


※例2

SELECT * FROM xxx;-- 1.看一下目前資料
UPDATE xxx SET comm = 50 WHERE empno = 7369;-- 2.修改
SELECT * FROM xxx;-- 3.確認有修改到
SAVEPOINT s1;-- 4.將目前狀態存起來
UPDATE xxx SET comm = 50 WHERE empno = 7566;-- 5.修改
SELECT * FROM xxx;-- 6.確認有修改到
ROLLBACK TO s1;-- 7.回到s1

※第7步因為前面沒有commit或rollback,所以回復成功


※例3

SELECT * FROM xxx;-- 1.看一下目前資料
UPDATE xxx SET comm = 50 WHERE empno = 7369;-- 2.修改
SELECT * FROM xxx;-- 3.確認有修改到
SAVEPOINT s1;-- 4.將目前狀態存起來
UPDATE xxx SET comm = 50 WHERE empno = 7566;-- 5.修改
SELECT * FROM xxx;-- 6.確認有修改到
SAVEPOINT s2;-- 7.將目前狀態存起來
ROLLBACK TO s1;-- 8.回到s1
SELECT * FROM xxx;-- 9.確定回復成功
ROLLBACK TO s2;-- 10.想回到s2

※因為第8步已經回到4和5之間,那時並沒有存s2,這時就會出
ORA-01086: 從未在此階段作業建立儲存點 'S2' 或此儲存點無效


※例4

SELECT * FROM xxx;-- 1.看一下目前資料
UPDATE xxx SET comm = 50 WHERE empno = 7369;-- 2.修改
SELECT * FROM xxx;-- 3.確認有修改到
SAVEPOINT s1;-- 4.將目前狀態存起來
UPDATE xxx SET comm = 50 WHERE empno = 7566;-- 5.修改
SELECT * FROM xxx;-- 6.確認有修改到
SAVEPOINT s2;-- 7.將目前狀態存起來
UPDATE xxx SET comm = 50 WHERE empno = 7698;-- 8.修改
SELECT * FROM xxx;-- 9.確認有修改到
ROLLBACK TO s2;-- 10.回到s2
SELECT * FROM xxx;-- 11.確定回復成功
ROLLBACK TO s1;-- 12.回到s1
SELECT * FROM xxx;-- 13.確定回復成功

※第10步回到s2,也就是6和7之間,有包括第4步的s1記錄,所以12步可以順利的回到s1



※鎖

在執行UPDATE、DELETE都有鎖,也就是其他使用者無法看到你鎖定的資料
又分成行級鎖定和表級鎖定

說其他使用者不夠正確,應該是說其他session,每個登入進來的都有一個session,就算是同一個帳號也是分多個session,所以以下測試都是用同一個帳號,一個用SQL Developer,另一個用SQL plus


※行級鎖定


※例1sessionA

SELECT * FROM xxx WHERE deptno = 10 FOR UPDATE;

※FOR UPDATE 是SELECT的行級鎖,部門10的 empno 有7782、7839、7934


※例1sessionB

SELECT * FROM xxx WHERE deptno = 10 FOR UPDATE;
SELECT * FROM xxx WHERE empno = 7782 FOR UPDATE;
SELECT * FROM xxx FOR UPDATE;
UPDATE xxx SET comm = 50 WHERE empno = 7369;
DELETE FROM xxx WHERE empno = 7369;

※sessionB在執行上的其中一行SQL時,只要sessionA還沒commit或rollback,畫面就好像在讀什麼一樣,都不動,還以為是當機了

※FOR UPDATE在工作中,常有人執行後,配合類似SQL Developer的軟體,然後用滑鼠修改值,算是一種應用吧!


※修改或刪除,例2sessionA

UPDATE xxx SET comm = 50 WHERE empno = 7369;

※這時還沒commit或rollback


※例2sessionB

UPDATE xxx SET comm = 50 WHERE empno = 7369;
DELETE FROM xxx WHERE empno = 7369;

※這時一樣會停住不動


※表級鎖定

官網連結

LOCK TABLE emp IN EXCLUSIVE MODE NOWAIT/WAIT;
WAIT是預設的選項,如果表被鎖起來會停住不動,不想等就用NOWAIT
類似 EXCLUSIVE 的有6種模式,官網都有寫,我盡我的能力翻出來如下:

1.ROW SHARE:
允許同時存取鎖定的表,但禁止使用者鎖定整個表獨占存取
ROW SHARE 和SHARE UPDATE一樣,這是因為早期版本兼容性的關係

2.ROW EXCLUSIVE:
很像ROW SHARE,但禁止在SHARE模式鎖定
ROW EXCLUSIVE在增刪改時是自動取得的

3.SHARE UPDATE:
和ROW SHARE一樣

4.SHARE:
允許同時查詢,但禁止更新鎖定的表

5.SHARE ROW EXCLUSIVE:
用在尋找整個表,而且允許看到表中的行
可是在SHARE模式禁止其他人鎖定表或修改行

6.EXCLUSIVE:
只允許查詢鎖定的表


※例

--sessionA
LOCK TABLE xxx IN SHARE MODE NOWAIT;
    
--sessionB
DELETE FROM xxx;

※sessionA執行後,sessionB執行DELETE就會停住不動
sessionA一rollback,sessionB才會執行



※解鎖

官網連結

※例

SELECT * FROM xxx WHERE deptno = 10 FOR UPDATE;
SELECT SESSION_ID FROM V$LOCKED_OBJECT;-- 23、250
SELECT SID, SERIAL#, LOCKWAIT FROM V$SESSION WHERE SID IN (23,250);
/*  
 23   5289
250  18727
*/

ALTER SYSTEM KILL SESSION '250,18727';

※第二行以後都必須用有權限的帳號才能執行

※第一行用兩個session分別執行,其中一個session會停住不動

※查詢V$LOCKED_OBJECT表,會發現有二筆鎖定了

※將查到的兩筆查詢V$SESSION表,取得SID和SERIAL#也會有兩筆

※刪除其中一筆後,解除鎖定

正則表達式二 (語法) (DML 十一)

※*

SELECT
    REGEXP_COUNT('ac aac abc axyzc abccc', 'a\w*c') C,-- 5
    
    REGEXP_SUBSTR('ac aac abc axyzc abccc', 'a\w*c', 1, 1) S1,-- ac
    REGEXP_SUBSTR('ac aac abc axyzc abccc', 'a\w*c', 1, 2) S2,-- aac
    REGEXP_SUBSTR('ac aac abc axyzc abccc', 'a\w*c', 1, 3) S3,-- abc
    REGEXP_SUBSTR('ac aac abc axyzc abccc', 'a\w*c', 1, 4) S4,-- axyzc
    REGEXP_SUBSTR('ac aac abc axyzc abccc', 'a\w*c', 1, 5) S5,-- abccc
    REGEXP_SUBSTR('ac aac abc axyzc abccc', 'a\w*?c', 1, 5) S6-- abc
FROM DUAL;

※*等同「a\w{0,}c」預設都是貪婪模式,也就是能匹配多一點就會多匹配一點

※「a\w*?c」或「a\w{0,}?c」為非貪婪,所以S6只有abc

※*、+、?、{}這四個後面多個「?」就是非貪婪模式



※+

SELECT
    REGEXP_COUNT('ac aac abc axyzc abccc', 'a\w+c') C,-- 4
    
    REGEXP_SUBSTR('ac aac abc axyzc abccc', 'a\w+c', 1, 1) S1,-- aac
    REGEXP_SUBSTR('ac aac abc axyzc abccc', 'a\w+c', 1, 2) S2,-- abc
    REGEXP_SUBSTR('ac aac abc axyzc abccc', 'a\w+c', 1, 3) S3,-- axyzc
    REGEXP_SUBSTR('ac aac abc axyzc abccc', 'a\w+c', 1, 4) S4-- abccc
FROM DUAL;

※+等同「a\w{1,}c」



※?

SELECT
    REGEXP_COUNT('ac aac abc axyzc abccc', 'a\w?c') C,-- 4
    
    REGEXP_SUBSTR('ac aac abc axyzc abccc', 'a\w?c', 1, 1) S1,-- ac
    REGEXP_SUBSTR('ac aac abc axyzc abccc', 'a\w?c', 1, 2) S2,-- aac
    REGEXP_SUBSTR('ac aac abc axyzc abccc', 'a\w?c', 1, 3) S3,-- abc
    REGEXP_SUBSTR('ac aac abc axyzc abccc', 'a\w?c', 1, 4) S4-- abc
FROM DUAL;

※?等同「a\w{0,1}c」



※|

SELECT
    REGEXP_COUNT('ac aac aBc axyzc abccC', 'a(a|b|B)c') C,-- 3
    
    REGEXP_SUBSTR('ac aac aBc axyzc abccC', 'a(a|b|B)c', 1, 1) S1,-- aac
    REGEXP_SUBSTR('ac aac aBc axyzc abccC', 'a(a|b|B)c', 1, 2) S2,-- aBc
    REGEXP_SUBSTR('ac aac aBc axyzc abccC', 'a(a|b|B)c', 1, 3) S3-- abc
FROM DUAL;

※|等同「a[abB]c」和「a[=abB=]c」



※.

SELECT
    REGEXP_COUNT('ac aac aBc axyzc a8ccC', 'a.c') C,-- 3
    
    REGEXP_SUBSTR('ac aac aBc axyzc a8ccC', 'a.c', 1, 1) S1,-- aac
    REGEXP_SUBSTR('ac aac aBc axyzc a8ccC', 'a.c', 1, 2) S2,-- aBc
    REGEXP_SUBSTR('ac aac aBc axyzc a8ccC', 'a.c', 1, 3) S3,-- a8c
    
    REGEXP_SUBSTR('ac aac abc axyzc abccc', 'a.c', 1, 3) S4-- abc
FROM DUAL;

※只能是非貪婪模式,S4可看得出來



※^$

SELECT
    REGEXP_SUBSTR('a1c' || chr(10) || 'a2c' || chr(10) || 'a3cc' || chr(10) || 'ba4c',
        '^a.c$', 1, 1, 'm') S1,-- a1c
    REGEXP_SUBSTR('a1c' || chr(10) || 'a2c' || chr(10) || 'a3cc' || chr(10) || 'ba4c',
        '^a.c$', 1, 2, 'm') S2,-- a2c
    REGEXP_SUBSTR('a1c' || chr(10) || 'a2c' || chr(10) || 'a3cc' || chr(10) || 'ba4c',
        '^a.c$', 1, 3, 'm') S3,-- null
    REGEXP_SUBSTR('a1c' || chr(10) || 'a2c' || chr(10) || 'a3cc' || chr(10) || 'ba4c',
        '[a\A].[c\Z]', 1, 3, 'm') S4-- a3c
FROM DUAL;

※一整行為尋找的條件,為了要找到更多,所以我用了換行「chr(10)」,並配合m

※\A\Z也是開頭結尾的意思,所以這個也可以這樣用「[a\A].[c\Z]」,但這個是非貪婪模式,所以S4有匹配成功



※英文字、數字、空格

SELECT
    REGEXP_COUNT('ac aac aBc axyzc abccC', 'a\wc') C1,-- 3
    REGEXP_SUBSTR('ac aac aBc axyzc abccC', 'a\wc', 1, 1) S1,-- aac
    REGEXP_SUBSTR('ac aac aBc axyzc abccC', 'a\wc', 1, 2) S2,-- aBc
    REGEXP_SUBSTR('ac aac aBc axyzc abccC', 'a\wc', 1, 3) S3,-- abc
    
    REGEXP_COUNT('ac a3c aBca5ccC', 'a\dc') C2,-- 2
    REGEXP_SUBSTR('ac a3c aBca5ccC', 'a\dc', 1, 1) SD1,-- a3c
    REGEXP_SUBSTR('ac a3c aBca5ccC', 'a\dc', 1, 2) SD2,-- a5c
    
    REGEXP_COUNT('ac a c a cc', 'a\sc') C3,-- 1
    REGEXP_SUBSTR('ac a c a cc', 'a\sc', 1, 1) SS1,-- a c
    REGEXP_SUBSTR('ac a c a cc', 'a\sc', 1, 2) SS2-- a c
FROM DUAL;

※a\wc等同「a[a-zA-Z0-9]c」和「a[[:alnum:]]c」

※a\dc,等同「[0-9]」和「a[[:digit:]]c」

※a\sc等同「a c」和「a[ ]c」和「a[[:blank:]]c」

※大寫是相反的意思,如\W是非英文字,\D是非數字;\S非空格



※跳脫字元

SELECT
    REGEXP_INSTR('\*+?|^$.()[]', '\\') I1,-- 1
    REGEXP_INSTR('\*+?|^$.()[]', '\*') I2,-- 2
    REGEXP_INSTR('\*+?|^$.()[]', '\+') I3,-- 3
    REGEXP_INSTR('\*+?|^$.()[]', '\?') I4,-- 4
    REGEXP_INSTR('\*+?|^$.()[]', '\|') I5,-- 5
    REGEXP_INSTR('\*+?|^$.()[]', '\^') I6,-- 6
    REGEXP_INSTR('\*+?|^$.()[]', '\$') I7,-- 7
    REGEXP_INSTR('\*+?|^$.()[]', '\.') I8,-- 8
    REGEXP_INSTR('\*+?|^$.()[]', '\(') I9,-- 9
    REGEXP_INSTR('\*+?|^$.()[]', '\[') I10-- 11
FROM DUAL;

※如果要比對關鍵字,要在前面多個「\」,「{}」不用



※[::]

這個語法,我在Oracle官網搜尋,並沒有找到全部的語法
POSIX的官網在這裡,目前總共有14個,寫得很清楚
我列出10個比較常用和好理解的
[:alnum:] = [a-zA-Z0-9]、\w
[:alpha:] = [a-zA-Z]
[:blank:] = tab和空格
[:digit:] = [0-9]、\d
[:lower:] = [a-z]
[:punct:] = 標點符號
[:space:] = 空格,\s
[:upper:] = [A-Z]
[:word:] = [A-Za-z0-9_],比[:alnum:]多個底線
[:xdigit:] = 十六進制,[0-9a-fA-F]

2016年4月4日 星期一

正則表達式一 (REGEXP方法) (DML 十)

官網連結
\:有四種意思
   1.代表本身
   2.引用下一個字符(跳脫字元)
   3.引入一個運算符
   4.沒做什麼
*:0~n
+:1~n
?:0~1
|:or
^:開頭
$:結尾
.:匹配任何字元,除了null
[]:[xyz]表示x或y或z,如xayaza,用a隔開還是可以匹配到,
    要注意在裡面使用「^」就不是開頭的意思了,是相反的意思
():分組表達式,作為一個單獨的子表達式處理
{m}:m次
{m,}:至少m次
{m, n}:至少匹配m次,但不超過n次
\n:n為1~9的數字,用「()」包起來是第1個
[..]:指定一個對照元件,可以是一個多字串元件,如[.ch.]是西班牙語
[::]:POSIX語法,如[:alpha:],下一篇會介紹
[==]:匹配相同的字元,如[=abc=],裡面的字元,其中之一就匹配

\d:數字
\D:非數字
\w:字元
\W:非字元
\s:空格
\S:非空格
\A:只匹配字串的開頭,或前一個字串後面的換行字元
\Z:只匹配結尾的字串
*?:匹配前面的模式元素0次或更多次(非貪婪)
+?:匹配前面的模式元素1次或更多次(非貪婪)
??:匹配前面的模式元素0次或1次(非貪婪)
{m}?:匹配前面的模式元素m次(非貪婪)
{m,}?:匹配前面的模式元素至少m次(非貪婪)
{m,n}?:匹配前面的模式元素至少m次,但不超過n次(非貪婪)



※REGEXP_COUNT

正則表達式匹配的次數,官網連結

總共有4個參數,前兩個參數是必要的
第1個參數:要比對的字串
第2個參數:正則表達式
第3個參數:從第幾個index開始匹配
第4個參數一定是小寫,有5種形式
i:不區分大小寫
c:區分大小寫,預設
n:換行符號可以使用「.」來匹配
m:遇到換行符時,可以使用「^」和「$」來匹配
x:忽略空格,指的是正則表達式的空格(第2個參數)
也可以不放任何字,就等同沒有第四個參數,以上可以搭配使用


※i、c、x

SELECT
    REGEXP_COUNT('abc abc ab cabc', 'abc') C1,-- 3
    REGEXP_COUNT('abc abc ab cabc', 'AbC') C2,-- 0
    REGEXP_COUNT('abc abc ab cabc', 'abc', 3) C3,-- 2
    REGEXP_COUNT('abc abc ab cabc', 'AbC', 3, 'c') C4,-- 0
    REGEXP_COUNT('abc abc ab cabc', 'AbC', 3, 'i') C5,-- 2
    REGEXP_COUNT('abc abc ab cabc', 'a b c', 3, 'x') C6,-- 2
    REGEXP_COUNT('abc abc ab cabc', 'A b C', 3, 'xi') C7-- 2
FROM DUAL;

※說明:
C1可以匹配3次
C2一次都沒有,因為預設有區分大小寫
C3從第3個字元開始讀取(包括c),所以是2次
C4其實就是預設的區分大小寫,所以一次都沒有
C5從第3個字元開始讀取(包括c),不分大小寫,所以是2次
C6從第3個字元開始讀取(包括c),而且忽略空格,所以是2次
C7從第3個字元開始讀取(包括c),而且忽略空格,又不區分大小寫,所以是2次



※n和m

SELECT
    REGEXP_COUNT('123 ' || chr(10) || '67 ' || chr(10), '.', 1, 'i') N1,-- 7
    REGEXP_COUNT('123 ' || chr(10) || '67 ' || chr(10), '.', 1, 'in') N2,-- 9
    REGEXP_COUNT('aac' || chr(10) || 'abcaca' || chr(10) || 'abc', '^a', 1, '') M1,-- 1
    REGEXP_COUNT('aac' || chr(10) || 'abcaca' || chr(10) || 'abc', '^a', 1, 'm') M2-- 3
FROM DUAL;

※說明:
N1還沒有加「n」,所以匹配到的是「123空67空」,所以是匹配7次
N2有加「n」,所以會多2次
N3還沒有加「m」,所以預設算一行,匹配a開頭的,只有1次,最後的參數是空,所以也可以不要第4個參數
N4有加「m」,所以有3行,而且剛好都是a開頭的,所以是3次



※REGEXP_INSTR

正則表達式匹配後回傳第幾個index,沒有匹配到就回傳0,官網連結

總共有7個參數,前兩個參數是必要的
第1個參數:要比對的字串
第2個參數:正則表達式
第3個參數:從第幾個index開始匹配
第4個參數:匹配可能會有多筆,這個數字表示匹配第幾次成功的
第5個參數:不是0就是1,大於1就是1的意思,如果是0就回傳當下的index
如果是1,回傳當下的index之後,也就是再加1
第6個參數:和REGEXP_COUNT的第4個參數一樣,請參考上面的REGEXP_COUNT
第7個參數:巢狀匹配,不好說,直接看下面的第二個例子


※前6個參數

SELECT
    REGEXP_INSTR('123.56.89.BC', '\.') I1,-- 4
    REGEXP_INSTR('123.56.89.BC', '\.', 5) I2,-- 7
    REGEXP_INSTR('123.56.89.BC', '\.', 1, 3) I3,-- 10
    REGEXP_INSTR('123.56.89.BC', 'B', 1, 1, 0) I4,-- 11
    REGEXP_INSTR('123.56.89.BC', 'B', 1, 1, 1) I5,-- 12
    REGEXP_INSTR('123.56.89.BC', 'b', 1, 1, 0, 'i') I6-- 11
FROM DUAL;

※說明:
I1要匹配的是「.」,但它是關鍵字,所以用跳脫字元「\」
I2從第5個字元開始匹配,所後是5
I3從第1個字元開始匹配,但要回傳第三次成功的結果,所以是10
I4要比對「B」,而第5個參數是0,所以回傳當下的11
I5第5個參數是1,所以還要再加1,回傳12
I6的第6個參數不分大小寫


※第7個參數

SELECT
    REGEXP_INSTR('1234567890', '1234567', 1, 1, 0, 'i', 0) S1,-- 1
    REGEXP_INSTR('1234567890', '(123)(4(56)(78))', 1, 1, 0, 'i', 1) S2,-- 1
    REGEXP_INSTR('1234567890', '(123)(4(56)(78))', 1, 1, 0, 'i', 2) S3,-- 4
    REGEXP_INSTR('1234567890', '(123)(4(56)(78))', 1, 1, 0, 'i', 3) S4,-- 5
    REGEXP_INSTR('1234567890', '(123)(4(56)(78))', 1, 1, 0, 'i', 4) S5-- 7
FROM DUAL;

※說明:
 S1最後1個參數給0,匹配的到,其他不行

巢狀
(123)(4(56)(78))的順序如下:
一.123
二.45678
三.56
四.78
這樣就可以知道S2~S5的結果了,第5次以上,以這個例子就回傳0了



※REGEXP_REPLACE

取代正則表達式匹配成功的字串,沒有匹配到就回傳原來的字串,官網連結

總共有6個參數,前三個參數是必要的
第1個參數:要比對的字串
第2個參數:正則表達式
第3個參數:要取代的字串
第4個參數:從第幾個index開始匹配
第5個參數:匹配可能會有多筆,這個數字表示匹配第幾次成功的
第6個參數:和REGEXP_COUNT的第4個參數一樣,請參考上面的REGEXP_COUNT


※前3個參數

SELECT
    REGEXP_REPLACE('123     456    789,   abc   de,   FG', '( ){2,}', ' ') R1,
    REGEXP_REPLACE('123     456    789,   abc   de,   FG', '\s+', ' ') R2,
    REGEXP_REPLACE('ABC', '.', '\1 ') R3,-- \1 \1 \1 
    REGEXP_REPLACE('ABC', '(.)', '\1 ') R4,-- A B C
    REGEXP_REPLACE('A123 B456 C789', '(.*) (.*) (.*)', '\3, \2, \1') R5 -- C789, B456, A123
FROM DUAL;

※R1和R2都是匹配空格兩次以上的(包括2次),取代成一個空格

※R3匹配任何字元,取代成\1空格

※R4因為有「()」,所以\1代表第1個()裡的正則表達式,而A、B、C都匹配成功,所以會將A、B、C後面多一個空格,看R5會比較容易了解

※R5會將第3個C789取代第1個A123;第2個還是第2個;第1個A123取代第三個C789

※第4~6個參數,,請參考上面的REGEXP_INSTR



※REGEXP_SUBSTR

從字串裡面取出子字串,如果都沒有匹配,回傳null,官網連結

總共有6個參數,前兩個參數是必要的
第1個參數:要比對的字串
第2個參數:正則表達式
第3個參數:從第幾個index開始匹配
第4個參數:匹配可能會有多筆,這個數字表示匹配第幾次成功的
第5個參數:和REGEXP_COUNT的第4個參數一樣,請參考上面的REGEXP_COUNT
第6個參數:巢狀匹配,請參考上面的REGEXP_INSTR

SELECT
    REGEXP_SUBSTR('12,345,678', ',[^,]+') S1,-- ,345
    REGEXP_SUBSTR('abc123def456', '[0-9]{1,}', 1, 2) S2,-- 456
    REGEXP_SUBSTR('aabab cde','^a.*b') S3-- aabab
FROM DUAL;

※S1是匹配以「,」開頭一直到非逗號的,所以是「,345」

※S2是匹配一次以上數字開頭的(\d無效),從第1個index開始找,找到2次的結果,所以是456

※S3是匹配a開頭,然後停在b

※第4~6個參數,請參考上面的REGEXP_INSTR



※REGEXP_LIKE

使用在判斷條件時,通常是在WHERE後,官網連結


SELECT * FROM emp
WHERE REGEXP_LIKE (ename, 'N$');

※只要是N結尾的就匹配

※還有第三個參數,和REGEXP_COUNT的第4個參數一樣,請參考上面的REGEXP_COUNT


2016年4月2日 星期六

PIVOT (DML 九)

PIVOT

簡單來說就是行列轉換


※先看資料

SELECT deptno, sum(sal)
FROM emp GROUP BY deptno ORDER BY deptno;
    
SELECT deptno, job, sum(sal)
FROM emp GROUP BY deptno, job ORDER BY deptno;

※這兩個 SQL 分別 GROUP BY 一個和二個欄位

※結果:



※原來的做法使用decode

SELECT 
    deptno,
    sum(sal),
    sum(decode(job, 'CLERK', sal, 0)) C,
    sum(decode(job, 'SALESMAN', sal, 0)) S,
    sum(decode(job, 'PRESIDENT', sal, 0)) P,
    sum(decode(job, 'MANAGER', sal, 0)) M,
    sum(decode(job, 'ANALYST', sal, 0)) A
FROM emp GROUP BY deptno ORDER BY deptno;
    
SELECT 
    deptno,
    job,
    sum(sal),
    sum(decode(job, 'CLERK', sal, 0)) C,
    sum(decode(job, 'SALESMAN', sal, 0)) S,
    sum(decode(job, 'PRESIDENT', sal, 0)) P,
    sum(decode(job, 'MANAGER', sal, 0)) M,
    sum(decode(job, 'ANALYST', sal, 0)) A
FROM emp GROUP BY deptno, job ORDER BY deptno;

※結果:

※可以看出SUM的結果是上往下,而經過decode變成左往右了(SUM的值等於右邊五個欄位的加總),也就是行列轉換



※語法

SELECT * FROM (
    SELECT job, sal FROM emp
)
PIVOT(
    sum(sal) FOR job IN(
        'CLERK' C,
        'SALESMAN' S,
        'PRESIDENT' P,
        'MANAGER' M,
        'ANALYST' A
    )
);

※結果:

※一定要用子查詢,否則編譯不會過

※這結果沒有GROUP BY,也就是CLERK的加總為C,SALESMAN的加總為S,依此類推



※隱藏的 GROUP BY

SELECT * FROM (
    SELECT deptno, job, sal FROM emp
)
PIVOT(
    sum(sal) FOR job IN(
        'CLERK' C,
        'SALESMAN' S,
        'PRESIDENT' P,
        'MANAGER' M,
        'ANALYST' A
    )
)
ORDER BY deptno;
    
    
    
SELECT * FROM (
  SELECT deptno, job j, sal, job FROM emp e
)
PIVOT(
    sum(sal) s FOR job IN(
        'CLERK' C,
        'SALESMAN' S,
        'PRESIDENT' P,
        'MANAGER' M,
        'ANALYST' A
    )
)
ORDER BY deptno;

※第一個SQL的job和sal,因為PIVOT會用到,所以不會GROUP BY,而deptno沒有用到,會自動的GROUP BY

※如果要GROUP BY多個就多打幾個欄位,但如果要打的欄位被PIVOT用了,所以只好如第二個SQL,打個別名

※PIVOT裡的IN一定要事先知道,無法用子查詢、ANY;如果用「>」、「NOT IN」編譯都不會過

※結果:

※這結果和使用decode一樣,只差在null的部分,但我用NVL無效



※多個彙總函數

SELECT * FROM (
    SELECT deptno, job, sal FROM emp
)
PIVOT(
    sum(sal) s, max(sal) m FOR job IN(
        'CLERK' C,
        'SALESMAN' S,
        'PRESIDENT' P,
        'MANAGER' M,
        'ANALYST' A
    )
)
ORDER BY deptno;

※結果:

※可以觀察它的命名方式



※多個條件

SELECT * FROM (
    SELECT e.deptno dno, job, sal, loc FROM emp e, dept d
    WHERE e.deptno = d.deptno
)
PIVOT(
    sum(sal) s FOR (job,loc) IN(
        ('CLERK', 'CHICAGO') C,
        ('SALESMAN', 'CHICAGO') S,
        ('PRESIDENT', 'CHICAGO') P,
        ('MANAGER', 'CHICAGO') M,
        ('ANALYST', 'CHICAGO') A
    )
)
ORDER BY dno;

※結果:

※注意 loc 有四個值,我只打出一個值(CHICAGO)



※轉成XML

SELECT * FROM (
    SELECT deptno, job, sal FROM emp
)
PIVOT XML(
    sum(sal) s FOR job IN(ANY)
)
ORDER BY deptno;

※結果:

※IN裡面放ANY,SOME和ALL都不行,而且只能配合PIVOT XML

※因為Oracle SQL Developer太長了,也不好觀看,所以我排列如下:
<PivotSet>
    <item>
        <column name = "JOB">CLERK</column>
        <column name = "S">1300</column>
    </item>
    
    <item>
        <column name = "JOB">MANAGER</column>
        <column name = "S">2450</column>
    </item>
    
    <item>
        <column name = "JOB">PRESIDENT</column>
        <column name = "S">5000</column>
    </item>
</PivotSet>



※UNPIVOT

也就是PIVOT的相反


WITH t AS (
    SELECT * FROM (
        SELECT deptno, job, sal FROM emp
    )
    PIVOT(
        sum(sal) S_SA FOR job IN(
            'CLERK' C,
            'SALESMAN' S,
            'PRESIDENT' P,
            'MANAGER' M,
            'ANALYST' A
        )
    )
    ORDER BY deptno
)
    
SELECT * FROM t
UNPIVOT(
    S_SA FOR job IN(
        C_S_SA as 'CLERK',
        S_S_SA as 'SALESMAN',
        P_S_SA as 'PRESIDENT',
        M_S_SA as 'MANAGER',
        A_S_SA as 'ANALYST'
    )
)
ORDER BY deptno;

※首先使用WITH將上面的「隱藏的GROUP BY」的寫法複製進去,然後下面再用UNPIVOT

※UNPIVOT裡面的名字,可以先執行上面的PIVOT,看到欄位名稱後打進去即可,但在UNPIVOT裡面,「as」一定要打,不然編譯不會過

※結果:

※可以看出結果又轉回來了



※INCLUDE NULLS/EXCLUDE NULLS

WITH t AS (
    SELECT * FROM (
        SELECT deptno, job, sal FROM emp
    )
    PIVOT(
        sum(sal) S_SA FOR job IN(
            'CLERK' C,
            'SALESMAN' S,
            'PRESIDENT' P,
            'MANAGER' M,
            'ANALYST' A
        )
    )
    ORDER BY deptno
)
SELECT * FROM t
UNPIVOT INCLUDE NULLS(
    S_SA FOR job IN(
        C_S_SA as 'CLERK',
        S_S_SA as 'SALESMAN',
        P_S_SA as 'PRESIDENT',
        M_S_SA as 'MANAGER',
        A_S_SA as 'ANALYST'
    )
)
ORDER BY deptno;

※這語法和上面一樣,只是在UNPIVOT後面多個 INCLUDE NULLS

※結果:

※可以看出null也顯示出來了,而 EXCLUDE NULLS是預設的,
也就是打PIVOT等同 PIVOT EXCLUDE NULLS

CONNECT BY (DML 八)

select date '2017-05-18' + (rownum - 1) dt
from dual
connect by rownum <= (date '2017-05-20' - date '2017-05-18' + 1)

※connect by 可以將連續的日期顯示出來



先看一下EMP的表

員工編號7369的上司員工編號是7902
員工編號7902的上司員工編號是7566
員工編號7566的上司員工編號是7839
員工編號7839的沒有上司,所以可能是老闆

※CONNECT BY可以方便的把這樣的關係做出來



※基本語法

SELECT LEVEL, empno, mgr, 
    lpad('=>', LEVEL * 2, ' ') || ename pic
FROM emp
CONNECT BY PRIOR empno = mgr
-- AND sal > 1500
START WITH mgr is null
ORDER BY LEVEL;

※START WITH 和 CONNECT BY 順序可調換

※不能有WHERE,要加條件直接在CONNECT BY 後面寫,如上面註解的部分

※CONNECT BY 是一定要寫的,START WITH、ORDER BY 可以視情況加進去

※START WITH 表示從哪裡開始,而LEVEL是搭配 CONNECT BY使用的,從圖中可以知道它顯示的是第幾層的關係

※結果:



※CONNECT_BY_ISLEAF、SYS_CONNECT_BY_PATH

SELECT LEVEL, empno, mgr, 
    lpad('=>', LEVEL * 2, ' ') || ename pic,
    decode(CONNECT_BY_ISLEAF, 0, 'root', 'leaf') leaf,
    SYS_CONNECT_BY_PATH(ename,'-->') all_path
FROM emp
CONNECT BY PRIOR empno = mgr
START WITH mgr is null;

※CONNECT_BY_ISLEAF:看下圖可以知道是不是有下屬,沒有就是leaf
SYS_CONNECT_BY_PATH:從START WITH設定的欄位開始往下

※結果:



※PRIOR

SELECT LEVEL, empno, mgr, 
    lpad('=>', LEVEL * 2, ' ') || ename pic,
    decode(CONNECT_BY_ISLEAF, 0, 'root', 'leaf') leaf,
    SYS_CONNECT_BY_PATH(ename,'-->') all_path
FROM emp
CONNECT BY PRIOR empno = mgr
START WITH empno = 7902;

※PRIOR可以在等號的左邊或右邊,或都不寫
CONNECT BY PRIOR empno = mgr:第一個結果,把7902的下屬都顯示出來(管多少人)
CONNECT BY empno = PRIOR mgr:第二個結果,把7902的上司都顯示出來(被多少人管)
CONNECT BY empno = mgr 或 CONNECT BY PRIOR empno = PRIOR mgr(兩邊都寫):
只會顯示 7902

結果:




※CONNECT_BY_ROOT

SELECT LEVEL, empno, mgr, 
    lpad('=>', LEVEL * 2, ' ') || ename pic,
    CONNECT_BY_ROOT ename conn_name
FROM emp
CONNECT BY PRIOR empno = mgr
START WITH empno = 7566;

※結果:

※開始是7566,7566叫JONES,所以以下都是JONES,PRIOR不管寫在哪都一樣,也就是START WITH設定誰,誰就是root



※ORDER siblings BY

SELECT LEVEL, empno, mgr, 
    lpad('=>', LEVEL * 2, ' ') || ename pic,
    decode(CONNECT_BY_ISLEAF, 0, 'root', 'leaf') leaf,
    SYS_CONNECT_BY_PATH(ename,'-->') all_path
FROM emp
CONNECT BY PRIOR empno = mgr
START WITH mgr is null
-- ORDER BY ename
ORDER siblings BY ename;

※ORDER BY結果可以看出,把整個PIC從小到大排好了,但大部分的人不會這樣用

※ORDER siblings BY結果可以看出是以層級為單位做排序的




※CONNECT_BY_ISCYCLE、NOCYCLE

假設自己的上司是123,123的上司是456,而456的上司又是123,這就是循環,使用這個語法可以判斷有沒有循環


UPDATE emp SET mgr = 7902 WHERE empno = 7839;
    
SELECT LEVEL, empno, mgr, 
    lpad('=>', LEVEL * 2, ' ') || ename pic,
    decode(CONNECT_BY_ISLEAF, 0, 'root', 'leaf') leaf,
    decode(CONNECT_BY_ISCYCLE, 0, 'x', 1, 'o') 有循環嗎
FROM emp
CONNECT BY NOCYCLE PRIOR empno = mgr
START WITH empno = 7839
ORDER siblings BY ename;

※因為目前沒有循環,所以執行第一個SQL,不要 commit 就可以執行了,測試完記得 rollback

※結果:

※7902有上司有下屬,但循環只會顯示7902

※使用 CONNECT_BY_ISCYCLE 一定要使用 NOCYCLE,不然編譯過不了(但使用 NOCYCLE 可以不用 CONNECT_BY_ISCYCLE)

※如果有循環但不打 NOCYCLE 會出現
ORA-01436: CONNECT BY 子句造成使用者資料迴路
01436. 00000 -  "CONNECT BY loop in user data"

2016年4月1日 星期五

分析函數二 (ROW_NUMBER、DENSE_RANK、KEEP、FIRST_VALUE、LEAD、CUME_DIST、NTILE、RATIO_TO_REPORT) (DML 七)

※ROW_NUMBER

顯示行號


SELECT
    deptno,
    sal,
    ROW_NUMBER() OVER(PARTITION BY deptno ORDER BY sal) rn1, 
    ROW_NUMBER() OVER(ORDER BY sal) rn2
FROM EMP ORDER BY deptno;

※結果:

可以看出RN1是照部門排列的,因為有PARTITION BY,而RN2很亂,所以將最外面的ORDER BY 改成sal後,結果如下:



※RANK、DENSE_RANK

按照排序的序號


SELECT 
    RANK() OVER(PARTITION BY deptno ORDER BY sal) r, 
    DENSE_RANK() OVER(PARTITION BY deptno ORDER BY sal) d, 
    sal,
    deptno 
FROM EMP WHERE deptno = 30;

※結果:

※第四行可以看出RANK會跳號,而DENSE_RANK不會跳號



※KEEP

必需要搭配GROUP BY使用


SELECT 
    -- MAX(sal) OVER(ORDER BY sal) r, 
    -- MIN(sal) OVER(ORDER BY sal) d, 
    deptno,
    MAX(sal) KEEP(DENSE_RANK FIRST ORDER BY sal) max, 
    MIN(sal) KEEP(DENSE_RANK FIRST ORDER BY sal) min,
    MAX(sal) KEEP(DENSE_RANK LAST ORDER BY sal) maxk, 
    MIN(sal) KEEP(DENSE_RANK LAST ORDER BY sal) mink
FROM EMP
GROUP BY deptno;

※結果:

※註解的部分不能執行,因為不能有GROUP BY



※FIRST_VALUE、LAST_VALUE

取出第一/最後的記錄


SELECT
    sal,
    FIRST_VALUE(sal) OVER(PARTITION BY deptno ORDER BY sal) first, 
    LAST_VALUE(sal) OVER(PARTITION BY deptno ORDER BY sal) last
FROM EMP
WHERE deptno = 20;

※結果:

※看左邊的SAL再對照右邊的,可以看出FIRST因為是第一筆,所以都是800



※LEAD、LAG

將資料往前/往後移動
LEAD是領導的意思,以資料為單位,所以會將後面的資料往上帶,下面變null(預設)
LAG是拖的意思,以資料為單位,所以會將上面的資料往下帶,上面變null(預設)


SELECT
    sal,
    LAG(sal) OVER(PARTITION BY deptno ORDER BY sal) lag1,
    LAG(sal, 1) OVER(PARTITION BY deptno ORDER BY sal) lag2,
    LAG(sal, 2) OVER(PARTITION BY deptno ORDER BY sal) lag3,
    LAG(sal, 2, -99) OVER(PARTITION BY deptno ORDER BY sal) lag4,
    LEAD(sal, 2) OVER(PARTITION BY deptno ORDER BY sal) lead
FROM EMP
WHERE deptno = 30;

※結果:

※LAG(sal)不加參數,等同LAG(sal, 1),1表示往下一行

※加第3個參數,可將移動的變成你加的第3個參數,不加就會顯示null,型態要和第2個參數一樣



※CUME_DIST

針對位置的百分比


SELECT
    deptno,
    sal,
    ROUND(CUME_DIST() OVER(PARTITION BY deptno ORDER BY sal), 2) c
FROM emp;

※結果:

※以20部門為例,總共有五筆,所以每筆是1/5=0.2,但要注意薪水一樣,全部都會以最後的為主



※NTILE

NTILE要看結果比較好說明


SELECT
    deptno,
    sal,
    -- SUM(sal) OVER(PARTITION BY deptno ORDER BY sal) sum,
    NTILE(1) OVER(PARTITION BY deptno ORDER BY sal) n1,
    NTILE(2) OVER(PARTITION BY deptno ORDER BY sal) n2,
    NTILE(3) OVER(PARTITION BY deptno ORDER BY sal) n3,
    NTILE(4) OVER(PARTITION BY deptno ORDER BY sal) n4,
    NTILE(5) OVER(PARTITION BY deptno ORDER BY sal) n5,
    NTILE(6) OVER(PARTITION BY deptno ORDER BY sal) n6,
    NTILE(7) OVER(PARTITION BY deptno ORDER BY sal) n7
FROM emp;

※結果:

※以30部門的N3為例,30部門有6筆,6/3=2,所以2筆資料為一組
20部門的N3則是5/3四捨五入後=2,所以也是2筆資料為一組



※RATIO_TO_REPORT

相對於欄位值的百分比


SELECT deptno, count(deptno) c FROM emp GROUP BY deptno ORDER BY deptno;
    
SELECT
    deptno,
    sum(sal) sum,
    ROUND(RATIO_TO_REPORT(sum(sal)) OVER(), 2) rs,
    ROUND(avg(sal), 2) avg,
    ROUND(RATIO_TO_REPORT(avg(sal)) OVER(), 2) ra
FROM emp
GROUP BY deptno
ORDER BY deptno;

※結果:

※第一張圖,可以看到10部門有3筆;20部門有5筆;30部門有6筆

※3個部門全部的薪水是 8750 + 10875 + 9400 = 29025
10部門的RS是用8750 / 29025算出來的,其他部門依此類推

※3個部門全部的平均薪水是2916.67 + 2175 + 1566.67 = 6658.34
10部門的RA是用 2916.67 / 6658.34算出來的,其他部門依此類推

Filter (Servlet 八)

※Web.xml設定

<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>
    
<filter>
    <filter-name>zzz</filter-name>
    <filter-class>filter.HelloFilter</filter-class>
    <init-param>
        <param-name>ghi</param-name>
        <param-value>333</param-value>
    </init-param>
    <init-param>
        <param-name>jkl</param-name>
        <param-value>444</param-value>
    </init-param>
</filter>
<filter-mapping>
    <filter-name>zzz</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>
    
<servlet>
    <servlet-name>ooo</servlet-name>
    <servlet-class>controller.TestServletConfig</servlet-class>
</servlet>
<servlet-mapping>
    <servlet-name>ooo</servlet-name>
    <url-pattern>/xxx</url-pattern>
</servlet-mapping>



public class HelloFilter implements Filter {
    @Override
    public void init(FilterConfig config) throws ServletException {
        System.out.println(config.getInitParameter("abc"));
        System.out.println(config.getInitParameter("ghi"));
        System.out.println("init方法!");
    }
    
    @Override
    public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain)
            throws IOException, ServletException {
        System.out.println("doFilter方法前!");
        // chain.doFilter(req, resp);
        // System.out.println("doFilter方法後!");
    }
    
    @Override
    public void destroy() {
        System.out.println("destroy方法!");
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

※abc是null,因為filter比較快

※註解的doFilter不寫會看不到頁面,因為它沒把棒子傳給下一位,沒給就表示自己是終點,頁面上就會一片空白,而之後的程式碼也會執行



※Annotation設定

@WebFilter(
    urlPatterns = "/*", 
    initParams = { 
        @WebInitParam(name = "ghi", value = "333"),
        @WebInitParam(name = "jkl", value = "444")
    }
)
public class HelloFilter implements Filter {
    // ...
}


※過濾器和攔截器的區別

1.攔截器使用反射;過濾器使用回調
2.攔截器不依賴 Servlet 容器;過濾器會依賴
3.攔截器只針對 request 請求;過濾器都可以
4.攔截器可多次調用;過濾器只在容器初始化時被調用