2018年1月16日 星期二

一般表格運算式(Common Table Expression: CTE)

本文將探討一般表格運算式(Common Table ExpressionCTE)的應用,MSSQLORACLE遵循SQL-99規範中所提供一種新的語法WITH,以是否為遞迴(Recursive)運算區分成查詢暫存遞迴呼叫兩種。

查詢暫存(非遞迴的運用)
過去需要執行較為複雜的SQL指令時,或局部相同SQL段落需多次被使用等狀況,前述情況下會考量效能的問題,而將某些局部運算的結果先暫存於資料庫的暫存表格中,以供後續再使用或重複使用, CTE可針對查詢指令予以命名及定義如同區域變數—般,以供同一命令中後續查詢參考使用。語法如下:
WITH <subquery_name>
AS
     (
     < SQL statement>
     )
SELECT <Column-List>
FROM <subquery_name>

以下將取得訂單資料中各種書籍訂購數量超過平均值的書籍資料,SQL語法及結果如下所示:
MSSQL可於WITH後進行UPDATEDELETE運算,先前文章中應用於刪除重複資料使用;ORACLE若於WITH運算後使用UPDATEDELETE指令,則將出現ORA-00928:遺漏SELECT關鍵字錯誤

非遞迴式的WITH指令與衍生資料表(Derived Table相當類似,可供後續參照使用,但與衍生表格不同之處在於每次使用查詢語句時無需重複輸入。在主查詢進行前,先以WITH宣告暫時性表格的名稱及內容,接著進行主查詢則以WITH名稱代替,並可重複出現,此與傳統的衍生資料表夾雜於主查詢中的語法相比更為簡潔易讀。


遞迴的運用
MSSQL 2005(含)以上提供遞迴查詢,ORACLE雖於9i R2提供WITH指令,但於11g R2(含)以上版本才提供遞迴功能。本節將只針對遞迴運算部分介紹,以下分別介紹ORACLESQL SERVERWITH上的使用,兩者語法極為類似,後續範例只需針對資料庫函數進行局部修改即可。
WITH cte_name ( column_name [,...n] )
AS
(
CTE_query_definition --遞迴起點(或稱錨點)
UNION ALL
CTE_query_definition--遞迴呼叫區塊,將參照本身即cte_name一次
)

遞迴WITH與一般程式語言撰寫遞迴呼叫基概念雷同,簡單來說,當一個函數呼叫自己時,此方式即可稱為遞迴呼叫,以下為遞迴WITH
l   遞迴WITH定義必須包含遞迴起點(或稱錨點)和遞迴呼叫區塊兩部分。
l   遞迴起點(或稱錨點)和遞迴呼叫區塊必須使用UNION ALL連結。
兩者是採用UNION ALL連結,UNION ALL使用上要求兩者資料的欄位數目相符及資料型態需為同一類型,在此要求更為嚴格資料型態及欄位長度(精準度)也必須完全相同
l   遞迴呼叫中的FROM子句中,只能呼叫參考CTE expression_name一次
l   確認終止條件:對於遞迴呼叫需特別注意遞迴結束條件,為了避免開發期間遞迴結束條件遺漏或不當使用,可設定最大遞迴深度以限制其展開深度。
l   遞迴呼叫中的FROM子句中,MSSQL僅可使用Inner Join不可使用Outer JoinORACLE則無此限。



首先,應用遞迴WITH指令產生連續的數值數列,為避免無窮迴圈對系統效能的影響,SQL SERVER預設最大遞迴深度為100,可採用MAXRECURSION設定最大遞迴深度,為動態產生110000序號,將設定最大遞迴深度為10000。第二個範例產生日期序列,並用以產生科學園區四二輪週報。SQL語法分別說明如下:

數值序列110000
WITH Tally(N)
AS
  (
  --1.錨點(Anchor)
  SELECT 1 N        --起始條件為
  UNION ALL        --UNION ALL串連兩區塊
  --2.遞迴區塊
  SELECT N+1       --遞迴條件為累加
  FROM Tally        --呼叫自已
  WHERE N<=10000  --結束條件
  )
  SELECT N
FROM TALLY
OPTION (MAXRECURSION 10000)  --SQL SERVER(設定最大深度)

註:以上SQLMSSQLORACLE則需增加FROM DUAL以及將OPTION部分移除。

前述SQL是利用Recursive產生數值序列,虛擬資料表欄位僅有一個數值型態的欄位欄位名稱為N,數列自1開始至10000,重點說明如下:
n   遞迴起點(或稱錨點)
SELECT 1 N
此例為自建數值序列並無任何參照資料表,將以自建方式產錨點,欄位僅有一個名稱為N,型態則由欄位資料值1所決定,資料判別其型態為INT

n   遞迴區塊
SELECT N+1       --遞迴條件為累加
FROM Tally       --呼叫自已
WHERE N<=10000  --結束條件
上述FROM子句所指定的Tally即為遞迴本身名稱,由此可達成自己呼叫自己的遞迴運算結構。WHERE子句指定的即為遞迴結束條件。SELECT子句則產生累加數值序列,前述省略欄位別名修改為N+1 AS N,資料型態同樣為以自行判別方式。遞迴起點將以UNION ALL將兩者連結。


前述兩項重點即可決定整個遞迴呼叫,由於SQL SERVER預設遞迴最大深度為100,為產生10000個序列則必須以指定方式變更最大遞迴上限為10000,語法如下:OPTION (MAXRECURSION 10000)

2018年1月13日 星期六

找到含有『特定字串』的Stored Procedure

以下說明是找到含有『特定字串』的所有Stored ProcedureORACLE可以查詢ALL_SOURCEMSSQL則可查詢sys.proceduressys.sql_modulesINFORMATION_SCHEMA.ROUTINES,在此只以sys.sql_modules進行說明。SQL如下:
SQL SERVER
ORACLE
SELECT OBJECT_NAME(object_id)
FROM sys.sql_modules
WHERE Definition LIKE '%DEL_FLAG%'
    AND OBJECTPROPERTY(object_id, 'IsProcedure') = 1

SELECT DISTINCT S.NAME, S.TYPE
FROM ALL_SOURCE S
WHERE TEXT LIKE '%WNTD_RATE%'
參考資料: sys.sql_modules (Transact-SQL), http://technet.microsoft.com/zh-tw/library/ms175081.aspx



剔除地址中全形數字字元

剔除地址中全型數字字元最直接作法即可使用REPLACE函數,但需採用呼叫函數10次,或近似迴圈想法才可解決,以下介紹TRANSLATE函數及REGEXP_REPLACE函數,新版MSSQL已提供TRANSLATE函數,MSSQL使用者可嘗試使用。

1.  TRANSLATE函數
此函數可使用字元對應轉換(即翻譯),此函數將依照轉換(string_to_replace:replacement_string)定義(N:1)進行轉換,如無可對應者則轉為空字串,請特別注意replacement_string不得為空字串,否則將轉為NULL。範例中也展示如何將全形數字轉為半形數字。
TRANSLATE( string1, string_to_replace, replacement_string )

2.  REGEXP_REPLACE函數
REGEXP_REPLACE(source_string
                , pattern
               [, replace_string [, position [,occurrence, [match_parameter]]]]
               )


SELECT Addr
       , TRANSLATE(Addr, '@0123456789', '@')
       , TRANSLATE(Addr, '@0123456789', '@0123456789')      
       , REGEXP_REPLACE(Addr, '[0123456789]','')
FROM
    (      
    SELECT '10090台北市中正區羅斯福路9段1號3樓' Addr
    FROM DUAL
    UNION ALL
    SELECT '100台北市中正區幸忠孝東路9段19號4樓'
    FROM DUAL
    UNION ALL
    SELECT '台北市中正區忠孝東路5段49號'
    FROM DUAL

     ) 

[MSSQL] 如何簡便快速將[日期]、[時間]兩欄位合併轉換為日期型態

許多設計偏好將[日期型態]資料拆分成[日期][時間]兩個欄位資料(字串或數值型態),對於ORACLE而言,可直接使用TO_DATE函數進行各態樣的日期字串的轉換成日期型態。MSSQL透過CAST進行日期轉換,但需符合兩項重點: 1.日期、時間中間需含空白及2. 時間間隔符號(:),因此需先處理以符合轉換格式,也可使用本範例所介紹agent_datetime內建函數,簡化煩瑣處理步驟。 

SELECT TRADE_DATE, TRADE_TIME
     --方法1: 一般作法 (需先處理符合日期轉換格)
     , CAST(E.TRADE_DATE + ' '
            + SUBSTRING(E.TRADE_TIME, 1, 2) + ':'
            + SUBSTRING(E.TRADE_TIME, 3, 2) + ':'
            + SUBSTRING(E.TRADE_TIME, 5, 2)
                 AS DATETIME
            ) TR_DT
     ----
     --方法2: 使用agent_datetime函數
     , msdb.dbo.agent_datetime(TRADE_DATE, TRADE_TIME) TR_DT2
FROM     
    (
    --嘗試轉型為數字
    --SELECT 20180112 TRADE_DATE,    081500 TRADE_TIME--重要範例
    --UNION ALL
    SELECT '20180112' TRADE_DATE, '081500' TRADE_TIME
    UNION ALL
    SELECT '20180112' TRADE_DATE, '131500' TRADE_TIME
    UNION ALL  
    SELECT '20180112' TRADE_DATE, '131530' TRADE_TIME
    ) E


[日期型態]資料拆分成[日期][時間]兩個文字或數值型態的欄位資料,雖可簡化純指令式的Ad hoc查詢處理,但後續如需日期運算則內建函數無法直接使用,需先行轉態。本範例所介紹agent_datetime內建函數可輕鬆完成完成換程序,如使用數值型態方式時,則更可凸顯其方便性,以早上08:15:00如以數值型態儲存時則變為081500,當轉換時,更需額外處理補足碼問題。

2018年1月10日 星期三

找出最後一碼為小寫字母之資料

MSSQL
方法(1):用ASCII函數取得小寫字母值域。
方法(2)LIKE指令,搜尋中括號所列任一之小寫字母。
方法(3)PATINDEX,與LIKE雷同。

SELECT Crncy
, RIGHT(Crncy, 1)
, CASE WHEN ASCII(RIGHT(Crncy, 1)) BETWEEN 96 AND 123 THEN 1 ELSE 0 END        "M-1"
, CASE WHEN Crncy COLLATE Latin1_General_CS_AI LIKE '%[abcdefghijklmnopqrestuvwxyz]'
THEN 1 ELSE 0 END "M-2"
, PATINDEX('%[abcdefghijklmnopqrestuvwxyz]%', Crncy COLLATE Latin1_General_CS_AI) "M-3" 
FROM
    (VALUES('UsD'), ('EUR'), ('BRL'), ('GBp')) AS Ex(Crncy)








ORACLE
方法(1):用ASCII函數取得小寫字母值域。
方法(2)REGEXP_INSTR
SELECT COLUMN_VALUE
   , SUBSTR(COLUMN_VALUE, -1) BACKEND
   , CASE WHEN ASCII(SUBSTR(COLUMN_VALUE, -1)) BETWEEN 97 AND 122 THEN 1 ELSE 0 END "M-1"
   , REGEXP_INSTR(COLUMN_VALUE, '[[:lower:]]$') "M-2a"
   , REGEXP_INSTR(COLUMN_VALUE, '[a-z]$') "M-2b"
FROM TABLE(SPLIT_TBL('UsD','EUR','BRL','GBp'))










註: CREATE OR REPLACE TYPE SPLIT_TBL AS TABLE OF VARCHAR2(32767)