【実務・中級編】ADOのRecordCountが「-1」になる理由と解決策:正しいレコード数取得法 – Access VBA解析バイブル

スポンサーリンク

ADOのRecordCountが「-1」になる理由と解決策:正しいレコード数取得法

開発現場でこんな絶望を味わったことはないだろうか。

「ADOを使ってSQLを実行し、さあ件数に基づいてループ処理を回そうと`RecordCount`を参照したら、冷酷にも `-1` が返ってきた……」

DAOに慣れ親しんだAccess開発者がADO(ActiveX Data Objects)へ移行した際、必ずと言っていいほど踏む地雷がこれだ。結論から言えば、これはバグではない。ADOのアーキテクチャ仕様(設計思想)の必然である。

今回は、なぜこの現象が起きるのかという根本原因を解き明かし、実務の現場で絶対に破綻しない「正しいレコード数取得の極意」を伝授する。

—

1. なぜ `RecordCount` は `-1` を返すのか?

犯人は 「カーソルタイプ(CursorType)」 と 「ネットワーク・メモリの最適化」 だ。

ADOのデフォルトのカーソルは、前進専用の `adOpenForwardOnly`、または動的カーソルの `adOpenDynamic` で開かれることが多い。これらは「パフォーマンス最優先」で作られている。

データベースサーバ(またはローカルのJet/ACEエンジン)からデータを取得する際、全レコードをメモリ上に一度にロードする(キーセット/静的カーソルを使う)のは、数万件以上のレコードを扱う業務システムにおいてメモリリークやパフォーマンス低下の元凶となる。そのため、デフォルトでは「今、何番目のデータを処理しているか」だけを保持し、全体で何件あるかを事前に数えない(=カウントしない)仕様になっているのだ。

この状態のRecordsetに対して `RecordCount` を叩くと、ADOは「数えてないから分からない」という意味を込めて `-1`(定数としては `adCounUnknown`)を返す。

—

2. 解決策のアプローチと「やってはいけないアンチパターン」

この問題を解決するため、ネット上の散らばる情報を見ると、次のような安易なコードが見つかる。

❌ やってはいけないアンチパターン

‘ 【悪手】すべてのレコードをメモリに抱え込ませる
rst.CursorType = adOpenStatic ‘ または adOpenKeyset
rst.Open “SELECT FROM T_LargeData”, conn, adOpenStatic, adLockReadOnly

MsgBox rst.RecordCount ‘ ここで無理やり件数を取得

なぜこれが実務でNGなのか?
`adOpenStatic` や `adOpenKeyset` を指定すると、結果セットの全スナップショットをメモリ(または一時ファイル)に構築するため、データ量が数万件を超えた瞬間にAccessのパフォーマンスが急激に劣化し、最悪の場合はメモリ不足でクラッシュする。件数を取得したいがためにパフォーマンスを犠牲にするのは、アーキテクチャ設計として本末転倒だ。

—

3. 実務で採用すべき「正しいアプローチ」

プロの現場で求められるのは、「メモリを圧迫せず、正確な件数を担保し、かつ高速に処理する」ことである。状況に応じた2つの正解を提示しよう。

アプローチA:件数確認は `SELECT COUNT()` に分離する(推奨)

データを取得するクエリと、件数を数えるクエリは完全に分離するべきだ。データ本体は軽量な `adOpenForwardOnly` で流し込み、件数はSQLエンジンに一瞬で計算させる。これが最も堅牢でモダンな手法である。

アプローチB:どうしてもRecordset側で完結させたい場合の正しいカーソル指定

処理の都合上、どうしても同一のRecordsetオブジェクト内で完結させたい場合は、カーソルタイプとロックタイプを明示的に指定する。

—

4. 【コピペOK】プロダクションコード実装例

実際の業務システムでそのまま使える、堅牢なエラーハンドリング付きのモジュールを提示する。

Option Explicit

‘ =========================================================================
‘ 処理名 : GetRecordCountSafely
‘ 概要 : ADOを使用した安全かつ高速なレコード件数取得およびデータ処理のサンプル
‘ 備考 : データベース接続文字列は環境に合わせて書き換えてください
‘ =========================================================================
Public Sub GetRecordCountSafely()
Dim conn As Object
Dim rst As Object
Dim strSQL As String
Dim lngCount As Long

‘ オブジェクト変数の初期化
Set conn = CreateObject(“ADODB.Connection”)
Set rst = CreateObject(“ADODB.Recordset”)

On Error GoTo ErrorHandler

‘ 1. 接続オープン (AccessカレントDBの例)
conn.Open “Provider=Microsoft.ACE.OLEDB.12.0;Data Source=” & CurrentDb.Name & “;Persist Security Info=False;”

‘ ———————————————————————
‘ 【王道パターン】件数取得は COUNT() を使う(メモリ効率最良)
‘ ———————————————————————
strSQL = “SELECT COUNT() AS TotalCount FROM T_MasterData WHERE Status = 1”

Set rst = conn.Execute(strSQL)
lngCount = rst.Fields(“TotalCount”).Value
rst.Close

Debug.Print “対象レコード数: ” & lngCount & ” 件”

If lngCount = 0 & Then
MsgBox “処理対象のデータが存在しません。”, vbInformation, “情報”
GoTo Finally
End If

‘ ———————————————————————
‘ 【データ処理フェーズ】
‘ 件数がわかったので、本体データは最速の ForwardOnly で取得する
‘ ———————————————————————
strSQL = “SELECT ID, DataName FROM T_MasterData WHERE Status = 1”

‘ adOpenForwardOnly (0), adLockReadOnly (1)
rst.Open strSQL, conn, 0, 1

Do While Not rst.EOF
‘ — ここに実際の業務ロジックを記述 —
‘ 例: Debug.Print rst.Fields(“ID”).Value
‘ ————————————
rst.MoveNext
Loop

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

Finally:
‘ クリーンアップ(必ず逆順で閉じる)
If Not rst Is Nothing Then
If rst.State = 1 Then rst.Close
Set rst = Nothing
End If
If Not conn Is Nothing Then
If conn.State = 1 Then conn.Close
Set conn = Nothing
End If
Exit Sub

ErrorHandler:
MsgBox “予期せぬエラーが発生しました。” & vbCrLf & _
“Error: ” & Err.Number & ” – ” & Err.Description, vbCritical, “システムエラー”
Resume Finally
End Sub

—

5. チーフアーキテクトからの総括

Access VBAにおけるADOの運用は、細部(プロパティの指定やオブジェクトのライフサイクル管理)へのこだわりがそのままシステムの寿命(安定性・速度)に直結する。

`RecordCount = -1` という仕様に直面したとき、安易にカーソルを静的(Static)に変えてメモリを食いつぶすようなコードを書くのは、プロのエンジニアの仕事ではない。

  • 件数だけが必要なら `SELECT COUNT()` を投げる。
  • 全件走査が必要なら、件数に依存せず `EOF` までループを回す設計にする。

この原則を遵守し、リソースを無駄に消費しない洗練されたアーキテクチャで、トラブル知らずの堅牢な業務システムを構築してほしい。

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