実行時エラーを「SQLの構文解析」で先読みする:QueryDef実行前のバリデーションロジック
Access VBAでの開発において、動的SQLの生成と実行は避けて通れないパスだ。条件によってWHERE句が変わり、JOINの結合条件が動的に変化する。そんなクエリを組み立て、「いざ実行!」というタイミングで飛んでくるあの憎たらしい実行時エラー。
「クエリ式 ‘…’ の構文エラーがありません (演算子がありません)」
このエラーが出た瞬間、開発者はデバッグモードに引きずり込まれ、生成された巨大なSQL文字列のどこに半角スペースやカンマの抜けがあるのかを目視で探す羽目になる。さらに最悪なのは、このエラーが現場のエンドユーザーの操作によって、本番環境で突然発生することだ。
プロフェッショナルな業務システムにおいて、「動かしてみるまでSQLが正しいか分からない」という設計は、アーキテクチャの敗北を意味する。今回は、DAOの隠された(あるいはあまり使われない)機能を駆使し、クエリを実行する前にSQLの構文を完全検証し、バグを未然に握りつぶすための極限の知見を伝授する。
—
なぜ「実行時エラー」をその場でキャッチするだけでは不十分なのか?
初学者や中級者の多くは、エラーハンドリング(`On Error GoTo`)の中にSQL実行を記述し、エラー起きたらメッセージボックスを出す、という設計で満足する。
‘ 【悪手】よくある素朴なエラーハンドリング
On Error GoTo ErrorHandler
CurrentDb.Execute “UPDATE T_Sales SET … ” ‘ここでコケる
Exit Sub
ErrorHandler:
MsgBox “エラーが発生しました: ” & Err.Description, vbCritical
Exit Sub
このアプローチの何が問題か?
1. トランザクションの無駄とロールバックのコスト: 処理の途中、あるいは重いバッチ処理の終盤で構文エラーが発覚した場合、それまでの処理が無駄になるか、複雑なロールバック制御が必要になる。
2. ユーザー体験(UX)の悪さ: エラーメッセージがAccessやデータベースエンジンからの生の声(英語混じり、あるいは不親切な構文エラー)であり、エンドユーザーは何を直せばいいか分からない。
3. 根本的な原因の隠蔽: データの不整合なのか、SQLのバグなのか、プログラムの欠陥なのかの切り分けが遅れる。
我々が目指すべきは、「データベースエンジンにSQLを評価(パース)させ、実行権を与える前に構文の正当性を証明する」ことだ。
—
—
DAOの `QueryDef` を利用した「SQL先読みバリデーション」のメカニズム
Access(DAO)には、一時的なクエリ定義オブジェクトである `QueryDef` を作成する機能がある。実はこの `QueryDef`、「実際に実行しなくても、SQL文を代入した時点で構文の妥当性をチェックできる」という強力な特性を持っている。
正確に言えば、`CurrentDb.CreateQueryDef(“”, sql)` を実行した瞬間、DAOはJet/ACEデータベースエンジンに対してSQLの構文解析(パーシンク)を要求する。ここで構文エラーがあれば、実行時ではなく、この「作成」の瞬間にエラーがキャッチできるのだ。
これを利用すれば、動的SQLの生成直後にバリデーション専用の関数を通すことで、一文字たりとも不正なSQLをデータベースに到達させない鉄壁のガードを構築できる。
—
【実装例】プロダクションコード:堅牢なSQLバリデーション付き実行基盤
以下のコードは、実際の業務システムでそのまま流用できる、動的SQLの生成・検証・実行をカプセル化モジュールだ。エラーハンドリングのレベルが一段引き上げられている点を確認してほしい。
Option Compare Database
Option Explicit
‘ =========================================================================
‘ 模範的な動的SQL実行モジュール
‘ 概要:動的生成されたSQLの構文をQueryDefで事前検証し、安全に実行する
‘ =========================================================================
Public Sub ExecuteDynamicQuerySafe(ByVal targetSQL As String)
On Error GoTo ErrorHandler
‘ 1. SQLの構文事前バリデーション(ここで構文エラーを先読みする)
ValidateSQLSyntax targetSQL
‘ 2. バリデーションを通過した安全なSQLのみを実行
‘ ※ここではアクションクエリ(UPDATE/INSERT/DELETE)を想定
CurrentDb.Execute targetSQL, dbFailOnError
MsgBox “処理が正常に完了しました。”, vbInformation, “完了”
Exit Sub
ErrorHandler:
‘ 致命的なエラーのハンドリング
MsgBox “システムエラーが発生しました。” & vbCrLf & _
“詳細: ” & Err.Description, vbCritical, “実行時エラー”
End Sub
‘ =========================================================================
‘ 【核心】DAOを用いたSQL構文の事前解析バリデーション関数
‘ =========================================================================
Private Sub ValidateSQLSyntax(ByVal sql As String)
Dim qdfTemp As DAO.QueryDef
On Error GoTo SyntaxError
‘ 名前のない一時的なQueryDefオブジェクトを作成し、SQLをバインドする
‘ ★ここでJet/ACEエンジンによるSQLの構文解析が走る
Set qdfTemp = CurrentDb.CreateQueryDef(“”, sql)
‘ クリーンアップ(メモリ解放)
qdfTemp.Close
Set qdfTemp = Nothing
Exit Sub
SyntaxError:
‘ 構文エラーが発生した場合、エラー内容をブラッシュアップして送出
Dim parseErrorMsg As String
parseErrorMsg = “生成されたSQLの構文に誤りがあります。” & vbCrLf & _
“【エラー詳細】 ” & Err.Description & vbCrLf & _
“【対象SQL】” & vbCrLf & sql
‘ オブジェクトの確実な解放
If Not qdfTemp Is Nothing Then
qdfTemp.Close
Set qdfTemp = Nothing
End If
‘ 呼び出し元へカスタムエラーをスロー
Err.Raise vbObjectError + 1000, “ValidateSQLSyntax”, parseErrorMsg
End Sub
この設計の優れている点
1. ゼロ・サイドエフェクト(副作用なし)
`CurrentDb.CreateQueryDef(“”, sql)` の第一引数に長さ0の文字列 `””` を渡すことで、永続的なクエリとして保存されることなく、メモリ上だけでQueryDefが生成・破棄される。Accessのナビゲーションウインドウを汚す心配が一切ない。
2. エラーのコンテキスト化
単に「構文エラーです」と言うのではなく、「どのSQL文字列のどこが原因でコケたのか」をエラーメッセージ内に埋め込んでスローするため、開発時のデバッグスピードが劇的に向上する。
3. `dbFailOnError` とのシナジー
事前の構文チェックをクリアしているため、`CurrentDb.Execute sql, dbFailOnError` におけるエラーは「構文ミス」ではなく、「外部キー制約違反」や「型不一致」といった純粋なデータ・業務ロジック上の例外に限定される。これにより、エラー切り分けの解像度が跳ね上がる。
—
アーキテクトからの現場への提言
Access VBAでの開発現場を見ていると、「動けば正義」というスパゲッティコードが蔓延しがちだ。しかし、システムが大規模化し、データの重要度が増すにつれて、こうした「目に見えないリスクを潰すコード」の積み重ねが生死を分ける。
動的SQLを組み立てるロジックを書いたら、必ず実行の直前に `ValidateSQLSyntax` を挟む――。この習慣をチーム全体に徹底させるだけで、リリース後の「謎のバグ報告」は激減する。
プログラミングとは、祈ることではなく、エラーをあらかじめ追い詰める作業だ。
あなたの書くAccessアプリを、ワンランク上の堅牢なシステムへ昇華させてほしい。
