【入門編】ADOでSQL Serverのバイナリデータを扱う:画像やPDFのBLOBデータ入出力 – Access VBA解析バイブル

スポンサーリンク
スポンサーリンク

Access VBAの深淵へようこそ:SQL ServerのBLOBデータを「チャンク」で支配する

「マクロの記録から一歩踏み出して、本格的な業務アプリを作りたい」
「SQL Serverに保存した画像やPDFを、Accessからスムーズに読み書きしたい」

もしあなたが今、そんな壁にぶつかっているのなら、おめでとうございます。あなたは今、プログラミングの「初級者」から「エンジニア」へと脱皮するための、最もエキサイティングで、かつ少しだけ険しい峠に差し掛かっています。

今回のテーマは、ADO(ActiveX Data Objects)を用いた、SQL Server上のバイナリデータ(BLOB)の操作です。

「ただデータを読み込むだけじゃないの?」と思うかもしれません。しかし、数MB、時には数十MBという画像やPDFを扱うとき、単純な代入操作ではメモリが悲鳴を上げ、アプリケーションは音を立てて崩れ落ちます。

そこで必要になるのが、「チャンク(塊)」という考え方です。
この記事を読み終える頃には、あなたは巨大なデータを、まるで砂時計の砂を一つずつ落とすように、精密かつ安全に制御する術を手にしているはずです。

さあ、一緒に深淵を覗いてみましょう。

1. なぜ「DAO」ではなく「ADO」なのか?

Accessを触っていると、必ず「DAO」と「ADO」という二つの言葉に出会います。

  • DAO (Data Access Objects): Accessの内部テーブルを操作するのに特化した、Accessの「ネイティブな使い手」です。
  • ADO (ActiveX Data Objects): SQL Serverのような「外部のデータベース」と対話するための、汎用的な「外交官」です。

SQL Serverにある画像データを扱う場合、私たちは「外交官」であるADOを呼び出す必要があります。なぜなら、ADOはネットワーク越しに巨大なバイナリデータをやり取りするための、高度なプロトコル(通信規約)を備えているからです。

2. 「一気に」ではなく「小分けに」:チャンク処理の本質

想像してみてください。あなたは、巨大な砂漠の砂を一気にバケツで運ぼうとしています。しかし、バケツは小さすぎます。一気に運ぼうとすれば、溢れてしまい、道(メモリ)を汚してしまいます。

これを解決するのが `GetChunk` と `AppendChunk` メソッドです。

  • `GetChunk` (読み込み): 巨大なデータの中から、指定したサイズ(スプーン一杯分)だけを切り出して取り出す。
  • `AppendChunk` (書き込み): 小さな塊を、何度も何度も繰り返しデータベースに流し込む。

この「小分けにして扱う」技術こそが、メモリ消費を最小限に抑え、システムを安定させる極意なのです。

3. 実践:SQL Serverから画像を取り出す(GetChunk)

まずは、SQL Serverに保存されている画像を、ローカルのPCにファイルとして保存するコードを見てみましょう。

【読み込みのロジック図解】

1. 接続: SQL ServerへADOで接続。
2. 探索: 対象のレコード(画像データがある行)を特定。
3. ループ:

  • `GetChunk` でデータの断片を取得。
  • 断片をファイルに書き込む。
  • 全て読み終わるまで繰り返す。

4. 解放: オブジェクトを丁寧に片付ける。

''' <summary>
''' SQL Server上のバイナリデータをローカルファイルに保存する
''' </summary>
''' <param name="connString">接続文字列</param>
''' <param name="recordID">取得したいデータのID</param>
''' <param name="savePath">保存先のフルパス</param>
Public Sub DownloadBlobToFile(ByVal connString As String, ByVal recordID As Long, ByVal savePath As String)
    Dim cn As ADODB.Connection
    Dim rs As ADODB.Recordset
    Dim fileNum As Integer
    Dim chunk() As Byte
    Dim chunkSize As Long
    Dim bytesRead As Long
    
    ' チャンクサイズの設定(例:64KBずつ取り出す)
    chunkSize = 65536 
    
    On Error GoTo ErrorHandler

    Set cn = New ADODB.Connection
    cn.Open connString

    Set rs = New ADODB.Recordset
    ' SQL文:対象のレコードを指定。バイナリ型(VARBINARY)のフィールドを指定
    rs.Open "SELECT ImageField FROM MyTable WHERE ID = " & recordID, cn, adOpenStatic, adLockReadOnly

    If Not rs.EOF Then
        fileNum = FreeFile
        Open savePath For Binary Access Write As #fileNum
        
        ' GetChunkでデータを少しずつ読み込み、ファイルに書き込むループ
        ' rs.Fields(0).GetChunk(chunkSize) が「スプーン一杯」の役割
        Do While Not IsNull(rs.Fields(0).Value)
            chunk = rs.Fields(0).GetChunk(chunkSize)
            bytesRead = UBound(chunk) - LBound(chunk) + 1
            
            ' ファイルへ書き込み
            Put #fileNum, , chunk
            
            ' もし取得したサイズが指定サイズより小さければ、それがデータの終端
            If bytesRead < chunkSize Then Exit Do
        Loop
        
        Close #fileNum
        MsgBox "保存が完了しました!", vbInformation
    Else
        MsgBox "対象のデータが見つかりませんでした。", vbExclamation
    End If

CleanUp:
    ' オブジェクトのライフサイクル管理(これ、超重要です!)
    If Not rs Is Nothing Then
        If rs.State = adStateOpen Then rs.Close
    End If
    Set rs = Nothing
    If Not cn Is Nothing Then
        If cn.State = adStateOpen Then cn.Close
    End If
    Set cn = Nothing
    Exit Sub

ErrorHandler:
    MsgBox "エラーが発生しました: " & Err.Description, vbCritical
    Resume CleanUp
End Sub

4. 実践:ファイルをSQL Serverへ送り込む(AppendChunk)

次は逆です。手元にあるPDFや画像を、SQL Serverのバイナリ型フィールドへ流し込みます。

【書き込みのロジック図解】

1. 準備: ファイルを「バイナリモード」で開く。
2. レコード作成: SQL Serverに新しい行(または既存行)を準備。
3. ループ:

  • ファイルから一定量を読み込む。
  • `AppendChunk` でデータベースに「継ぎ足していく」。

4. 完了: 全て送り終えたらコミット。

''' <summary>
''' ローカルファイルをSQL Server上のバイナリ型フィールドに保存する
''' </summary>
''' <param name="connString">接続文字列</param>
''' <param name="recordID">更新対象のID</param>
''' <param name="filePath">読み込むファイルのフルパス</param>
Public Sub UploadFileToBlob(ByVal connString As String, ByVal recordID As Long, ByVal filePath As String)
    Dim cn As ADODB.Connection
    Dim rs As ADODB.Recordset
    Dim fileNum As Integer
    Dim buffer() As Byte
    Dim chunkSize As Long
    Dim bytesRead As Long

    chunkSize = 65536 ' 64KB単位

    On Error GoTo ErrorHandler

    Set cn = New ADODB.Connection
    cn.Open connString

    ' レコードセットを開く(更新可能な状態で開くのがポイント)
    Set rs = New ADODB.Recordset
    rs.Open "SELECT ImageField FROM MyTable WHERE ID = " & recordID, cn, adOpenStatic, adLockOptimistic

    If Not rs.EOF Then
        fileNum = FreeFile
        Open filePath For Binary Access Read As #fileNum
        
        ' まずフィールドを空にする(既存データの上書き準備)
        rs.Fields(0).Value = Null
        rs.Update

        ' ファイルを読み込みながら、AppendChunkで継ぎ足していく
        Do While Not EOF(fileNum)
            ' ファイルからチャンク分を読み込み
            Get #fileNum, , buffer
            
            ' AppendChunkでデータベースに流し込む
            ' これが「砂を少しずつ積み上げる」操作です
            rs.Fields(0).AppendChunk buffer
        Loop
        
        ' 最後に変更を確定させる
        rs.Update
        Close #fileNum
        MsgBox "アップロードが完了しました!", vbInformation
    Else
        MsgBox "対象のレコードが見つかりません。", vbExclamation
    End If

CleanUp:
    If Not rs Is Nothing Then
        If rs.State = adStateOpen Then rs.Close
    End If
    Set rs = Nothing
    If Not cn Is Nothing Then
        If cn.State = adStateOpen Then cn.Close
    End If
    Set cn = Nothing
    Exit Sub

ErrorHandler:
    MsgBox "エラーが発生しました: " & Err.Description, vbCritical
    Resume CleanUp
End Sub

5. 伝説のアーキテクトからのアドバイス:陥りやすい罠

ここまで辿り着いたあなたに、現場で血を流さないための「極限の知見」を授けましょう。

① オブジェクトのライフサイクルを疎かにしない

VBAにおいて、`Set rs = Nothing` を忘れることは、使い終わったバケツを現場に放置するようなものです。特にループの中でオブジェクトを生成する場合、メモリリークを引き起こし、Accessが突然「メモリ不足です」と泣き言を言う原因になります。「使ったら、必ず閉じて、消す」。これを徹底してください。

② カーソルタイプに注意

`AppendChunk` を使うとき、Recordsetの開き方(CursorType)が適切でないと、エラーになることがあります。基本的には `adOpenStatic` や `adOpenKeyset` を使い、書き込みができる `adLockOptimistic` を指定してください。

③ エラーハンドリングは「防波堤」

バイナリ操作は、ファイルパスの間違い、ネットワークの瞬断、ファイルの使用中(ロック)など、エラーの温床です。`On Error GoTo ErrorHandler` は、単なるおまじないではなく、ユーザーのデータを守るための「防波堤」として機能させてください。

結びに

いかがでしたか?
`GetChunk` と `AppendChunk`。一見すると少し面倒な手順に見えるかもしれません。しかし、この「小分けにして扱う」という考え方は、単なるプログラミングのテクニックではなく、コンピューターリソースを尊重し、堅牢なシステムを構築するためのエンジニアの哲学そのものです。

ここをクリアできれば、あなたはもう「マクロを記録する人」ではありません。データを自在に操る「システムビルダー」です。

自信を持って、次のコードを書き始めてください。応援していますよ!

タイトルとURLをコピーしました