Accessで「画面フリーズ」は罪である:QueryDefの非同期CSV出力を極める
現場のAccess開発者諸君。数万行のレコードをCSVに吐き出す際、画面が「応答なし」になり、ユーザーが不安げにマウスを連打する光景を放置していないか?
Access VBAはシングルスレッドだ。重い処理を走らせればUIスレッドは当然死ぬ。だが、「QueryDefを使い、DoEventsを戦略的に配置する」という基本を正しく理解すれば、この悪夢は回避できる。今回は、単なるコピペコードではなく、OSのリソースを尊重し、堅牢なプロダクションコードを生み出すための設計思想を伝授する。
—
なぜ「そのまま」実行してはいけないのか
初心者が陥る罠は、`DoCmd.TransferText` を単発で呼び出し、処理が終わるまでユーザーを放置することだ。これでは、処理の進捗も分からず、異常終了時のデバッグも困難になる。
我々が目指すべきは、以下の3つの原則を守った設計だ。
1. 疎結合なQueryDef生成: SQL文字列をハードコードせず、`QueryDef`オブジェクトにパラメータを渡して動的に構築する。
2. UIの生存維持: `DoEvents`をループの適切な粒度で挿入し、OSからのイベントキューを処理する。
3. ストリームの安全な解放: ファイルハンドルを確実に閉じ、例外発生時にもリソースリークを起こさないこと。
—
プロダクションコード:非同期CSV出力エンジンの実装
以下のコードは、大量のQueryDefの結果をチャンク(塊)ごとに読み込み、CSVに書き出す実用的なモジュールだ。
Option Compare Database
Option Explicit
‘ 大量データ処理のためのCSV出力エンジン
Public Sub ExportQueryToCSVAsync(queryName As String, filePath As String)
Dim db As DAO.Database
Dim qdf As DAO.QueryDef
Dim rs As DAO.Recordset
Dim fileNum As Integer
Dim i As Long, rowCount As Long
Set db = CurrentDb
Set qdf = db.QueryDefs(queryName)
‘ パラメータクエリの場合はここで値をバインドする
‘ qdf.Parameters(“[入金日]”) = Date
Set rs = qdf.OpenRecordset(dbOpenSnapshot)
If rs.EOF Then
MsgBox “出力対象データがありません。”, vbExclamation
Exit Sub
End If
fileNum = FreeFile
Open filePath For Output As #fileNum
‘ ヘッダー出力
‘ ※必要に応じてフィールド名ループを実装すること
On Error GoTo Cleanup
‘ 処理ループ:DoEventsによるUIの解放
rowCount = 0
Do While Not rs.EOF
‘ ここでCSV形式の文字列を作成(カンマ区切り、ダブルクォーテーション囲み推奨)
Print #fileNum, rs!Field1 & “,” & rs!Field2
rs.MoveNext
rowCount = rowCount + 1
‘ 1000レコードごとにUIを更新(頻度が高すぎると逆に遅くなるので調整が必要)
If rowCount Mod 1000 = 0 Then
DoEvents
Application.Echo True, “処理中: ” & rowCount & “行目まで完了…”
End If
Loop
Cleanup:
‘ 異常終了時も必ずリソースを解放する堅牢な設計
If Not rs Is Nothing Then rs.Close
Set rs = Nothing
Set qdf = Nothing
Close #fileNum
Application.Echo True
If Err.Number <> 0 Then
MsgBox “エラー発生: ” & Err.Description, vbCritical
Else
MsgBox “CSV出力が完了しました。”, vbInformation
End If
End Sub
—
この設計が「プロ」である理由
1. `dbOpenSnapshot` の選択
`dynaset`(更新可能)ではなく `snapshot`(静的)を使うこと。大量データを読み込む際、Accessはトランザクションや更新監視のオーバーヘッドを背負う。読み取り専用であれば、Snapshot一択だ。これによりメモリ使用量が劇的に改善される。
2. `DoEvents` の粒度制御
`DoEvents`をすべてのループに入れるのは愚策だ。OSのイベントループを回しすぎると、逆に処理全体のオーバーヘッドが増大する。上記のコードのように `Mod 1000` で条件分岐させ、適度な頻度で呼吸させるのが、パフォーマンスとUIレスポンスの黄金比だ。
3. `On Error GoTo Cleanup` によるリソース保護
VBAで最も多いバグの一つが「ファイルが開かれたまま終了する」ことだ。`Cleanup`ラベルに必ず`Close #fileNum`を置くことで、何が起きてもファイルハンドルをOSに返却する。これが業務ツールにおける「堅牢性」の正体である。
—
エンジニアへのアドバイス
このコードは「始まり」に過ぎない。もし扱うデータが数百万行を超えるなら、VBAの限界を認め、`ADODB.Stream`を用いたバイナリ書き込みや、さらにはPowerShellやPythonの呼び出しを検討すべきだ。
しかし、Accessの枠組みの中で完結させる必要があるのなら、「QueryDefを使い、Snapshotで読み、DoEventsでUIを殺さない」。この作法を徹底するだけで、君の作るツールは「止まる」ことから「サクサク動く」へと劇的に進化するはずだ。
さあ、コードを書き換えろ。ユーザーがマウスを連打する時間は、君がシステムを磨き上げるための時間ではないはずだ。
