【入門編】ADOのBatchUpdateによる一括更新と、競合発生時の手動解決フロー – Access VBA解析バイブル

スポンサーリンク
スポンサーリンク

Access VBAの深淵へようこそ:ADO BatchUpdateで極める「一括更新」と「競合解決」の極意

「マクロの記録」から一歩踏み出し、コードを自分で書き始めたあなた。
素晴らしい一歩です。でも、もしあなたが「データを更新するコード」をただの `UPDATE` 文の繰り返しや、レコードを一つずつループで回して書き換える処理だけで済ませているとしたら……少しだけ立ち止まって、私の話を聞いてください。

実務、特に多人数で共有するデータベース開発において、最も恐ろしいのはエラーではありません。「誰かが更新したデータが、知らないうちに上書きされて消えてしまうこと」。これこそが、システムエンジニアが最も避けるべき「データの消失」です。

今日は、Access VBAの限界を突破し、プロフェッショナルなデータ操作を実現するための武器、ADO(ActiveX Data Objects)の `BatchUpdate` と、その最大の難所である 「競合(コンフリクト)の解決」 について、魂を込めて解説します。

1. なぜDAOではなく、ADOなのか? アーキテクチャの視点

Access開発において、DAO(Data Access Objects)は「地元の名士」のような存在です。Accessのテーブルを直接操作するなら、DAOは非常に高速で安定しています。

しかし、あなたがもし「SQL Serverなどの外部サーバーへ接続したい」「ネットワーク越しに大量のデータを効率よくやり取りしたい」と願うなら、ADO(ActiveX Data Objects) という「世界を知る旅人」が必要になります。

DAOとADOの決定的な違い

特徴 DAO (Data Access Objects) ADO (ActiveX Data Objects)
主な用途 Access内のローカルテーブル操作 外部DB(SQL Server等)との連携
動作モデル 常にサーバー(ファイル)と接続 「切断型(Disconnected)」 が可能
更新の考え方 レコードを一つずつ開いて更新 データをメモリに溜めて一括送信

ここで重要なのが、ADOの真骨頂である「切断型レコードセット(Disconnected Recordset)」という概念です。これは、一度データをメモリ上に丸ごとコピーして、データベースとの接続を一旦切ってしまう手法です。これにより、ネットワーク負荷を劇的に減らし、ユーザーが入力している間、データベースの接続を占有せずに済みます。

2. BatchUpdate:爆速の裏に潜む「楽観的排他制御」

大量のデータを更新する際、一つずつ `Update` を発行するのは非効率です。そこで登場するのが `BatchUpdate` メソッドです。

これは、メモリ上で行った複数の変更(追加・修正・削除)を、「最後の一撃」としてまとめてサーバーへ送り込む手法です。非常に高速ですが、ここで一つの問題に直面します。

競合(コンフリクト)の恐怖

あなたがデータを編集している間に、他のユーザーが同じレコードを更新してしまったらどうなるでしょうか?

ADOが採用しているのは「楽観的排他制御(Optimistic Concurrency)」という考え方です。「誰も更新しないだろう」と楽観的に構え、更新の瞬間(`UpdateBatch` を実行した瞬間)にだけ、「自分が読み込んだ時と、今のサーバーの状態は同じか?」をチェックするのです。

もし、誰かが先に書き換えていたら…… `UpdateBatch` は「エラー」を吐いて止まります。これが、私たちが解決すべき「競合」です。

3. 実装の極意:競合を検知し、ユーザーに判断を仰ぐフロー

プロのコードは、エラーで止まることはありません。エラーを「予測」し、ユーザーに「選択肢」を与えます。

競合が発生した際、理想的なフローは以下の通りです。
1. `UpdateBatch` を実行。
2. 競合エラー(エラー番号等)をキャッチ。
3. サーバーの「最新データ」と、自分の「編集中のデータ」を比較。
4. ユーザーに「上書きするか?」「サーバーのデータを取り込むか?」を問いかける。

それでは、具体的なコードを見ていきましょう。

【実践】BatchUpdateによる一括更新と競合解決の実装例

Option Explicit

' ADOの参照設定が必要です: "Microsoft ActiveX Data Objects x.x Library"
Public Sub ExecuteSmartBatchUpdate()
    Dim cn As ADODB.Connection
    Dim rs As ADODB.Recordset
    Dim strConn As String
    
    ' 接続文字列(環境に合わせて変更してください)
    strConn = "Provider=Microsoft.ACE.OLEDB.12.0;Data Source=C:\YourPath\YourDatabase.accdb;"
    
    On Error GoTo ErrorHandler

    ' 1. 接続の確立
    Set cn = New ADODB.Connection
    cn.Open strConn

    ' 2. レコードセットの作成(ここが重要!)
    Set rs = New ADODB.Recordset
    
    ' 切断型レコードセットの設定
    rs.CursorLocation = adUseClient ' クライアント側でカーソルを管理
    rs.Open "SELECT ID, UserName, Salary FROM Employees", cn, _
            adOpenStatic, adLockOptimistic, adUseClient
            
    ' 3. 【シミュレーション】メモリ上でのデータ変更
    ' 本来はフォーム上の操作などを通じて、ここで行われます
    If Not rs.EOF Then
        rs.Fields("UserName").Value = "新しい名前"
        rs.Fields("Salary").Value = 500000
    End If
    
    ' 4. 一括更新の実行
    Debug.Print "一括更新を試行中..."
    rs.UpdateBatch
    
    MsgBox "更新が正常に完了しました!", vbInformation
    
CleanUp:
    If Not rs.EOF Then rs.Close
    If Not cn Is Nothing Then cn.Close
    Set rs = Nothing
    Set cn = Nothing
    Exit Sub

ErrorHandler:
    ' 5. 競合エラーの検知
    ' ADOの競合エラー(通常はエラー番号に関わらず、更新失敗として検知)
    ' 実際の実装では、エラー内容を精査して判断します
    If Err.Number = 3022 Or Err.Number = -2147217887 Then ' 環境によりエラー番号は変動
        Debug.Print "競合を検知しました!"
        Call HandleConflict(rs)
    Else
        MsgBox "予期せぬエラーが発生しました: " & Err.Description, vbCritical
        Resume CleanUp
    End If
End Sub

''' <summary>
''' 競合発生時の手動解決フロー
''' </summary>
Private Sub HandleConflict(ByRef rs As ADODB.Recordset)
    Dim res As VbMsgBoxResult
    
    ' ここでは簡略化していますが、本来はサーバーの最新データを
    ' 別途取得して、ユーザーに「現在の値」と「変更後の値」を比較提示するのがベストです。
    
    res = MsgBox("他のユーザーがこのデータを更新しています。" & vbCrLf & _
                 "あなたの変更を強制的に上書きしますか?" & vbCrLf & _
                 "(「いいえ」を押すと、サーバーの最新データで上書きされます)", _
                 vbYesNoCancel + vbExclamation, "競合解決")
                 
    Select Case res
        Case vbYes
            ' 強制上書きのロジック
            ' 注意: 本来はエラーをリセットして、再度UpdateBatchをかけるか、
            ' サーバーの値を読み直してから再度書き込む手順が必要です。
            Debug.Print "強制上書きを試みます..."
            ' (実装の詳細はプロジェクトのポリシーに依存します)
            
        Case vbNo
            ' サーバーのデータに合わせる(自分の変更を捨てる)
            Debug.Print "サーバーの最新データに同期します。"
            rs.Requery ' レコードセットを最新状態にリフレッシュ
            MsgBox "最新の状態に更新されました。再度編集してください。", vbInformation
            
        Case vbCancel
            ' 何もしない
            Debug.Print "ユーザーによって更新がキャンセルされました。"
    End Select
    
    ' 処理を終了させるためにクリーンアップへ誘導
    Err.Clear
    ' ここでは簡易的に終了させますが、実務では再試行ループを組むこともあります
End Sub

4. 伝説のアーキテクトからのアドバイス

このコードを見て、「少し難しいな」と感じたかもしれません。しかし、この「エラーを捕まえて、ユーザーに判断を仰ぐ」というプロセスこそが、「動くプログラム」と「信頼されるシステム」の境界線なのです。

最後に、この実装を行う上での「重み」を伝えておきます。

1. `CursorLocation = adUseClient` を忘れないこと
これを忘れると、`BatchUpdate` を使うための「切断型」の動きができず、パフォーマンスの恩恵を受けられません。
2. ユーザー体験(UX)を設計すること
競合が起きたとき、「エラーです」とだけ表示するのは最悪の設計です。ユーザーは「自分の仕事が消えた!」とパニックになります。「誰かが変えたみたいですよ、どうしますか?」と優しくガイドする。これが一流のエンジニアの仕事です。
3. トランザクションの意識
`BatchUpdate` は内部的に一つのトランザクションとして扱われます。途中で失敗すれば、中途半端なデータが書き込まれることはありません。この「全か無か(All or Nothing)」の性質を理解して使いこなしてください。

ここをクリアすれば、あなたのAccess VBAは、単なるツールの域を超え、エンタープライズ級の堅牢なシステムへと進化します。

さあ、自信を持ってコードを書いてください。応援していますよ!

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