【入門編】QueryDefの実行結果をRecordsetとして取得する際、メモリリークを確実に防ぐオブジェクト解放の全手順 – Access VBA解析バイブル

スポンサーリンク

こんにちは!Access VBAの世界へようこそ。
今回は、実務で必ず直面する「QueryDef(クエリ定義)と動的SQL、そして完璧なメモリ解放」という、プログラミングの核心に迫るテーマをお届けします。

「マクロの記録から脱却したい」「自分で書いたコードでバリバリ動くシステムを作りたい」という方にとって、今日の知識はまさに一生モノの武器になります。

「`Set rs = Nothing` を書いておけば安心なんでしょ?」――実は、それだけでは不十分なケースがあるんです。
複雑なクエリを自在に操り、Accessのエンジン(ACE)に負担をかけない「プロの作法」を、優しく紐解いていきましょう。ここをクリアすれば、あなたのVBAスキルは一段上のステージに到達しますよ!

—

1. なぜ「QueryDef」と「パラメータクエリ」が必要なのか?

まずは、今回主役となる QueryDef について整理しておきましょう。

普段、Accessの画面上で「クエリ」を作りますよね。あのクエリの設計図を、VBAのプログラム側から直接操作・実行できるようにしたものが `QueryDef` オブジェクトです。

文字列結合SQLの「危険な罠」

動的にSQLを作る際、よくやりがちなのが以下のようなコードです。

‘ 【やってはいけません!】文字列結合によるSQL生成
Dim strSQL As String
strSQL = “SELECT FROM T_受注 WHERE 顧客名 = ‘” & Me.txtSearch & “‘;”
Set rs = CurrentDb.OpenRecordset(strSQL)

これ、一見動くので初心者のうちはよく使ってしまいますが、以下の致命的な問題があります。
1. SQLインジェクション: 意図しない文字列(シングルクォーテーションなど)でエラーや不正操作を招く。
2. パフォーマンスの低下: SQL文が変わるたびに、Access(データベースエンジン)が実行計画をイチから作り直すため、重くなります。

パラメータクエリという解決策

ここで登場するのが QueryDef とパラメータ(Parameters) です。
「クエリの骨組みは固定し、可変の部分だけをパラメータとして安全に渡す」という仕組みを使うことで、安全かつ高速にデータを取得できます。

—

2. 実践!QueryDefを使った安全なRecordset取得コード

百聞は一見にしかず。まずは、プロの現場で使われている「安全で美しい動的クエリの実行と、確実にリソースを解放する全手順」のコードを見てください。

Public Sub GetCustomerOrders(ByVal customerID As Long)
‘ —————————————————————-
‘ 変数の宣言(オブジェクト変数は必ず明示的に宣言します)
‘ —————————————————————-
Dim db As DAO.Database
Dim qdf As DAO.QueryDef
Dim rs As DAO.Recordset

On Error GoTo ErrorHandler ‘ エラーハンドリングの準備

‘ 1. データベース参照の取得
Set db = CurrentDb

‘ 2. 一時的なQueryDefオブジェクトの作成(または既存の永続クエリの利用)
‘ ※今回はコード内で完結する一時クエリ(名前に「~」を使うか、新規作成する手法)の基本形を示します
‘ あらかじめデザイン画面で作ったクエリなら CurrentDb.QueryDefs(“qryMyQuery”) で取得します。
Set qdf = db.CreateQueryDef(“”, “SELECT FROM T_受注 WHERE 顧客ID = [p_ID]”)

‘ 3. パラメータに値を安全にセット
qdf.Parameters(“p_ID”) = customerID

‘ 4. Recordsetとして実行結果を取得
Set rs = qdf.OpenRecordset(dbOpenSnapshot) ‘ 参照のみなら Snapshot が最速

‘ 5. データの処理(例:レコード数をイミディエイトウィンドウに出力)
If Not (rs.BOF And rs.EOF) Then
rs.MoveLast
Debug.Print “取得レコード数: ” & rs.RecordCount
rs.MoveFirst

‘ ここに実際の処理を書く
Do While Not rs.EOF
‘ 例: Debug.Print rs!受注日
rs.MoveNext
Loop
Else
Debug.Print “該当するデータはありませんでした。”
End If

CleanUp:
‘ —————————————————————-
‘ 【超重要】メモリ解放の全手順(生成した逆順で解放するのが鉄則)
‘ —————————————————————-
‘ Recordsetを閉じて解放
If Not rs Is Nothing Then
rs.Close
Set rs = Nothing
End If

‘ QueryDefを解放(一時クエリの場合、閉じればメモリから消えます)
If Not qdf Is Nothing Then
qdf.Close
Set qdf = Nothing
End If

‘ Databaseを解放
Set db = Nothing

Exit Sub

ErrorHandler:
MsgBox “エラーが発生しました: ” & Err.Description, vbCritical
Resume CleanUp ‘ エラーが起きても必ず解放処理を通るようにする
End Sub

—

3. 「Set Nothing」だけでは不十分?メモリリークを防ぐ本質

さて、今回のテーマの核心です。
「使い終わったら `Set rs = Nothing` を書いているから大丈夫」――本当にそうでしょうか?

Access VBA(DAO)の裏側では、C++レベルの重いリソースが動いています。以下のポイントを見落としていると、知らず知らずのうちにメモリリーク(メモリの食い潰し)を起こし、Accessが突然フリーズしたり、動作が異常に重くなったりします。

① 解放の順序は「生成の逆順」が鉄則

オブジェクトは、新しく作ったもの(子)から順に閉じ、最後に大元(親)を解放するのが正しい作法です。
上記のコードを見てください。

1. `Recordset` (`rs`) を `.Close` し、`Set rs = Nothing`
2. `QueryDef` (`qdf`) を `.Close` し、`Set qdf = Nothing`
3. `Database` (`db`) を `Set db = Nothing`

この依存関係の逆順を崩すと、メモリ上にゴーストのような参照が残り、Accessのプロセスがメモリ上に居座り続ける原因になります。

② エラー発生時の「取り残し」を防ぐ (On Error GoTo)

プログラムの途中で万が一エラーが起きたとき、`CleanUp` ラベルを通らずに処理が中断してしまうと、`rs` や `qdf` が開きっぱなし(メモリに常駐した状態)になります。

プロのコードには必ず `On Error GoTo ErrorHandler` があり、どんなエラーが発生しようとも、必ず `CleanUp`(解放処理)を通る仕組みが担保されています。これが「確実にリソースを解放する」ということの本質なのです。

③ 一時QueryDefの破棄

コード内で `db.CreateQueryDef(“”, “…”)` のように名前を空文字 `””` で作成した一時クエリは、`Close` すれば自動的に破棄されますが、もし名前をつけて永続クエリとして保存した場合は、不要になったら `db.QueryDefs.Delete “クエリ名”` で削除する配慮も時には必要です。

—

まとめ:ここをクリアすれば、Access VBAの基本はバッチリです!

お疲れ様でした!少し高度な話でしたが、ここまで理解できたあなたはもう「初心者」ではありません。

  • 文字列結合ではなく、パラメータを使って安全かつ高速にSQLを構築する
  • `Recordset`、`QueryDef`、`Database` の関係性を理解する
  • 生成の逆順で `.Close` と `Set Nothing` を行う
  • エラーが起きても必ず解放処理を通る構造(`On Error GoTo`)を作る

この4つを意識するだけで、あなたの書くAccess VBAは「動くだけの不安定なコード」から、「実務で安心して使えるプロフェッショナルなコード」へと生まれ変わります。

ぜひ、次の開発案件からこの設計を取り入れてみてくださいね。応援しています!

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