假設要根據(jù)EXPIRE_DATE字段,統(tǒng)計2005年每月到期的用戶數(shù)。CHAR類型日期和DATE類型日期對應的統(tǒng)計SQL語句分別分析如下。
·CHAR類型日期
這種日期的表,要在日期字段上做統(tǒng)計是很方便的,下面的SQL語句統(tǒng)計出2005每月的T_USER記錄:
select substr(EXPIRE_DATE,5,2) MONTH,count(USER_ID) from T_USER where EXPIRE_DATE like '2005%'group by substr(EXPIRE_DATE,5,2) |
其中substr(EXPIRE_DATE,5,2)字符串處理函數(shù)獲取日期的月份,如"20060102"通過該函數(shù)即得到"01"的月份值。而"EXPIRE_DATE like '2005%'"的條件式即過濾出所有2005年的數(shù)據(jù),并且可以使用EXPIRE_DATE字段上的索引。
·DATE類型日期
對于DATE類型日期按月進行統(tǒng)計,乍一看,可以采用和CHAR類型日期相似的統(tǒng)計SQL語句:
select extract(month from EXPIRE_DATE) MONTH,count(USER_ID) from T_USER where extract(year from EXPIRE_DATE ) = 2005 group by extract(month from EXPIRE_DATE) |
注:在Oracle中通過extract(<日期域名> from Date)函數(shù)獲取日期的某特定日期域仁值。
但是仔細一分析,就會發(fā)現(xiàn)由于where條件式中對索引字段EXPIRE_DATE使用了extract()函數(shù),因此EXPIRE_DATE上的索引在此統(tǒng)計SQL中將無法使用,所以該統(tǒng)計將引發(fā)一個全表掃描。
當然,你可以在EXPIRE_DATE字段上建立函數(shù)索引,但EXPIRE_DATE上的查詢可以不僅僅只會用到extract()函數(shù),一一為這些EXPIRE_DATE字段建立多個函數(shù)索引不但麻煩而且會影響T_USER上數(shù)據(jù)更新操作的性能。
當然,除了上式的統(tǒng)計方法外,還可以采用另外一種方法:
select extract(month from EXPIRE_DATE) month,count(USER_ID) from T_USER where EXPIRE_DATE between to_date('20050101','yyyymmdd') and to_date('20051231','yyyymmdd') group by extract(month from EXPIRE_DATE) |
表 7的方法可以使用EXPIRE_DATE字段上的索引,但又引入了一個我們前面已經(jīng)提到過的不可避免的問題:必須按日期語義構造出開始和結束日期,以形成一個日期區(qū)間。在該例中,由于是對一整年進行統(tǒng)計,因而開始結束日期容易獲得,如果是精確到月的日期區(qū)間,則需要計算出某月的最后一天,程序就復雜多了。但如果是CHAR類型日期的表,構造統(tǒng)計SQL語句,也易如反掌:如我們要統(tǒng)計200304~200402每月到期的用戶數(shù),可以通過以下SQL語句:
select substr(EXPIRE_DATE,1,6) year_month ,count(USER_ID)from T_USER where EXPIRE_DATE between '20030400' and '20040299'group by substr(EXPIRE_DATE,1,6) |
即將開始日期以0補齊到8位,將結束日期以9補齊到8位。
5、在數(shù)據(jù)庫移植上的比較
由于CHAR類型日期實際上是一個字符類型字段,字符類型是最基本的數(shù)據(jù)類型,在構造Insert ,Update,Delete,Selete的SQL時,各種數(shù)據(jù)庫對字符類型字段的處理幾乎一致,因此在數(shù)據(jù)庫的移植上比較容易。
對于DATE類型的日期,由于不同數(shù)據(jù)庫對日期的操作差異很大,如獲取數(shù)據(jù)庫的時間函數(shù),Oracle為sysdate,SqlServer為getdate(),而MySql為now();從Date字段中抽取年的數(shù)值,Oracle為extract(year from
也許,有人會說現(xiàn)在都采用Hibernate進行映射ORM了,Hibernate已經(jīng)屏蔽了具體數(shù)據(jù)庫的不同,何來的數(shù)據(jù)庫移植?這話在一定程度上是沒有錯的,但是Hibernate框架由于通過對象映射的方法產(chǎn)生SQL語句,有時往往很難獲得最優(yōu)的查詢性能的SQL語句。所以,對于一些有性能要求較高的查詢,往往采用直接編寫SQL語句,或采用iBatis框架,后兩者都需要直接使用SQL語句,此時數(shù)據(jù)庫的移植問題就暴露出來了。
不但在數(shù)據(jù)庫的移植問題上,CHAR類型日期比DATE類型日期擁有絕對的優(yōu)勢,在數(shù)據(jù)的導入/導出,數(shù)據(jù)傳輸?shù)确矫妫珻HAR類型日期比DATE類型日期也具有較多的優(yōu)勢。字符型的數(shù)據(jù)可以直接不失真地用文本或XML表示,而Date類型導出為文本時,如果不指定好轉(zhuǎn)換格式往往難于處理,如2001-01-01的Date數(shù)據(jù)在轉(zhuǎn)換為文本時,可能變?yōu)?st Mon 2001,也可能為2001-01-01 00:00:00 ,甚至可能是01-01-01。這樣,在導入時顯得難以操作,因為導入/導出都需要指定好日期格式。
6、總結
有一句很經(jīng)典的關于軟件設計的話:如果你的程序邏輯變得很復雜,也許并不是問題域本身的復雜度造成的,往往將歸因于設計上的缺陷和瑕疵。同樣一個問題,采用不同的策略,往往造成大相徑庭的解決復雜度。Spring框架功能強大,我本以為代碼一定很復雜,但是當我研讀了Spring框架的代碼時,才詫異地發(fā)現(xiàn)Spring框架的源碼很少有超過300行代碼的類,類和方法大多簡潔明了,真是應了那句大巧若拙,大道至簡的話了。
在庫表設計時,日期字段究竟是采用CHAR類型日期還是DATE類型日期,在作出選擇時,需要考慮在程序邏輯中該數(shù)據(jù)的加工操作邏輯,畢竟表字段是需要在程序邏輯中使用和操作的,而非僅僅做一個簡單的記錄而已。
通過上文的分析,我們發(fā)現(xiàn)CHAR類型日期可以在較大的程度上簡化程序的開發(fā),并且充分利用索引,獲取較好的性能。但又一個問題產(chǎn)生了,既然CHAR類型日期這么不好用,各數(shù)據(jù)庫又都提供了Date日期格式,是否Date數(shù)據(jù)類型就成了尸位素餐的擺設了呢?換言之,Date數(shù)據(jù)類型適合在什么場合使用呢?筆者個人的建議是,在幾乎任何時候都不要使用Date類型作為表字段類型,Date類型僅在存儲過程,數(shù)據(jù)庫函數(shù)這些數(shù)據(jù)庫程序邏輯代碼編寫場合使用,即把它看成是一個運算過程的中間工具而不要用其作數(shù)據(jù)的存儲形式。
另外還有一個需要探討的問題,那就是Date類型長度是7,而Char(8)或Char(14)要比之浪費不少的存儲空間,其中這個問題在古代確實是一個大問題,那時候一間茅屋都要合理利用,現(xiàn)在很容易就可以得到廣廈千萬間。由于硬件性價比持續(xù)提升,也就可以使我們采用一些軟件上更簡便的方法來改善程序的設計,如Java的代碼反射,IoC的實現(xiàn)注入,XML形式的數(shù)據(jù)表示都是受惠于硬件的提升。因此,現(xiàn)在,一般而言,很微小的存儲空間占用和性能的影響并不需要設計人員特別的關注,他們要更多關注的往往是如何使邏輯簡化,如何使系統(tǒng)更具擴展性,可維護性和移植性上