Access VBA データの重複登録を防ぐ!DCount関数とBeforeUpdate(更新前処理)の実装方法
Accessでフォーム入力を作っていると必ず直面するのが、''「データの重複登録をどう防ぐか?」''という問題です。
今回は、DCount関数を使って件数を数え、BeforeUpdateイベントで保存を阻止する、という一連の流れを解説します。
さらに、単に重複を弾くだけでなく、''「マスターに存在しないコード」のチェック''や、''「別フォームから値を代入した時にイベントが走らない問題」''の解決策まで踏み込んでいきます。

説明したかったこと・伝えたかったこと
- DCount関数を使って、テーブル内の重複レコード件数を正確に把握する。
- BeforeUpdate(更新前処理)で「Cancel = True」を使い、エラー時に保存させない。
- .OldValueプロパティを活用し、自分自身の修正(値が変わっていない場合)はチェックをスルーさせる。
- チェック処理を「標準モジュール」に共通関数として出し、どこからでも呼び出せるようにする。
1. 基本のDCountテスト(単体テスト)
まずはボタンのクリックイベントなどで、正しく件数が数えられるかテストします。
Private Sub btnTest_Click()
Dim nCNT As Integer
' T_注文履歴テーブルから、入力された商品コードの件数を数える
nCNT = DCount("商品コード", "T_注文履歴", "[商品コード] = '" & Me.商品コード & "'")
MsgBox nCNT & " 個データがあります"
End Sub[01:55](https://www.youtube.com/watch?v=Irr_9wx6CAY&t=115s) 頃の解説にある通り、まずは単体で動くことを確認するのがデバッグの鉄則です。
2. 鉄壁のチェック関数(標準モジュール)
フォームごとに同じコードを書くのは非効率です。また、別フォームから値をセットした時にイベントが発火しない問題を解決するため、チェック処理を共通関数化します。
' 商品コードをチェックし、エラーがあればTrueを返す
Public Function chk商品コード(str商品コード As String) As Boolean
Dim nCNT As Integer
' 1. マスター(T_メニュー一覧)に存在するかチェック
nCNT = DCount("商品コード", "T_メニュー一覧", "[商品コード] = '" & str商品コード & "'")
If nCNT = 0 Then
MsgBox str商品コード & "が見つかりません。マスターを確認してください。"
chk商品コード = True ' エラー
Exit Function
End If
' 2. 注文履歴に既に登録(重複)がないかチェック
nCNT = DCount("商品コード", "T_注文履歴", "[商品コード] = '" & str商品コード & "'")
If nCNT > 0 Then
MsgBox str商品コード & "は重複しています。再入力してください。"
chk商品コード = True ' エラー
Exit Function
End If
' エラーなし
chk商品コード = False
End Function
3. フォーム側での実装(BeforeUpdate)
フォームの「商品コード」コントロールの更新前処理に記述します。ここで ''.OldValue'' を使うのがポイントです。
Private Sub 商品コード_BeforeUpdate(Cancel As Integer)
' 変更がない場合はチェック不要
If Me.商品コード.Value = Me.商品コード.OldValue Then
Exit Sub
End If
' 共通関数を呼び出してエラーならキャンセル
If chk商品コード(Me.商品コード.Value) = True Then
Cancel = True ' 更新処理の中断
End If
End Sub[20:04](https://www.youtube.com/watch?v=Irr_9wx6CAY&t=1204s) では、修正時に自分自身の値を重複と判定させないための「OldValue」活用法を解説しています。
4. 選択用サブフォームからの呼び出し
一覧フォーム(別窓)から商品を選択して親フォームに代入する場合、そのままでは親のBeforeUpdateが走りません。そのため、セットする直前に同じ関数を呼び出します。
Private Sub 選択ボタン_Click()
Dim menuCode As String
menuCode = Me.商品コード.Value
' 親フォームに代入する前にチェック
If chk商品コード(menuCode) = True Then
Exit Sub ' エラーならここで中断(画面を閉じない)
End If
' 正常なら親フォームにセットして閉じる
Forms(Me.OpenArgs).商品コード.Value = menuCode
DoCmd.Close acForm, Me.Name
End Sub
デバッグの手順と動画リンク
- [00:22](https://www.youtube.com/watch?v=Irr_9wx6CAY&t=22s) デバッグ方針の決定(単体テストから結合テストへ)
- [03:00](https://www.youtube.com/watch?v=Irr_9wx6CAY&t=180s) DCount関数の構文とMicrosoftヘルプの活用方法
- [06:36](https://www.youtube.com/watch?v=Irr_9wx6CAY&t=396s) BeforeUpdateイベントでのCancel=Trueによる保存阻止
- [15:24](https://www.youtube.com/watch?v=Irr_9wx6CAY&t=924s) マスターチェックの重要性(履歴にない=登録OKとは限らない)
- [23:09](https://www.youtube.com/watch?v=Irr_9wx6CAY&t=1389s) 別フォームからセットした時にイベントが走らない問題の露呈
- [30:48](https://www.youtube.com/watch?v=Irr_9wx6CAY&t=1848s) 解決策:標準モジュールへの移行と共通化
今後の課題とアドバイス
今回の実装で基本的な重複は防げますが、実務レベルでは以下の点も考慮するとさらに良くなります。
- ''マルチユーザー環境'': 厳密には保存の瞬間に再度チェックをかけないと、二人同時に同じコードを打った際にすり抜ける可能性があります。
- ''UIの親切さ'': 商品コードを弾くだけでなく、正常な場合はそのまま「商品名」を自動表示させるロジックを組み合わせるとユーザーに喜ばれます。