【Access VBA極限解説】ADOのRecordCountが「-1」になる理由と、実務で絶対に破綻しないレコード数取得の極意
レガシーシステムの深部、あるいは他系統のRDBMS(SQL ServerやOracle等)とAccessをADO(ActiveX Data Objects)で繋ぎ込んだ瞬間、開発者は必ず一度はこの絶望に直面する。
`RecordCount` プロパティを参照したところ、無情にも返ってくる `-1` という数値。
「おい、レコードは確実に1,500件存在しているはずなのに、なぜカウントが取れないんだ?」
この現象に直面し、ネット上の浅いサンプルコードをコピペして場当たり的な修正で凌ごうとするエンジニアを、私は数え切れないほど見てきた。しかし、メモリのライフサイクル、COMコンポーネントの挙動、そしてデータベースのカーソル機構の本質を理解していなければ、大規模データ処理の現場においてシステムは必ずメモリリークやパフォーマンス劣化で破綻する。
今回は、ADOのカーソルアーキテクチャの深淵に潜り込み、なぜ `-1` が返るのかのメカニズムと、実務の現場で絶対に採用すべき「正しいレコード数取得の極知見」を叩き込む。
—
1. なぜ RecordCount は `-1` を返すのか?(カーソルタイプの真実)
結論から言えば、`RecordCount` が `-1`(厳密には `adRecCountUnknown`)を返すのは、現在のレコードセットが「動的カーソル(adOpenDynamic)」または「前方きびカーソル(adOpenForwardOnly)」で開かれているからだ。
ADOのバックエンドで動くOLE DBプロバイダやODBCドライバは、メモリとネットワーク帯域を極限まで節約するよう設計されている。
前方きびカーソルや動的カーソルを使用している場合、クライアント側(あるいはサーバー側)は「今、何件のレコードが存在するか」を全件フェッチするまで把握していない。というか、把握する必要がないのだ。ストリーミングのようにデータを順次流し込んでいる最中に「全体の総数を出せ」と言われても、プロバイダ側は「まだ全部読み込んでないからわからん(-1)」と答えるしかない。
レコード数を正確に取得したいがために、安易にすべてのレコードセットを `adOpenStatic`(静的カーソル)や `adUseClient`(クライアントサイドカーソル)に変更する者がいる。しかし、これがパフォーマンスの殺人行為であることを彼らは知らない。数百万件のテーブルに対してクライアントサイドカーソルを強制すれば、ネットワークを経由して全データをローカルメモリに引きずり下ろし、Accessのワークスペース(MDB/ACCDBファイル)を肥大化させ、最悪の場合クラッシュを引き起こす。
—
2. 実務で許される「正しいレコード数取得」の2つのアプローチ
では、パフォーマンスを犠牲にせず、かつ確実にレコード数を把握するにはどうすればよいのか。
シニアアーキテクチャとして、現場で採用すべき2つのアプローチを提示する。
アプローチ A: `SELECT COUNT()` を投げる(王道かつ最速)
レコードセットの中身自体をイテレーションする前に、件数だけが必要なのであれば、最初からデータ本体を取ってくる必要はない。件数カウント専用のクエリを別途発行するのが、データベースサーバーへの負荷、メモリ消費量の観点から最も美しい。
アプローチ B: `MoveLast` を使う際の最適化と罠
どうしても取得したレコードセットの構造を維持したまま件数を知りたい場合は、カーソルが対応している前提のもとで `MoveLast` を実行し、レコードポインタを強制的に末尾に移動させる必要がある。ただし、この手法は前方きびカーソルではエラーになるため、適切なカーソル指定が前提となる。
—
3. 実装コード:極限まで最適化されたADOラッパー関数
以下のコードは、実務のエンタープライズ環境に耐えうる、メモリ管理とエラーハンドリングを極めたADO接続・件数取得の模範解答である。オブジェクトの明示的な解放(`Nothing`代入)を徹底し、Access特有のメモリリークを完全に排除している。
Option Compare Database
Option Explicit
‘ ==============================================================================
‘ 概要: 指定されたSQLのレコード件数を安全かつ高速に取得する関数
‘ 備考: メモリリーク防止のため、ADOオブジェクトのライフサイクルを厳格に管理する
‘ ==============================================================================
Public Function GetRecordCountSafe(ByVal connectionString As String, ByVal sqlQuery As String) As Long
Dim conn As Object
Dim rs As Object
Dim countResult As Long
countResult = -1 ‘ 初期値として異常値を設定
On Error GoTo ErrorHandler
‘ 1. コネクションの生成とオープン
Set conn = CreateObject(“ADODB.Connection”)
conn.CommandTimeout = 30
conn.ConnectionString = connectionString
conn.Open
‘ 2. パフォーマンス最適化のため、件数取得専用のSQL(COUNT)を動的に構築するか、
‘ あるいは高速なサーバーサイドカーソルでRecordCountを取得する
‘ 今回は汎用的にRecordCountを検証するパターンを示す
Set rs = CreateObject(“ADODB.Recordset”)
‘ adOpenStatic (3) と adLockReadOnly (1) を指定することで、
‘ レコード数を正確に取得できる状態を強制する(ただし軽量なクエリに限定すべき)
rs.Open sqlQuery, conn, 3, 1
‘ 3. レコードが存在するか判定
If Not (rs.BOF And rs.EOF) Then
‘ 正確な件数を取得するためにカーソルを最終行へ移動
‘ ※データ量が膨大な場合は絶対にこの手法をとらず、SELECT COUNT() を使うこと
rs.MoveLast
countResult = rs.RecordCount
Else
countResult = 0
End If
CleanUp:
‘ 4. オブジェクトのライフサイクル管理(逆順での明示的解放)
If Not rs Is Nothing Then
If rs.State = 1 Then rs.Close
Set rs = Nothing
End If
If Not conn Is Nothing Then
If conn.State = 1 Then conn.Close
Set conn = Nothing
End If
GetRecordCountSafe = countResult
Exit Function
ErrorHandler:
‘ ログ出力やエラーハンドリングをここに記述
MsgBox “ADO処理エラー: ” & Err.Description, vbCritical, “致命的エラー”
countResult = -1
Resume CleanUp
End Function
—
4. チーフアーキテクトからの最終勧告
Access VBAとADOを用いたシステム開発において、`RecordCount = -1` という現象は単なる「バグ」ではなく、「データベースのカーソル機構を理解していないことに対するシステムからの警告」である。
- 大規模なデータセットに対して `rs.MoveLast` やクライアントサイドカーソルを安易に使わないこと。
- 件数だけが必要な場合は、必ず `SELECT COUNT()` を使った別クエリを発行し、データベースエンジン側に集計処理をオフロードすること。
- COMオブジェクトはガベージコレクションに頼らず、必ず自らの手で `Close` し、`Nothing` を代入してメモリ空間からパージすること。
この鉄則を遵守する者だけが、何年稼働してもメモリリークを起こさない、美しく強靭なAccessアプリケーションを構築することができる。コードの行末に宿る細部にまで、プロフェッショナとしての誇りを持て。
