こんにちは!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は「動くだけの不安定なコード」から、「実務で安心して使えるプロフェッショナルなコード」へと生まれ変わります。
ぜひ、次の開発案件からこの設計を取り入れてみてくださいね。応援しています!
