【SSIS】スクリプトコンポーネントでStream(BLOB)を扱う

SQLのデータ型の中でも最も影が薄いと言うか、使うタイミングがよくわからないBLOBなんですが、SSISも一応対応しています。

SQL Serverだとntext、text、image(それぞれnvarchar(max)、varchar(max)、varbinary(max)の方を使うよう推奨されている)あたりがそれに該当します。SSISではDT_NTEXT、DT_TEXT、DT_IMAGEに変換されるようです。

まぁ、あんまり使うことはないんですが、うっかり4000字をこえるような文字列を使うような場合はDT_NTEXT or DT_TEXTに変換しないといけないですし、フラットファイルソースのエラー出力で出てくるエラー出力列もなぜかDT_NTEXT or DT_TEXTで出てきます。

更にうっかりこれをスクリプトコンポーネントで扱うことになるとやや面倒です、って話を今回はしていきます。

要件

こんなカンマ区切りのCSVがあったとします。文字コードSJIS、改行はCRLFです。

hoge,fuga
12345678901,1
12345678901,2

hogeは10桁想定ですが、何故か11桁でやって来ました。このファイルを作成した担当者に出来る限り汚い言葉を投げつけたいところですが、こんなこともあろうかと切捨てが発生するとそのデータをそのまま別のエラーログファイルに吐き出すようにしていました。

が、「エラーとなったデータのfugaだけをログに吐き出して欲しい。」と頼まれたらどうするか、って話です。

まぁエラーログをソースにして別のデータフロー作ればいいんですけどね。要はスクリプトコンポーネントでfugaの値だけ取り出すにはどうすればいいかって話です。

public override void 入力0_ProcessInputRow(入力0Buffer Row)
{
    do
    {
        var blob = Row.フラットファイルソースのエラー出力列;
        // TODO:BlobをStringに変換する
        
        var fuga = string.Empty;
        Row.fuga = fuga;
    } while (Row.NextRow());
}

Row.フラットファイルソースのエラー出力列と言うしょうもない名前のプロパティには、1行のStringをBlobにしたデータが詰まっています。ここからデータを取得しStringに変換、splitで分解してLastをとればいけそうです。

GetBlobData

blobの型はMicrosoft.SqlServer.Dts.Pipeline.BlobColumnです。BlobColumn.GetBlobDataメソッドを使うことでBLOBをbyteの配列に変換することができます。

シグネチャはoffsetとcountです。offsetは0でいいんですが、この一行のBLOBがどれだけの大きさなのかは…と思ったらBlobColumn.Lengthプロパティなんておあつらえ向きなものがあるのでそれを使います。

public override void 入力0_ProcessInputRow(入力0Buffer Row)
{
    do
    {
        var blob = Row.フラットファイルソースのエラー出力列;
        var data = blob.GetBlobData(0, (int)blob.Length);
        // TODO:BlobをStringに変換する
        
        var fuga = string.Empty;
        Row.fuga = fuga;
    } while (Row.NextRow());
}

Encoding.GetString

これでデータのbyteの配列を手に入れることができました。テキストのStreamを扱ったり、無理矢理文字コードを変換しようとしたことがある人ならわかると思いますが、文字列のbyte配列をStringに変換するにはEncodingクラスを使います。

今回はSJISなのでSJISのEncodingを取得し、そのインスタンスEncoding.GetString(byte[] bytes)メソッドを呼び出せば文字列を取得できます。

public override void 入力0_ProcessInputRow(入力0Buffer Row)
{
    do
    {
        var blob = Row.フラットファイルソースのエラー出力列;
        var data = blob.GetBlobData(0, (int)blob.Length);
        // コードページ932はSJISだよ
        var fuga = Encoding.GetEncoding(932).GetString(data).Split(',').Last();
        Row.fuga = fuga;
    } while (Row.NextRow());
}

まとめ

後はfugaだけを吐き出すフラットファイルの接続マネージャを作成し、適当にマッピングしてあげればOKです。

ただこのままだと改行も一緒に吐き出してしまうので、その辺は適宜Trimしてあげてください。

また、imageに関しても同じ要領でデコードできると思います。(流石にEncodingクラスは使えないと思うけど)

参考