2010年10月2日 星期六

T-SQL 顯示民國年

在 Hunterpo's IT Vision 看到一篇「自訂 SQL Server 日期轉民國年格式函數」文章,Hunterpo 用心製作了一個很完善的 Function,並考慮了各種日期輸出格式。但保哥回了一段要注意,使用Function 可能會影響效能的話,著實讓我想著是否有更簡單的方式可以完成民國年的需求。

後來發現,其實也不需要用到自訂函數這麼華麗,用短短幾行也可以完成。


declare @now datetime
set @now=getdate()

select convert(varchar(3),datepart(year,@now)-1911) 
+right('0'+ convert(varchar(2),datepart(month,@now)),2)
+right('0'+ convert(varchar(2),datepart(day,@now)),2) as ChineseYear

至於使用 Function 是否會影響效能?答案是肯定會的。簡單的比喻,如果你自己撰寫了一個自訂的 Function,假設執行一次要花 0.01 秒,當你有 50000筆資料時,

select  dbo.myFunction(x) from myTable

就會多花 0.01X50000 的時間。然而,這只是簡單推算,實際上你可能會有加上 where 條件,將資料筆數限制在比較少的情況,正因為所篩選的結果資料量不大,同時也就減少了使用 Function 所帶來額外花費時間的衝擊。

然而, 使用 Function 最大的危險,並不在於 select 敘述裡,而是在 where 條件敘述裡。剛剛有提到,如果只是放在 select 敘述裡,花費時間只會因為搜尋後結果資料集的大小而有影響;但如果把他放在 where 條件敘述裡,則很有可能是每一筆資料都要去執行一次你的 Function,甚至破壞您原先建立 Index 的效益。這裡用「有可能」這字眼,代表著,只要你使用正確,還是可以開開心心使用 Function的,這裡所指的 Function,是同時包括使用者自訂 Function 與系統內建的 Function。

建議可以參考「Improving SQL Server Performance」這篇文章,裡面的一小節「Avoid Explicit or Implicit Functions in WHERE Clauses」有個很棒的範例可以說明。如果你在 where 條件敘述句裡,使用了 Function ,且 Function 裡面是用了某個資料欄位當參數,那這就犯了兵家大忌,要打屁股了。

小弟不才,剽竊了原文的程式碼,也不想告知(人家是 msdn 嘛...)。

SELECT OrderID FROM NorthWind.dbo.Orders WHERE DATEADD(day, 15, 
OrderDate) = '07/23/1996'

SELECT OrderID FROM NorthWind.dbo.Orders WHERE OrderDate = DATEADD(day, 
-15, '07/23/1996')

以上兩行指令,結果是一樣的,但第一行指令,他會使用 Index Scan,每筆資料一行一行去比對;而第二行指令,則是使用 Index Seek。這兩種方法,搜尋成本就差了4倍以上。所以,即便你只是使用系統內建的 Function,但還是要注意擺放的時機與位置,避免日後無眠無夜的調校程式。


參考來源:
1.http://www.dotblogs.com.tw/hunterpo/archive/2010/08/15/17208.aspx
2.http://msdn.microsoft.com/en-us/library/ff647793.aspx

2010年9月19日 星期日

小冰箱之除霜記

凡那比颱風今天正在肆虐台灣,無聊之餘,想到冰箱裡冷凍櫃的白鬍子太多了,順手把它刮一刮。過了幾小時後,發現除霜盤上的積水滿到快溢出來了,此時如果不將水排出,過沒多久我冰箱就會淹水,但如果稍稍移動除霜盤,那它上面的積水又會不小心濺的到處都是,有夠為難的。

這時想到以前小學教的毛細效應,再配合自己刻意安排的位能差來持續不斷地把高處的水傳遞到低處,並於低處放個小鋼杯去接水,而高地處的連接橋樑就透過一條萬能的抹布就搞定了。如此過沒多久,就可以看到小鋼杯裡盛滿了除霜後的水,真是開心啊!以後除霜就不用弄的到處濕答答了。


補充:20110613

今晚回到家,發現前天除霜後,一直忘了自己有在除霜,原本已將裝滿水的盛水盤,目前都已經結冰了。剛開始,我拿著裝滿熱水的鋼杯盤,疊放在冰塊上,的確有加快溶冰的速度,但...真的等到快睡著了。最後,在我喪失理智的情況下,把冰箱電源關掉,另外拿起吹風機,把溫度調到最高,接著...對著我那結冰的盛水盤狂吹,不到5分鐘,整個冰就化了快1/4,趁著我那排冷管與冰分離的時候,將整個盛水盤抽出,直接拿去沖熱水,不到30分鐘就把這冰都除光了。

2010年9月17日 星期五

在 Console Mode下, 透過 FreeSMTP 定期發通知信

要定期的去發通知信,可以利用 SQL Server 的 SQL Server Agent 或 Windows 排程來觸發。而發信的工具可以使用 SQL Mail 或 FreeSMTP。

最近剛好有機會測試了 FreeSMTP 搭配 Windows 排程的方案。原先主要是想透過撰寫 thread 來完成定期發信功能,但考量在不增加系統 loading 與避免該 thread 莫名被終止,所以才考慮用 windows 排程。然而,原先的 ASP.Net 專案如何用 windows 排程來觸發呢?是個難題。所以,我將發信的功能改寫成 Console Mode,也就是可以用 DOS 指令來執行。但,FreeSMTP 可以用 DOS 指令來發信嗎?猶豫了許久,就寫了一段程式來測試。

首先,建立了一個 console mode 的專案,並在該專案加入了 FreeSMTP.Net.dll 的參考。

接著 Imports Quiksoft.FreeSMTP.SMTP
接著撰寫 DoSend( ) 事件以供 Main( )來叫用。

以下是程式碼節錄:


Imports Quiksoft.FreeSMTP.SMTP

Module Module1

    Sub Main()
        DoSend()
    End Sub

    'Sub that does the sending.
    Sub DoSend()

        '宣告 SMTP
        Dim objSMTP As Quiksoft.FreeSMTP.SMTP

        '設定 SMTP Server        
        objSMTP = New Quiksoft.FreeSMTP.SMTP("3probe.com.tw")

        '設定 信件內容
        Dim objMsg As Quiksoft.FreeSMTP.EmailMessage = New Quiksoft.FreeSMTP.EmailMessage("paladin@3probe.com.tw", _
                                    "paladinlee@3probe.com.tw", _
                                    "paladin", _
                                    "Dear paladin,
這封是我特別給您的 :測試信。", _
                                    Quiksoft.FreeSMTP.BodyPartFormat.HTML)
        '設定信件編碼
        objMsg.CharsetEncoding = System.Text.Encoding.GetEncoding("BIG5")

        '發信
        objSMTP.Send(objMsg)


    End Sub


End Module


直接測試這段程式,信件可以正常寄出,接著自己也加入一項 windows 排程,把內容設定為執行剛剛撰寫好的 console mode 專案的 .exe 執行檔。現下自我感覺良好中,可以開開心心去泡茶了...

2010年9月16日 星期四

利用 ASP.Net 壓縮檔案

若要透過 ASP.Net 將 Web 站台的某些檔案進行壓縮,之後再讓使者下載,這需求透過 DotNetZip 來協助,問題就幾乎解決一大半,剩下的,就是如何讓產生的壓縮檔自動下載到使用者的電腦。

可以到 DotNetZip 下載最新 release 的版本。以目前所抓到的 v1.9版來說,解開之後,只要將 DotNetZip-v1.9\Release 裡的 Ionic.Zip.dll 加入到 Visual Studio 的參考就可完成 DotNetZip 的引用。重點是,這是免費的,作者甚至認為,有了他,就不用去買價值 29 美金的 winzip 了,作者也在網站上介紹他自己撰寫的程式,用以取代 winzip。

要將資料夾壓縮的語法,非常簡潔:

using Ionic.Zip;


using (ZipFile zip = new ZipFile())
{
    //將資料夾加入 zip
    zip.AddDirectory(MapPath(".\\SourceFiles"));

    //產生 zip 檔
    zip.Save(MapPath(".\\") + "MyZipFile.zip");
    
}

產生好壓縮檔後,如何讓這檔案下載到使用者電腦呢?當然,可以在網頁上放個超連結,給使用者點選,但如過進一步想更自動一點,則就是直接將附件檔自動下載。

這時,我在 .aspx 頁面放了一個 iframe ,並去設定這 iframe 的 src 屬性為下載連結。而我下載的連結則是透過 .ashx 來處理(可參考測試程式)。

<iframe id="ifies" runat="server" style="visibility:hidden"></iframe>
</form>

這裡我偷偷埋了一個 iframe,並將他的 visibility 設為隱藏,這樣使用者在網頁上就看不到他的存在了。最後,在產生壓縮檔程式裡頭加上 :

ifies.Attributes["src"] = string.Format("filedownload.ashx?filename={0}&path={1}", "MyZipFile.zip",MapPath(".\\"));

using Ionic.Zip;


using (ZipFile zip = new ZipFile())
{
    //將資料夾加入 zip
    zip.AddDirectory(MapPath(".\\SourceFiles"));

    //產生 zip 檔
    zip.Save(MapPath(".\\") + "MyZipFile.zip");

    //讓網頁執行下載動作
    ifies.Attributes["src"] = string.Format("filedownload.ashx?filename={0}&path={1}", "MyZipFile.zip",MapPath(".\\"));
    
}

最後可以觀察執行結果:

FireFox:直接跳出另存新檔


IE6:第一次會詢問安全性問題


Chrome:直接就幫你下載了


DotNetZip 除了能壓縮,也能解壓縮、支援 Dos 指令來操作,是程式開發人員不可或缺的好工具。

參考:http://dotnetzip.codeplex.com/
測試程式:下載

補充:20100929
最近使用 dotnetzip 時發生一個問題,就是中文檔名的文件,壓縮後都會變成亂碼。還好在一篇文章中有提到解法:

http://cheeso.members.winisp.net/DotNetZipHelp/html/aecbb638-1ecf-807e-b933-5bb1a2a95e81.htm

所以,要壓縮中文檔名時,記得程式要加上:

(承接上面的範例)
zip.ProvisionalAlternateEncoding = System.Text.Encoding.GetEncoding("big5")

2010年9月11日 星期六

你是否習慣問自己問題?



媽媽,為什麼樹上的小鳥會飛?為什麼雞會生蛋?...

還記得自己小時候,也問過一連串類似的問題嗎。如今的你我,又有多少機會再提出這麼多問題了呢?若是說,隨著年紀的增長,經驗多了,知識廣了,所以問題少了,你相信嗎?我比較相信年紀大了,油條了,所以變懶了。

人的大腦,有兩個很重要的能力,一個是思考能力,一個是記憶能力。當你還是小娃兒時,透過哭聲開始了與父母親的基本溝通。而你慢慢開始知道,不舒服、飢餓時,只要哭,就會有人幫你解決。

漸漸地,你開始對這世界產生興趣,日常生活周遭的人事物,對你來說,都是如此的新鮮,此時此刻,你的心中隨時都存有著十萬個「為什麼?』。

你如何去解決心裡頭埋藏的十萬個「為什麼?」,這與你的父母、家庭環境有著密不可分的關係。有些父母亦師亦友,對你的問題無所不談;有些家庭藏書豐厚,或懂得利用圖書資源;此外,尚有權威一言堂的家庭,也有鬼神論者,而這些生長環境都是決定著一個小孩「解決問題」的能力。

快樂的 6 年過去,小孩子開始入學了,學校裡開始傳授更多的學理知識給你,但此刻的你,畢竟童心未泯,還是比較喜歡玩樂。如果遇到高明的老師,他會讓你知道學習與玩樂是可以兼得的。如果遇到普通的老師,則是學習與玩樂只能取其一。更慘的,是遇到玩也不能玩,讀也讀不會的情況,怎麼辦?只好自己四處尋找刺激、快感了。

在求學的年紀,不管有收穫、沒收穫,但上千個日子經過之後,總是會讓既定的思考行為進而成為你潛意識裡的一部分。其實,對人類進化而言,它是有幫助的,將你平日的反應記憶下來,日後如果遇到突發狀況時,不就可以立即反應了嗎?

舉例來說,如果有人說話說到你的痛處時,你的自我防衛心理開始起來,而你這時刻的直覺反應,就是最真實的你。這真實的自己,則是經歷了無數次的思考之後所慢慢塑成的,是日後你遇事決斷時的第一個念頭。

初念淺,轉念深。每個念頭,都是過去經驗所留下。人,總不能老是用老方法來處理事、來生活吧!所以要時時鼓勵自己,再多想一些,再深入一點,去探索每一個思想的盲點。你小時候那種「為什麼,為什麼,為什麼...」的精神還在嗎?把他找回來吧!

2010年9月9日 星期四

透過 jQuery 實現攔截鍵盤的 Ctrl+S 事件

想要在網頁上,允許使用者透過鍵盤的 Ctrl+S 來觸發事件,jQuery 可以幫你。

Ganeshji 在其部落格文章「Ctrl + Key Combination – Simple Jquery Plugin」介紹當如果只需要簡單的 Ctrl+? 鍵,又不想使用類似 js-hotkeys 功能這麼完整的外掛套件時,單單只靠幾行 jQuery 就可以完成了。

Ganeshji 首先使用 jQuery 的擴展定義了自己的 ctrl 函式:

$.ctrl = function(key, callback, args) {
    var isCtrl = false;
    $(document).keydown(function(e) {
        if(!args) args=[]; // IE barks when args is null

        if(e.ctrlKey) isCtrl = true;
        if(e.keyCode == key.charCodeAt(0) && isCtrl) {
            callback.apply(this, args);
            return false;
        }
    }).keyup(function(e) {
        if(e.ctrlKey) isCtrl = false;
    });
};

日後要引用 ctrl 函式,方法如下:

$.ctrl('S', function() {
    alert("Saved");
});

這範例,只要使用 Ctrl + S ,就會觸發 alert("Saved"); 實際上測試了不同瀏覽器,IE, Chrome, 都正常,但 FireFox 有點惱人的問題,就是當執行 Ctrl+S 時,FireFox 會跳出兩次另存新檔的對話視窗,這其實並不是我想要的結果。

為了避免 FireFox 跳出另存新檔的對話視窗,可以去攔截 keypress 的動作,並將原始的 Ctrl+S 事件濾掉。所以加了以下一段程式來完成。

$(window).keypress(function(event) {
      if ((event.which == 115 && event.ctrlKey)){
          event.preventDefault();
      }
    }); 

如此修改後,整個程式就更趨完整了。

附上範例程式:[下載]

參考:
01:Ctrl + Key Combination – Simple Jquery Plugin
02:js-hotkeys

後記:

在 Ganeshji 文章中有提到 js-hotkeys ,實際去玩了一下,發現他在 FireFox 也同樣存在著使用 Ctrl+S 會跳出兩次另存新檔視窗的問題,但用本編文章介紹的方式去攔截 keypress 就可解決。的確, js-hotkeys 真的是解決 web 上使用 keyboard 快捷鍵的好幫手,而 Ganeshji 則是提供了羽量級的解決方案,各位可斟酌使用。

附上 js-hotkeys 的測試程式:[下載]

2010年8月27日 星期五

如何在SQL Server 資料庫中 找出符合你的關鍵字的Store Procedure

要讓你在資料庫中,找出符合某關鍵字的 Store Procedure,要怎麼去完成呢?

以前龐統曾獻三計給劉備奪川蜀,現在也有三種方法來取 Store Procedure。

下計:
一個一個點開 Stroe Procedure,並用搜尋的方式,將每個符合你關鍵字的 Store Procedure都記錄下來。

中計:
使用 SQL Server 提供的「產生指令碼」功能,並將所有 Store Procedure都點選,匯出成一個檔案。開啟這檔案,用搜尋的方式找到符合你關鍵字的 Stre Procedure。

上計:
善用SQL Sever 提供的 view :INFORMATION_SCHEMA.ROUTINES 來完成。

劉備認為上計過急,下計又太緩,故依中計而行。而我早已被 user 逼急了,所以應該會用上計吧!

在資料庫裡,每一支 Store Procedre 或 Function ,都會有對應的一筆 INFORMATION_SCHEMA.ROUTINES紀錄。而這 view 裡的欄位 ROUTINE_DEFINITION 就保存著目前使用者所定義的程式碼。如果今天你想要取得符合某個關鍵字的所有 Store Procedure,那只要在 INFORMATION_SCHEMA.ROUTINES 查詢 ROUTINE_DEFINITION 欄位就可以達成。如果你想更進一步去限制只要查詢Store Procedure類型,那可以在 ROUTINE_TYPE 設定條件為 「PROCEDURE」。(目前有兩種類型:PROCEDURE / FUNCTION)

以下是個範例,找出所有關鍵字為 delete prog_base 的 Store Procedure:
SELECT ROUTINE_SCHEMA, ROUTINE_NAME, ROUTINE_TYPE
FROM INFORMATION_SCHEMA.ROUTINES
WHERE ROUTINE_DEFINITION LIKE '%prog_base%'
AND ROUTINE_TYPE = 'PROCEDURE';

INFORMATION_SCHEMA.ROUTINES是系統預設提供的 view,換言之,他也是從某些現有的資料表或 view 萃取資料而來。所以,我們當然也可以不透過這 view,直接去從資料表抓。以下,是更原始些的語法,而效果也是一樣。

SELECT distinct sys.sysobjects.name, sys.sysobjects.type
FROM sys.sysobjects INNER JOIN syscomments 
ON sys.sysobjects.id = sys.syscomments.id
WHERE sys.syscomments.text LIKE '%prog_base%' 
AND sys.sysobjects.type = 'P'
ORDER BY sys.sysobjects.NAME

如果要考慮彈性的話,可以考慮使用 syscomments 的使用方法,因為他可以支援的類型,不侷限於 Stroe Procedure , Function,他還支援 Table , View 。所以使用 syscomments 是一個比較完整的解決方案。

而近來自己實際比較了使用 INFORMATION_SCHEMA.ROUTINES 與 syscomments 查詢結果,發現兩邊查出來的資料竟然會不一致,細究原因,原來是 INFORMATION_SCHEMA.ROUTINES 裡的 ROUTINE_DEFINITION 欄位,只支援到長度 4000,換言之,如果有一個 Store Procedure 寫了超過 4000 字以上的程式碼,剛好你的查詢關鍵字又落到 4000 字以後,這樣使用 INFORMATION_SCHEMA.ROUTINES 就查不出資料了。發現了這原因後,就更建議使用 syscomments 來完成你的需求了。詳細原因可參考 Madhivanan 部落格的介紹。他建議若要完整取得 ROUTINE_DEFINITION 欄位 裡的資料,要使用以下方式:

--Method 1
select object_definition(object_id('your_procedure'))

--Method 2
EXEC sp_helptext 'your_procedure'