【実務・中級編】CurrentDb.Executeの「dbSeeChanges」が必須となるSQL Server連携時の注意点 – Access VBA解析バイブル

スポンサーリンク

Access VBAを掌握する極限の知見:SQL Server連携における `CurrentDb.Execute` と `dbSeeChanges` の鉄則

開発現場でよく見聞きする光景がある。「ローカルのテスト環境では完璧に動いていたINSERT/UPDATEクエリが、本番のSQL Server(ODBC接続)に切り替えた途端、原因不明の実行時エラーで沈黙する」という現象だ。

犯人は大抵決まっている。`dbSeeChanges` の不在だ。

今回は、Accessをフロントエンド、SQL Serverをバックエンドに据えたクライアント/サーバー(C/S)アーキテクトの領域において、データ整合性とパフォーマンスの極限を担保するための「正しいSQL実行設計」を伝授する。

1. なぜ `CurrentDb.Execute` なのか?(DoCmd.RunSQLとの決別)

まず大前提として、VBAからアクションクエリを実行する際、`DoCmd.RunSQL` を使っていないだろうか? もし使っているなら、今すぐコードから抹消してほしい。

`DoCmd` はAccessのUI(マクロの代替)を操作するためのオブジェクトであり、以下のような致命的なデメリットを抱えている。

  • UIへの依存: 実行のたびに「〇件のレコードを追加します」といった警告ダイアログがポップアップし、VBAの処理をブロックする(`SetWarnings False` で無理やり消すのは悪しきハックに過ぎない)。
  • トランザクション管理の脆弱性: エラーハンドリングと連動したアトミックな処理(ロールバック)の制御が極めて困難。
  • パフォーマンスのオーバーヘッド: 画面描画やUIイベントを巻き込むため、一括処理において圧倒的に遅い。

対して、`CurrentDb.Execute`(厳密には DAO の Database オブジェクトの `Execute` メソッド)は、純粋なデータベースエンジン層でクエリを叩く。UIを介さず、圧倒的な速度で安全にSQLを流し込むためのプロフェッショナルな選択肢だ。

2. 悪夢の「更新の競合」と `dbSeeChanges` の正体

では、本題に入ろう。
AccessからODBC経由でSQL Server(またはADP)のテーブルを操作する際、DAOはデフォルトで楽観的同時実行制御(Optimistic Concurrency Control)を行おうとする。

SQL Server側で以下のような機能が有効になっている場合がある:

  • IDENTITY列(自動採番)
  • タイムスタンプ列 / rowversion列
  • トリガーによるデータの自動書き換え

これらが絡むテーブルに対して、`CurrentDb.Execute` でレコードの追加(INSERT)や更新(UPDATE)を行った瞬間、Accessは「自分が書き込んだ直後の値と、サーバー上の最新値が一致しているか?」を検証する。この時、サーバー側で自動採番されたIDやトリガーによる変更をAccessが即座に把握できないと、Accessエンジンは「他のユーザーによってデータが改ざんされた(更新の競合)」と誤認し、無慈悲に実行時エラーを発生させるのだ。

> エラー番号 3197 / 3622
> 「データが他のユーザーによって変更されています。…」

この致命的な誤検知を防ぎ、「サーバー側で何が起なかろうと、私の発行したクエリを強制的に通せ(あるいは最新の変更を前提とせよ)」とDAOに命令するフラグこそが、`dbSeeChanges` である。

3. 実務で絶対にバグらせないための堅牢な実装パターン

「じゃあ、すべての `Execute` に `dbSeeChanges` をつければいいのか?」
答えは半分YESだが、保守性の観点から設計を洗練させる必要がある。

特にSQL Server連携を行うプロジェクトでは、エラーハンドリングとトランザクション、そしてオプションの付与をセットにした「ラッパー関数」の発想を持つべきだ。

以下に、現場のプロダクション環境でそのまま使える堅牢なコードを示す。

プロダクションコード例:安全なSQL実行モジュール

Option Compare Database
Option Explicit

‘ ==============================================================================
‘ 担当者必携:SQL Server連携を考慮した安全なクエリ実行ラッパー
‘ ==============================================================================
Public Sub SafeExecuteSql(ByVal sqlText As String, Optional ByVal useSeeChanges As Boolean = True)
Dim db As DAO.Database
Dim options As Long

‘ 実行時のパフォーマンス最適化とエラーバウンダリの設定
On Error GoTo ErrorHandler

Set db = CurrentDb

‘ dbSeeChangesが必要なケース(SQL Server連携など)に対応するオプション構築
‘ ※トランザクションと併用する場合の定数bitwise演算
options = dbFailOnError

If useSeeChanges Then
‘ dbSeeChanges (512) を付与することで、SQL Serverの自動採番・競合エラーを回避
options = options + dbSeeChanges
End If

‘ トランザクションの開始(データ整合性の担保)
db.BeginTrans

‘ クエリの実行
db.Execute sqlText, options

‘ コミット
db.CommitTrans

Exit Sub

ErrorHandler:
‘ 異常系:ロールバックの実行
If Not db Is Nothing Then
On Error Resume Next
db.Rollback
On Error GoTo 0
End If

‘ 実務ではここでログ出力基盤(Windowsイベントログや専用エラーテーブル)を呼び出す
MsgBox “データベースの更新に失敗しました。” & vbCrLf & _
“エラー番号: ” & Err.Number & vbCrLf & _
“詳細: ” & Err.Description, vbCritical, “致命的なエラー”

‘ 呼び出し元へエラーを伝播させる
Err.Raise Err.Number, “SafeExecuteSql”, Err.Description
End Sub

この設計が優れている理由

1. `dbFailOnError` との組み合わせ:
単なる `db.Execute` は、途中でエラーが発生しても部分的に処理が進んでしまうことがある(サイレント失敗の温床)。`dbFailOnError` を付与することで、1件でもエラーがあれば即座に例外を発生させ、トランザクションのロールバックに繋げられる。
2. `dbSeeChanges` の動的制御:
ローカルのMS Jet/ACEデータベース(純粋なAccessファイル内)に対して `dbSeeChanges` を指定すると、逆に構文エラーや不要なオーバーヘッドになることがある。そのため、対象がSQL Server等の外部ODBC接続であることを意識した設計(引数での切り替え)がプロフェッショナルには求められる。
3. トランザクションの厳格な管理:
C/S環境ではネットワークの瞬断や排他制御の競合が常に隣り合わせだ。`BeginTrans` と `CommitTrans` / `Rollback` をセットにした構造化例外処理を徹底することで、幽霊レコード(中途半端なデータ)の発生を完全防衛する。

4. チーフアーキテクトからの最終提言

Accessは「手軽なプロトタイプツール」として語られがちだが、バックエンドにSQL Serverを据えた瞬間に立派な「エンタープライズ・クライアント/サーバーシステム」のフロントエンドへと変貌する。

その事実を忘れて、「昔覚えた適当なマクロや `DoCmd.RunSQL`」をそのまま持ち込む開発者は、遅かれ早かれ本番環境の「更新の競合」という名のモンスターに足元をすくわれることになる。

コードを書くときは常に自問してほしい。
「このINSERT文は、SQL Serverの自動採番(IDENTITY)を直撃していないか?」
「その時、DAOはサーバーの変更を検知してパニックを起こさないか?」

答えがYESであるならば、迷わず `CurrentDb.Execute SQL, dbFailOnError + dbSeeChanges` を選択せよ。その一手間が、あなたの作る業務システムを「止まらない堅牢なインフラ」へと昇華させる。

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