2013年9月3日 星期二

取得目前自動編號值

取得目前自動編號值對於相依性資料非常重要,如訂單/訂單明細,先將訂單資料儲存至訂單資料表,存入後取得此筆訂單所產生的序號,再者將訂單各筆明細資料與此筆序號一併存入訂單明細表中,以兩者建立關聯。ORACLESEQUENCE物件及SQL SERVERIDENTITY欄位屬性,雖然可分別利用CURRVAL虛擬欄位及IDENT_CURRENT函數取得現行序號值,但此種方式所取得序號值,僅可提供序列值現值概略參考之用,並不可代表實際存入序號值需取得該筆資料實際存入該欄位的資料值才可確知ORACLE提供RETURNING指令,取得剛存入資料表中欄位的資料值,可使用於取得SEQUENCE物件產生並存入資料表時的實際數值MSSQL可由資料表的欄位屬性中指定IDENTITY屬性,則該欄位即可自動產生連續的數值序號,可採用SCOPE_IDENTITY()取得工作階段(Session)有效範圍內最後一個產生的識別值,或OUTPUT指令取得表格異動資料值兩種方式。 以下將以C#為例,取得存入該筆資料的自動編號(MSSQL官方文件稱為識別值)

MSSQL
1.  SCOPE_IDENTITY()
MSSQL2000()提供SCOPE_IDENTITY()函數,可用來取得目前工作階段(Session),有效範圍內最後產生的識別值,『範圍』是指在同一個預儲程序、觸發程序、函數或批次等。另外的類似函數@@IDENTITY,由於此函數並未限制特定範圍,部分情況下將發生錯誤,因此不建議使用。以下將採用SCOPE_IDENTITY()函數以取得存入Event_Log表格中所產生的識別值。
string sConnStr ="Data Source=localhost;Initial Catalog=sqltips;User ID=sa;Password=admin;";
string sSQL = string.Empty;
sSQL += "INSERT INTO Event_Log \n ";
sSQL += "         (Event ) \n ";
sSQL += "VALUES \n ";
sSQL += "         (@Event ) \n ";
sSQL += "SET @LogId = SCOPE_IDENTITY() \n ";
//1. SET指令將SCOPE_IDENTITY()儲存至@LogId變數中

SqlConnection conn = new SqlConnection(sConnStr);
conn.Open();

SqlCommand cmd = new SqlCommand(sSQL, conn);
cmd.Parameters.Add("@Event", SqlDbType.Char);
cmd.Parameters["@Event"].Value = "'~!@#$%^&*";

//2. 設定輸出參數@LogId,接收傳回值
SqlParameter pmtLogId = new SqlParameter("@LogId", SqlDbType.Int);
pmtLogId.Direction = ParameterDirection.Output;
cmd.Parameters.Add(pmtLogId);

cmd.ExecuteNonQuery();
//3. 將輸出參數資料值轉換型態並儲存至nLogId
int nLogId = (int)pmtLogId.Value;

SQL SERVER提供3種取得IDENTITY識別值的函數,以下一併列出3種函數及其說明。
函數
說明
@@IDENTITY
傳回目前工作階段中,所有範圍內任何資料表所產生的最後一個識別值。
SCOPE_IDENTITY()
傳回目前工作階段中,目前範圍中任何資料表產生的最後一個識別值。
IDENT_CURRENT()
傳回特定資料表的最後一個識別值。
語法: IDENT_CURRENT('table_name')
其中SCOPE_IDENTITY()函數將取得目前工作階段中,目前範圍中內任何資料表所產生的最後一個識別值;而@@IDENTITY將取得目前工作階段中,所有範圍內任何資料表所產生的最後一個識別值,恐有誤取識別值之可能;IDENT_CURRENT()函數則取得特定資料表中最後一個識別值。

2.  OUTPUT
OUTPUT指令可擷取INSERTUPDATEDELETE等操作所影響資料列狀態,可應用於取得新增作業所產生的識別值的作法,此指令可將異動後/前資料列狀態(請注意對應順序)資料分別置於INSERTEDDELETED虛擬資料表中,藉此將更加有效率且完全精準取得識別值。以下將採用OUTPUT命令以取得存入Event_Log表格中所產生的識別值。
string sConnStr ="Data Source=localhost;Initial Catalog=sqltips;User ID=sa;Password=admin;";
string sSQL = string.Empty;
sSQL += "INSERT INTO Event_Log \n ";
sSQL += "         (Event ) \n ";
sSQL += "OUTPUT INSERTED.Log_Id \n";
sSQL += "VALUES \n ";
sSQL += "         (@Event ) \n ";
//1. OUTPUT子句將INSERTED虛擬表格欄位Log_Id輸出

SqlConnection conn = new SqlConnection(sConnStr);
conn.Open();

SqlCommand cmd = new SqlCommand(sSQL, conn);

cmd.Parameters.Add("@Event", SqlDbType.Char);
cmd.Parameters["@Event"].Value = "'~!@#$%^&*";

//2.執行,並將結果(輸出值)存入變數sLogId
string sLogId = cmd.ExecuteScalar().ToString();

上述程式碼中INSERTED為虛擬資料表,用以代表用新增或更新作業後所加入的值。在此將取得在入Event_Log表格中所產生的識別值,即當寫入Event_Log資料表時,Log_Id欄位的IDENTITY識別值,其語法為INSERTED.Log_Id

ORACLE
ORALCE8i起提供RETURNING子句,可用於取得存入表格的實際資料值,範例程式如下。
string sConnStr ="Data Source=demodb;User Id=sqltips;Password=admin;";
string sSQL = string.Empty;
sSQL += "INSERT INTO Event_Log \n ";
sSQL += "         (Log_Id, Event) \n ";
sSQL += "VALUES \n ";          
sSQL += "         (Event_Log_Seq.NEXTVAL, :Event) \n ";
sSQL += "RETURNING Log_Id INTO :newLogId \n";

OracleConnection conn = new OracleConnection(sConnStr);
conn.Open();

OracleCommand cmd = new OracleCommand(sSQL,conn);

cmd.Parameters.Add("Event", OracleDbType.Varchar2);
cmd.Parameters["Event"].Value = "'~!@#$%^&*";

cmd.Parameters.Add("newLogId", OracleDbType.Int32, ParameterDirection.Output);
cmd.ExecuteNonQuery();

string sLogId=cmd.Parameters["newLogId"].Value.ToString();

前述是以RETURNING指令,語法如下。
RETURN <expr> INTO :<variable>
將存入Log_id欄位的實際序列值,儲存至newLogId變數中,變數前方需以冒號(:)為引導字元,否則將發生錯誤。另外C#中再建立一個輸出參數以接收此變數值。曾嘗試採用微軟的OLEDB Provider以連結ORACLE,但發生不支援RETURNING的錯誤訊息,本範例中是以ORACLE官方提供的Oracle Data Access Components(ODAC)元件,可自行至ORACLE官方網站中下載。

2013年9月1日 星期日

以產品代碼第一碼為查詢條件找出符合之資料-Regular Express概述

本範例將查詢特定類型的產品資料,以產品代碼(Prod_No)第一碼為判斷依據,將列出第一碼為BCD等三類產品,SQL及執行結果如下:
SQL
結果
SELECT *
FROM
 (       
SELECT 'AC20' Prod_No
UNION ALL
SELECT 'BD20'
UNION ALL
SELECT 'CF20'
UNION ALL
SELECT 'DF40'
UNION ALL
SELECT 'EB20'
UNION ALL
SELECT 'FA30'
) Prod   
WHERE 1=1
 AND SUBSTRING(Prod_No, 1, 1) IN ('B', 'C', 'D')
ORDER BY 1

* 1. 以上資料只是譂述概念,實務上通常會在產品資料表(Prod)中產品代碼建立INDEX
    2. MSSQL語法為例,ORACLE請自行修改。

以上SQL利用SUBSTRING擷取出第一碼,並據以判斷是否符合BCD三者,查詢結果確實可符合所需,語法上非常具有可讀性,由於已應用函數於查詢欄位上,不論Prod_No是否已建立INDEX,均迫使資料庫以FULL TABLE SCAN方式進行,以下將列出SUBSTR(ING)LIKE+ORRegular Express3種方法,不過建議採行第2或第3種方法。

MSSQL
ORACLE
SELECT *
FROM
       (       
       SELECT 'AC20' Prod_No
       UNION ALL
       SELECT 'BD20'
       UNION ALL
       SELECT 'CF20'
       UNION ALL
       SELECT 'DF40'
       UNION ALL
       SELECT 'EB20'
       UNION ALL
       SELECT 'FA30'
    ) A   
WHERE 1=1
--方法1. SUSBSTRING
-- AND SUBSTRING(Prod_No, 1, 1)
--               IN ('B', 'C', 'D')
--方法2: LIKE + OR
/*AND (Prod_No LIKE 'B%'             
            OR Prod_No LIKE 'C%'
                 OR Prod_No LIKE 'D%'
                )*/
--方法3: LIKE [] (類似regular express語法)
       AND Prod_No LIKE '[BCD]%'       
ORDER BY 1
SELECT *
FROM
       (       
       SELECT 'AC20' Prod_No
     FROM DUAL
       UNION ALL
       SELECT 'BD20' FROM DUAL  
       UNION ALL
       SELECT 'CF20' FROM DUAL 
       UNION ALL
       SELECT 'DF40' FROM DUAL 
       UNION ALL
       SELECT 'EB20' FROM DUAL 
       UNION ALL
       SELECT 'FA30' FROM DUAL 
    ) A   
WHERE 1=1
--方法1: SUSBSTR
-- AND SUBSTR(Prod_No, 1, 1)
--             IN ('B', 'C', 'D')
--方法2: LIKE + OR
/*AND (Prod_No LIKE 'B%'                         
            OR Prod_No LIKE 'C%'
                 OR Prod_No LIKE 'D%'
                )*/
--方法3: REGEXP_LIKE
AND REGEXP_LIKE (Prod_No, '^[BCD]') 
ORDER BY 1
以上MSSQL/ORACLE兩種資料庫,所列之3種方法為對應之類似用法,將3種方法整理/說明如下表:

#
方法
語法
IX
說明
1
SUBSTR(ING)
SUBSTRING(Prod_No, 1, 1)              
     IN ('B', 'C', 'D')
N
INDEX失效,不建議使用。
2
LIKE + OR
Prod_No LIKE 'B%'            
OR Prod_No LIKE 'C%'
OR Prod_No LIKE 'D%'
Y
INDEX有效,若所查詢類別較少可用,但較多時因可讀性較差,則不建議使用。
3
Regular Express
S: Prod_No LIKE '[BCD]%'
O: REGEXP_LIKE (Prod_No, '^[BCD]')
Y
INDEX有效,語法上較少人使用,建議增加註解或教育訓練提昇技術能力。


本範例如何應用模糊比對進行篩選,以下簡述其概念用法,建議可自行至官方網站查閱相關文件。
MSSQL
是基於LIKE子句增加特徵(Pattern)比對運算,因此由萬用字元(%)置放位置即可決定字首、字尾或任何位置的模糊比對,可提供類似正則表示式(Regular Express)運算模式

ORACLE
10g起支援Regular Express的應用,本範例將使用REGEXP_LIKE函數,除使用特徵(Pattern)比對運算外,尚需增加位置指示字元如^對應輸入的字首、$』對應輸入的結尾,因『^』在特徵(Pattern)比對中代表『非』、『不存在』,請特別注意此兩者的差異。如下:


常見SQL (MSSQL )
MSSQL
ORACLE
身份
字號

SUBSTRING(ID_NO, 1, 1)
    between 'A' and 'Z'
身份字號第一碼為A-Z
ID_NO LIKE '[A-Z]%'
REGEXP_LIKE(ID_NO, '^[A-Za-z]')
員工
編號
SUBSTRING(EMP_NO, 1, 1) <>'0'
員工編號第1碼不為0
EMP_NO LIKE '[^0]%'
註: ^代表『非』
REGEXP_LIKE(EMP_NO, '^[^0]')
註: 1^代表字首, 2個為『非』

最後,此範例或有人會建議建立Functional Index以解決FULL TABLE SCAN的問題,就資源有限性及有效分配立場難以贊同,應多加思考如何在現有/簡單化之模式之下解決問題。

參考網站