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

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

Access VBAを掌握する:ADOとStreamオブジェクトで挑む「バイナリデータの極意」

こんにちは。開発の現場へようこそ。

「マクロの記録」から一歩踏み出し、自分でコードを書き始めたあなた。素晴らしい一歩を踏み出しましたね。しかし、実務の現場、特にSQL Serverのような強力なデータベースとAccessを連携させるフェーズに入ると、これまでとは全く異なる「壁」にぶつかることがあります。

その壁の一つが、「画像やPDFといった、テキストではないデータ(BLOB)をどう扱うか」という問題です。

「ただの文字なら簡単なのに、画像になると途端にエラーが出る」「メモリが足りないと言われる」「ファイルが壊れてしまう」……。これらは、データの「型」だけでなく、「オブジェクトのライフサイクル」と「メモリへの流れ」を理解していないことから起こる現象です。

今日は、私が長年現場で培ってきた、ADOと`ADODB.Stream`を組み合わせた「バイナリデータの入出力」の極意を、余すことなくお伝えしましょう。ここをクリアすれば、あなたのAccess VBAは「単なるツール」から「プロフェッショナルなシステム」へと進化します。

1. なぜ「DAO」ではなく「ADO」なのか? アーキテクチャの境界線

まず、混乱しやすい「DAO」と「ADO」の違いを整理しておきましょう。ここを曖昧にすると、設計段階で手痛いミスを犯します。

特徴 DAO (Data Access Objects) ADO (ActiveX Data Objects)
得意領域 Access内部のテーブル操作(高速) SQL Server等の外部DBへの接続
主な接続先 ローカルの `.accdb` / `.mdb` SQL Server, Oracle, Web API等
データ型 Access固有の型に最適化 多様なデータ型(BLOB含む)に強い

エンジニアの視点:
Access内のテーブルをいじるだけならDAOが最速です。しかし、SQL Serverに保存された画像(`VARBINARY(MAX)`型)を扱う場合、DAOではその能力をフルに引き出せません。外部の強力なエンジンと対話するためには、標準的なプロトコルである「ADO」を使うのが鉄則です。

2. 鍵となるのは「ADODB.Stream」という名のバッファ

画像やPDFは、文字(String)ではありません。0と1が並んだ「バイナリデータ」です。
これをVBAで扱う際、単純な変数に代入しようとすると、データの欠落やメモリのオーバーフローが発生します。

そこで登場するのが `ADODB.Stream` オブジェクトです。
これは、メモリ上に「データの通り道(ストリーム)」を作り出し、バイナリデータを安全に流し込んだり、ファイルとして書き出したりするための、いわば「高度なダム」のような役割を果たします。

3. 実践:SQL Serverから画像をローカルへ保存する(ダウンロード)

まずは、データベースに保存されているバイナリデータを、指定したパスにファイルとして書き出す処理を見てみましょう。

''' <summary>
''' SQL Server上のバイナリデータをローカルファイルとして保存する
''' </summary>
''' <param name="conn">接続済みのADODB.Connectionオブジェクト</param>
''' <param name="recordID">対象のレコードID</param>
''' <param name="savePath">保存先のフルパス</param>
Public Sub DownloadBlobToFile(ByRef conn As ADODB.Connection, ByVal recordID As Long, ByVal savePath As String)
    Dim rs As ADODB.Recordset
    Dim stream As ADODB.Stream
    Dim sql As String

    ' 1. SQL文の準備(対象のIDを指定)
    sql = "SELECT ImageColumn FROM MyTable WHERE ID = " & recordID

    On Error GoTo ErrorHandler

    ' 2. レコードセットのオープン
    Set rs = New ADODB.Recordset
    rs.Open sql, conn, adOpenStatic, adLockReadOnly

    If Not rs.EOF Then
        ' 3. Streamオブジェクトの生成と設定
        Set stream = New ADODB.Stream
        
        ' 【重要】Typeを1 (adTypeBinary) に設定することがバイナリ処理の絶対条件!
        stream.Type = adTypeBinary
        
        ' 4. レコードセットからバイナリデータをStreamへ書き込む
        ' GetChunkを使うことで、巨大なデータでもメモリを圧迫しにくくします
        stream.Write rs.Fields("ImageColumn").GetChunk(-1)

        ' 5. ファイルとして保存
        ' 既存ファイルがある場合は上書きするように設定
        stream.SaveToFile savePath, adSaveCreateOverWrite
        
        MsgBox "ファイルの保存に成功しました:" & savePath, vbInformation
    Else
        MsgBox "対象のレコードが見つかりません。", vbExclamation
    End If

CleanUp:
    ' 6. オブジェクトのライフサイクル管理(メモリ解放)
    ' これを忘れると、ファイルがロックされたままになったりメモリリークの原因になります
    If Not stream Is Nothing Then
        If stream.State = adStateOpen Then stream.Close
        Set stream = Nothing
    End If
    If Not rs Is Nothing Then
        If rs.State = adStateOpen Then rs.Close
        Set rs = Nothing
    End If
    Exit Sub

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

ここがプロの勘所!

  • `stream.Type = adTypeBinary`: これを忘れると、データを「テキスト」として解釈しようとしてしまい、画像が壊れます。
  • `GetChunk(-1)`: `-1`を指定すると、そのフィールドの全データを取得します。非常に大きなファイルの場合、分割して取得するテクニックもありますが、通常の画像ならこれで十分です。
  • `CleanUp` セクション: `Set obj = Nothing` は、初学者が最も忘れがちな、しかし最も重要な作法です。

4. 実践:ローカルファイルをSQL Serverへアップロードする(アップロード)

次に、逆のパターンです。パソコン上のPDFや画像を、データベースへ送り込みます。ここでは、`Command`オブジェクトを使用して、パラメータとしてStreamを渡す、最も堅牢な手法を使います。

''' <summary>
''' ローカルファイルをSQL Serverのバイナリ列へアップロードする
''' </summary>
''' <param name="conn">接続済みのADODB.Connectionオブジェクト</param>
''' <param name="recordID">更新対象のレコードID</param>
''' <param name="filePath">読み込むファイルのフルパス</param>
Public Sub UploadFileToBlob(ByRef conn As ADODB.Connection, ByVal recordID As Long, ByVal filePath As String)
    Dim stream As ADODB.Stream
    Dim cmd As ADODB.Command
    Dim sql As String

    On Error GoTo ErrorHandler

    ' 1. ファイルを読み込むためのStream準備
    Set stream = New ADODB.Stream
    stream.Type = adTypeBinary
    stream.Open
    stream.LoadFromFile filePath ' ファイルをメモリ上にロード

    ' 2. SQL文の準備(パラメータクエリを使用)
    ' 直接SQLに値を埋め込むのはSQLインジェクションの危険があるため、必ずCommandを使用します
    sql = "UPDATE MyTable SET ImageColumn = ? WHERE ID = ?"

    ' 3. Commandオブジェクトの設定
    Set cmd = New ADODB.Command
    With cmd
        .ActiveConnection = conn
        .CommandText = sql
        .CommandType = adCmdText
        
        ' パラメータの追加(バイナリデータ用)
        ' 第2引数にStreamオブジェクトをそのまま渡せるのがADOの強みです
        .Parameters.Append .CreateParameter("@img", adLongVarBinary, adParamInput, , stream)
        ' パラメータの追加(ID用)
        .Parameters.Append .CreateParameter("@id", adInteger, adParamInput, , recordID)
        
        ' 4. 実行
        .Execute
    End With

    MsgBox "アップロードが完了しました。", vbInformation

CleanUp:
    ' 5. 後始末
    If Not stream Is Nothing Then
        If stream.State = adStateOpen Then stream.Close
        Set stream = Nothing
    End If
    If Not cmd Is Nothing Then Set cmd = Nothing
    Exit Sub

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

ここがプロの勘所!

  • `adLongVarBinary`: SQL Serverの `VARBINARY(MAX)` に対応させるための指定です。
  • パラメータクエリ (`?`): 文字列結合でSQLを作ると、ファイルパスに `’`(シングルクォート)が含まれていただけでエラーになります。`Command`オブジェクトを使うことで、こうしたトラブルを未然に防ぎ、セキュリティも高まります。

5. 陥りやすい罠と、それを回避する「設計思想」

最後に、現場で私が何度も見てきた「失敗パターン」を共有します。

1. メモリ不足(Out of Memory):
数GBあるような巨大な動画ファイルを一度に `stream.Write` しようとすると、VBAのメモリ限界に達します。巨大ファイルを扱う場合は、ファイルを一定サイズ(例:1MBずつ)に分割して、繰り返し書き込む「チャンク処理」を検討してください。
2. トランザクションの欠如:
「データベースのレコードは更新されたが、ファイルの保存に失敗した」という状態は、データの整合性を破壊します。重要な処理では、`conn.BeginTrans` と `conn.CommitTrans` を使い、「すべて成功するか、すべて失敗するか」を制御しましょう。
3. ファイルロックの放置:
`stream.Close` を忘れると、そのファイルはプログラムが終了するまで「使用中」となり、他のユーザーやプロセスが操作できなくなります。必ず `CleanUp` で確実に閉じる癖をつけてください。

まとめ:技術の「深さ」が、システムの「信頼」を作る

いかがでしたでしょうか?

単に「データを保存する」だけでなく、「どのようなオブジェクトを使い、どのようにメモリを制御し、どのようにエラーを防ぐか」。この視点を持つことが、初級者から上級者へと駆け上がるための唯一の道です。

ADOとStreamオブジェクトを使いこなせれば、Accessはもはや「小さな事務ツール」ではなく、大規模なエンタープライズシステムのフロントエンドとして、恐ろしいほどの力を発揮します。

ここをクリアすれば、Access VBAの基本はバッチリですよ。自信を持って、次の実装へ進んでくださいね。応援しています!

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