Access VBAを掌握する極限の知見:SQL Serverパススルー・クエリの動的生成とライフサイクル統御
レガシーシステムの寿命を延ばし、なおかつモダンなRDBのポテンシャルを極限まで引き出す――。この背反する命題に直面したとき、シニアエンジニアが手にする究極の武器が「SQL Serverパススルー・クエリ」である。
Accessの標準機能であるJET/ACEエンジンにクエリ処理を委ねるな。あれはローカルの小規模データのための玩具にすぎない。数百万件規模のビュー(View)を扱う際、Accessにクエリを評価させれば、ネットワーク帯域は飽和し、クライアントPCのメモリはスワップの嵐に呑まれる。
今回は、VBAの `QueryDef` オブジェクトを動的に生成・操作し、SQL Server側で全ての処理を完結させるパススルー・クエリの極意を、メモリ管理とライフサイクルの観点から徹底解説する。
—
1. なぜ「リンクテーブル」ではなく「パススルー」なのか
多くの開発者は、SQL Serverのビューを参照するために「リンクテーブル」を作成する。しかし、リンクテーブルを介したクエリ(例:`SELECT FROM リンクテーブル WHERE 日付 >= …`)は、AccessのJET/ACEエンジンが賢しらに最適化を試みた結果、SQL Server側から全件データを引き抜いてローカルでフィルタリングするという、エンジニアリングの悪夢を引き起こす。
パススルー・クエリは、発行したSQLテキストを一切の改変なしにそのままSQL Serverへ丸投げする。処理はすべてサーバー側のCPUとメモリで行われ、結果セット(あるいはDMLの結果)だけが返却される。
この圧倒的な優位性を動的SQLの生成と組み合わせることで、Accessを「超軽量かつ堅牢なUI層」へと昇華させることが可能となる。
—
2. パススルー・クエリ実装における3大鉄則
実務でパススルーを扱う際、以下の3点を怠ると、メモリリーク、接続情報のハードコーディングによるセキュリティホール、そしてマルチユーザー環境でのコンカレンシー競合を引き起こす。
1. 接続文字列(Connect)の動的注入:パスワードをコードに直書きしない。また、環境(開発・本番)に応じた切り替えを担保する。
2. ReturnsRecordsプロパティの厳格な管理:データを返すクエリ(SELECT)と、返さないクエリ(INSERT/UPDATE/EXEC)で明示的に切り替える。これを誤ると、予期せぬランタイムエラーの温床となる。
3. QueryDefの完全な破棄(Dispose):VBAのランタイムはGC(ガベージコレクション)が貧弱である。一時的なQueryDefは必ずコード内で生成・削除を行い、データベースコンテナの肥大化を防ぐ。
—
3. 実装コード:動的パススルー・クエリの極限最適化
以下に、接続文字列を安全に構築し、SQL Serverのビューに対してパラメータ付きの動的パススルー・クエリを発行する実用的なモジュールを示す。
Option Compare Database
Option Explicit
‘ =========================================================================
‘ módulo名: modSqlPassthroughManager
‘ 概要 : SQL Server パススルー・クエリ動的生成・実行エンジン
‘ 著者 : Chief Architect
‘ =========================================================================
‘ 接続タイムアウト(秒)
Private Const QUERY_TIMEOUT As Long = 60
Public Sub ExecuteDynamicPassThrough()
Dim qdf As DAO.QueryDef
Dim db As DAO.Database
Dim strSQL As String
Dim strConn As String
Dim queryName As String
queryName = “tmp_DynamicPassThrough”
Set db = CurrentDb()
On Error GoTo ErrorHandler
‘ 1. 既存の同名一時QueryDefが存在する場合は物理削除
Call DeleteQueryIfExists(db, queryName)
‘ 2. セキュアな接続文字列の動的構築
‘ ※ODBC Driver 17/18を使用し、Trusted_Connectionまたは認証情報を適切に設定
strConn = BuildSecureConnectionString(“Your_SQL_Server_Name”, “Your_DB_Name”)
‘ 3. サーバー側で実行するSQLの構築(例:ストアドプロシージャの呼び出し、または複雑なビューの検索)
‘ ※SQL Server側のタイムゾーンやANSI準拠の構文を使用すること
strSQL = “EXEC sp_GetMonthlySalesSummary @TargetYear = 2024, @DepartmentCode = ‘DEV01’;”
‘ 4. QueryDefオブジェクトの生成(第2引数にSQLを渡さず、後からプロパティ設定するのがセオリー)
Set qdf = db.CreateQueryDef(queryName)
‘ 5. パススルー専用プロパティの構成
With qdf
.Connect = strConn
.Sql = strSQL
.ReturnsRecords = True ‘ データを返す場合はTrue(アクションクエリならFalse)
.OdbcTimeout = QUERY_TIMEOUT
End With
‘ 6. 実行・結果の処理(ここではイミディエイトウィンドウへの出力例、実際はRecordset等にバインド)
Call ProcessResultSet(qdf)
CleanUp:
‘ 7. オブジェクトの明示的解放(メモリリークの根絶)
If Not qdf Is Nothing Then
db.QueryDefs.Delete queryName ‘ 一時クエリとしての役割を終えたらデータベースコンテナから完全抹消
Set qdf = Nothing
End If
If Not db Is Nothing Then Set db = Nothing
Exit Sub
ErrorHandler:
MsgBox “パススルー実行エラー: ” & Err.Number & ” – ” & Err.Description, vbCritical, “System Error”
Resume CleanUp
End Sub
‘ ————————————————————————-
‘ 接続文字列動的ビルダー(ODBC Driver 18 推奨仕様)
‘ ————————————————————————-
Private Function BuildSecureConnectionString(ByVal serverName As String, ByVal dbName As String) As String
Dim conn As String
‘ 統合Windows認証(Trusted_Connection)を使用する場合
conn = “ODBC;DRIVER={ODBC Driver 17 for SQL Server};” & _
“SERVER=” & serverName & “;” & _
“DATABASE=” & dbName & “;” & _
“Trusted_Connection=Yes;” & _
“Encrypt=Yes;” & _
“TrustServerCertificate=Yes;”
‘ ※SQL Server認証を使用する場合は以下のようにUID/PWDを動的結合する(平文保存厳禁)
‘ conn = conn & “UID=” & user & “;PWD=” & pass & “;”
BuildSecureConnectionString = conn
End Function
‘ ————————————————————————-
‘ 既存QueryDefの安全な削除
‘ ————————————————————————-
Private Sub DeleteQueryIfExists(ByRef db As DAO.Database, ByVal queryName As String)
Dim qdfCheck As DAO.QueryDef
On Error Resume Next
Set qdfCheck = db.QueryDefs(queryName)
If Err.Number = 0 Then
db.QueryDefs.Delete queryName
End If
On Error GoTo 0
If Not qdfCheck Is Nothing Then Set qdfCheck = Nothing
End Sub
‘ ————————————————————————-
‘ 結果セットの高速処理
‘ ————————————————————————-
Private Sub ProcessResultSet(ByRef qdf As DAO.QueryDef)
Dim rs As DAO.Recordset
‘ パススルー経由のRecordsetを取得(サーバー側でカーソルが展開される)
Set rs = qdf.OpenRecordset(dbOpenSnapshot)
If Not (rs.BOF And rs.EOF) Then
rs.MoveLast ‘ レコード数の正確な取得(スナップショットの場合のみ有効)
Debug.Print “取得レコード数: ” & rs.RecordCount
‘ 必要なデータ処理をここに記述
‘ rs.MoveFirst
‘ Do While Not rs.EOF
‘ ‘ 処理
‘ rs.MoveNext
‘ Loop
Else
Debug.Print “対象データは存在しません。”
End If
‘ レコードセットのクローズと解放
If Not rs Is Nothing Then
rs.Close
Set rs = Nothing
End If
End Sub
—
4. チーフアーキテクトからの実践的警告:データベースコンテナの肥大化を防げ
上記のコードで最も注目すべきは、「使い捨てのQueryDefをループ内や処理の都度 `CreateQueryDef` し、処理が終わったら `db.QueryDefs.Delete` で即座に削除している」点である。
Accessの `.accdb` ファイルは、バックグラウンドでJet/ACEデータベースエンジンが動いている。コード内で `db.CreateQueryDef(“tmp_xxx”, …)` のように永続的なクエリとして名前付きで登録すると、その定義はAccessファイル内部のシステムテーブル(MSysObjects等)に書き込まれ続ける。
これを削除せずに放置すると、プログラムを走らせるたびにゴミデータが蓄積し、ファイルサイズが数日で数百MBに膨れ上がり、最終的にデータベース破損(Corrupt)を引き起こす。
もし名を持たない「真の動的パススルー」を構築したい場合は、フォームやレポートの `RecordSource` に直接接続文字列とSQLを流し込むか、上記のように確実なライフサイクル管理(生成・実行・即時削除)をコードで担保しなければならない。
—
5. まとめ
Access VBAは、決して「古いおもちゃ」ではない。そのアーキテクチャの制約と挙動の裏側にあるメモリ管理、そしてSQL Serverとの通信プロトコルを完全に理解したエンジニアが操る時、Accessは「最強にして最小のクライアント・フロントエンド」へと生まれ変わる。
リンクテーブルの呪縛を断ち切り、パススルー・クエリによるサーバーサイド処理の恩恵を今すぐあなたのシステムに導入せよ。妥協なきコードだけが、システムの寿命を永遠のものにする。
