【テクニカル・上級編】【上級】DAO.Recordsetの「AbsolutePosition」を活用した、進捗バーの動的更新とユーザー体験の向上 – Access VBA解析バイブル

スポンサーリンク

【上級】DAO.Recordsetの「AbsolutePosition」を活用した、進捗バーの動的更新とユーザー体験の向上

レガシーシステムの最前線に身を置く我々にとって、数万、数百万レコードを伴う一括処理は日常の風景だ。
しかし、画面がフリーズしたかのように沈黙するバッチ処理は、現代のユーザーにとって耐え難い「苦痛」でしかない。「本当に動いているのか?」「落ちたのではないか?」という不安を取り除くために進捗バーを実装するのはエンジニアの責務である。

今回は、Access VBAにおけるバッチ処理のUX(ユーザー体験)を極限まで高めるため、`DAO.Recordset`の `AbsolutePosition` プロパティを軸にした進捗可視化のアーキテクチャを解説する。

1. なぜ `AbsolutePosition` なのか?

VBAにおけるレコード走査のループでは、単に `MoveNext` を繰り返すだけでは「全体に対して今どの位置にいるのか」を把握できない。よく見かけるアンチパターンとして、処理のたびに `DCount` を発行して進捗率を計算する実装があるが、これはデータベースのI/Oを爆発的に増加させ、パフォーマンスを致命的に劣化させる。

ここで `DAO.Recordset` の `AbsolutePosition` の出番だ。

‘ AbsolutePositionの基本特性
‘ 0ベースのインデックスを返す (-1 はカレントレコードなしを意味する)
Dim currentPos As Long
currentPos = rs.AbsolutePosition

`AbsolutePosition` は、現在のレコード位置を高速に返す。これを利用すれば、ループ内での重い集計クエリを排除し、極めて軽量に進捗率を算出できる。

2. 実装上の罠:`RecordCount` の同期問題

`AbsolutePosition` を活用する上で、シニアエンジニアが必ず知っておくべき仕様上の罠がある。
それは、DAOのレコードセットが完全にポップュレート(データ読み込み)されていないと、正確な `RecordCount` を返さないという点だ。

特に `dbOpenDynaset` や `dbOpenSnapshot` を使った場合、非同期でレコードが読み込まれるため、開いた直後の `RecordCount` は「これまでに読み込んだ件数」しか示さない。全件を正確に取得するには、強制的に最終レコードへジャンプ(`MoveLast`)させる必要がある。

Dim db As DAO.Database
Dim rs As DAO.Recordset
Dim totalRecords As Long

Set db = CurrentDb
‘ ライブデータを扱うためダイナセットを使用
Set rs = db.OpenRecordset(“T_HeavyProcessData”, dbOpenDynaset)

If rs.RecordCount = 0 Then
‘ レコードなしのハンドリング
rs.Close: Set rs = Nothing
Exit Sub
End If

‘ 【極意】全レコード数を確定させるために末尾へ移動
rs.MoveLast
totalRecords = rs.RecordCount

‘ 先頭へ巻き戻す
rs.MoveFirst

この `MoveLast` を怠ると、分母が狂い、進捗バーが途中で100%を超えたり、正しく描画されなくなったりする。

3. 実践:カスタムフォームを活用した進捗バーの動的更新

単にループごとに `DoEvents` を入れるだけでは、画面描画のオーバーヘッドで逆に処理速度が落ちる。パフォーマンスを維持しつつ、滑らかなUIを実現するプロダクションコードのテンプレートを提示する。

実装コード(標準モジュール)

Option Explicit

Public Sub ExecuteHeavyBatchProcess()
Dim db As DAO.Database
Dim rs As DAO.Recordset
Dim totalRecords As Long
Dim currentPos As Long
Dim progressRate As Double
Dim updateInterval As Long
Dim counter As Long

Set db = CurrentDb
Set rs = db.OpenRecordset(“T_HeavyProcessData”, dbOpenSnapshot) ‘ 読み取り専用で高速化

If rs.RecordCount = 0 Then
MsgBox “処理対象データが存在しません。”, vbInformation
GoTo Cleanup
End If

rs.MoveLast
totalRecords = rs.RecordCount
rs.MoveFirst

‘ 進捗フォームのロードと表示
DoCmd.OpenForm “frmProgressBar”
Forms(“frmProgressBar”).Controls(“lblStatus”).Caption = “データを処理中…”

counter = 0
updateInterval = IIf(totalRecords > 1000, totalRecords \ 100, 1) ‘ 1%毎に画面更新(過剰な描画負荷を防ぐ)

‘ メインループ
Do Until rs.EOF
‘ ————————————————–
‘ ここに実際の重い処理を記述
‘ 例: データの加工や別DBへの転送など
Call ProcessSingleRecord(rs)
‘ ————————————————–

counter = counter + 1

‘ 毎回画面を更新するとVBAは重くなるため、一定間隔または最後尾でのみ更新
If counter Mod updateInterval = 0 Or rs.AbsolutePosition = totalRecords – 1 Then
‘ AbsolutePositionは0から始まるため、+1して現在の処理件数とする
currentPos = rs.AbsolutePosition + 1
progressRate = (currentPos / totalRecords) 100

‘ フォーム側のメソッドを呼び出してバーを伸縮
Forms(“frmProgressBar”).UpdateProgress progressRate, currentPos, totalRecords

‘ UIスレッドに制御を戻し、画面のフリーズ(応答なし)を防止
DoEvents
End If

rs.MoveNext
Loop

MsgBox “すべての処理が正常に完了しました。”, vbInformation

Cleanup:
‘ 【鉄則】オブジェクトの明示的解放によるメモリリークの根絶
If Not rs Is Nothing Then
rs.Close
Set rs = Nothing
End If
Set db = Nothing

‘ 進捗フォームを閉じる
If IsLoaded(“frmProgressBar”) Then
DoCmd.Close acForm, “frmProgressBar”
End If
End Sub

Private Sub ProcessSingleRecord(ByRef rs As DAO.Recordset)
‘ 模擬的なレコード処理
‘ 実際にはここでフィールド値の読取や書き込みを行う
Dim dummy As String
dummy = rs!FieldName & “”
End Sub

Private Function IsLoaded(ByVal formName As String) As Boolean
Dim frm As Form
IsLoaded = False
For Each frm In Forms
If frm.Name = formName Then
IsLoaded = True
Exit For
End If
Next frm
End Function

進捗フォーム(`frmProgressBar`)側のコード

進捗フォームには、進捗を表すための矩形コントロール(例: `boxBar`)と、テキスト表示用のラベルを配置しておく。

Option Explicit

‘ バーの最大幅(ピクセル換算、フォームの幅等に合わせて調整)
Private Const MAX_BAR_WIDTH As Long = 400

Public Sub UpdateProgress(ByVal rate As Double, ByVal current As Long, ByVal total As Long)
‘ プログレスバーの幅を動的に変更
Me.boxBar.Width = CLng(MAX_BAR_WIDTH (rate / 100))

‘ ステータスラベルの更新
Me.lblPercent.Caption = Format$(rate, “0.0”) & ” %”
Me.lblCount.Caption = Format$(current, “#,

0″) & ” / ” & Format$(total, “#,##0″) & ” 件”

‘ 再描画を強制
Me.Repaint
End Sub

4. チーフアーキテクトからの警鐘:パフォーマンスチューニングの極意

この実装において、エンジニアが陥りがちな罠が2つある。

1. `DoEvents` の乱用によるオーバーヘッド
すべてのレコードごとに `DoEvents` と `Repaint` を呼ぶと、OSの描画キューが飽和し、かえって処理速度が何倍にも跳ね上がる。上記のコードのように、全体の1%ごと、あるいは一定の件数ごとに絞って(スロットリングして)更新を入れるのが、ハードウェアリソースを効率的に使うプロの技法だ。
2. オブジェクトの寿命管理
VBAのガベージコレクションは頼りにならない。`DAO.Recordset` や `DAO.Database` をローカル変数として定義した際、プロシージャの終了時に `Set rs = Nothing` を明示的に記述しないと、Accessの内部メモリにキャッシュが残り続け、長時間の運用でメモリリークを引き起こす。

結び

進捗バーの滑らかな動作は、単なる「見た目の良さ」にとどまらない。それは、システムに対するユーザーの信頼感そのものである。
`AbsolutePosition` というプリミティブな機能を正しく理解し、データベースの仕様とVBAの挙動の境界線をコントロールすること。それこそが、レガシーとモダンを繋ぐ我々エンジニアが持つべき、錆びない武器なのだ。

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