【実務・中級編】QueryDefの実行結果を「CSV」として非同期出力する:UIを止めないバックグラウンド処理 – Access VBA解析バイブル

スポンサーリンク

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を殺さない」。この作法を徹底するだけで、君の作るツールは「止まる」ことから「サクサク動く」へと劇的に進化するはずだ。

さあ、コードを書き換えろ。ユーザーがマウスを連打する時間は、君がシステムを磨き上げるための時間ではないはずだ。

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