沈黙の失敗を許さない。DAOの「dbFailOnError」で手に入れる、プロのデータ整合性
「プログラムは動いているけれど、なぜかデータが中途半端に書き換わっている……」
Access VBAを学び始めた方が、最初に突き当たる「見えない壁」があります。それは、エラーメッセージすら出さずに、処理の一部だけが「こっそり失敗する」という現象です。
マクロの記録や、ネットで見つけたコードをそのままコピーして動かしている段階では、この恐怖には気づけません。しかし、実務で「在庫管理」や「売上集計」といった、1件のミスも許されないシステムを構築するなら、この「沈黙の失敗」を完全に封じ込める必要があります。
今日は、DAO(Data Access Objects)を扱うエンジニアにとっての「隠れた切り札」、`dbFailOnError` オプションについて、その本質を解き明かしていきましょう。ここをマスターすれば、あなたのコードは「ただ動くもの」から「信頼されるシステム」へと進化します。
1. なぜ「エラーが出ない失敗」が起きるのか?
まず、問題の核心を知ることから始めましょう。
例えば、あなたは「100人の従業員の給与を一括更新する」というプログラムを書いたとします。
プログラムは順調に動き、99人分の更新は成功しました。しかし、たった1人だけ、「入力された値が数値型ではない」といった理由で、データベースのルール(制約)に抵触してしまったとします。
このとき、標準的な書き方(`DoCmd.RunSQL` や、オプションを指定しない `DAO.Execute`)では、Accessはこう振る舞います。
> Accessの思考: 「おっと、50番目の人の更新はルール違反だね。じゃあ、その人だけ飛ばして、残りの人は更新を続けちゃおう。エラーは出さなくていいよね?」
これが「沈黙の失敗」です。
プログラムの実行結果は「成功」として終了しますが、実際にはデータが不完全な状態(整合性が崩れた状態)になっています。これが、業務システムにおいて最も恐ろしい事態なのです。
2. 救世主 `dbFailOnError` の正体
この「お節介なスルー」を許さず、「1件でも失敗したら即座にエラーを吐いて止まれ!」と命令するのが、`dbFailOnError` というオプションです。
これは、DAOの `Database.Execute` メソッドに渡すことができる「命令のフラグ」です。
図解:処理の違い
- 通常時 (`dbSeeChanges` 等のみ、または指定なし)
`[成功] → [成功] → [失敗(スルー)] → [成功] → [完了(一見、成功!)]`
👉 結果:データが不整合になる(最悪)
- `dbFailOnError` 使用時
`[成功] → [成功] → [失敗(即エラー発生!)] → [処理中断]`
👉 結果:どこで失敗したかが明確になり、不完全な更新を防げる
3. 実践:プロフェッショナルな書き方
それでは、実際にどのようにコードを書くべきか見ていきましょう。
「マクロの記録」から脱却した、エンジニアらしい堅牢なコードの書き方です。
❌ 避けるべき書き方(初心者によくあるパターン)
' DoCmd.RunSQL はユーザーに確認ダイアログを出したり、
' エラーが起きても「一部実行されました」という曖昧な挙動になりがちです。
DoCmd.RunSQL "UPDATE T_在庫 SET 在庫数 = 在庫数 - 1 WHERE 商品ID = 101"
✅ 推奨される書き方(プロのアーキテクチャ)
Sub UpdateStockSafe()
Dim db As DAO.Database
Dim strSql As String
Set db = CurrentDb
strSql = "UPDATE T_在庫 SET 在庫数 = 在庫数 - 1 WHERE 商品ID = 101"
On Error GoTo ErrorHandler ' エラーハンドリングを必ずセットする
' 【ここが肝!】 dbFailOnError を指定することで、
' 1件でも制約違反があれば、即座にエラーイベントを発生させます。
' dbSeeChanges は、更新可能なクエリを実行する際に必須となるフラグです。
db.Execute strSql, dbFailOnError + dbSeeChanges
MsgBox "更新が正常に完了しました。", vbInformation
ExitSub:
Set db = Nothing
Exit Sub
ErrorHandler:
' エラーが発生した場合、ここへジャンプします。
' 失敗したまま処理が進むことを防ぎ、ユーザーに警告を出せます。
MsgBox "更新に失敗しました!データは変更されていません。" & vbCrLf & _
"エラー内容: " & Err.Description, vbCritical, "システムエラー"
Resume ExitSub
End Sub
コードのポイント解説
1. `db.Execute …, dbFailOnError + dbSeeChanges`:
複数のオプションを組み合わせる際は `+` で繋ぎます。これにより、「失敗したらエラーを出せ」かつ「更新可能なクエリとして扱え」という二つの命令を同時に伝えています。
2. `On Error GoTo ErrorHandler`:
`dbFailOnError` は「エラーを発生させる」ためのスイッチです。発生したエラーをキャッチして、適切に処理(ユーザーへの通知やログ記録)するために、このエラーハンドリングはセットで必須となります。
4. さらに高みへ:トランザクションとの組み合わせ
ここまでで、「一つのSQL文の中での失敗」は防げるようになりました。
しかし、実務では「複数のSQL文を、すべて成功するか、すべて失敗するか(All or Nothing)のどちらかにしたい」という場面が多々あります。
例えば、「注文テーブルにデータを追加」し、次に「在庫テーブルを減らす」という2つの処理を行う場合。在庫を減らす処理で失敗したなら、注文の追加もキャンセルしなければなりません。
ここで登場するのが、トランザクション処理です。
Sub ComplexTransactionExample()
Dim db As DAO.Database
Set db = CurrentDb
On Error GoTo TransactionError
' 1. トランザクションの開始(ここから「仮の更新」が始まる)
DBEngine.BeginTrans
' 2. 注文の追加
db.Execute "INSERT INTO T_注文 (商品ID, 数量) VALUES (101, 1)", dbFailOnError + dbSeeChanges
' 3. 在庫の更新(ここで失敗すると、2の処理も巻き戻される)
db.Execute "UPDATE T_在庫 SET 在庫数 = 在庫数 - 1 WHERE 商品ID = 101", dbFailOnError + dbSeeChanges
' 4. すべて成功したら、確定させる
DBEngine.CommitTrans
MsgBox "すべての処理が正常に確定しました。", vbInformation
ExitSub:
Set db = Nothing
Exit Sub
TransactionError:
' 5. どこかでエラーが起きたら、すべての変更を「なかったこと」にする
DBEngine.Rollback
MsgBox "エラーが発生したため、すべての処理を取り消しました。" & vbCrLf & _
"エラー内容: " & Err.Description, vbCritical
Resume ExitSub
End Sub
アーキテクトの視点
- `dbFailOnError` は「単発の弾丸の精度」を高めるもの。
- `BeginTrans / CommitTrans` は「一連の作戦の成否」を管理するもの。
この二つを組み合わせることで、あなたのプログラムは、どんな過酷なデータ入力に対しても、データの整合性を守り抜く最強の盾を手に入れるのです。
まとめ:ここをクリアすれば、Access VBAの基本はバッチリですよ
「とりあえず動けばいい」という段階から、「データが絶対に壊れない仕組みを作る」というプロフェッショナルの段階へ。その境界線にあるのが、今回解説した `dbFailOnError` です。
1. `DoCmd.RunSQL` ではなく `db.Execute` を使う。
2. 必ず `dbFailOnError` オプションを添える。
3. `On Error GoTo` でエラーを捕まえる準備を忘れない。
この3点を守るだけで、あなたの作るシステムの信頼性は劇的に向上します。
一歩ずつ、着実に。コードの裏側にある「データの命」を大切にするエンジニアを目指していきましょう。応援していますよ!
