2016年4月7日 星期四

&、DEFINE、ACCEPT (DML 十三)

互動變數並不是Oracle SQL裡,是在一本叫SQL*PlusR User's Guide and Reference裡面
這篇的最後一張圖,去官網往下拉一點就會看到

首先要啟用互動變數的功能,其實預設已經啟用,但我不確定每個版本都預設啟用
官網有說明,我的這篇PL/SQL也有講到一些,如果不懂PL/SQL就看截圖就好,這篇就不截圖了

只要打&xxx,xxx自己隨便取,放在SQL的任何地方都可以,反正就是個字串,一執行就會跳出畫面,這時你打什麼就是什麼,但如果沒啟用就會出「ORA-01008: 部分變數未被連結」的錯誤



※啟用/停用 換符號

SET DEFINE ON/OFF;
SELECT * FROM emp where deptno = &p1;
    
SET DEFINE &c;

※第一行就是啟用/停用,第二行就可以用了,預設要用「&」

※如果不想用預設的「&」,那就打最後一行,一打會跳出視窗,你輸入什麼就用什麼

※官網的DEF[INE]表示打DEF和DEFINE都可以,「[]」不是必要的,下面介紹的語法還有很多都是這樣



※&、&&

SELECT * FROM emp where deptno = &p1;
SELECT * FROM emp where deptno = &&p1;

※第一行每次執行都要打一次

※第二行打一次就可以執行很多次,如果要清除下面會介紹

※第二行的變數名稱和第一行一樣,所以執行過第二行,第一行的變數值也有值了,就不會再跳出畫面了



※清除

SELECT &p, sum(sal) FROM emp GROUP BY &p;
SELECT &&p, sum(sal) FROM emp GROUP BY &p;

※第一行兩個變數一樣,但要打兩次,所以改成第二行的寫法

※第二行的&&要寫在前面,後面的變數才知道不用再打了

※變數存起來只是暫時的,重登入就消失了,或者執行以下的語法
UNDEFINE p;
    
UNDEFINE p p1;

※第一行清除一個變數的值

※第二行是要清除多個變數,中間用空格隔開



※DEFINE

DEFINE xxx = 20;
SELECT * FROM emp WHERE deptno = &xxx;

※除了用「&」,也可以用DEFINE,看官網介紹



※ACCEPT

前本介紹的互動還不夠好,ACCEPT可以有文字,看官網介紹
語法如下:
ACC[EPT] variable [NUM[BER] | CHAR | DATE | BINARY_FLOAT | BINARY_DOUBLE] [FOR[MAT] format] [DEF[AULT] default] [PROMPT text|NOPR[OMPT]] [HIDE]


--1
ACCEPT yyy DATE FOR 'YYYY-MM-DD' NOPROMPT
SELECT * FROM emp WHERE hiredate < to_date('&yyy', 'YYYY-MM-DD');
    
--2
ACCEPT xxx DEFAULT 30 PROMPT '請輸入部門編號:'
SELECT * FROM emp WHERE deptno = &xxx;
    
--3
ACCEPT ooo FORMAT A2 PROMPT '請輸入部門編號:' HIDE
SELECT * FROM emp WHERE deptno = &ooo;

※第一組的NOPROMPT(NOPROMPT不寫也是一樣)和上面介紹的一樣,沒有文字訊息
但這一組有設定日期的格式

※第二組有預設值,但跳出的視窗有確定和取消,要按確定才有用

※第三組後本有個HIDE,表示打出來的字變成「*」號

2016年4月6日 星期三

JSP 和 Servlet 的關係 (JSP 2.x 一)

Oracle的JSP spec,各版本連結 2.02.12.22.3
在spec的1.8.3 有9個隱含物件(就是不用new了,直接拿來使用)

.request:javax.servlet.http.HttpServletRequest
.response:javax.servlet.http.HttpServletResponse
.pageContext:javax.servlet.jsp.PageContext
.session:javax.servlet.http.HttpSession
.application:javax.servlet.ServletContext
.out:javax.servlet.jsp.JspWriter
.config:javax.servlet.ServletConfig
.page:java.lang.Object
.exception:java.lang.Throwable

以上的9個的scope如下:
request 為 request scope
session 為 session scope
application 為 application scope
其他都是page scope



※測試準備

<h1>JSP路徑</h1>
<%=request.getServletContext().getAttribute("javax.servlet.context.tempdir")%>

※以上的程式碼在Servlet 四有提過

※此行印出
D:\eclipse-jee-mars-R-win32-x86_64\workspace\.metadata\.plugins\org.eclipse.wst.server.core\tmp0\work\Catalina\localhost\TestJSP,最後的TestJSP是專案名稱

※不同的container可能不一樣,我是用tomcat8

※到目錄後還要再進去org\apache\jsp,這樣才會看到由servlet變出來的.java和.class



※測試
<%!int i = 0;%>
<%
    int j = 10;
    i++;
    j++;
%>
<%=i%><br />
<%=j%>

※寫這樣然後去剛剛的.java看一下,大部分都是自動產生出來的,我把自動的去除以後,大概如下的樣子:
public final class exercise_jsp extends HttpJspBase implements JspSourceDependent, JspSourceImports {
    int i = 0;
    
    try {
        out.write('很多HTML');
        
        int j = 10;
        i++;
        j++;
        
        out.write('很多HTML');
        out.print(i);
        out.print(j);
    } catch (Throwable t) {
        // ...
    } finally {
        // ...
    }
}

※可以看出JSP一樣有_jspInit、_jspDestroy、_jspService

※exercise.jsp是我取的名字,會變成 exercise_jsp.java,也就是將「.」變成「_」,然後產生.java,最後編譯成.class

※類別是final的

※繼承和實作都是org.apache.jasper.runtime 包
繼承 HttpJspBase,實作 JspSourceDependent 和 JspSourceImports

※<%! %>:寫全域變數和方法的地方

※<% %>:寫區域變數和方法內容的地方,看生出來的.java就可以知道,所以方法前沒辦法加synchronized

※<%= %>:印出變數,類似servlet的PrintWriter,要注意最後沒有「;」

※<%-- --%>:註解,和以下三種相同
<%// ... %>
<%
  /*
...
*/
%>
<%
/**
...
*/
%>
因為<% %>和<%! %>本來就是寫java的地方,所以當然可以用這三種註解,以上這幾種註解叫伺服器端註解

<!-- ... -->:這是HTML的註解,不是JSP的,這叫用戶端註解
和伺服器端註解比較起來,不同的地方是按右鍵-->檢視原始碼時,伺服器端的看不到

※剛剛沒有在<%! %>裡面寫方法,這裡寫一下
<%!
    public String xxx(){
        return "abc";
    }
%>
<%
    String s = this.xxx() + "def";
%>
<%=s%>

※寫static也可以,但不能用exercise_jsp.xxx(),直接呼叫就好

※如果將方法寫在<% %>就會編譯錯誤

登入跳轉 (Servlet 九)

跳轉之前講的不太清楚,這裡寫一個登入功能會比較容易了解

我看了API後,理解如下:
HttpServletResponse的sendRedirect
參數不是「/」開頭,為相對路徑
是「/」開頭,表示ServletContext路徑
如果resp已commit,會拋出IllegalStateException
1.瀏覽器req給servlet
2.servlet將設定的路徑回傳給瀏覽器
3.瀏覽器依這個路徑再req
4.servlet再回傳
所以有兩次的req
因為是resp,所以網址會改變


ServletRequest的getRequestDispatcher取得javax.servlet的RequestDispatcher
有以下兩個方法

1.forward(主控權是別人)
轉發給servlet、JSP、HTML
如果resp已commit,會拋出IllegalStateException

2.include(主控權是自己)
將servlet、JSP、HTML包括到自己的servlet
所包含的servlet不能改變resp狀態碼或設定header,如果有改變將被忽略。

這兩個方法都是req,所以網址不會改變


※web.xml

<filter>
    <filter-name>zzz</filter-name>
    <filter-class>org.apache.catalina.filters.SetCharacterEncodingFilter</filter-class>
    <init-param>
        <param-name>encoding</param-name>
        <param-value>UTF-8</param-value>
    </init-param>
</filter>
<filter-mapping>
    <filter-name>zzz</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>



※LoginServlet.java

@WebServlet(urlPatterns = { "/xxx" })
public class LoginServlet extends HttpServlet {
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        String enc = super.getServletConfig().getInitParameter("encoding");
        resp.setContentType("text/html; charset=" + enc);
    
        String username = req.getParameter("username");
        String password = req.getParameter("password");
    
        if (username.trim().equals("aaa") && password.trim().equals("bbb")) {
            String fruit = req.getParameter("ooo");
            String[] xxx = req.getParameterValues("xxx");
            List<String> tl = Arrays.asList("ttt", "lll");
    
            if (xxx != null) {
                for (String s : req.getParameterValues("xxx")) {
                    // 老虎獅子吃香蕉
                    if (tl.contains(s) && "bbb".equals(fruit)) {
                        // forward 轉發給設定的servlet、jsp、HTML,主導權也送出去
                        System.out.println("before:" + req.getServletPath());
                        req.getRequestDispatcher("/abc").forward(req, resp);
                        // req.getRequestDispatcher("/WEB-INF/success.jsp").forward(req,
                        // resp);
                        System.out.println("after:" + req.getServletPath());
    
                        // include包含設定的servlet、jsp、HTML,主導權並沒有送出去
                        // System.out.println("before:" + req.getServletPath());
                        // req.getRequestDispatcher("/abc").include(req, resp);
                        // req.getRequestDispatcher("/WEB-INF/success.jsp").include(req,
                        // resp);
                        // System.out.println("after:" + req.getServletPath());
                        break;
                    } else {
                        if ("rrr".equals(s)) {
                            resp.sendRedirect(req.getContextPath() + "/fail.jsp");
                            System.out.println("456");// 有執行到
                        } else {
                            continue;
                        }
                    }
                }
            } else {
                resp.sendRedirect("http://www.google.com");
            }
        } else {
            // sendRedirect是兩次的request,所以req.setAttribute抓不到
            // req.setAttribute("fail", "帳密錯誤");
            req.getSession().setAttribute("fail", "帳密錯誤");
            resp.sendRedirect(req.getContextPath() + "/login.jsp");
        }
    }
}

※sendRedirect也可以直接呼叫一般的網址,而WEB-INF是特殊的資料夾,resp當然不能拿來用

※include、forward 差在 getRequestDispatcher 是另一個 servlet 時
forward 後,主導權不在,include 還在
所以 forward 的 response 不會執行;而 include 跳轉的頁面返回後,繼續執行 response

假設有 ServletA 和 ServletB
使用 forward 跳頁後就不管了
使用 include 跳頁執行完後,會再回來繼續執行其他的部分


※TestAaa.java

@WebServlet(urlPatterns = { "/abc" })
public class TestAaa extends HttpServlet {
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        System.out.println("in:" + req.getServletPath());// include進來是xxx,forward進來是abc
    }
    
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        System.out.println("get接收");
    }
}

※include和forward呼叫這支servlet時,會發現不一樣的地方

※如果login.jsp呼叫doGet,那abc也會呼叫doGet



※login.jsp

<form action="xxx" method="post">
    <font color="red">${fail}</font><br />
    使用者:<input type="text" name="username" value="user" /><br />
    
    密碼:<input type="password" name="password" value="pass" /><br />
    
    登入密語:
    <input type="checkbox" name="xxx" value="ttt" />tiger
    <input type="checkbox" name="xxx" value="rrr" />rabit
    <input type="checkbox" name="xxx" value="lll" />lion<br />
    
    <input type="radio" name="ooo" value="aaa" />apple
    <input type="radio" name="ooo" value="bbb" />banana
    <input type="radio" name="ooo" value="ppp" />pineapple<br />
    <input type="reset" />
    <input type="submit" />
</form>

※fail.jsp和success.jsp自己隨便寫

※有些書用servlet寫jsp,可是現在工作時都在用MVC,所以現在應該很少人這樣寫了,檢核使用者輸入的值也都是前端用jQuery、ajax了,所以我全部沒寫

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