2012年11月18日 星期日

Validation-Engine研究



以前在使用者輸入的TextBox我們要進行資料檢測,我都使用ASP.NETRequiredFieldValidator,但一直以來感覺他的效果不是很好,因為複雜一點的規則,基本上還是自己要去寫Javascript,再來是當使用者輸入錯誤時出現提示訊息時,出現的文字會去擠壓原本的表格,這次因為要檢測使用者輸入是否為數字,想說找一下JQuery的作法,找的過程中發現有JQuery.Valdation,突然想到之前有聽過JQuery有整套的驗證機制,找了一下看到黑暗執行緒大介紹了一套Validation-Engine,看了一下效果感覺真的很不錯,因此決定來研究一下

一開始因為不曉得怎麼使用,而且看到不同的範例用的Validation-Engine都是不同版本且差異頗大,因此花了不少時間去找這個元件能夠Run起來,走了不少冤望路,主要只要以下幾個檔案就能夠執行(有時候直接去看作者原始碼比較快)
validationEngine.jquery.css
有兩種,一種為紅色系,一種為黑色系,個人覺得紅色比較好看
jquery.validationEngine-zh_TW.js
語言檔,判斷資料正確與否的正則表達式也是寫在這裡
jquery.validationEngine.js
元件主程式

知道了如何使用後再來就是測試自己所需要的功能,測試的過程中碰到一些問題記錄如下:
原本在1.7.2的版本中提供了驗證長度的方法,但在最新的2.6.1中不支援這個方法,不太明白為什麼作者將這個方法移除,照理說這個應該非常常用
所以只能夠用自訂Function

Function的寫法可以參考以下,但我尚未測試過
方法一
1、注:調整一個漢字字符長度為2,一個英文字符長度為1 
for (var i = 0; i < val.length; i++)  
 { 
   len = len + ((val.charCodeAt(i) >= 0x4e00 && val.charCodeAt(i) <= 0x9fa5) ? 2 : 1);  
 } 
2validationEngine 字符長度問題(漢字字符,英文字符長度) 
var len = field.val().length;替換為
var len = 0; 
var val = field.val(); 
for (var i = 0; i < val.length; i++)  
{ 
len = len + ((val.charCodeAt(i) >= 0x4e00 && val.charCodeAt(i) <= 0x9fa5) ? 2 : 1);  
} 

方法二
function checkDate(oTextbox) {
var regex = new RegExp("^(?:(?:([0-9]{4}(-|\/)(?:(?:0?[1,3-9]|1[0-2])(-|\/)(?:29|30)|((?:0?[13578]|1[02])(-|\/)31)))|([0-9]{4}(-|\/)(?:0?[1-9]|1[0-2])(-|\/)(?:0?[1-9]|1\\d|2[0-8]))|(((?:(\\d\\d(?:0[48]|[2468][048]|[13579][26]))|(?:0[48]00|[2468][048]00|[13579][26]00))(-|\/)0?2(-|\/)29))))$");
if (!regex.test(oTextbox)) {
return false;
}
return true;
}
方法三Client Side and Server Side
http://www.eion.com.tw/Blogger/?Pid=1047

再來是日期輸入的驗證,元件中預設的驗證日期的正則表達式其實不太能夠真的將各種狀況擋住,我搜尋了網路上的很多方法進行測試,效果都不好,無法完全處理各種狀況,最後是用一種偷懶的方式,就是在TextBox上設定一個日期元件讓使用者日期只能用選的,但從Framework 2.0後,當我們將TextBox設定為唯讀,則程式是無法存取到該TextBox的值,因此這邊要繞個彎,在TextBox設定OnBlurreturn掉使用者企圖在輸入視窗上做任何事

最後是ajax驗證
一開始就碰到問題,因為作者網站的範例不能跑,而範例中是用php寫的我無法觀察要回傳什麼json的值給元件接,如果你直接去跑作者ajax驗證那頁會發現,那一頁傳回的是空值,所以當然無法正常運作,因此找了幾個人家做好的範例參考
單純的流氓那篇算是寫得相當清楚,不過裡面有誤解了原作者的地方,就是他請大家去改原作者的元件,因為我不太相信這個元件這麼久了會有需要去改元件才能使用的問題,因此決定查個清楚,還好在RIA之家的範例是可以跑的
這邊是以2.2版的做範例,我比對過跟2.6.1要做驗證的地方一模一樣,因此我想如果他能夠正常運作,那麼他回傳的json值也一定是正確,所以我手動的去跑他ajax驗證那頁(還好可以看到程式碼)
果然他有正常傳回值,如下:
["user",true]
這樣我們就知道自己所寫的後端驗證要傳回什麼值了,因此事實上是不需要修改原作者元件的。
註:
這邊可以有三個參數
["user",true,”請輸入123”]
第一個參數為TextBoxid名稱,第二個參數為驗證成功或失敗,第三個參數則是要給使用者看到什麼訊息

以下是ajax呼叫所需要的一些參數
"ajaxTest":{
"url":"webservice.asmx/Vaildate",
"extraData":"extraData=xxx",
"alertTextOk":"*ok",
"alertText":"*fail",
"alertTextLoad":"*正在確認,請稍等。"
說明一下extraData的作用為用get的方式去傳回後端當作參數使用

作者在js\languages裡提供了各種語系檔,連中文都有,不過可以多利用jquery.validationEngine-zh_TW.js去作搜尋人家已經做好的各式驗證
要如何動態挑選適合使用者的語系,可以參考以下
http://pramaire.pixnet.net/blog/post/45322227-jquery%E5%8F%96%E7%B6%B2%E9%A0%81url%E5%8F%83%E6%95%B8-%26-%E5%8B%95%E6%85%8B%E8%BC%89%E5%85%A5js%E6%AA%94

最後,因為之前都是使用post的方式去送參數到後端,所以一送出資料以後就出現「無法辨認要求格式,因為URL 未預期地以{0} 結束。」,查了一下發現在這邊
type: options.ajaxFormValidationMethod,
ajaxFormValidationMethod定義為使用get的方式
ajaxFormValidationMethod: 'get',
因此我手動將元件改為post是可以正常運作的,因此查了一下發現預設的get是關閉的,因此程式無法運作,所以將以下這段加入web.config就正常了
   
       
       
   
如果要關閉這兩個服務的話,則加入以下這段
 
   
   
 

這裡稍微深入的研究一下
   
       
       
   

似乎可以只讓localhost呼叫
似乎可以只讓某些服務才有作用

也有看到像下面的寫法
 
   
     
       
       
     
   
 
但這些就要再找時間仔細測試了

參考網址
謙卑式jQuery檢核-動態訊息
Validation-Engine介紹
NuGet程式包-改良ASP.NET MVC驗證訊息顯示
使用jQuery.validate.unobtrusive.js
Example of Using JQuery Validation Engine Plugin with ASP.NET Controls
Validation-Engine討論區
Validation-Engine作者網站
jQuery inline form validation Engine
jquery.validationEngine ajax驗證 不通過也提交表單
jQuery Validation Engine的表單驗證與字段ajax驗證

關鍵字
jquery 檢查數字
jquery.validate.unobtrusive.js
jquery.validate.inline
validationEngine.jquery.css
validation engine length
validation engine "asp.net" ashx
validationEngine ajax
無法辨認要求格式,因為 URL 未預期地以 結束
web.config web Services protocols

日期正則表達式
正則 "yyyy/mm/dd"
一些正則表達式的參考,只有測試過日期的部分,都不太能用
http://mengqingyu.iteye.com/blog/426454

2012年11月17日 星期六

Javascript Chart收集

Highcharts-InteractiveJavaScriptchartsforyourwebpage
http://www.highcharts.com/
非常漂亮,不過商用要買License

dygraphsJavaScriptVisualizationLibrary
http://www.danvk.org/dygraphs/
JavaScript圖表,看起來很像Flash,像GoogleAnalytics一樣.

flotr2
http://www.humblesoftware.com/flotr2/
HTML5JavaScriptChartLibrary

20FreshJavaScriptDataVisualizationLibraries
http://sixrevisions.com/javascript/20-fresh-javascript-data-visualization-libraries/

gRaphaël—ChartingJavaScriptLibrary
http://g.raphaeljs.com/
JavaScript圓餅圖、長條圖

開發人員指南-Google圖表API-GoogleCode
http://code.google.com/intl/zh-TW/apis/chart/
GoogleChartAPI

flot-ProjectHostingonGoogleCode
http://code.google.com/p/flot/
jQueryflot餵jsonformat來畫圖.

JavaScriptDiagramBuilder
http://www.lutanho.net/diagram/
畫面普通,但是基本實用.

PlotKit-JavascriptChartPlotting|liquidx
http://www.liquidx.net/plotkit/
JavaScriptchartPlot

EmpriseJavaScriptCharts™::100%PureJavaScriptCharts
http://www.ejschart.com/
JavaScriptChart可以有像GoogleAnalytics的效果.

JSCharts–FreeJavaScriptcharts
http://www.jscharts.com/
JSChart,商用要付費.

YUI2:Charts
http://developer.yahoo.com/yui/charts/
YUIChart

jQuerySparklines
http://omnipotent.net/jquery.sparkline/
要做股市那種會跑的圖,可以用這個~

moBlur.org-jQueryFlot-Plots,CanvasandCharts
http://blog.shinylittlething.com/workshop/flot_intro/

Bluff:BeautifulgraphsinJavaScript
http://bluff.jcoglan.com/
非常輕巧的Chatlibrary.

javascriptRRD|freshmeat.net
http://freshmeat.net/projects/javascriptrrd
JavaScriptRRD

5JavascriptChartAlternatives
http://www.reynoldsftw.com/2009/03/javascript-chart-alter...

DHTML:DrawLine,Ellipse,Oval,Circle,Polyline,Polygon,TrianglewithJavaScript
http://www.walterzorn.com/jsgraphics/jsgraphics_e.htm

jQueryVisualizePlugin:AccessibleCharts
http://www.filamentgroup.com/lab/jquery_visualize_plugin_...
jQueryTable畫成圖表.

Graphing/ChartingDataonWebPages:JavaScriptSolutions
http://sixrevisions.com/javascript/graph_chart_plot_data_...
JavaScriptFlotLibrary.

YUIFlot
http://github.com/bluesmoon/yui-flot
YUIFlot

28RichDataVisualizationTools-InsideRIA
http://www.insideria.com/2009/12/28-rich-data-visualizati...
AJAXChart

AJAXAPIsPlayground
http://code.google.com/apis/ajax/playground/
VisualizationAPI部份,全部都是Chart.

Morris.js
http://oesmith.github.com/morris.js/
jQuerypluginSVG

d3.js
http://mbostock.github.com/d3/
做3D動畫圖表

資料來源
http://wowtree.com/tree.php?aid=864

正則表達式收集

先將收集到的放在這邊,找時間在做測試與驗證

匹配中文字符的正則表達式: [u4e00-u9fa5]
評注:匹配中文還真是個頭疼的事,有了這個表達式就好辦了

匹配雙字節字符(包括漢字在內):[^x00-xff]
評注:可以用來計算字符串的長度(一個雙字節字符長度計2,ASCII字符計1)
匹配空白行的正則表達式:ns*r
評注:可以用來刪除空白行
匹配HTML標記的正則表達式:<(S*?)[^>]*>.*?|<.*? />
評注:網上流傳的版本太糟糕,上面這個也僅僅能匹配部分,對於複雜的嵌套標記依舊無能為力
匹配首尾空白字符的正則表達式:^s*|s*$
評注:可以用來刪除行首行尾的空白字符(包括空格、制表符、換頁符等等),非常有用的表達式
匹配Email地址的正則表達式:w+([-+.]w+)*@w+([-.]w+)*.w+([-.]w+)*
評注:表單驗證時很實用
匹配網址URL的正則表達式:[a-zA-z]+://[^s]*
評注:網上流傳的版本功能很有限,上面這個基本可以滿足需求
匹配帳號是否合法(字母開頭,允許5-16字節,允許字母數字下劃線):^[a-zA-Z][a-zA-Z0-9_]{4,15}$
評注:表單驗證時很實用
匹配國內電話號碼:d{3}-d{8}|d{4}-d{7}
評注:匹配形式如 0511-4405222 或 021-87888822
匹配騰訊QQ號:[1-9][0-9]{4,}
評注:騰訊QQ號從10000開始
匹配中國郵政編碼:[1-9]d{5}(?!d)
評注:中國郵政編碼為6位數字
匹配身份證:d{15}|d{18}
評注:中國的身份證為15位或18位
匹配ip地址:d+.d+.d+.d+
評注:提取ip地址時有用

最強驗證日期的正則表達式,添加了閏年的驗證

這個日期正則表達式支持
YYYY-MM-DD
YYYY/MM/DD
YYYY_MM_DD
YYYY.MM.DD的形式

match : 2008-2-29 2008/02/29

not match : 2008-2-30     2007-2-29


完整的正則表達式如下:
((^((1[8-9]\d{2})|([2-9]\d{3}))([-\/\._])(10|12|0?[13578])([-\/\._])(3[01]|[12][0-9]|0?[1-9])$)|(^((1[8-9]\d{2})|([2-9]\d{3}))([-\/\._])(11|0?[469])([-\/\._])(30|[12][0-9]|0?[1-9])$)|(^((1[8-9]\d{2})|([2-9]\d{3}))([-\/\._])(0?2)([-\/\._])(2[0-8]|1[0-9]|0?[1-9])$)|(^([2468][048]00)([-\/\._])(0?2)([-\/\._])(29)$)|(^([3579][26]00)([-\/\._])(0?2)([-\/\._])(29)$)|(^([1][89][0][48])([-\/\._])(0?2)([-\/\._])(29)$)|(^([2-9][0-9][0][48])([-\/\._])(0?2)([-\/\._])(29)$)|(^([1][89][2468][048])([-\/\._])(0?2)([-\/\._])(29)$)|(^([2-9][0-9][2468][048])([-\/\._])(0?2)([-\/\._])(29)$)|(^([1][89][13579][26])([-\/\._])(0?2)([-\/\._])(29)$)|(^([2-9][0-9][13579][26])([-\/\._])(0?2)([-\/\._])(29)$))
閏年的2月份有29天,因此匹配閏年日期格式為YYYY-MM-DD的正則表達式為:

(([0-9]{2})(0[48]|[2468][048]|[13579][26])|((0[48]|[2468][048]|[3579][26])00))-02-29

最後,將平年和閏年的日期驗證表達式合併,我們得到最終的驗證日期格式為YYYY-MM-DD的正則表達式為:

(([0-9]{3}[1-9]|[0-9]{2}[1-9][0-9]{1}|[0-9]{1}[1-9][0-9]{2}|[1-9][0-9]{3})-(((0[13578]|1[02])-(0[1-9]|[12][0-9]|3[01]))|((0[469]|11)-(0[1-9]|[12][0-9]|30))|(02-(0[1-9]|[1][0-9]|2[0-8]))))|((([0-9]{2})(0[48]|[2468][048]|[13579][26])|((0[48]|[2468][048]|[3579][26])00))-02-29)

DD/MM/YYYY格式的正則驗證表達式為:

(((0[1-9]|[12][0-9]|3[01])/((0[13578]|1[02]))|((0[1-9]|[12][0-9]|30)/(0[469]|11))|(0[1-9]|[1][0-9]|2[0-8])/(02))/([0-9]{3}[1-9]|[0-9]{2}[1-9][0-9]{1}|[0-9]{1}[1-9][0-9]{2}|[1-9][0-9]{3}))|(29/02/(([0-9]{2})(0[48]|[2468][048]|[13579][26])|((0[48]|[2468][048]|[3579][26])00)))


匹配特定數字:
^[1-9]d*$    //匹配正整數
^-[1-9]d*$   //匹配負整數
^-?[1-9]d*$   //匹配整數
^[1-9]d*|0$  //匹配非負整數(正整數 + 0)
^-[1-9]d*|0$   //匹配非正整數(負整數 + 0)
^[1-9]d*.d*|0.d*[1-9]d*$   //匹配正浮點數
^-([1-9]d*.d*|0.d*[1-9]d*)$  //匹配負浮點數
^-?([1-9]d*.d*|0.d*[1-9]d*|0?.0+|0)$  //匹配浮點數
^[1-9]d*.d*|0.d*[1-9]d*|0?.0+|0$   //匹配非負浮點數(正浮點數 + 0)
^(-([1-9]d*.d*|0.d*[1-9]d*))|0?.0+|0$  //匹配非正浮點數(負浮點數 + 0)
評注:處理大量數據時有用,具體應用時注意修正
匹配特定字符串:
^[A-Za-z]+$  //匹配由26個英文字母組成的字符串
^[A-Z]+$  //匹配由26個英文字母的大寫組成的字符串
^[a-z]+$  //匹配由26個英文字母的小寫組成的字符串
^[A-Za-z0-9]+$  //匹配由數字和26個英文字母組成的字符串
^w+$  //匹配由數字、26個英文字母或者下劃線組成的字符串

在使用RegularExpressionValidator驗證控件時的驗證功能及其驗證表達式介紹如下:

只能輸入數字:“^[0-9]*$”
只能輸入n位的數字:“^d{n}$”
只能輸入至少n位數字:“^d{n,}$”
只能輸入m-n位的數字:“^d{m,n}$”
只能輸入零和非零開頭的數字:“^(0|[1-9][0-9]*)$”
只能輸入有兩位小數的正實數:“^[0-9]+(.[0-9]{2})?$”
只能輸入有1-3位小數的正實數:“^[0-9]+(.[0-9]{1,3})?$”
只能輸入非零的正整數:“^+?[1-9][0-9]*$”
只能輸入非零的負整數:“^-[1-9][0-9]*$”
只能輸入長度為3的字符:“^.{3}$”
只能輸入由26個英文字母組成的字符串:“^[A-Za-z]+$”
只能輸入由26個大寫英文字母組成的字符串:“^[A-Z]+$”
只能輸入由26個小寫英文字母組成的字符串:“^[a-z]+$”
只能輸入由數字和26個英文字母組成的字符串:“^[A-Za-z0-9]+$”
只能輸入由數字、26個英文字母或者下劃線組成的字符串:“^w+$”
驗證用戶密碼:“^[a-zA-Z]w{5,17}$”正確格式為:以字母開頭,長度在6-18之間,

只能包含字符、數字和下劃線。
驗證是否含有^%&',;=?$"等字符:“[^%&',;=?$x22]+”
只能輸入漢字:“^[u4e00-u9fa5],{0,}$”
驗證Email地址:“^w+[-+.]w+)*@w+([-.]w+)*.w+([-.]w+)*$”
驗證InternetURL:“^http://([w-]+.)+[w-]+(/[w-./?%&=]*)?$”
驗證電話號碼:“^((d{3,4})|d{3,4}-)?d{7,8}$”

正確格式為:“XXXX-XXXXXXX”,“XXXX-XXXXXXXX”,“XXX-XXXXXXX”,

“XXX-XXXXXXXX”,“XXXXXXX”,“XXXXXXXX”。
驗證身份證號(15位或18位數字):“^d{15}|d{}18$”
驗證一年的12個月:“^(0?[1-9]|1[0-2])$”正確格式為:“01”-“09”和“1”“12”
驗證一個月的31天:“^((0?[1-9])|((1|2)[0-9])|30|31)$”

正確格式為:“01”“09”和“1”“31”。

匹配中文字符的正則表達式: [u4e00-u9fa5]
匹配雙字節字符(包括漢字在內):[^x00-xff]
匹配空行的正則表達式:n[s| ]*r
匹配HTML標記的正則表達式:/<(.*)>.*
|<(.*) />/
匹配首尾空格的正則表達式:(^s*)|(s*$)
匹配Email地址的正則表達式:w+([-+.]w+)*@w+([-.]w+)*.w+([-.]w+)*
匹配網址URL的正則表達式:http://([w-]+.)+[w-]+(/[w- ./?%&=]*)?
(1)應用:計算字符串的長度(一個雙字節字符長度計2,ASCII字符計1)
String.prototype.len=function(){return this.replace([^x00-xff]/g,"aa").length;}
(2)應用:javascript中沒有像vbscript那樣的trim函數,我們就可以利用這個表達式來實現
String.prototype.trim = function()
{
return this.replace(/(^s*)|(s*$)/g, "");
}
(3)應用:利用正則表達式分解和轉換IP地址
function IP2V(ip) //IP地址轉換成對應數值
{
re=/(d+).(d+).(d+).(d+)/g //匹配IP地址的正則表達式
if(re.test(ip))
{
return RegExp.$1*Math.pow(255,3))+RegExp.$2*Math.pow(255,2))+RegExp.$3*255+RegExp.$4*1
}
else
{
throw new Error("Not a valid IP address!")
}
}
(4)應用:從URL地址中提取文件名的javascript程序
s="http://www.9499.net/page1.htm";
s=s.replace(/(.*/){0,}([^.]+).*/ig,"$2") ; //Page1.htm
(5)應用:利用正則表達式限制網頁表單裡的文本框輸入內容
用 正則表達式限制只能輸入中文:onkeyup="value=value.replace(/[^u4E00-u9FA5]/g,'') " onbeforepaste="clipboardData.setData('text',clipboardData.getData('text').replace(/[^u4E00-u9FA5]/g,''))"
用 正則表達式限制只能輸入全角字符: onkeyup="value=value.replace(/[^uFF00-uFFFF]/g,'') " onbeforepaste="clipboardData.setData('text',clipboardData.getData('text').replace(/[^uFF00-uFFFF]/g,''))"
用 正則表達式限制只能輸入數字:onkeyup="value=value.replace(/[^d]/g,'') "onbeforepaste= "clipboardData.setData('text',clipboardData.getData('text').replace(/[^d]/g,''))"
用 正則表達式限制只能輸入數字和英文:onkeyup="value=value.replace(/[W]/g,'') "onbeforepaste="clipboardData.setData('text',clipboardData.getData('text').replace(/[^d]/g,''
資料來源
http://mengqingyu.iteye.com/blog/426454

信用卡檢查

  1. //信用卡檢查
  2. ‘/^(?:4[0-9]{12}(?:[0-9]{3})?|5[1-5][0-9]{14}|6011[0-9]{12}|622((12[6-9]|1[3-9][0-9])|([2-8][0-9][0-9])|(9(([0-1][0-9])|(2[0-5]))))[0-9]{10}|64[4-9][0-9]{13}|65[0-9]{14}|3(?:0[0-5]|[68][0-9])[0-9]{11}|3[47][0-9]{13})*$/’

檢查字串只能有文字與數字

  1. //檢查字串只能有文字與數字
  2. ‘/^[a-zA-Z0-9]*$/’

檢查字串只能有文字

  1. //檢查字串只能有文字
  2.  ‘/^[a-zA-Z]*$/’

檢查字串只能有小寫字母

  1. //檢查字串只能有數字
  2. ‘/^[0-9]*$/’

檢查日期型態 (MM/DD/YYYY)

  1. //檢查日期型態 (MM/DD/YYYY)
  2. ‘/^((0?[1-9]|1[012])[- /.](0?[1-9]|[12][0-9]|3[01])[- /.](19|20)?[0-9]{2})*$/’

檢查日期型態 (YYYY/MM/DD)

  1. //檢查日期型態 (YYYY/MM/DD)
  2.  ‘#^((19|20)?[0-9]{2}[- /.](0?[1-9]|1[012])[- /.](0?[1-9]|[12][0-9]|3[01]))*$#’

簡易的Email檢查

  1. //簡易的Email檢查
  2. ‘/^([a-zA-Z0-9._%-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,4})*$/’

檢查IP位址

  1. //檢查IP位址
  2. ‘/^((?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?))*$/’

檢查密碼:密碼長度必須有八碼,並且包含至少一個小寫字母與一個大寫字母和一個數字

  1. //檢查密碼
  2.  ‘/^(?=^.{8,}$)((?=.*[A-Za-z0-9])(?=.*[A-Z])(?=.*[a-z]))^.*$/’

檢查網址

  1. //檢查網址
  2. ‘/^(((http|https|ftp):\/\/)?([[a-zA-Z0-9]\-\.])+(\.)([[a-zA-Z0-9]]){2,4}([[a-zA-Z0-9]\/+=%&_\.~?\-]*))*$/’
資料來源
http://lab.hsdn.net/blog/2009/09/%E5%B8%B8%E7%94%A8%E7%9A%84%E6%AD%A3%E8%A6%8F%E8%A1%A8%E7%A4%BA%E5%BC%8Fregular-expression/


一些正則表達式的學習
http://blog.roodo.com/rocksaying/archives/2670695.html
http://neural.cs.nthu.edu.tw/jang/books/javascript/regExp03.asp?title=9-3%20%E9%80%9A%E7%94%A8%E5%BC%8F%E7%9B%B8%E9%97%9C%E5%88%97%E8%A1%A8\\

其他參考
http://s.yanghao.org/program/viewdetail.php?i=268190
http://www.csharpwin.com/csharpspace/9442r8768.shtml
http://renjin.blogspot.tw/2008/08/regular-expression-examples.html

2012年10月25日 星期四

SQL中Case的使用方法

Case具有兩種格式。簡單Case函數和Case搜索函數。
--簡單Case函數
CASE sex
         WHEN '1' THEN '男'
         WHEN '2' THEN '女'
ELSE '其他' END
--Case搜索函數
CASE WHEN sex = '1' THEN '男'
         WHEN sex = '2' THEN '女'
ELSE '其他' END

這兩種方式,可以實現相同的功能。簡單Case函數的寫法相對比較簡潔,但是和Case搜索函數相比,功能方面會有些限制,比如寫判斷式。
還有一個需要注意的問題,Case函數只返回第一個符合條件的值,剩下的Case部分將會被自動忽略。
--比如說,下面這段SQL,你永遠無法得到「第二類」這個結果
CASE WHEN col_1 IN ( 'a', 'b') THEN '第一類'
         WHEN col_1 IN ('a')       THEN '第二類'
ELSE'其他' END

下面我們來看一下,使用Case函數都能做些什麼事情。

一,已知數據按照另外一種方式進行分組,分析。

有如下數據:(為了看得更清楚,我並沒有使用國家代碼,而是直接用國家名作為Primary Key)
國家(country) 人口(population)
中國 600
美國 100
加拿大 100
英國 200
法國 300
日本 250
德國 200
墨西哥 50
印度 250

根據這個國家人口數據,統計亞洲和北美洲的人口數量。應該得到下面這個結果。
人口
亞洲 1100
北美洲 250
其他 700

想要解決這個問題,你會怎麼做?生成一個帶有洲Code的View,是一個解決方法,但是這樣很難動態的改變統計的方式。
如果使用Case函數,SQL代碼如下:
SELECT  SUM(population),
        CASE country
                WHEN '中國'     THEN '亞洲'
                WHEN '印度'     THEN '亞洲'
                WHEN '日本'     THEN '亞洲'
                WHEN '美國'     THEN '北美洲'
                WHEN '加拿大'  THEN '北美洲'
                WHEN '墨西哥'  THEN '北美洲'
        ELSE '其他' END
FROM    Table_A
GROUP BY CASE country
                WHEN '中國'     THEN '亞洲'
                WHEN '印度'     THEN '亞洲'
                WHEN '日本'     THEN '亞洲'
                WHEN '美國'     THEN '北美洲'
                WHEN '加拿大'  THEN '北美洲'
                WHEN '墨西哥'  THEN '北美洲'
        ELSE '其他' END;

同樣的,我們也可以用這個方法來判斷工資的等級,並統計每一等級的人數。SQL代碼如下;
SELECT
        CASE WHEN salary <= 500 THEN '1'
             WHEN salary > 500 AND salary <= 600  THEN '2'
             WHEN salary > 600 AND salary <= 800  THEN '3'
             WHEN salary > 800 AND salary <= 1000 THEN '4'
        ELSE NULL END salary_class,
        COUNT(*)
FROM    Table_A
GROUP BY
        CASE WHEN salary <= 500 THEN '1'
             WHEN salary > 500 AND salary <= 600  THEN '2'
             WHEN salary > 600 AND salary <= 800  THEN '3'
             WHEN salary > 800 AND salary <= 1000 THEN '4'
        ELSE NULL END;

二,用一個SQL語句完成不同條件的分組。

有如下數據
國家(country) 性別(sex) 人口(population)
中國 1 340
中國 2 260
美國 1 45
美國 2 55
加拿大 1 51
加拿大 2 49
英國 1 40
英國 2 60

按照國家和性別進行分組,得出結果如下
國家
中國 340 260
美國 45 55
加拿大 51 49
英國 40 60

普通情況下,用UNION也可以實現用一條語句進行查詢。但是那樣增加消耗(兩個Select部分),而且SQL語句會比較長。
下面是一個是用Case函數來完成這個功能的例子
SELECT country,
       SUM( CASE WHEN sex = '1' THEN 
                      population ELSE 0 END),  --男性人口
       SUM( CASE WHEN sex = '2' THEN 
                      population ELSE 0 END)   --女性人口
FROM  Table_A
GROUP BY country;

這樣我們使用Select,完成對二維表的輸出形式,充分顯示了Case函數的強大。

三,在Check中使用Case函數。

在Check中使用Case函數在很多情況下都是非常不錯的解決方法。可能有很多人根本就不用Check,那麼我建議你在看過下面的例子之後也嘗試一下在SQL中使用Check。
下面我們來舉個例子
公司A,這個公司有個規定,女職員的工資必須高於1000塊。如果用Check和Case來表現的話,如下所示
CONSTRAINT check_salary CHECK
           ( CASE WHEN sex = '2'
                  THEN CASE WHEN salary > 1000
                        THEN 1 ELSE 0 END
                  ELSE 1 END = 1 )

如果單純使用Check,如下所示
CONSTRAINT check_salary CHECK
           ( sex = '2' AND salary > 1000 )

女職員的條件倒是符合了,男職員就無法輸入了。

四,根據條件有選擇的UPDATE。

例,有如下更新條件
  1. 工資5000以上的職員,工資減少10%
  2. 工資在2000到4600之間的職員,工資增加15%
很容易考慮的是選擇執行兩次UPDATE語句,如下所示
--條件1
UPDATE Personnel
SET salary = salary * 0.9
WHERE salary >= 5000;
--條件2
UPDATE Personnel
SET salary = salary * 1.15
WHERE salary >= 2000 AND salary < 4600;

但是事情沒有想像得那麼簡單,假設有個人工資5000塊。首先,按照條件1,工資減少10%,變成工資4500。接下來運行第二個SQL時候,因 為這個人的工資是4500在2000到4600的範圍之內, 需增加15%,最後這個人的工資結果是5175,不但沒有減少,反而增加了。如果要是反過來執行,那麼工資4600的人相反會變成減少工資。暫且不管這個 規章是多麼荒誕,如果想要一個SQL 語句實現這個功能的話,我們需要用到Case函數。代碼如下:
UPDATE Personnel
SET salary = CASE WHEN salary >= 5000
             THEN salary * 0.9
WHEN salary >= 2000 AND salary < 4600
THEN salary * 1.15
ELSE salary END;

這裡要注意一點,最後一行的ELSE salary是必需的,要是沒有這行,不符合這兩個條件的人的工資將會被寫成NUll,那可就大事不妙了。在Case函數中Else部分的默認值是NULL,這點是需要注意的地方。
這種方法還可以在很多地方使用,比如說變更主鍵這種累活。
一般情況下,要想把兩條數據的Primary key,a和b交換,需要經過臨時存儲,拷貝,讀回數據的三個過程,要是使用Case函數的話,一切都變得簡單多了。
p_key col_1 col_2
a 1 張三
b 2 李四
c 3 王五


假設有如上數據,需要把主鍵ab相互交換。用Case函數來實現的話,代碼如下
UPDATE SomeTable
SET p_key = CASE WHEN p_key = 'a'
THEN 'b'
WHEN p_key = 'b'
THEN 'a'
ELSE p_key END
WHERE p_key IN ('a', 'b');

同樣的也可以交換兩個Unique key。需要注意的是,如果有需要交換主鍵的情況發生,多半是當初對這個表的設計進行得不夠到位,建議檢查表的設計是否妥當。

五,兩個表數據是否一致的檢查。

Case函數不同於DECODE函數。在Case函數中,可以使用BETWEEN,LIKE,IS NULL,IN,EXISTS等等。比如說使用IN,EXISTS,可以進行子查詢,從而 實現更多的功能。
下 面具個例子來說明,有兩個表,tbl_A,tbl_B,兩個表中都有keyCol列。現在我們對兩個表進行比較,tbl_A中的keyCol列的數據如果 在tbl_B的keyCol列的數據中可以找到, 返回結果'Matched',如果沒有找到,返回結果'Unmatched'。
要實現下面這個功能,可以使用下面兩條語句
--使用IN的時候
SELECT keyCol,
CASE WHEN keyCol IN ( SELECT keyCol FROM tbl_B )
THEN 'Matched'
ELSE 'Unmatched' END Label
FROM tbl_A;
--使用EXISTS的時候
SELECT keyCol,
CASE WHEN EXISTS ( SELECT * FROM tbl_B
WHERE tbl_A.keyCol = tbl_B.keyCol )
THEN 'Matched'
ELSE 'Unmatched' END Label
FROM tbl_A;

使用IN和EXISTS的結果是相同的。也可以使用NOT IN和NOT EXISTS,但是這個時候要注意NULL的情況。

六,在Case函數中使用合計函數

假設有下面一個表
學號(std_id) 課程ID(class_id) 課程名(class_name) 主修flag(main_class_flg)
100 1 經濟學 Y
100 2 歷史學 N
200 2 歷史學 N
200 3 考古學 Y
200 4 計算機 N
300 4 計算機 N
400 5 化學 N
500 6 數學 N

有的學生選擇了同時修幾門課程(100,200)也有的學生只選擇了一門課程(300,400,500)。選修多門課程的學生,要選擇一門課程作 為主修,主修flag裡面寫入 Y。只選擇一門課程的學生,主修flag為N(實際上要是寫入Y的話,就沒有下面的麻煩事了,為了舉例子,還請多多包含)。
現在我們要按照下面兩個條件對這個表進行查詢
  1. 只選修一門課程的人,返回那門課程的ID
  2. 選修多門課程的人,返回所選的主課程ID

簡單的想法就是,執行兩條不同的SQL語句進行查詢。
條件1
--條件1:只選擇了一門課程的學生
SELECT std_id, MAX(class_id) AS main_class
FROM Studentclass
GROUP BY std_id
HAVING COUNT(*) = 1;

執行結果1
STD_ID   MAIN_class
------   ----------
300      4
400      5
500      6

條件2
--條件2:選擇多門課程的學生
SELECT std_id, class_id AS main_class
FROM Studentclass
WHERE main_class_flg = 'Y' ;

執行結果2
STD_ID  MAIN_class
------  ----------
100     1
200     3

如果使用Case函數,我們只要一條SQL語句就可以解決問題,具體如下所示
SELECT  std_id,
CASE WHEN COUNT(*) = 1  --只選擇一門課程的學生的情況
THEN MAX(class_id)
ELSE MAX(CASE WHEN main_class_flg = 'Y'
THEN class_id
ELSE NULL END
)
END AS main_class
FROM Studentclass
GROUP BY std_id;

運行結果
STD_ID   MAIN_class
------   ----------
100      1
200      3
300      4
400      5
500      6

通過在Case函數中嵌套Case函數,在合計函數中使用Case函數等方法,我們可以輕鬆的解決這個問題。使用Case函數給我們帶來了更大的自由度。
最後提醒一下使用Case函數的新手注意不要犯下面的錯誤
CASE col_1
WHEN 1        THEN 'Right'
WHEN NULL  THEN 'Wrong'
END

在這個語句中When Null這一行總是返回unknown,所以永遠不會出現Wrong的情況。因為這句可以替換成WHEN col_1 = NULL,這是一個錯誤的用法,這個時候我們應該選擇用WHEN col_1 IS NULL。

參考網址
http://www.cnblogs.com/Ronin/archive/2006/07/20/455388.html
http://www.cnblogs.com/Ronin/archive/2006/07/20/455756.html