2009年12月11日金曜日

SQL CLRで、文字数単位ではなくてバイト単位で文字列を扱う関数を作る

今回の記事では、SQL-Server 2008 Expressなどにおいて、文字列を「文字数単位」ではなくて、「バイト数単位」で扱う関数を作ってみます。

さて、最新世代のコンピュータは、文字列を扱う時に、もはや半角・全角という概念を持ったバイト数単位ではなくて、単なる文字数単位で扱うようになりました
英語圏では都合が良いでしょうが、日本では、全角=2バイト、半角=1バイトが絶対の前提条件という場合が極めて多いわけです。

SQL-Server 2008 Expressもその例にもれず、文字列の扱いを文字数単位で行います。
そこで、SQL CLRによって、文字列をバイト数で扱う関数を作ることとしました。
ちなみに、帰宅してから、ウンウン言いながらAspire 1410で作ったばかりなので、テストは十分に出来ていません
もしこんなコードでも参考にしていただけるなら、そのあたりはご容赦下さいませ・・・。

さて、文字列関数というと、任意の位置を切り出すSubString()、右側から文字数分切り出すRight()、左側から文字数分切り出すLeft()関数の三つがメジャーですが、これら三つのバイト単位処理のプログラムを実現します。

まず、全ての関数の中心となるプライベートなコア関数、直接は外部から呼び出せない「fnc_Core_StringB()」関数を作り、全ての関数はこれをコールして動作する・・・という仕組みにします。
コア関数一つ作っておいて、あとは簡単にSQL CLRのバリエーション展開で済ませたいなと。

■バイト単位=Shift JISで文字列を扱う!!■

バイト単位ということで、通常のUnicode形式の文字列のままではどうにもならないので、文字列をShift JISのバイナリ配列に変換して操作します。

実のところ、変換するだけなら、非常に簡単にコトが済むのです。
しかし、バイト単位で絶対に忘れてはならない点は、全角の文字を半分に断ち割ってしまうケースがあるということです。

たとえば、「あいうえお」という文字列があったとして、0バイト目から2バイトを切り出すなら結果は「あ」でOKですが、これが1バイト目から2バイトなんてやってしまうと、「あ」の半分を断ち割ってスタートしてしまいますよね。こういうのがマズイのです。

全角の前半分を1バイト目、後ろ半分を2バイト目と呼称しますが、どっちかを断ち割った状態で、そのまんま変換すると、文字化けしてしまって使い物になりません。
たとえば、半角の空白(16進数で0x20)に置き換える処理をしてあげないといけないです。

しかし、Shift JISコードの恐ろしさはここからなんですよね。
Shift JISコードは、全角1バイト目と、全角の2バイト目が、単体では識別が出来ないという全く恐怖の仕様になってます。

バイナリを先頭から1バイトづつ順番に見て行き、全角の1バイト目を示す値だったら、次に来るバイトは必ず全角の2バイト目としてペアにする・・・という見方をしないとうまくいかないのです。
全角の1バイト目を示す値というのは、16進数「0x81」から「0x9F」、または、「0xE0」から「0xFF」の範囲に該当する、ということです。
じゃ、単体でこれ見れば済む話じゃないか!!先頭からズラズラ見る必要なし!!って思いますよね。

問題は、全角の2バイト目であっても、平気でこの1バイト目を示す範囲に入って来るという点です。知らないとヤバイくらいハマる危険アリ!!
だから、先頭から順番に見ないと、うまくないのです。単体のバイトを取り上げてチェックしてもだめなんです。この事実を知った時は、結構途方に暮れますよね。

というわけで、多少強引ではありますが、今回の記事では、文字列のバイト配列と1:1で対応したステータスの配列を作って並べて使う事にしてみました。

このあたりの処理は、もはやSQL-Serverの標準関数では作る気が失せてしまうくらいなのですが、SQL CLRがあってくれて、本当に良かったー・・・としか言いようがないです。
マイクロソフトの担当者には足を向けて寝られない気持ちですね。

詳しくは、プログラムコードのスクリーンショットを見ていただきたいのですが、C#言語で関数が組める幸せを噛み締めるしかないくらい、好きなように作れますよね。
パフォーマンスも、データ内容によりますが、単純な切り出しならば1万件程度ではパフォーマンスもほとんど悪化しなかったです。単純なテストデータをパパッと作っただけなんで、限定的な話でしょうけれど。

さて、今回の関数ですが、コア関数のfnc_Core_StringB()関数は、外部から直接は呼び出せませんがも、内部にあって全ての処理を行う文字通りのコア、メインです。

任意の位置で文字列をバイト単位で切り出すSQLCLR_SubStringB()
右側から文字列をバイト単位で切り出すSQLCLR_RightB()
左側から文字列をバイト単位で切り出すSQLCLR_LeftB()
の三つは、このコア関数をささっとコールしてるだけで実現してます。

ちなみに、Listというジェネリック型のクラスがありますが、これは、従来の.NET FrameworkでArrayListと呼ばれていたクラスの進化バージョンです。

かならずobject型になり、object型から実際に使用する型への変換オーバーヘッドの存在したArrayListと違って、型を指定出来る分高速化しています。

でもでも、古いバージョンの.NET Frameworkではコンパイルエラーになるので注意が必要です。