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

スポンサーリンク

Access VBAを掌握する極限の知見:DAO QueryDefとADOの境界線を見極めろ

開発現場でこんなコードを見たことはないだろうか。

‘ 悪夢のような動的SQL生成の散財
Dim strSQL As String
strSQL = “SELECT FROM T_受注明細 WHERE 顧客ID = ” & Me.txtCustomerID & ” AND 注文日 >= #” & Me.txtStartDate & “#”
CurrentDb.Execute strSQL

一見して動いているように見えるこのコードは、実務の現場においては「爆弾」を抱えていると同義だ。SQLインジェクションの脆弱性、日付のシリアル値変換漏れによるクエリの誤作動、そして実行計画のキャッシュが効かないことによるパフォーマンスの劣化――。

Access(Jet/ACEエンジン)によるデータベース開発において、「SQLをどう組み立て、どう実行するか」の選択は、システムの寿命を決定づける極めて重大なアーキテクチャ上の決断である。

今回は、Access VBAにおける最強の武器である DAO QueryDef と、外部連携の要である ADO の境界線をどこに引き、どう使い分けるべきか。その極限の知見を授けよう。

—

1. 根本思想:なぜ「その場しのぎのSQL文字列連結」は滅びゆくのか

Access VBAでのデータ操作において、私たちは常に2つのエンジン、すなわち DAO (Data Access Objects) と ADO (ActiveX Data Objects) の選択に迫られる。

結論から言おう。
「Access内部のテーブル・クエリを操作するのに、ADOを持ち出す理由は1ミリもない。そして、DAOの真髄である『QueryDef(クエリ定義)』を使いこなせていないコードは、すべて技術的負債である。」

QueryDefが圧倒的な優位性を持つ理由

1. 実行計画のプリコンパイル(キャッシュ):
QueryDefにあらかじめSQLを登録しておけば、Jet/ACEエンジンはクエリの最適化(実行計画の生成)を事前に行い、キャッシュする。文字列連結SQLを毎回`CurrentDb.OpenRecordset`で投げる手法は、毎回SQLのパースと最適化を走らせるため、大規模データでは致命的な遅延を生む。
2. 型安全なパラメータクエリ:
パラメータをコレクションとして明示的に渡すため、SQLインジェクションを完全に無効化し、日付や数値の暗黙的な型変換エラー(いわゆる「日付の罠」)を根絶できる。
3. ストアドプロシージャに近いカプセル化:
複雑な集計ロジックをAccessの「クエリコンテナ」に実体として保存できるため、VBAコード側がスパゲッティ化するのを防げる。

—

2. 徹底比較:DAO QueryDef vs ADO のアーキテクチャ境界

実務設計において、迷わず以下のマトリクスに従って使い分けるべきだ。

| 評価項目 | DAO (QueryDef / Recordset) | ADO (Command / Recordset) |
| :— | :— | :— |
| 主戦場 | Access内部テーブル、Linked Table(Jet/ACE) | SQL Server, Oracle, PostgreSQL, WebAPI等の外部DB |
| パフォーマンス | 【最適】 Accessローカルでは最速 | Access内部ではオーバースペック(遅い) |
| パラメータ処理 | `Parameters` コレクションによる強固な型バインディング | `Parameters.Append` によるプレースホルダー制御 |
| トランザクション | ワークスペース単位での堅牢な排他制御 | コネクション単位での柔軟な制御 |
| DDL/DMLの表現力 | Access方言(Jet SQL)に依存 | 接続先RDBMSのネイティブSQLを直叩き可能 |

—

3. 【実践】バグの起きないQueryDef動的パラメータクエリの実装

「動的な条件で絞り込みたいから、SQL文を文字列結合で作るしかない」という幻想は今すぐ捨ててほしい。QueryDefとパラメータを組み合わせれば、「静的に定義されたクエリに対し、動的に値をバインドする」という、最も安全で高速なアーキテクチャが構築できる。

以下のプロダクションコードは、安全かつ高速に売上データを抽出するモジュールだ。そのままコピー&ペーストして現場で活用してほしい。

‘ =========================================================================
‘ モジュール名: Mdl_OrderService
‘ 概要 : DAO QueryDefを活用した安全かつ高速なパラメータクエリ実行サンプル
‘ 依存関係 : DAO 3.6以上
‘ =========================================================================
Option Explicit

Public Sub GetFilteredOrders(ByVal customerID As Long, ByVal targetDate As Date)
Dim qdf As DAO.QueryDef
Dim rst As DAO.Recordset
Const QUERY_NAME As String = “qry_DynamicOrderSearch”

On Error GoTo ErrorHandler

‘ 1. あらかじめAccess上に作成(またはコードで一時作成)してあるQueryDefを取得
‘ ※あらかじめクエリコンテナに “SELECT FROM T_Orders WHERE CustomerID = [p_CustomerID] AND OrderDate >= [p_Date]”
‘ というパラメータクエリを作っておくのがベストプラクティス。
Set qdf = CurrentDb.QueryDefs(QUERY_NAME)

‘ 2. パラメータに明示的に値をバインド(型安全の担保)
qdf.Parameters(“p_CustomerID”) = customerID
qdf.Parameters(“p_Date”) = targetDate

‘ 3. レコードセットのオープン(この時点で最適化済みの実行計画が走る)
Set rst = qdf.OpenRecordset(dbOpenSnapshot)

‘ 4. データ処理のイテレーション
If rst.RecordCount > 0 Then
rst.MoveFirst
Do Until rst.EOF
‘ 実業務の処理(例:イミディエイトウィンドウに出力)
Debug.Print “受注ID: ” & rst!OrderID & ” / 金額: ” & rst!Amount
rst.MoveNext
Loop
Else
MsgBox “該当するデータは存在しません。”, vbInformation, “通知”
End If

CleanUp:
‘ 5. オブジェクトの明示的な解放(メモリリークの完全防止)
If Not rst Is Nothing Then rst.Close: Set rst = Nothing
If Not qdf Is Nothing Then Set qdf = Nothing
Exit Sub

ErrorHandler:
MsgBox “予期せぬエラーが発生しました: ” & Err.Description, vbCritical, “システムエラー”
Resume CleanUp
End Sub

このコードのアーキテクチャ的優位性

  • SQLインジェクションが物理的に不可能:ユーザー入力値がSQL文の一部として解釈されず、純粋な「パラメータ値」として渡されるため。
  • 日付・数値のフォーマットミスを排除:`#`で囲む必要すらなく、VBAの`Date`型や`Long`型をそのまま渡せば、Jetエンジンが安全に型キャストする。
  • リソースの確実な解放:`On Error GoTo CleanUp` イディオムにより、例外発生時でも確実にメモリリークを防ぐ。

—

4. ADOを選ぶべき「唯一にして最大の例外ケース」

では、DAO/QueryDef万能論かというと、そうではない。以下のケースに該当する場合は、迷わず ADO(Microsoft ActiveX Data Objects) を採用すべきだ。

1. 外部RDBMS(SQL Server等)へのパススルー・ストアドプロシージャ実行

Accessをフロントエンド、SQL Serverをバックエンド(UPS:Upsizing)にした場合、ローカルのQueryDefで外部テーブルを叩くと、Access側がデータを全件フェッチしてローカルで絞り込むという「ネットワーク帯域の殺人行為」が発生する。
外部RDBMS側で処理を完結させるためには、ADOの `Command` オブジェクトを使い、SQL Server側のストアドプロシージャを呼び出すべきだ。

‘ ADOによるSQL Serverストアドプロシージャ実行の骨子
Dim cmd As Object
Set cmd = CreateObject(“ADODB.Command”)
With cmd
.ActiveConnection = CurrentProject.Connection ‘ または明示的なODBC接続文字列
.CommandText = “sp_ProcessHeavyCalculation”
.CommandType = 4 ‘ adCmdStoredProc
.Parameters.Refresh
.Parameters(“@BatchID”) = 1005
.Execute
End With
Set cmd = Nothing

2. 非同期処理や高度なコネクション制御が必要な場合

DAOのトランザクションはAccessのワークスペースに縛られるため、複数系統の外部リソースをまたいだ分散トランザクション等には対応できない。こうした場合もADOの出番となる。

—

5. チーフアーキテクトからの最終提言

Access VBA開発における品質の差は、突き詰めると「道具の選定思想の差」に行き着く。

  • 内部テーブルの操作、ローカルの集計、動的な条件絞り込み = DAO QueryDef + パラメータコレクション を使え。文字列連結のSQLなど二度と書くな。
  • SQL Serverや外部基幹システムとの連携、ストアドプロシージャのキック = ADO (ADODB.Connection / Command) を使え。

この境界線を厳格に守り、オブジェクトのライフサイクル(生成・利用・解放)を美しく制御できた時、あなたの作るAccessシステムは、冗談抜きで「数年はノーメンテで爆速稼働する堅牢な業務基盤」へと生まれ変わる。

妥協のないコードを書き続けろ。プロフェッショナルとは、細部へのこだわりによってのみ証明されるのだから。

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