<p><!--META
{
"title": "VBA Access ADO Recordsetの効率的な更新手法とパフォーマンス最適化",
"primary_category": "VBA",
"secondary_categories": ["Microsoft Access", "Microsoft Excel", "ADO"],
"tags": ["VBA", "ADO", "Recordset", "UpdateBatch", "パフォーマンス最適化", "データベース更新", "排他制御", "トランザクション"],
"summary": "VBA ADO Recordsetの効率的な更新とパフォーマンス最適化を、具体的なコード例とWin32 API活用で解説。",
"mermaid": true,
"verify_level": "L0",
"tweet_hint": {"text":"VBA ADO Recordsetの更新を劇的に高速化!バッチ更新、カーソル設定、排他制御など、Access/Excelでの実践的なパフォーマンス最適化テクニックを徹底解説。
#VBA #ADO #Access #Excel","hashtags":["#VBA","#ADO","#Access","#Excel"]},
"link_hints": ["https://learn.microsoft.com/"]
}
-->
本記事は<strong>Geminiの出力をプロンプト工学で整理した業務ドラフト(未検証)</strong>です。</p>
<h1 class="wp-block-heading">VBA Access ADO Recordsetの効率的な更新手法とパフォーマンス最適化</h1>
<h2 class="wp-block-heading">背景と要件</h2>
<p>VBA (Visual Basic for Applications) からのデータベース操作は、Microsoft AccessアプリケーションやMicrosoft Excelからの外部データ連携において不可欠な要素です。特に大量のデータを更新する際、処理速度は業務効率に直結します。</p>
<p>ADO (ActiveX Data Objects) は、様々なデータソースへの柔軟なアクセスを提供する強力なフレームワークですが、その使い方によってはパフォーマンスに大きな差が生じます。本記事は、<strong>JST 2024年7月28日</strong>時点でのVBA ADOのベストプラクティスに基づき、Recordsetを用いたデータ更新において、効率性と堅牢性を両立させるための具体的な手法、パフォーマンス最適化、および実務で役立つコード例を提供します。
、外部ライブラリに依存せず、VBA標準機能と必要に応じてWin32 APIのみを使用することを前提とします。</p>
<h2 class="wp-block-heading">設計</h2>
<p>ADO Recordsetを利用したデータ更新処理は、一般的に以下のステップで構成されます。効率的な更新を実現するためには、特にRecordsetのオープン時におけるカーソル位置(<code>CursorLocation</code>)とロックタイプ(<code>LockType</code>)の設定が重要になります。</p>
<ol class="wp-block-list">
<li><p><strong>接続確立</strong>: データベースへの<code>ADODB.Connection</code>オブジェクトを確立します。</p></li>
<li><p><strong>Recordset取得</strong>: 更新対象のデータを<code>ADODB.Recordset</code>として開きます。この際、<code>CursorLocation</code>と<code>LockType</code>を適切に設定します。</p></li>
<li><p><strong>データ更新</strong>: <code>Recordset</code>内のレコードを移動し、<code>AddNew</code>/<code>Edit</code>/<code>Delete</code>メソッドで変更を適用します。バッチ更新の場合は、変更はメモリ上に一時的に保持されます。</p></li>
<li><p><strong>変更の永続化</strong>: 個別更新の場合は<code>Update</code>メソッド、バッチ更新の場合は<code>UpdateBatch</code>メソッドで、変更をデータベースに書き込み永続化します。</p></li>
<li><p><strong>トランザクション管理</strong>: 複数の更新操作をアトミックに扱うため、<code>BeginTrans</code>、<code>CommitTrans</code>、<code>RollbackTrans</code>でトランザクションを管理します。</p></li>
<li><p><strong>切断</strong>: <code>Connection</code>および<code>Recordset</code>オブジェクトを適切に閉じ、リソースを解放します。</p></li>
</ol>
<h3 class="wp-block-heading">データ更新処理フロー</h3>
<p>ADO Recordsetを用いたバッチ更新の一般的な処理フローを以下に示します。</p>
<div class="wp-block-merpress-mermaidjs diagram-source-mermaid"><pre class="mermaid">
graph TD
A["開始"] --> B{"データベース接続の確立"};
B --> C["Recordsetの生成とオープン"];
C --> D{"カーソル位置設定|Recordset.CursorLocation = adUseClient"};
D --> E{"ロックタイプ設定|Recordset.LockType = adLockBatchOptimistic"};
E --> F["レコードの追加/編集/削除"];
F -- レコードごとに処理 --> F;
F --> G{"変更の一括書き込み|Recordset.UpdateBatch"};
G -- 成功 --> H{"トランザクションコミット|Connection.CommitTrans"};
G -- 失敗 --> K{"トランザクションロールバック|Connection.RollbackTrans"};
H --> I["RecordsetとConnectionを閉じる"];
K --> I;
I --> J["終了"];
</pre></div>
<h2 class="wp-block-heading">実装</h2>
<p>ここでは、Accessのローカルデータベース(<code>.accdb</code>)のテーブルデータを、VBAから更新する例を示します。パフォーマンス比較のため、一般的な個別更新とバッチ更新の両方を実装します。</p>
<h3 class="wp-block-heading">共通設定</h3>
<p>以下のコードは、パフォーマンス計測用のWin32 API宣言と、データベースパスおよびテーブル名の定数を含みます。</p>
<pre data-enlighter-language="generic">' 参照設定:
' VBAエディタで「ツール」->「参照設定」を開き、
' 「Microsoft ActiveX Data Objects x.x Library」にチェックを入れてください。
' (x.x は環境により異なるバージョン、例: 6.1)
' Win32 APIによる高精度タイマー (パフォーマンス計測用)
' PtrSafeは64bit環境で必須。32bit環境ではDeclare Functionで良いが、PtrSafeで統一推奨。
Private Declare PtrSafe Function QueryPerformanceCounter Lib "kernel32" (lpPerformanceCount As Currency) As Long
Private Declare PtrSafe Function QueryPerformanceFrequency Lib "kernel32" (lpFrequency As Currency) As Long
Private Const DB_PATH As String = "C:\Users\Public\TestDB.accdb" ' 実際のデータベースパスに修正
Private Const TABLE_NAME As String = "T_Products" ' 更新対象テーブル名
</pre>
<h3 class="wp-block-heading">コード例1: 個別更新(非効率な例)</h3>
<p>この方法は、Recordsetのレコードを1つずつ更新し、その都度<code>Update</code>メソッドを呼び出すものです。データベースへのI/Oが頻繁に発生するため、大量データでは性能問題が発生しやすいです。</p>
<pre data-enlighter-language="generic">Sub UpdateRecords_Individual()
Dim cn As ADODB.Connection
Dim rs As ADODB.Recordset
Dim strSQL As String
Dim lStartTime As Currency, lEndTime As Currency, lFrequency As Currency
Dim i As Long
'--- パフォーマンス計測開始 ---
QueryPerformanceFrequency lFrequency
QueryPerformanceCounter lStartTime
Set cn = New ADODB.Connection
cn.ConnectionString = "Provider=Microsoft.ACE.OLEDB.12.0;Data Source=" & DB_PATH & ";"
cn.Open
'--- トランザクション開始 ---
cn.BeginTrans
' 例として最初の1000件を対象に選択。実際はWHERE句で更新対象を絞る。
strSQL = "SELECT ProductID, ProductName, Price, StockQuantity FROM " & TABLE_NAME & " WHERE ProductID <= 1000;"
Set rs = New ADODB.Recordset
' 個別更新ではadLockOptimisticまたはadLockPessimisticが一般的
rs.Open strSQL, cn, adOpenKeyset, adLockOptimistic, adCmdText
' Excelの場合、画面更新や自動計算を一時停止してパフォーマンスを向上させる
' Application.ScreenUpdating = False
' Application.Calculation = xlCalculationManual
If Not rs.EOF Then
rs.MoveFirst
Do While Not rs.EOF
rs.Edit ' レコードの編集を開始
rs!ProductName = "Updated_" & rs!ProductID ' 商品名を更新
rs!Price = rs!Price * 1.05 ' 価格を5%上昇
rs!StockQuantity = rs!StockQuantity + 10 ' 在庫を10増やす
rs.Update ' レコードを更新し、データベースに即座に書き込む (I/O発生)
rs.MoveNext
i = i + 1
If i Mod 100 = 0 Then Debug.Print "個別更新: " & i & "件処理済み..."
Loop
End If
'--- トランザクションコミット ---
cn.CommitTrans
' Excelの場合、画面更新や自動計算を再開
' Application.ScreenUpdating = True
' Application.Calculation = xlCalculationAutomatic
'--- パフォーマンス計測終了 ---
QueryPerformanceCounter lEndTime
Debug.Print "個別更新処理時間: " & Format$((lEndTime - lStartTime) / lFrequency, "0.000") & " 秒 (" & i & "件)"
'--- オブジェクトのクリーンアップ ---
If Not rs Is Nothing Then If rs.State = adStateOpen Then rs.Close
Set rs = Nothing
If Not cn Is Nothing Then If cn.State = adStateOpen Then cn.Close
Set cn = Nothing
End Sub
</pre>
<p><strong>コード説明:</strong></p>
<ul class="wp-block-list">
<li><p><strong>前提条件</strong>: <code>DB_PATH</code>と<code>TABLE_NAME</code>を実際の環境に合わせて設定し、<code>T_Products</code>テーブルに<code>ProductID</code>(主キー)、<code>ProductName</code>、<code>Price</code>、<code>StockQuantity</code>フィールドが存在すると仮定します。</p></li>
<li><p><strong>入出力</strong>: 指定されたSQLで選択されたレコードの<code>ProductName</code>、<code>Price</code>、<code>StockQuantity</code>フィールドを更新します。</p></li>
<li><p><strong>計算量</strong>: N個のレコードを更新する場合、レコードセットの移動(O(N))とデータベースへの個別書き込み(N回のI/O)が発生するため、全体としてO(N)の計算量となります。データベースへのI/Oがボトルネックとなりやすいです。</p></li>
<li><p><strong>メモリ条件</strong>: <code>adOpenKeyset</code>カーソルはキーセット(レコードの一意な識別子)をメモリに保持しますが、実際のデータは必要に応じてフェッチされるため、非常に大きなデータを一度にロードすることはありません。</p></li>
</ul>
<h3 class="wp-block-heading">コード例2: バッチ更新(効率的な例)</h3>
<p><code>CursorLocation</code>を<code>adUseClient</code>、<code>LockType</code>を<code>adLockBatchOptimistic</code>に設定することで、レコードセットをクライアント側のメモリ上にロードし、全ての変更をまとめてデータベースに書き込むことができます。これにより、データベースへのI/O回数を大幅に削減し、パフォーマンスを劇的に向上させます。</p>
<pre data-enlighter-language="generic">Sub UpdateRecords_Batch()
Dim cn As ADODB.Connection
Dim rs As ADODB.Recordset
Dim strSQL As String
Dim lStartTime As Currency, lEndTime As Currency As Currency, lFrequency As Currency
Dim i As Long
Dim errObj As ADODB.Error ' エラーオブジェクトを定義
'--- パフォーマンス計測開始 ---
QueryPerformanceFrequency lFrequency
QueryPerformanceCounter lStartTime
Set cn = New ADODB.Connection
cn.ConnectionString = "Provider=Microsoft.ACE.OLEDB.12.0;Data Source=" & DB_PATH & ";"
cn.Open
'--- トランザクション開始 (バッチ更新はConnection側で管理) ---
cn.BeginTrans
' 例として最初の1000件を対象に選択。実際はWHERE句で更新対象を絞る。
strSQL = "SELECT ProductID, ProductName, Price, StockQuantity FROM " & TABLE_NAME & " WHERE ProductID <= 1000;"
Set rs = New ADODB.Recordset
' クライアントサイドカーソルとバッチロックタイプを設定
rs.CursorLocation = adUseClient ' クライアントサイドカーソルを使用
' adOpenStaticでメモリにデータをロードし、adLockBatchOptimisticでバッチ更新を許可
rs.Open strSQL, cn, adOpenStatic, adLockBatchOptimistic, adCmdText
' Excelの場合、画面更新や自動計算を一時停止してパフォーマンスを向上させる
' Application.ScreenUpdating = False
' Application.Calculation = xlCalculationManual
If Not rs.EOF Then
rs.MoveFirst
Do While Not rs.EOF
rs.Edit ' レコードの編集を開始
rs!ProductName = "Updated_Batch_" & rs!ProductID ' 商品名を更新
rs!Price = rs!Price * 1.10 ' 価格を10%上昇
rs!StockQuantity = rs!StockQuantity + 20 ' 在庫を20増やす
' ここではrs.Updateは呼び出さない。変更はメモリに保持される
rs.MoveNext
i = i + 1
If i Mod 100 = 0 Then Debug.Print "バッチ更新: " & i & "件処理済み..."
Loop
End If
'--- 全ての変更をデータベースに一括書き込み ---
On Error GoTo ErrorHandler_Batch ' エラー発生時はErrorHandler_Batchへジャンプ
rs.UpdateBatch adAffectAll ' adAffectAllで全ての変更を適用 (データベースへのI/Oはここで一回発生)
cn.CommitTrans ' トランザクションコミット
' Excelの場合、画面更新や自動計算を再開
' Application.ScreenUpdating = True
' Application.Calculation = xlCalculationAutomatic
'--- パフォーマンス計測終了 ---
QueryPerformanceCounter lEndTime
Debug.Print "バッチ更新処理時間: " & Format$((lEndTime - lStartTime) / lFrequency, "0.000") & " 秒 (" & i & "件)"
Exit_Sub:
'--- オブジェクトのクリーンアップ ---
If Not rs Is Nothing Then If rs.State = adStateOpen Then rs.Close
Set rs = Nothing
If Not cn Is Nothing Then If cn.State = adStateOpen Then cn.Close
Set cn = Nothing
Exit Sub
ErrorHandler_Batch:
If Not cn Is Nothing Then
If cn.State = adStateOpen Then
cn.RollbackTrans ' エラー時はトランザクションをロールバック
Debug.Print "エラー発生。トランザクションをロールバックしました。"
For Each errObj In cn.Errors ' ADO Errorsコレクションから詳細情報を取得
Debug.Print "エラーコード: " & errObj.NativeError & ", ソース: " & errObj.Source & ", 説明: " & errObj.Description
Next errObj
End If
End If
Resume Exit_Sub ' エラーハンドリング後、クリーンアップへ
End Sub
</pre>
<p><strong>コード説明:</strong></p>
<ul class="wp-block-list">
<li><p><strong>前提条件</strong>: 上記の個別更新と同じです。</p></li>
<li><p><strong>入出力</strong>: 指定されたSQLで選択されたレコードの<code>ProductName</code>、<code>Price</code>、<code>StockQuantity</code>フィールドを更新します。</p></li>
<li><p><strong>計算量</strong>: レコードセットの移動はO(N)ですが、データベースへの書き込みは<code>UpdateBatch</code>の1回に集約されるため、I/Oオーバーヘッドが大幅に削減されます。これにより、実質的な処理時間はメモリ内のデータ操作と一括書き込みの時間に依存し、個別I/Oの多い個別更新よりも高速になります。</p></li>
<li><p><strong>メモリ条件</strong>: <code>adUseClient</code>カーソルは、対象となるレコードセット全体をクライアント側のメモリにロードします。非常に大きなデータセット(例: 数十万件以上、数GB)を扱う場合は、クライアントPCのメモリ消費に注意が必要です。</p></li>
</ul>
<h2 class="wp-block-heading">性能チューニング</h2>
<p>ADO Recordsetの更新パフォーマンスを最適化するための主要な手法を以下に示します。</p>
<ul class="wp-block-list">
<li><p><strong>バッチ更新の活用 (<code>UpdateBatch</code>):</strong></p>
<ul>
<li><p><strong>最も効果的な最適化手法</strong>。データベースへの書き込み回数を減らすことで、特にネットワーク経由でのデータベースアクセスや、ディスクI/Oのオーバーヘッドを劇的に削減します。</p></li>
<li><p><strong>比較例(10,000件の更新、目安):</strong></p>
<ul>
<li><p>個別更新 (<code>rs.Update</code>を10,000回): 約 5秒〜20秒以上 (DBサーバーの負荷、ネットワーク速度、ディスク性能に強く依存)</p></li>
<li><p>バッチ更新 (<code>rs.UpdateBatch</code>を1回): 約 0.1秒〜1秒 (大幅な改善が見込まれます。環境によっては数十倍の速度差が出ることもあります。)</p></li>
</ul></li>
</ul></li>
<li><p><strong>カーソル位置 (<code>CursorLocation</code>):</strong></p>
<ul>
<li><p><code>adUseClient</code> (クライアントサイドカーソル): クライアント側のメモリにレコードセット全体をロードします。サーバーへの負荷が軽減され、オフライン作業やバッチ更新(<code>adLockBatchOptimistic</code>と組み合わせて)が可能になります。ただし、大量データではクライアントのメモリを消費します。</p></li>
<li><p><code>adUseServer</code> (サーバーサイドカーソル): サーバー側のカーソルを使用します。データは逐次フェッチされるため、クライアントのメモリ消費は少ないですが、サーバーとの通信頻度が増加します。バッチ更新には不向きです。</p></li>
</ul></li>
<li><p><strong>ロックタイプ (<code>LockType</code>):</strong></p>
<ul>
<li><p><code>adLockBatchOptimistic</code>: バッチ更新を行うために必須の排他制御。レコードが変更された行のみを更新対象とします。</p></li>
<li><p><code>adLockOptimistic</code>: レコードの更新時のみロックをかけます。参照中はロックしません。多くのWebアプリケーションなどで採用される一般的な方式です。</p></li>
<li><p><code>adLockPessimistic</code>: レコードを編集のために開いた瞬間にロックをかけ、更新が完了するまで解放しません。競合は防げますが、他のユーザーのアクセスを妨げるため、並行性が低下しパフォーマンスに影響します。</p></li>
</ul></li>
<li><p><strong>トランザクションの利用:</strong></p>
<ul>
<li><code>Connection.BeginTrans</code>, <code>CommitTrans</code>, <code>RollbackTrans</code> を使用することで、複数の更新操作をアトミックに実行できます。これにより、処理途中でエラーが発生した場合でも、データベースの一貫性を保ちながら元に戻すことが可能です。パフォーマンス的には、複数の更新をまとめてコミットすることで、トランザクションログへの書き込みを効率化し、オーバーヘッドを削減する効果も期待できます。</li>
</ul></li>
<li><p><strong>不要なUI更新の抑制(Excel/Access共通、特にExcelで顕著):</strong></p>
<ul>
<li><p><code>Application.ScreenUpdating = False</code>: 処理中に画面描画を停止し、処理速度を向上させます。処理終了後に<code>True</code>に戻すことで、一気に結果が表示されます。</p></li>
<li><p><code>Application.Calculation = xlCalculationManual</code> (Excelの場合): 計算モードを手動に設定し、セル値の変更による自動再計算を停止します。処理終了後に<code>xlCalculationAutomatic</code>に戻します。</p></li>
<li><p>これらの設定はExcelに特有の最適化ですが、Accessの場合もフォームのリアルタイム表示を抑制する<code>DoCmd.Echo False</code>などのUI操作の抑制は有効です。ADOによる直接的なDB操作自体はUIとは分離されているため、これらの影響はDB側のパフォーマンスに比べると小さい場合が多いです。</p></li>
</ul></li>
</ul>
<h2 class="wp-block-heading">検証</h2>
<p>上記コード例には、<code>QueryPerformanceCounter</code>と<code>QueryPerformanceFrequency</code>を用いたWin32 APIベースの高精度タイマーを組み込んでいます。これを実行することで、個別更新とバッチ更新それぞれの処理時間をミリ秒単位で比較し、バッチ更新の性能向上効果を数値で確認できます。</p>
<h3 class="wp-block-heading">実行手順</h3>
<ol class="wp-block-list">
<li><p><strong>データベースの準備:</strong></p>
<ul>
<li><p>Microsoft Accessを起動し、新しい空のデータベースを作成します。例として、<code>C:\Users\Public\TestDB.accdb</code>に保存します。</p></li>
<li><p>作成したデータベース内で「作成」タブから「テーブル」を選択し、<code>T_Products</code>という名前のテーブルを作成します。</p></li>
<li><p>テーブルのフィールドは以下のように設定してください:</p>
<ul>
<li><p><code>ProductID</code>: データ型「数値」、フィールドサイズ「長整数型」、主キーに設定。</p></li>
<li><p><code>ProductName</code>: データ型「短いテキスト」、フィールドサイズ「255」。</p></li>
<li><p><code>Price</code>: データ型「通貨型」。</p></li>
<li><p><code>StockQuantity</code>: データ型「数値」、フィールドサイズ「長整数型」。</p></li>
</ul></li>
<li><p><code>T_Products</code>テーブルに数千〜数万件のテストデータを挿入します(例: <code>ProductID</code>を1から順に、他のフィールドも適当な値で埋めます。VBAで初期データ挿入プロシージャを作成しても良いでしょう)。</p></li>
</ul></li>
<li><p><strong>VBAプロジェクトの準備:</strong></p>
<ul>
<li><p><strong>Accessの場合</strong>: 任意のモジュール(例: <code>標準モジュール1</code>)を開き、上記「共通設定」と「コード例1: 個別更新」、「コード例2: バッチ更新」の各プロシージャを貼り付けます。</p></li>
<li><p><strong>Excelの場合</strong>: 新しいExcelブックを開き、VBAエディタ(Alt + F11キー)で「挿入」->「標準モジュール」を選択し、同様にコードを貼り付けます。</p></li>
</ul></li>
<li><p><strong>参照設定の確認:</strong></p>
<ul>
<li>VBAエディタで「ツール」->「参照設定」を開き、「Microsoft ActiveX Data Objects x.x Library」にチェックが入っていることを確認します。<code>x.x</code>はバージョンにより異なりますが、最新版(例: <code>6.1</code>)を選択してください。</li>
</ul></li>
<li><p><strong>コードの実行:</strong></p>
<ul>
<li><p><code>Private Const DB_PATH As String</code> の値を、作成した<code>TestDB.accdb</code>への正しいパスに修正します。</p></li>
<li><p>VBAエディタで<code>UpdateRecords_Individual</code>プロシージャを選択し、F5キーを押して実行します。実行後、イミディエイトウィンドウ(Ctrl + Gキーで表示)で処理時間を確認します。</p></li>
<li><p>同様に、<code>UpdateRecords_Batch</code>プロシージャを選択し、F5キーを押して実行します。イミディエイトウィンドウで処理時間を確認します。</p></li>
<li><p>両者の処理時間を比較し、バッチ更新の効果を検証します。</p></li>
</ul></li>
</ol>
<h2 class="wp-block-heading">運用</h2>
<ul class="wp-block-list">
<li><p><strong>エラーハンドリング:</strong></p>
<ul>
<li><code>On Error GoTo</code>ステートメントを使用して、ADODBオブジェクトで発生する可能性のあるエラーを捕捉し、適切に処理します。特にデータベース関連のエラーは、接続切れ、権限不足、データ型不一致など多岐にわたります。<code>Connection.Errors</code>コレクションを検査することで、詳細なエラー情報を取得し、ログ記録やユーザーへのフィードバックに活用できます。</li>
</ul></li>
<li><p><strong>リソース管理:</strong></p>
<ul>
<li><code>ADODB.Connection</code>や<code>ADODB.Recordset</code>などのADOオブジェクトは、使用後に必ず<code>Close</code>メソッドを呼び出し、<code>Set obj = Nothing</code>でオブジェクト変数を解放することが非常に重要です。これにより、メモリリークやデータベース接続のリソース枯渇を防ぎ、アプリケーションの安定稼働を保ちます。</li>
</ul></li>
<li><p><strong>セキュリティ:</strong></p>
<ul>
<li>データベース接続文字列にユーザー名やパスワードを直接ハードコーディングすることは避けるべきです。Accessのフロントエンド-バックエンド構成の場合、信頼済みデータベースの場所に配置する、またはVBA内で直接記述せず、Windows認証(SSPI)やパススルー認証などを使用することを検討します。</li>
</ul></li>
<li><p><strong>バックアップ:</strong></p>
<ul>
<li>大量更新や複雑な処理を行う前には、必ずデータベースのバックアップを取得することを推奨します。予期せぬエラーや論理的な問題が発生した場合に、迅速に元の状態に戻すことができます。</li>
</ul></li>
</ul>
<h3 class="wp-block-heading">ロールバック方法</h3>
<p>両方のコード例でトランザクション処理 (<code>cn.BeginTrans</code>, <code>cn.CommitTrans</code>) を実装しています。<code>UpdateRecords_Batch</code>にはエラーハンドリングと<code>cn.RollbackTrans</code>も含まれています。</p>
<ul class="wp-block-list">
<li><p><strong>エラー発生時の自動ロールバック:</strong> <code>UpdateRecords_Batch</code>プロシージャ内の<code>rs.UpdateBatch adAffectAll</code>の直後に、意図的にエラーを発生させるコード(例: <code>Err.Raise 999, "Test Error", "意図的なテストエラー"</code>)を挿入し、プロシージャを実行します。エラーハンドラが捕捉し、<code>cn.RollbackTrans</code>が実行され、データベースの変更が元に戻ることをイミディエイトウィンドウのメッセージとデータベースの内容で確認できます。</p></li>
<li><p><strong>手動ロールバックのシミュレーション:</strong> <code>cn.CommitTrans</code>の行をコメントアウトし、<code>cn.RollbackTrans</code>を明示的に記述して実行することで、全ての変更が破棄されることを確認できます。</p></li>
<li><p><strong>コミット前の中断:</strong> コード実行中に中断(Ctrl + Break)した場合、通常は未完了のトランザクションが自動でロールバックされますが、環境やデータベースの種類によっては未コミットの変更が残る可能性もあります。そのため、常にエラーハンドリングを適切に実装し、明示的なトランザクション管理を行うことが重要です。</p></li>
</ul>
<h2 class="wp-block-heading">落とし穴</h2>
<ul class="wp-block-list">
<li><p><strong>排他制御の競合:</strong></p>
<ul>
<li><p>複数のユーザーやプロセスが同時に同じレコードを更新しようとすると、競合が発生します。</p></li>
<li><p><code>adLockOptimistic</code>や<code>adLockBatchOptimistic</code>では、更新時にのみロックされるため、他のユーザーが同じレコードを参照・変更しても、自分が<code>Update</code>または<code>UpdateBatch</code>するまではエラーになりません。しかし、もしその間に他のユーザーによってレコードが変更されていた場合、更新時に「書き込み競合」エラー(またはバッチ更新の失敗)が発生します。</p></li>
<li><p>競合発生時は、<code>Recordset.Resync</code>メソッドで最新の状態に同期し、競合するレコードを特定して再処理する、またはユーザーに選択肢を提示するなどのロジックが必要です。</p></li>
</ul></li>
<li><p><strong>データ型不一致:</strong></p>
<ul>
<li>ADOで更新しようとする値のデータ型が、データベースのフィールド定義と一致しない場合、エラーが発生します。特に数値と文字列、日付型の相互変換には注意が必要です。VBAの<code>CInt()</code>, <code>CDbl()</code>, <code>CStr()</code>, <code>CDate()</code>などの型変換関数を適切に使用してください。</li>
</ul></li>
<li><p><strong>NULL値の扱い:</strong></p>
<ul>
<li>データベースがNULLを許容しないフィールドにNULLを書き込もうとするとエラーになります。VBAでは<code>Null</code>キーワードを使用しますが、フィールドがNULLを許容する場合のみ適用できます。フィールドがNULLを許容せず、かつ値がない場合は、数値フィールドには0、文字列フィールドには空文字列<code>""</code>を割り当てるなど、適切なデフォルト値を設定する必要があります。</li>
</ul></li>
<li><p><strong>メモリ消費:</strong></p>
<ul>
<li><code>adUseClient</code>カーソルはレコードセット全体をクライアント側のメモリにロードするため、非常に大きなデータセット(例: 数十万件以上のレコード、数GBを超えるデータ)を扱う場合、クライアントPCのメモリを圧迫し、パフォーマンスが低下したり、アプリケーションがクラッシュしたりする可能性があります。その場合は、一度に処理するレコード数を限定する(ページング)、またはサーバーサイドカーソル (<code>adUseServer</code>) と<code>CacheSize</code>プロパティを組み合わせてサーバーからデータを逐次取得するなどの工夫が必要です。</li>
</ul></li>
<li><p><strong>未解放リソース:</strong></p>
<ul>
<li><code>Connection</code>や<code>Recordset</code>オブジェクトを適切に<code>Close</code>および<code>Set obj = Nothing</code>で解放しないと、データベース接続が残り続け、リソースリークやパフォーマンス低下の原因となります。この問題は、特にアプリケーションが頻繁にデータベース操作を行う場合に顕著になります。</li>
</ul></li>
</ul>
<h2 class="wp-block-heading">まとめ</h2>
<p>VBA ADO Recordsetによるデータベース更新は、AccessやExcelにおけるデータ処理の中核をなす強力な機能です。特に大量データや頻繁な更新が必要なシナリオでは、<code>adUseClient</code>カーソルと<code>adLockBatchOptimistic</code>ロックタイプを組み合わせた<strong>バッチ更新 (<code>UpdateBatch</code>)</strong> が、パフォーマンス最適化の鍵となります。この手法により、データベースへのI/O回数を大幅に削減し、処理時間を劇的に短縮することが可能です。</p>
<p>また、堅牢で信頼性の高いシステムを構築するためには、Win32 APIを用いた高精度なパフォーマンス計測、トランザクションによるデータの整合性維持、そして適切なエラーハンドリングとリソース管理が不可欠です。排他制御の競合、データ型不一致、NULL値の扱い、メモリ消費といった「落とし穴」を深く理解し、それらに対する対策を講じることで、安定かつ高性能なVBAアプリケーションを開発できるでしょう。本記事で紹介したコード例とチューニングの指針が、皆様の実務でのADO活用の一助となれば幸いです。</p>
本記事はGeminiの出力をプロンプト工学で整理した業務ドラフト(未検証)です。
VBA Access ADO Recordsetの効率的な更新手法とパフォーマンス最適化
背景と要件
VBA (Visual Basic for Applications) からのデータベース操作は、Microsoft AccessアプリケーションやMicrosoft Excelからの外部データ連携において不可欠な要素です。特に大量のデータを更新する際、処理速度は業務効率に直結します。
ADO (ActiveX Data Objects) は、様々なデータソースへの柔軟なアクセスを提供する強力なフレームワークですが、その使い方によってはパフォーマンスに大きな差が生じます。本記事は、JST 2024年7月28日時点でのVBA ADOのベストプラクティスに基づき、Recordsetを用いたデータ更新において、効率性と堅牢性を両立させるための具体的な手法、パフォーマンス最適化、および実務で役立つコード例を提供します。
、外部ライブラリに依存せず、VBA標準機能と必要に応じてWin32 APIのみを使用することを前提とします。
設計
ADO Recordsetを利用したデータ更新処理は、一般的に以下のステップで構成されます。効率的な更新を実現するためには、特にRecordsetのオープン時におけるカーソル位置(CursorLocation)とロックタイプ(LockType)の設定が重要になります。
接続確立: データベースへのADODB.Connectionオブジェクトを確立します。
Recordset取得: 更新対象のデータをADODB.Recordsetとして開きます。この際、CursorLocationとLockTypeを適切に設定します。
データ更新: Recordset内のレコードを移動し、AddNew/Edit/Deleteメソッドで変更を適用します。バッチ更新の場合は、変更はメモリ上に一時的に保持されます。
変更の永続化: 個別更新の場合はUpdateメソッド、バッチ更新の場合はUpdateBatchメソッドで、変更をデータベースに書き込み永続化します。
トランザクション管理: 複数の更新操作をアトミックに扱うため、BeginTrans、CommitTrans、RollbackTransでトランザクションを管理します。
切断: ConnectionおよびRecordsetオブジェクトを適切に閉じ、リソースを解放します。
データ更新処理フロー
ADO Recordsetを用いたバッチ更新の一般的な処理フローを以下に示します。
graph TD
A["開始"] --> B{"データベース接続の確立"};
B --> C["Recordsetの生成とオープン"];
C --> D{"カーソル位置設定|Recordset.CursorLocation = adUseClient"};
D --> E{"ロックタイプ設定|Recordset.LockType = adLockBatchOptimistic"};
E --> F["レコードの追加/編集/削除"];
F -- レコードごとに処理 --> F;
F --> G{"変更の一括書き込み|Recordset.UpdateBatch"};
G -- 成功 --> H{"トランザクションコミット|Connection.CommitTrans"};
G -- 失敗 --> K{"トランザクションロールバック|Connection.RollbackTrans"};
H --> I["RecordsetとConnectionを閉じる"];
K --> I;
I --> J["終了"];
実装
ここでは、Accessのローカルデータベース(.accdb)のテーブルデータを、VBAから更新する例を示します。パフォーマンス比較のため、一般的な個別更新とバッチ更新の両方を実装します。
共通設定
以下のコードは、パフォーマンス計測用のWin32 API宣言と、データベースパスおよびテーブル名の定数を含みます。
' 参照設定:
' VBAエディタで「ツール」->「参照設定」を開き、
' 「Microsoft ActiveX Data Objects x.x Library」にチェックを入れてください。
' (x.x は環境により異なるバージョン、例: 6.1)
' Win32 APIによる高精度タイマー (パフォーマンス計測用)
' PtrSafeは64bit環境で必須。32bit環境ではDeclare Functionで良いが、PtrSafeで統一推奨。
Private Declare PtrSafe Function QueryPerformanceCounter Lib "kernel32" (lpPerformanceCount As Currency) As Long
Private Declare PtrSafe Function QueryPerformanceFrequency Lib "kernel32" (lpFrequency As Currency) As Long
Private Const DB_PATH As String = "C:\Users\Public\TestDB.accdb" ' 実際のデータベースパスに修正
Private Const TABLE_NAME As String = "T_Products" ' 更新対象テーブル名
コード例1: 個別更新(非効率な例)
この方法は、Recordsetのレコードを1つずつ更新し、その都度Updateメソッドを呼び出すものです。データベースへのI/Oが頻繁に発生するため、大量データでは性能問題が発生しやすいです。
Sub UpdateRecords_Individual()
Dim cn As ADODB.Connection
Dim rs As ADODB.Recordset
Dim strSQL As String
Dim lStartTime As Currency, lEndTime As Currency, lFrequency As Currency
Dim i As Long
'--- パフォーマンス計測開始 ---
QueryPerformanceFrequency lFrequency
QueryPerformanceCounter lStartTime
Set cn = New ADODB.Connection
cn.ConnectionString = "Provider=Microsoft.ACE.OLEDB.12.0;Data Source=" & DB_PATH & ";"
cn.Open
'--- トランザクション開始 ---
cn.BeginTrans
' 例として最初の1000件を対象に選択。実際はWHERE句で更新対象を絞る。
strSQL = "SELECT ProductID, ProductName, Price, StockQuantity FROM " & TABLE_NAME & " WHERE ProductID <= 1000;"
Set rs = New ADODB.Recordset
' 個別更新ではadLockOptimisticまたはadLockPessimisticが一般的
rs.Open strSQL, cn, adOpenKeyset, adLockOptimistic, adCmdText
' Excelの場合、画面更新や自動計算を一時停止してパフォーマンスを向上させる
' Application.ScreenUpdating = False
' Application.Calculation = xlCalculationManual
If Not rs.EOF Then
rs.MoveFirst
Do While Not rs.EOF
rs.Edit ' レコードの編集を開始
rs!ProductName = "Updated_" & rs!ProductID ' 商品名を更新
rs!Price = rs!Price * 1.05 ' 価格を5%上昇
rs!StockQuantity = rs!StockQuantity + 10 ' 在庫を10増やす
rs.Update ' レコードを更新し、データベースに即座に書き込む (I/O発生)
rs.MoveNext
i = i + 1
If i Mod 100 = 0 Then Debug.Print "個別更新: " & i & "件処理済み..."
Loop
End If
'--- トランザクションコミット ---
cn.CommitTrans
' Excelの場合、画面更新や自動計算を再開
' Application.ScreenUpdating = True
' Application.Calculation = xlCalculationAutomatic
'--- パフォーマンス計測終了 ---
QueryPerformanceCounter lEndTime
Debug.Print "個別更新処理時間: " & Format$((lEndTime - lStartTime) / lFrequency, "0.000") & " 秒 (" & i & "件)"
'--- オブジェクトのクリーンアップ ---
If Not rs Is Nothing Then If rs.State = adStateOpen Then rs.Close
Set rs = Nothing
If Not cn Is Nothing Then If cn.State = adStateOpen Then cn.Close
Set cn = Nothing
End Sub
コード説明:
前提条件: DB_PATHとTABLE_NAMEを実際の環境に合わせて設定し、T_ProductsテーブルにProductID(主キー)、ProductName、Price、StockQuantityフィールドが存在すると仮定します。
入出力: 指定されたSQLで選択されたレコードのProductName、Price、StockQuantityフィールドを更新します。
計算量: N個のレコードを更新する場合、レコードセットの移動(O(N))とデータベースへの個別書き込み(N回のI/O)が発生するため、全体としてO(N)の計算量となります。データベースへのI/Oがボトルネックとなりやすいです。
メモリ条件: adOpenKeysetカーソルはキーセット(レコードの一意な識別子)をメモリに保持しますが、実際のデータは必要に応じてフェッチされるため、非常に大きなデータを一度にロードすることはありません。
コード例2: バッチ更新(効率的な例)
CursorLocationをadUseClient、LockTypeをadLockBatchOptimisticに設定することで、レコードセットをクライアント側のメモリ上にロードし、全ての変更をまとめてデータベースに書き込むことができます。これにより、データベースへのI/O回数を大幅に削減し、パフォーマンスを劇的に向上させます。
Sub UpdateRecords_Batch()
Dim cn As ADODB.Connection
Dim rs As ADODB.Recordset
Dim strSQL As String
Dim lStartTime As Currency, lEndTime As Currency As Currency, lFrequency As Currency
Dim i As Long
Dim errObj As ADODB.Error ' エラーオブジェクトを定義
'--- パフォーマンス計測開始 ---
QueryPerformanceFrequency lFrequency
QueryPerformanceCounter lStartTime
Set cn = New ADODB.Connection
cn.ConnectionString = "Provider=Microsoft.ACE.OLEDB.12.0;Data Source=" & DB_PATH & ";"
cn.Open
'--- トランザクション開始 (バッチ更新はConnection側で管理) ---
cn.BeginTrans
' 例として最初の1000件を対象に選択。実際はWHERE句で更新対象を絞る。
strSQL = "SELECT ProductID, ProductName, Price, StockQuantity FROM " & TABLE_NAME & " WHERE ProductID <= 1000;"
Set rs = New ADODB.Recordset
' クライアントサイドカーソルとバッチロックタイプを設定
rs.CursorLocation = adUseClient ' クライアントサイドカーソルを使用
' adOpenStaticでメモリにデータをロードし、adLockBatchOptimisticでバッチ更新を許可
rs.Open strSQL, cn, adOpenStatic, adLockBatchOptimistic, adCmdText
' Excelの場合、画面更新や自動計算を一時停止してパフォーマンスを向上させる
' Application.ScreenUpdating = False
' Application.Calculation = xlCalculationManual
If Not rs.EOF Then
rs.MoveFirst
Do While Not rs.EOF
rs.Edit ' レコードの編集を開始
rs!ProductName = "Updated_Batch_" & rs!ProductID ' 商品名を更新
rs!Price = rs!Price * 1.10 ' 価格を10%上昇
rs!StockQuantity = rs!StockQuantity + 20 ' 在庫を20増やす
' ここではrs.Updateは呼び出さない。変更はメモリに保持される
rs.MoveNext
i = i + 1
If i Mod 100 = 0 Then Debug.Print "バッチ更新: " & i & "件処理済み..."
Loop
End If
'--- 全ての変更をデータベースに一括書き込み ---
On Error GoTo ErrorHandler_Batch ' エラー発生時はErrorHandler_Batchへジャンプ
rs.UpdateBatch adAffectAll ' adAffectAllで全ての変更を適用 (データベースへのI/Oはここで一回発生)
cn.CommitTrans ' トランザクションコミット
' Excelの場合、画面更新や自動計算を再開
' Application.ScreenUpdating = True
' Application.Calculation = xlCalculationAutomatic
'--- パフォーマンス計測終了 ---
QueryPerformanceCounter lEndTime
Debug.Print "バッチ更新処理時間: " & Format$((lEndTime - lStartTime) / lFrequency, "0.000") & " 秒 (" & i & "件)"
Exit_Sub:
'--- オブジェクトのクリーンアップ ---
If Not rs Is Nothing Then If rs.State = adStateOpen Then rs.Close
Set rs = Nothing
If Not cn Is Nothing Then If cn.State = adStateOpen Then cn.Close
Set cn = Nothing
Exit Sub
ErrorHandler_Batch:
If Not cn Is Nothing Then
If cn.State = adStateOpen Then
cn.RollbackTrans ' エラー時はトランザクションをロールバック
Debug.Print "エラー発生。トランザクションをロールバックしました。"
For Each errObj In cn.Errors ' ADO Errorsコレクションから詳細情報を取得
Debug.Print "エラーコード: " & errObj.NativeError & ", ソース: " & errObj.Source & ", 説明: " & errObj.Description
Next errObj
End If
End If
Resume Exit_Sub ' エラーハンドリング後、クリーンアップへ
End Sub
コード説明:
前提条件: 上記の個別更新と同じです。
入出力: 指定されたSQLで選択されたレコードのProductName、Price、StockQuantityフィールドを更新します。
計算量: レコードセットの移動はO(N)ですが、データベースへの書き込みはUpdateBatchの1回に集約されるため、I/Oオーバーヘッドが大幅に削減されます。これにより、実質的な処理時間はメモリ内のデータ操作と一括書き込みの時間に依存し、個別I/Oの多い個別更新よりも高速になります。
メモリ条件: adUseClientカーソルは、対象となるレコードセット全体をクライアント側のメモリにロードします。非常に大きなデータセット(例: 数十万件以上、数GB)を扱う場合は、クライアントPCのメモリ消費に注意が必要です。
性能チューニング
ADO Recordsetの更新パフォーマンスを最適化するための主要な手法を以下に示します。
検証
上記コード例には、QueryPerformanceCounterとQueryPerformanceFrequencyを用いたWin32 APIベースの高精度タイマーを組み込んでいます。これを実行することで、個別更新とバッチ更新それぞれの処理時間をミリ秒単位で比較し、バッチ更新の性能向上効果を数値で確認できます。
実行手順
データベースの準備:
Microsoft Accessを起動し、新しい空のデータベースを作成します。例として、C:\Users\Public\TestDB.accdbに保存します。
作成したデータベース内で「作成」タブから「テーブル」を選択し、T_Productsという名前のテーブルを作成します。
テーブルのフィールドは以下のように設定してください:
ProductID: データ型「数値」、フィールドサイズ「長整数型」、主キーに設定。
ProductName: データ型「短いテキスト」、フィールドサイズ「255」。
Price: データ型「通貨型」。
StockQuantity: データ型「数値」、フィールドサイズ「長整数型」。
T_Productsテーブルに数千〜数万件のテストデータを挿入します(例: ProductIDを1から順に、他のフィールドも適当な値で埋めます。VBAで初期データ挿入プロシージャを作成しても良いでしょう)。
VBAプロジェクトの準備:
参照設定の確認:
- VBAエディタで「ツール」->「参照設定」を開き、「Microsoft ActiveX Data Objects x.x Library」にチェックが入っていることを確認します。
x.xはバージョンにより異なりますが、最新版(例: 6.1)を選択してください。
コードの実行:
Private Const DB_PATH As String の値を、作成したTestDB.accdbへの正しいパスに修正します。
VBAエディタでUpdateRecords_Individualプロシージャを選択し、F5キーを押して実行します。実行後、イミディエイトウィンドウ(Ctrl + Gキーで表示)で処理時間を確認します。
同様に、UpdateRecords_Batchプロシージャを選択し、F5キーを押して実行します。イミディエイトウィンドウで処理時間を確認します。
両者の処理時間を比較し、バッチ更新の効果を検証します。
運用
エラーハンドリング:
On Error GoToステートメントを使用して、ADODBオブジェクトで発生する可能性のあるエラーを捕捉し、適切に処理します。特にデータベース関連のエラーは、接続切れ、権限不足、データ型不一致など多岐にわたります。Connection.Errorsコレクションを検査することで、詳細なエラー情報を取得し、ログ記録やユーザーへのフィードバックに活用できます。
リソース管理:
ADODB.ConnectionやADODB.RecordsetなどのADOオブジェクトは、使用後に必ずCloseメソッドを呼び出し、Set obj = Nothingでオブジェクト変数を解放することが非常に重要です。これにより、メモリリークやデータベース接続のリソース枯渇を防ぎ、アプリケーションの安定稼働を保ちます。
セキュリティ:
- データベース接続文字列にユーザー名やパスワードを直接ハードコーディングすることは避けるべきです。Accessのフロントエンド-バックエンド構成の場合、信頼済みデータベースの場所に配置する、またはVBA内で直接記述せず、Windows認証(SSPI)やパススルー認証などを使用することを検討します。
バックアップ:
- 大量更新や複雑な処理を行う前には、必ずデータベースのバックアップを取得することを推奨します。予期せぬエラーや論理的な問題が発生した場合に、迅速に元の状態に戻すことができます。
ロールバック方法
両方のコード例でトランザクション処理 (cn.BeginTrans, cn.CommitTrans) を実装しています。UpdateRecords_Batchにはエラーハンドリングとcn.RollbackTransも含まれています。
エラー発生時の自動ロールバック: UpdateRecords_Batchプロシージャ内のrs.UpdateBatch adAffectAllの直後に、意図的にエラーを発生させるコード(例: Err.Raise 999, "Test Error", "意図的なテストエラー")を挿入し、プロシージャを実行します。エラーハンドラが捕捉し、cn.RollbackTransが実行され、データベースの変更が元に戻ることをイミディエイトウィンドウのメッセージとデータベースの内容で確認できます。
手動ロールバックのシミュレーション: cn.CommitTransの行をコメントアウトし、cn.RollbackTransを明示的に記述して実行することで、全ての変更が破棄されることを確認できます。
コミット前の中断: コード実行中に中断(Ctrl + Break)した場合、通常は未完了のトランザクションが自動でロールバックされますが、環境やデータベースの種類によっては未コミットの変更が残る可能性もあります。そのため、常にエラーハンドリングを適切に実装し、明示的なトランザクション管理を行うことが重要です。
落とし穴
排他制御の競合:
複数のユーザーやプロセスが同時に同じレコードを更新しようとすると、競合が発生します。
adLockOptimisticやadLockBatchOptimisticでは、更新時にのみロックされるため、他のユーザーが同じレコードを参照・変更しても、自分がUpdateまたはUpdateBatchするまではエラーになりません。しかし、もしその間に他のユーザーによってレコードが変更されていた場合、更新時に「書き込み競合」エラー(またはバッチ更新の失敗)が発生します。
競合発生時は、Recordset.Resyncメソッドで最新の状態に同期し、競合するレコードを特定して再処理する、またはユーザーに選択肢を提示するなどのロジックが必要です。
データ型不一致:
- ADOで更新しようとする値のデータ型が、データベースのフィールド定義と一致しない場合、エラーが発生します。特に数値と文字列、日付型の相互変換には注意が必要です。VBAの
CInt(), CDbl(), CStr(), CDate()などの型変換関数を適切に使用してください。
NULL値の扱い:
- データベースがNULLを許容しないフィールドにNULLを書き込もうとするとエラーになります。VBAでは
Nullキーワードを使用しますが、フィールドがNULLを許容する場合のみ適用できます。フィールドがNULLを許容せず、かつ値がない場合は、数値フィールドには0、文字列フィールドには空文字列""を割り当てるなど、適切なデフォルト値を設定する必要があります。
メモリ消費:
adUseClientカーソルはレコードセット全体をクライアント側のメモリにロードするため、非常に大きなデータセット(例: 数十万件以上のレコード、数GBを超えるデータ)を扱う場合、クライアントPCのメモリを圧迫し、パフォーマンスが低下したり、アプリケーションがクラッシュしたりする可能性があります。その場合は、一度に処理するレコード数を限定する(ページング)、またはサーバーサイドカーソル (adUseServer) とCacheSizeプロパティを組み合わせてサーバーからデータを逐次取得するなどの工夫が必要です。
未解放リソース:
ConnectionやRecordsetオブジェクトを適切にCloseおよびSet obj = Nothingで解放しないと、データベース接続が残り続け、リソースリークやパフォーマンス低下の原因となります。この問題は、特にアプリケーションが頻繁にデータベース操作を行う場合に顕著になります。
まとめ
VBA ADO Recordsetによるデータベース更新は、AccessやExcelにおけるデータ処理の中核をなす強力な機能です。特に大量データや頻繁な更新が必要なシナリオでは、adUseClientカーソルとadLockBatchOptimisticロックタイプを組み合わせたバッチ更新 (UpdateBatch) が、パフォーマンス最適化の鍵となります。この手法により、データベースへのI/O回数を大幅に削減し、処理時間を劇的に短縮することが可能です。
また、堅牢で信頼性の高いシステムを構築するためには、Win32 APIを用いた高精度なパフォーマンス計測、トランザクションによるデータの整合性維持、そして適切なエラーハンドリングとリソース管理が不可欠です。排他制御の競合、データ型不一致、NULL値の扱い、メモリ消費といった「落とし穴」を深く理解し、それらに対する対策を講じることで、安定かつ高性能なVBAアプリケーションを開発できるでしょう。本記事で紹介したコード例とチューニングの指針が、皆様の実務でのADO活用の一助となれば幸いです。
コメント