【極限の知見】Err.Descriptionに頼るな。ADO「Errorsコレクション」を掌握し、SQL Serverの深淵をログに刻め
業務自動化の現場において、Access VBAは「単なるデスクトップアプリの開発言語」ではない。それは、堅牢なSQL Serverという巨大なエンジンを制御するための、フロントエンド・コントローラーである。
しかし、多くの開発者が陥る致命的な罠がある。それは、「エラーハンドリングの設計が、あまりにも浅すぎる」ということだ。
`On Error GoTo ErrHandler` を記述し、`MsgBox Err.Description` を表示して終わる。もし君がこれだけを実装しているなら、君は「バグが起きた時に、原因を推測するしかない」という、エンジニアとして最も不毛な状況を自らに強いていることになる。
特に、バックエンドにSQL Serverを据えた分散システムにおいては、VBA側のエラー(ランタイムエラー)と、SQL Server側から返されるエラー(データベースエンジンエラー)は全くの別物だ。
本稿では、ADO(ActiveX Data Objects)の真の力を引き出し、SQL Serverが吐き出す詳細なエラー情報を余さず捕捉・記録するための、プロフェッショナルなアーキテクチャを伝授する。
1. なぜ `Err.Description` では不十分なのか
VBAの組み込みオブジェクトである `Err` オブジェクトは、あくまで「VBAランタイム」が発生させたエラーを保持するものだ。
一方で、SQL Serverで制約違反(Constraint Violation)やデータ型の不一致、トリガーによるエラーが発生した場合、ADOはそれらを「プロバイダー経由のエラー」として受け取る。このとき、`Err.Description` に格納されるのは、「ADOがラップした抽象的なエラーメッセージ」に過ぎない。
「実行時エラー ‘-2147217900 (80040e14)’: 構文エラーまたはデータ型が不一致です。」
これを見て、君は即座にSQLのどの行の、どのカラムが、どのような制約に抵触したのかを特定できるか? 答えは「NO」だ。現場のエンジニアが真に必要としているのは、SQL Serverが直接返してきた `NativeError` や `SQLState` なのだ。
2. ADO `Errors` コレクションの真価
ADOの `Connection` オブジェクト、あるいは `Command` オブジェクトには、`Errors` というコレクションが存在する。
SQL Serverでエラーが発生した際、プロバイダーは単一のエラーだけでなく、関連するエラーのスタックをこのコレクションに格納する。ここをループで回し、各エラーの詳細を抽出することこそが、プロフェッショナルなログ設計の第一歩である。
捕捉すべき主要なプロパティは以下の通りだ:
- Description: SQL Serverが返した具体的なメッセージ
- SQLState: 標準SQLの状態コード
- NativeError: SQL Server固有のエラー番号(これこそが解決の鍵となる)
3. 実装:堅牢なエラーログ生成アーキテクチャ
ここでは、単なるログ出力関数ではなく、「エラー発生時に、ビジネスロジックを汚さず、かつ詳細なコンテキストを保存する」ための実戦的なコード例を示す。
構成案
1. `modLogger`: ファイルへの書き出しを担当する低レイヤーモジュール。
2. `modDB`: ADO接続と、エラー捕捉ロジックを含むメインモジュール。
# 【Module: modLogger】
ログ出力は、DB接続が切断されている最中に発生する可能性がある。そのため、ログ自体をDBに書き込む設計は避け、必ずファイルシステム(FileSystemObject)を利用せよ。
Option Explicit
' ログ出力の核となる関数
' 引数: logPath - ログファイルのフルパス
' msg - 出力するメッセージ
' isCritical - 致命的なエラーかどうかのフラグ
Public Sub WriteLog(ByVal logPath As String, ByVal msg As String, Optional ByVal isCritical As Boolean = False)
On Error Resume Next ' ログ出力自体の失敗でメイン処理を止めない
Dim fso As Object
Set fso = CreateObject("Scripting.FileSystemObject")
Dim ts As Object
' ForAppending: 追記モード, True: ファイルがなければ作成
Set ts = fso.OpenTextFile(logPath, 8, True)
Dim prefix As String
prefix = IIf(isCritical, "[CRITICAL] ", "[INFO] ")
' タイムスタンプとメッセージの書き込み
ts.WriteLine Now & " " & prefix & msg
ts.Close
Set ts = Nothing
Set fso = Nothing
End Sub
# 【Module: modDB】
ここが心臓部だ。`Connection.Errors` を走査し、詳細情報を文字列として集約する。
Option Explicit
Private Const LOG_FILE_PATH As String = "C:\Logs\App_Error_Log.txt"
' データベース操作のメインルーチン
Public Sub ExecuteDatabaseTask()
Dim conn As ADODB.Connection
Dim cmd As ADODB.Command
Dim sql As String
' 接続文字列(実務では環境変数や設定テーブルから取得せよ)
Dim connStr As String
connStr = "Provider=SQLOLEDB;Data Source=YOUR_SERVER;Initial Catalog=YOUR_DB;Integrated Security=SSPI;"
On Error GoTo ErrorHandler
Set conn = New ADODB.Connection
conn.Open connStr
' テスト用のエラー誘発SQL(制約違反を想定)
sql = "INSERT INTO Employees (ID, Name) VALUES (1, 'Test User');"
Set cmd = New ADODB.Command
With cmd
.ActiveConnection = conn
.CommandText = sql
.Execute
End With
MsgBox "処理が正常に完了しました。", vbInformation
CleanUp:
' オブジェクトのライフサイクル管理を徹底せよ
If Not conn Is Nothing Then
If conn.State = adStateOpen Then conn.Close
End If
Set cmd = Nothing
Set conn = Nothing
Exit Sub
ErrorHandler:
' 1. VBA側のエラー情報を取得
Dim vbaErrDetail As String
vbaErrDetail = "VBA Error #" & Err.Number & ": " & Err.Description
' 2. ADO/SQL Server側の詳細エラーを収集
Dim adoErrDetail As String
adoErrDetail = CaptureADOErrors(conn)
' 3. ログへの統合出力
Dim fullLogMsg As String
fullLogMsg = vbaErrDetail & vbCrLf & IIf(adoErrDetail <> "", "SQL Server Details:" & vbCrLf & adoErrDetail, "")
WriteLog LOG_FILE_PATH, fullLogMsg, True
MsgBox "システムエラーが発生しました。管理者に連絡してください。" & vbCrLf & _
"Error Code: " & Err.Number, vbCritical, "Execution Error"
Resume CleanUp
End Sub
' ADOのエラーコレクションを走査する職人芸
Private Function CaptureADOErrors(ByRef conn As ADODB.Connection) As String
Dim errColl As ADODB.Errors
Dim errItem As ADODB.Error
Dim strDetails As String
' Connectionが有効でない場合は、エラー情報は取れない
If conn Is Nothing Then Exit Function
Set errColl = conn.Errors
If errColl.Count > 0 Then
For Each errItem In errColl
strDetails = strDetails & _
" - [SQLState: " & errItem.SQLState & "] " & _
"[NativeError: " & errItem.NativeError & "] " & _
"Msg: " & errItem.Description & vbCrLf
Next errItem
End If
CaptureADOErrors = strDetails
End Function
4. アーキテクトからの警告:設計上の注意点
この実装をプロダクション環境に投入するにあたり、以下の3点を遵守せよ。
① オブジェクトのライフサイクルと「接続喪失」への備え
エラーが発生した際、既に `Connection` オブジェクトが破棄されていたり、ネットワーク断絶で `State` が `adStateClosed` になっていたりする場合がある。`CaptureADOErrors` 内で `If conn Is Nothing` のチェックを入れているのは、そのためだ。接続が死んでいる状態でのエラー収集は不可能であることを理解しておけ。
② トランザクションの整合性
`On Error GoTo` でエラーを捕捉した際、もしその前に `conn.BeginTrans` を実行していたなら、必ず `conn.RollbackTrans` を呼び出さなければならない。ログ出力だけして処理を終えるのは、データの整合性を破壊する行為だ。
③ I/O パフォーマンスとロック
ログファイルへの書き込みは、頻繁に発生する可能性がある。高頻度なトランザクションが発生するシステムでは、ログを直接ファイルに書くと、ファイルロックがボトルネックになる。その場合は、ログをメモリ上のキューに溜め、バッチ処理で書き出すか、あるいは Windows イベントログへの転送を検討せよ。
結論
「動けばいい」という思考は、保守フェーズにおいて牙を剥く。
`Err.Description` という薄い膜を剥ぎ取り、`Errors` コレクションを通じてサーバーの叫びを直接聞き取ること。それが、トラブルシューティングの時間を劇的に短縮し、システムの信頼性を極限まで高める唯一の道である。
このコードを、君のプロジェクトの「標準」として組み込め。
