【実務・中級編】SQL ServerのビューをQueryDef経由で参照する際の「パススルー」最適化設定 – Access VBA解析バイブル

スポンサーリンク

Access VBAを掌握する極限の知見:SQL Serverパススルー・クエリの動的生成とコネクション管理

開発現場でよく見かける光景がある。数百万レコードを抱えるSQL Serverのビューに対し、Access側でローカルのリンクテーブルを作成し、そのまま`DoCmd.OpenQuery`や、Accessの標準SQL(Jet/ACE SQL)でSELECTクエリを発行してしまう設計だ。

「なぜ、私の書いたクエリはこんなにも遅いのか?」

答えは残酷なほどシンプルだ。Accessのクレージェンジン(ACE)が、SQL Server側のビューからすべての生データをローカルに引き抜こうとしているからである。WHERE句での絞り込みや集計処理すらAccess側で実行しようとするため、ネットワーク帯域は飽和し、クライアントPCのメモリは破綻する。

この悪夢を断ち切る唯一の解が 「SQL Serverパススルー・クエリ(Pass-Through Query)」 だ。
Accessのエンジンを完全にバイパスし、SQL文をそのままSQL Serverに丸投げする。処理はすべてサーバー側で完結し、結果セットだけが返ってくる。

今回は、このパススルー・クエリをVBAから動的に生成・書き換え、堅牢に実行するための極限の知見を伝授する。

—

1. パススルー・クエリ設計における「3つの鉄則」

現場でそのまま使えるコードを示す前に、アーキテクトとして絶対に譲れない設計思想を共有しておく。これを怠ると、本番環境で確実にシステムが沈黙する。

① 接続文字列のハードコーディングを排除せよ

パススルー・クエリのプロパティには接続文字列(`Connect`)を持たせる必要があるが、これをクエリのオブジェクトに直接埋め込んではならない。接続先(開発環境、ステージング、本番環境)が変わるたびにオブジェクトを修正するなどナンセンスだ。接続文字列は一元管理し、実行時にVBAから動的に注入する。

② 「ODBCTimeout」の罠を制圧せよ

デフォルトのタイムアウト時間は60秒だ。重い集計や複雑な結合を持つSQL Serverのビューを叩いた場合、容赦なく「タイムアウト」エラーが発生する。動的生成時に必ずタイムアウト値を適切に(あるいは無制限に)設定すること。

③ 戻り値が必要ないなら `ReturnsRecords = False` にせよ

データ取得(SELECT)ではなく、ストアドの実行や一時テーブルの操作などをパススルーで行う場合、このプロパティを偽(False)にしなければならない。これを忘れると、サーバー側は処理が終わっているのにAccess側が無駄な結果セット待ちでフリーズする。

—

2. 【コピペ即実戦】堅牢な動的パススルー実行エンジン

以下に提供するコードは、単なる「動的クエリ生成サンプル」ではない。
エラーハンドリング、接続文字列の安全な動的バインド、既存クエリのクリーンナップ、そしてパフォーマンスを最大化するパラメータ設定を網羅したプロダクションコードだ。

Option Compare Database
Option Explicit

‘ ==============================================================================
‘ 担当者・モジュール名: 業務自動化アーキテクト
‘ 概要: SQL Serverのビューに対し、パススルー・クエリを動的生成して実行するモジュール
‘ ==============================================================================

Public Sub ExecuteServerPassThrough()
On Error GoTo ErrorHandler

Dim db As DAO.Database
Dim qdf As DAO.QueryDef
Const QUERY_NAME As String = “tmp_DynamicPassThrough”

‘ 接続先の定義(※実際の環境に合わせてODBC接続文字列を変更してください)
‘ 例: 統合Windows認証の場合
Dim strConn As String
strConn = “ODBC;DRIVER={ODBC Driver 17 for SQL Server};” & _
“SERVER=sqlserver_hostname_or_ip;” & _
“DATABASE=ProductionDB;” & _
“Trusted_Connection=Yes;”

‘ 実行したいSQL Server側のビューを対象としたSQL
‘ ※注意: ここに書く方言は Access SQL ではなく “T-SQL” である必要がある
Dim strSQL As String
strSQL = “SELECT CustomerID, CompanyName, TotalSales ” & _
“FROM vw_CustomerAnnualSales ” & _
“WHERE FiscalYear = 2023 ” & _
“ORDER BY TotalSales DESC;”

Set db = CurrentDb()

‘ 1. 既存の同名一時クエリが存在する場合は安全に削除
If QueryExists(QUERY_NAME) Then
db.QueryDefs.Delete QUERY_NAME
End If

‘ 2. 新規にQueryDefオブジェクトを作成(最初はローカルクエリとして作成される)
Set qdf = db.CreateQueryDef(QUERY_NAME)

‘ 3. 【最重要】パススルー・クエリ化の魔術
‘ ConnectプロパティにODBC接続文字列を設定した瞬間から、このクエリはパススルー化する
qdf.Connect = strConn

‘ 4. SQL文を設定
qdf.SQL = strSQL

‘ 5. サーバー側の処理完了を待つタイムアウト設定(例: 120秒)
qdf.ODBCTimeout = 120

‘ 6. レコードセットを返すクエリであることを明示
qdf.ReturnsRecords = True

‘ — ここから先は通常のクエリと同様に利用可能 —
‘ 例: フォームのレコードソースに指定する、またはレコードセットとして処理する

MsgBox “パススルー・クエリの動的生成に成功しました。” & vbCrLf & _
“サーバー側での処理が実行されます。”, vbInformation, “アーキテクチャ・通知”

‘ 【実践的活用例】生成したクエリを元にレコードセットを高速取得する
Dim rs As DAO.Recordset
Set rs = qdf.OpenRecordset(dbOpenSnapshot)

‘ サーバー側で絞り込まれた最小限のデータだけがメモリにロードされる
MsgBox “取得レコード数: ” & rs.RecordCount & ” 件”, vbInformation, “処理完了”

‘ クリーンアップ
rs.Close
Set rs = Nothing

‘ 用途が終わった一時クエリを削除(クエリタブの肥大化を防ぐ)
db.QueryDefs.Delete QUERY_NAME

Exit Sub

ErrorHandler:
MsgBox “致命的なエラーが発生しました。” & vbCrLf & _
“Error No: ” & Err.Number & vbCrLf & _
“Description: ” & Err.Description, vbCritical, “システムエラー”

‘ 異常終了時のオブジェクトクリーンアップ
If QueryExists(QUERY_NAME) Then
db.QueryDefs.Delete QUERY_NAME
End If
End Sub

‘ ==============================================================================
‘ 補助関数: 指定したクエリが存在するかを判定する
‘ ==============================================================================
Private Function QueryExists(ByVal queryName As String) As Boolean
Dim qdfCheck As DAO.QueryDef
On Error Resume Next
Set qdfCheck = CurrentDb.QueryDefs(queryName)
QueryExists = (Err.Number = 0)
Set qdfCheck = Nothing
On Error GoTo 0
End Function

—

3. 実務でハマる「落とし穴」とプロの回避策

この手法を導入する際、現場の開発者が必ず直面する壁と、そのスマートな回避策を伝授する。

落とし穴 A: 「Accessの方言」が混入してSQL Server側でSyntax Errorになる

パススルー・クエリ内のSQLは、Accessのエンジン(ACE)ではなく、SQL Server(T-SQL)が直接解釈する。

  • やりがちなミス:
  • 文字列結合に `&` を使う(T-SQLでは `+` または `CONCAT()`)
  • 日付リテラルを `#2023/01/01#` で囲む(T-SQLでは `’2023-01-01’`)
  • `IIF()` 関数を使う(T-SQLでは `IIF()` も使えるが、基本は `CASE WHEN` を推奨)
  • 対策: 動的SQLを組み立てる際は、必ずSQL Serverの構文規則に従うこと。

落とし穴 B: ユーザーごとの動的パラメータ(WHERE句の条件変更)の渡し方

「画面のテキストボックスに入力された値で、SQL Serverのビューを絞り込みたい」という要件は多い。これを実現する場合、VBA側でSQL文字列を安全に結合(あるいは構築)する必要がある。

‘ ユーザー入力を安全に組み込む例(SQLインジェクション対策の意識)
Dim userInputID As String
userInputID = Me.txtSearchID.Value

‘ 数値であればそのまま結合、文字列であればシングルクォーテーションで囲む
Dim strSQL As String
strSQL = “SELECT FROM vw_SecureView WHERE CompanyID = ” & Val(userInputID) & “;”

※文字列を扱う場合は、シングルクォーテーションのエスケープ処理(SQLインジェクションや構文エラーを防ぐためのサニタイジング)を忘れないこと。

—

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

Accessは「古いデータベース」と揶揄されることがある。しかし、それは使い手陣営がAccessの限界(ローカルエンジンの非力さ)をそのまま放置しているからに他ならない。

今回解説した「SQL Serverビュー + 動的パススルー・クエリ」のアーキテクチャを採用すれば、Accessは「リッチで開発生産性の高いクライアントUI」へと生まれ変わり、重いデータ処理や巨大なビューの演算は、すべて堅牢で強大なSQL Serverにオフロードできる。

「遅いAccessアプリ」を嘆くフェーズは終わりだ。今すぐコードを書き換え、真のパフォーマンスを手に入れてほしい。

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