こんにちは!現場でバリバリ使えるAccess VBAのテクニック、楽しくマスターしていきましょう。
マクロの記録から一歩抜け出し、「自分の手でシステムをコントロールしたい!」という熱意を持つあなたへ。今回は、Access開発の現場で避けて通れない「DAOのDeleteメソッドで発生する『削除不可』エラー」の正体を暴き、スマートに解決する極上の知見を伝授します。
ここをクリアすれば、データベースの裏側の動きが手に取るようにわかるようになりますよ。さあ、一緒に扉を開けましょう!
—
1. なぜDAOの「Delete」は突然失敗するのか?
Access VBAでデータを削除する際、私たちは主に DAO(Data Access Objects) というエンジンを使います。コードを書くのはとってもシンプルです。
‘ レコードを削除するだけのシンプルなコード
db.Execute “DELETE FROM T_受注 WHERE 受注ID = 123”, dbFailOnError
あるいは、Recordsetオブジェクトを使ったこんな書き方もあります。
rs.Seek “=”, 123
rs.Delete
「よし、これで完璧!」と思って実行したのに、ある日突然、冷酷なエラーメッセージが画面に突きつけられます。
「ランタイムエラー:レコードを削除できませんでした。」
……初心者泣かせの代表格、エラー番号のオンパレードです。なぜ消せないのでしょうか? 理由は主に以下の3つに集約されます。
1. 参照整合性の呪縛(リレーションシップの罠)
2. 目に見えないレコードロック(他のユーザーや自分自身の呪縛)
3. トランザクションのロールバック漏れ
これらを一つずつ、優しく紐解いていきましょう。
—
2. 削除を阻む「3つの壁」とデバッグ手法
壁①:参照整合性(子レコードが親にしがみついている)
データベース設計の基本として、親テーブル(例:顧客マスタ)を消すとき、子テーブル(例:受注データ)にデータが残っていると、データベースエンジンは「おいおい、親が消えたら子が行き場を失うだろ!」と怒って削除を拒絶します。これが参照整合性エラーです。
【デバッグの極意】
いきなりコードで消そうとせず、まずはAccessのクエリデザイナ等で「本当にそのレコードに関連づく子データが残っていないか」を確認する癖をつけましょう。
壁②:排他制御(ロックがかかっている)
Recordsetを開いている最中、あるいは他のフォームがそのレコードを掴んでいる(編集モードに入っている)場合、DAOは安全のために削除を許可しません。
壁③:トランザクションの未完了
複数のテーブルを操作する際、`BeginTrans` でトランザクションを開始したものの、途中でエラーが起きて宙ぶらりんになっている状態です。
—
3. 【実践】「削除不可」エラーをスマートに捕捉し、ユーザーを導くVBAコード
ここからが本題です。エラーで強制終了するプログラムは、現場では「バグが多いシステム」と呼ばれて嫌われます。
エラーを予測して優しく捕捉し、原因をログに残し、ユーザーには分かりやすく伝える。 これができるのが、一流のエンジニアへの第一歩です。
以下のコードを、あなたのフォームや標準モジュールにそのまま貼り付けてみてください。現場でそのまま使える実用的な設計にしています。
Public Sub SafeDeleteRecord(ByVal targetID As Long)
Dim db As DAO.Database
Dim rs As DAO.Recordset
Dim strSQL As String
‘ エラーハンドラの準備
On Error GoTo ErrorHandler
‘ データベースの参照を取得
Set db = CurrentDb()
‘ トランザクションを開始(安全性の確保)
db.BeginTrans
‘ 対象のレコードを排他制御モード(編集可能)で取得
strSQL = “SELECT FROM T_顧客マスタ WHERE 顧客ID = ” & targetID
Set rs = db.OpenRecordset(strSQL, dbOpenDynaset, dbDenyWrite)
‘ レコードが存在するか確認
If rs.EOF And rs.BOF Then
MsgBox “指定されたデータは既に別のユーザーによって削除されています。”, vbExclamation, “確認”
GoTo CleanUp
End If
‘ 削除実行
rs.Delete
‘ トランザクションの確定(コミット)
db.CommitTrans
MsgBox “データの削除が正常に完了しました。”, vbInformation, “成功”
CleanUp:
‘ オブジェクトの解放(メモリリークを防ぐプロの作法)
If Not rs Is Nothing Then
rs.Close
Set rs = Nothing
Set db = Nothing
Exit Sub
ErrorHandler:
‘ トランザクション中のエラーであれば変更を破棄(ロールバック)
db.Rollback
‘ エラー番号に応じた親切なメッセージ分岐
Select Case Err.Number
Case 3200
‘ 参照整合性エラーのトラップ
MsgBox “このデータは関連する受注データが存在するため削除できません。” & vbCrLf & _
“先に子データを整理してください。”, vbCritical, “削除不可(参照整合性)”
Case 3218, 3260
‘ ロック関連のエラー
MsgBox “現在、他のユーザー(または他の画面)がこのデータを使用しています。” & vbCrLf & _
“しばらく待ってから再度実行してください。”, vbExclamation, “ビジーエラー”
Case Else
‘ 予期せぬエラー
MsgBox “予期せぬエラーが発生しました。” & vbCrLf & _
“エラー番号: ” & Err.Number & vbCrLf & _
“内容: ” & Err.Description, vbCritical, “システムエラー”
End Select
Resume CleanUp
End Sub
—
4. コードの深掘りポイント(ここがプロの技)
上記のコードには、Access VBAを極めるためのエッセンスが凝縮されています。
1. `db.BeginTrans` と `db.Rollback` のコンボ
データベース操作は「全部成功するか、全部なかったことにするか」のどちらかでなければなりません(ACID特性)。途中でエラーが起きたときに `Rollback` を挟むことで、中途半端にデータが壊れるのを防ぎます。
2. `Err.Number` によるエラーの仕分け
「3200」というエラー番号を知っているだけで、「あ、リレーションシップが原因だな」と一瞬で特定できます。汎用的なエラーメッセージで済ませず、原因に応じた案内を出すことで、ユーザーからの問い合わせ激減させることができます。
3. オブジェクトの確実な解放 (`CleanUp` ラベル)
VBAでは、開いた `Recordset` や `Database` をそのままにしておくとメモリリーク(メモリの無駄遣い)を引き起こします。必ず最後に `Close` と `Set = Nothing` を通る動線を作りましょう。
—
まとめ
いかがでしたでしょうか?
「レコードが削除できない」という現象の裏には、データベースが守ろうとする「データの整合性」という重要なルールが隠されています。
エラーを恐れる必要はありません。エラー番号を読み解き、適切な例外処理(エラーハンドリング)を実装してあげれば、Accessはあなたの頼もしい相棒になってくれます。
ここをクリアしたあなたなら、もうマクロの記録の卒業生ではありません立派なVBAエンジニアです。ぜひ実際の業務アプリで試してみてくださいね。応援しています!
