【実務・中級編】DAO.Database.Executeの「dbFailOnError」オプションで更新失敗を検知する – Access VBA解析バイブル

スポンサーリンク

【Access VBA】DAO.Database.Executeの「dbFailOnError」:その更新処理、本当に安全ですか?

開発現場でよく見かける光景がある。
「何千件ものレコードを一括更新するVBAを書いた。正常時は動くが、途中でデータ型エラーや重複エラーが起きると、中途半端な状態でデータが書き込まれてしまう……」

もし、あなたがこのようなコードを放置しているなら、今すぐ手を止めてほしい。
Accessのデータベース開発において、データの整合性(トランザクションの原子性)を担保することはエンジニアの生命線だ。

今回は、DAOを用いたデータ操作の急所であり、堅牢なシステムを作るための必須オプション`dbFailOnError`について、プロのアーキテクトの視点から徹底的に解説する。

1. なぜ「DoCmd.RunSQL」や「無印 Execute」では不十分なのか

多くの初学者、あるいは古いスタイルの開発者は、アクションクエリを実行する際に以下のようなコードを書く。

‘ 【アンチパターン】エラーを無視する危険な書き方
DoCmd.RunSQL “UPDATE T_Stock SET Quantity = Quantity – 1 WHERE ProductID = ‘A001′”

あるいは、DAOの`Execute`メソッドをオプションなしで使う。

‘ 【これも不十分】エラー検知が漏れる書き方
CurrentDb.Execute “INSERT INTO T_Log (LogDate) VALUES (Now())”

何が問題なのか?

これらのコードには「途中でエラーが発生しても、実行できるところまで実行してしれっと完了してしまう」という致命的な欠陥がある。

例えば、100件のデータを一括更新する処理の「50件目」で主キー重複やデータ型不一致エラーが発生したとする。

  • `DoCmd.RunSQL` や無印の `Execute` は、エラーが発生した瞬間、それまでの50件の変更を確定(コミット)したまま処理を止めるか、あるいは警告ダイアログを出してユーザーを混乱させる。
  • 結果、データベースには「半分更新された壊れたデータ」が残り、データの整合性は完全に崩壊する。

業務システムにおいて、中途半端なデータほど厄介なものはない。エラーで止まるならまだマシで、「エラーに気づかず不正なデータが蓄積されること」が最も恐ろしいバグなのだ。

2. 救世主 `dbFailOnError` とトランザクションの真実

ここで登場するのが、`DAO.Database.Execute` メソッドの第2引数に指定する定数 `dbFailOnError` だ。

CurrentDb.Execute “UPDATE T_Master SET Status = 1”, dbFailOnError

`dbFailOnError` がもたらす2つの絶対的なメリット

1. エラーの確実な捕捉
途中のクエリ実行で1件でもエラーが発生した場合、VBAの実行時エラー(トラップ可能なエラー)として即座に処理が中断される。`On Error` 構文と組み合わせることで、エラーを完全にコントロール下における。
2. 自動トランザクションのロールバック
ここが最重要だ。`dbFailOnError` を指定して `Execute` を実行した場合、内部で暗黙のトランザクションが張られる。つまり、「1件でも失敗すれば、その Execute で実行された変更はすべてなかったこと(ロールバック)になる」。データベースは一瞬たりとも汚染されない。

3. 【実践】プロダクションコードで学ぶ堅牢な更新処理

実務でそのまま使える、堅牢性を極めたサンプルコードを提示する。
ここでは、単発のSQL実行だけでなく、複数テーブルの更新を完全にコントロールするトランザクション制御のパターンを実装している。

Option Explicit

”’

”’ 顧客ステータスを一括更新し、関連ログを書き込む堅牢なトランザクション処理
”’

Public Sub UpdateCustomerStatusSafely()
Dim db As DAO.Database
Dim sql1 As String
Dim sql2 As String

‘ 明示的にCurrentDbを変数に格納(パフォーマンスとオブジェクト参照の安定化)
Set db = CurrentDb

‘ エラーハンドラの設定
On Error GoTo ErrorHandler

‘ 【重要】明示的なトランザクションの開始
‘ 複数の Execute を一つの不可分な処理(アトミック)として扱う場合、
‘ DBengine.BeginTrans を使用する。
DBEngine.BeginTrans

‘ SQL構築
sql1 = “UPDATE T_Customers SET Status = 2 WHERE LastAccessDate < #2023/01/01#" sql2 = "INSERT INTO T_SystemLog (LogText, LogTime) VALUES ('Inactive customers updated', Now())" ' Execute実行(必ず dbFailOnError を付与する) db.Execute sql1, dbFailOnError db.Execute sql2, dbFailOnError ' すべて成功した場合のみコミット DBEngine.CommitTrans MsgBox "更新処理が正常に完了しました。", vbInformation, "成功" GoTo Finally ErrorHandler: ' 異常発生時はロールバックして変更を完全破棄 DBEngine.Rollback MsgBox "予期せぬエラーが発生しました。" & vbCrLf & _ "エラー番号: " & Err.Number & vbCrLf & _ "エラー内容: " & Err.Description, vbCritical, "致命的エラー" Finally: ' オブジェクトの解放 Set db = Nothing End Sub

アーキテクトからの設計アドバイス

  • `CurrentDb` の変数保持: `CurrentDb` は呼び出すたびに新しいデータベースオブジェクトのインスタンスを生成する。同一プロシージャ内で何度も `CurrentDb.Execute` を叩くのはパフォーマンス上の無駄であり、オブジェクト参照の不安定さを招く。必ず冒頭で変数を取得して使い回せ。
  • 明示的トランザクション(`BeginTrans` / `CommitTrans` / `Rollback`): 単一の `Execute` であれば `dbFailOnError` だけでも自動ロールバックされるが、「複数のSQLを連動させて、全部成功するか全部失敗するか」を保証したい場合は、必ず `DBEngine.BeginTrans` で囲むこと。

4. まとめ:プロとアマを分ける境界線

Access VBAによる開発は、手軽であるがゆえに「動けばいいや」という雑なコードが蔓延しがちだ。しかし、企業の基幹データを扱うシステムにおいて、データの整合性を軽視するコードは「爆弾」を抱えているのと変わらない。

  • `DoCmd.RunSQL` は画面フィードバックや警告制御が必要な特殊なケースを除き、基本使わない。
  • DAOの `Execute` を使うときは、思考停止で `dbFailOnError` を付与する。
  • 複数クエリの連動には `BeginTrans` との組み合わせを徹底する。

この鉄則を守るだけで、あなたの書くAccess VBAの信頼性はプロフェッショナルレベルへと劇的に跳ね上がる。
明日からのコードに、ぜひ取り入れてほしい。

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