【テクニカル・上級編】DAOとADOの使い分け:QueryDefが適しているケースとそうでないケース – Access VBA解析バイブル

スポンサーリンク

Access VBAを掌握する極限の知見:DAO QueryDefとADOの境界線、そしてアーキテクチャの真実

レガシーシステムの最前線に立ち続ける我々にとって、Microsoft Accessはその強烈な利便性と、一歩間違うとシステム全体を崩壊させる諸刃の剣としての側面を併せ持っている。

特に、データアクセスの根幹を担うDAO(Data Access Objects)の`QueryDef`と、ADO(ActiveX Data Objects)の使い分けは、システム全体の寿命を左右する極めて重大なアーキテクチャ上の決断だ。ネットの海を漂う「動けばいい」的場当たり的なコードは、マルチユーザー環境や数百万件規模のデータ錯綜の前では一巻の終わりを迎える。

今回は、Access内部テーブルと外部データベース連携の交差点において、`QueryDef`をどこまで信用し、どこからADOにバトンタッチすべきか。その判断基準とメモリ管理、そして極限のパフォーマンスを引き出すための知見を叩き込む。

—

1. 根本思想の理解:なぜ「QueryDef」なのか?

多くのVBAプログラマは、SQL文をその場で文字列結合し、`CurrentDb.OpenRecordset`に投げ込んで満足している。だが、それはシニアの仕事ではない。

`QueryDef`の本質は、「Accessのデータベースエンジン(Jet / ACE)のクエリプランナーに事前コンパイルと最適化を行わせ、その実行計画を永続的あるいは一時的なオブジェクトとしてキャッシュする仕組み」にある。

QueryDefが圧倒的に優位なケース

1. Access内部テーブル(Jet/ACEエンジン)の処理
ストアドプロシージャを持たないAccessにおいて、`QueryDef`は唯一の「サーバーサイド(エンジンサイド)で最適化された実行計画」を持つオブジェクトである。
2. パラメータークエリによるプランの再利用とSQLインジェクション対策
文字列連結によるSQL構築は、クエリプランのキャッシュ汚染(Parameter Sniffingの悪化やプラン再コンパイルの頻発)を招く。`QueryDef`のParametersコレクションを明示的に指定した実行は、メモリ効率の面でもセキュリティの面でも最高峰の選択肢となる。

—

2. DAO QueryDefの極限活用:一時QueryDefのライフサイクル管理

動的SQLを安全かつ高速に実行するため、我々はあらかじめ保存されたクエリだけでなく、「コード内で一時的に生成し、即座に破棄するQueryDef」を駆使する。

ここで重要になるのが、オブジェクトのライフサイクルとメモリリークの完全な制御である。VBAのガベージコレクションは頼りにならない。明示的に破棄しなければ、Accessのコンテナ(SysObjects)はゴミで溢れかえり、最終的にデータベースが破損(Corruption)する。

以下のコードは、数百万件の内部データ処理において、メモリリークを完全に封じ込めつつ、動的パラメーターを安全に処理するアーキテクチャの実装例だ。

‘ =========================================================================
‘ 模範実装:一時QueryDefを用いた高速・安全なパラメータクエリの実行
‘ =========================================================================
Public Sub ExecuteOptimizedQueryDef(ByVal targetID As Long, ByVal criteriaDate As Date)
Dim db As DAO.Database
Dim qdf As DAO.QueryDef
Dim rst As DAO.Recordset

‘ CurrentDbをローカル変数に保持(毎回CurrentDbを呼ぶと別インスタンスが生成されメモリリークの温床になる)
Set db = CurrentDb

On Error GoTo ErrorHandler

‘ 1. 一時的なQueryDefの生成(名前に “~” をプレフィックスとして付与するとシステムコンテナを汚染しにくい)
‘ ※あらかじめデザインビューで作られた静的QueryDefを db.QueryDefs(“q_Name”) で取得する手法でも同等
Set qdf = db.CreateQueryDef(“”, _
“SELECT ID, ProcessData, UpdatedAt ” & _
“FROM t_LargeMaster ” & _
“WHERE ID = [prmID] AND UpdatedAt >= [prmDate];”)
明示的なパラメータ型のバインド(型安全の担保とクエリプランの最適化)
qdf.Parameters(“prmID”).Value = targetID
qdf.Parameters(“prmDate”).Value = criteriaDate

‘ 3. レコードセットの取得(DBEngineのキャッシュを最大限に活かす)
Set rst = qdf.OpenRecordset(dbOpenSnapshot)

‘ 4. データ処理ループ
Do Until rst.EOF
‘ — 業務ロジックの展開 —
‘ Debug.Print rst!ProcessData
rst.MoveNext
Loop

ErrorHandler:
If Err.Number <> 0 Then
MsgBox “エラー発生: ” & Err.Description, vbCritical, “致命的エラー”
End If

‘ — 厳格なオブジェクトの解放順序 (LIFOの原則) —
If Not rst Is Nothing Then
rst.Close
Set rst = Nothing
End If

If Not qdf Is Nothing Then
‘ 一時QueryDefはCloseメソッドを持たないが、コンテナからの削除(あるいは変数解放)が必要
‘ ※CreateQueryDefの第一引数をvbNullStringにした場合は変数解放時に自動破棄されるが、
‘ 永続的QueryDefの場合は db.QueryDefs.Delete を慎重に行うこと。
Set qdf = Nothing
End If

If Not db Is Nothing Then
db.Close
Set db = Nothing
End If
End Sub

アーキテクチャ上の急所:`CurrentDb`の乱用禁止

上記のコードで最も重要なのは、`Set db = CurrentDb` を1回だけ行っている点だ。
`CurrentDb`メソッドは呼び出すたびに新しいDAO.Databaseオブジェクトのインスタンスをメモリ上に生成する。これをループ内やプロシージャのあちこちで呼び出すと、オブジェクトポインタが解放されずにメモリリークを引き起こし、Access特有の「リソース不足(Out of Memory)」エラーや突然のクラッシュを誘発する。DAOを使う際は「`CurrentDb`は一度変数に受けて使い回す」が鉄則である。

—

3. QueryDefが「適していない」ケース:ADOへ舵を切るべき境界線

では、すべてのデータ操作をDAOの`QueryDef`でやるべきか? 答えは明確に「No」だ。以下の要件に直面した瞬間、あなたはDAOを捨て、ADO(ActiveX Data Objects)へ移行しなければならない。

1. 外部RDBMS(SQL Server, PostgreSQL, Oracle等)との直結・Upsizing環境

Accessをフロントエンド、SQL Serverをバックエンド(UPSIZED)にした環境、あるいは完全なリモートDB接続において、DAOを経由したクエリ実行は地獄を生む。
DAOはJet/ACEエンジンの癖が強いため、SQL ServerにODBC経由でクエリを投げた際、「ローカル側へ全件データを転送してから絞り込みを行う(Client-side filtering)」という最悪の挙動をとることがある。

  • 解決策: ADO (`ADODB.Command` と `ADODB.Connection`) を用い、`CommandType = adCmdText` またはサーバー側のストアドプロシージャ (`adCmdStoredProc`) を直叩きし、処理を完全にリモートサーバー側で完結させる。

2. 非同期処理(Asynchronous Execution)の必要性

重い集計クエリを走らせた際、AccessのUIが完全にフリーズ(「応答なし」)する現象に悩まされたことはないか?
DAOの`QueryDef`や`Recordset`には、真の意味での非同期実行能力はない。

  • 解決策: ADOの `Connection.Execute` に `adAsyncExecute` オプションを付与することで、バックグラウンドスレッドでクエリを走らせ、UIの応答性を維持した高度な非同期処理が可能になる。

3. 特殊なデータ型やストアドプロシージャの出力パラメータ(Output Parameters)の制御

DAOの`QueryDef`は、パラメーターを渡すことは得意だが、SQL Server側のストアドプロシージャが返す「リターン値」や「OUTPUTパラメータ」を綺麗にハンドリングするには限界がある。

  • 解決策: `ADODB.Command` オブジェクトの `Parameters.Append` を用いて明示的に `Direction = adParamOutput` を設定する。

—

4. ADOによる極限の外部連携実装例

ここで、SQL Serverなどの外部RDBMSに対してADOの`Command`オブジェクトを使用し、安全かつ高速にデータを操作するパターンを示す。これは`QueryDef`のADO版とも言えるアプローチだ。

‘ =========================================================================
‘ 模範実装:ADO Commandオブジェクトを用いた外部DB連携
‘ =========================================================================
Public Sub ExecuteExternalServerProcess(ByVal paramValue As String)
Dim cnn As Object ‘ ADODB.Connection
Dim cmd As Object ‘ ADODB.Command
Dim prm As Object ‘ ADODB.Parameter

‘ レイトバインディングによるADOオブジェクトの生成(参照設定のバージョン差異トラブルを回避)
Set cnn = CreateObject(“ADODB.Connection”)
Set cmd = CreateObject(“ADODB.Command”)

On Error GoTo ADOError

‘ 接続文字列の設定(OLEDB または ODBCドライバ)
cnn.ConnectionString = “Provider=MSOLEDBSQL;Server=myServerAddress;Database=myDataBase;Trusted_Connection=yes;”
cnn.Open

‘ Commandオブジェクトにコネクションを紐付け
Set cmd.ActiveConnection = cnn
cmd.CommandText = “sp_SecureDataProcessing” ‘ 外部DBのストアドプロシージャ名
cmd.CommandType = 4 ‘ adCmdStoredProc

‘ パラメータの明示的追加(型・方向の定義によるインジェクション防御と型安全)
Set prm = cmd.CreateParameter(“prmInput”, 200, 1, 50, paramValue) ‘ 200 = adVarChar, 1 = adParamInput
cmd.Parameters.Append prm

‘ 実行(レコードセットを返さない更新系クエリの例)
cmd.Execute , , 128 ‘ 128 = adExecuteNoRecords

MsgBox “外部サーバーでの処理が正常終了しました。”, vbInformation
GoTo CleanUp

ADOError:
MsgBox “ADOエラー [” & Err.Number & “]: ” & Err.Description, vbCritical

CleanUp:
‘ 厳格なオブジェクト解放
On Error Resume Next
If Not cnn Is Nothing Then
If cnn.State = 1 Then cnn.Close
Set cnn = Nothing
End If
Set cmd = Nothing
Set prm = Nothing
On Error GoTo 0
End Sub

—

5. チーフアーキテクトからの最終提言:使い分けの黄金律

レガシーシステムの寿命を延ばし、パフォーマンスの限界を突破するための判断基準をここに総括する。

| 評価軸 | DAO (QueryDef) を選ぶべき領域 | ADOを選ぶべき領域 |
| :— | :— | :— |
| データソース | Access内部テーブル、リンクテーブル(Jet/ACEネイティブ) | SQL Server, PostgreSQL, Oracle等の外部RDBMS |
| クエリの性質 | ローカルでの複雑なJOIN、一時テーブルを伴う処理 | サーバーサイド・ストアドプロシージャ、バッチ更新、非同期処理 |
| 型・パラメータ | `QueryDef.Parameters` による強型付け | `ADODB.Command` と `Parameters.Append` による明示的定義 |
| 環境依存度 | Access環境に完全に依存 | OLEDB / ODBC ドライバのインストール環境に依存 |

「なんとなく動くから」という理由で、すべての処理を文字列結合のSQLや場当たり的なADO/DAO混在で書く時代は終わった。
データの置き場所がどこにあり、エンジンがどこでクエリを解釈するのか。そのレイヤー構造を正確に理解した者だけが、Accessという巨大な遺産をモダンで堅牢な基幹システムへと昇華させることができる。

コードの隅々にまで意図を宿せ。メモリのライフサイクルを支配せよ。それこそが、真のプロフェッショナルの仕事である。

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