【VBAリファレンス】VBA練習問題VBA100本ノック 39本目:数値リストの統合(マージ)

スポンサーリンク

VBA100本ノック39本目:数値リストの統合(マージ)の技術的深掘り

Excel VBAを用いたデータ処理において、複数のソースから得られたリストを一つに統合し、特定のキーに基づいて情報を集約する作業は、実務で最も頻繁に発生するタスクの一つです。「VBA100本ノック」の39本目として知られる「数値リストの統合」は、一見単純な転記作業に見えますが、データ量が増大した際のパフォーマンス、コードの保守性、そしてエラーハンドリングの観点から、エンジニアとしてのスキルが如実に表れる課題です。

本記事では、単なる解答の提示にとどまらず、なぜその手法が最適なのか、計算量やメモリ消費の観点から解説します。

詳細解説:なぜ単純なループでは不十分なのか

多くの初心者が陥る罠は、セルを一つずつ読み込み、別のシートの最終行を毎回検索して追記していくという手法です。

例えば、Sheet1にあるリストとSheet2にあるリストを統合する場合、以下のプロセスを想像しがちです。
1. Sheet1の行をループする。
2. Sheet2の最終行を取得する。
3. 値を書き込む。

この手法の最大の問題点は「計算量(オーダー)」です。最終行を取得するRange.End(xlUp).RowやUsedRangeは、実行のたびにシート全体を走査するため、データ件数がN件ある場合、O(N^2)の計算量が発生します。数千件程度のデータであればPCの性能でカバーできますが、数万件を超えると処理時間が指数関数的に増大します。

プロフェッショナルなアプローチでは、以下の手法を推奨します。

1. 配列への一括取り込み(Read to Array)
2. 連想配列(Scripting.Dictionary)を用いたキーベースの統合
3. 配列からの結果の一括書き出し(Write from Array)

これにより、シートへのアクセス回数を最小限(読み込み1回、書き込み1回)に抑えることができ、処理速度を劇的に向上させることが可能です。特にDictionaryオブジェクトを活用することで、重複排除や値の合算を高速に行うことができます。

サンプルコード:Dictionaryを用いた効率的なマージ処理

以下に、実務でそのまま利用可能な、堅牢かつ高速なマージ処理のコードを提示します。このコードでは、A列をキーとして、B列の数値を合算する処理を想定しています。


Option Explicit

' 参照設定不要でDictionaryを利用するためのLate Binding方式
Sub MergeNumericLists()
    Dim wsSource1 As Worksheet, wsSource2 As Worksheet
    Dim wsDest As Worksheet
    Dim dataRange As Variant
    Dim dict As Object
    Dim i As Long
    Dim key As Variant
    Dim val As Double
    
    Set dict = CreateObject("Scripting.Dictionary")
    Set wsSource1 = ThisWorkbook.Sheets("Sheet1")
    Set wsSource2 = ThisWorkbook.Sheets("Sheet2")
    Set wsDest = ThisWorkbook.Sheets("Output")
    
    ' 1. データの取り込みとDictionaryへの格納
    ' Sheet1のデータを処理
    dataRange = wsSource1.Range("A2:B" & wsSource1.Cells(wsSource1.Rows.Count, 1).End(xlUp).Row).Value
    For i = 1 To UBound(dataRange, 1)
        key = dataRange(i, 1)
        val = dataRange(i, 2)
        If dict.Exists(key) Then
            dict(key) = dict(key) + val
        Else
            dict.Add key, val
        End If
    Next i
    
    ' Sheet2のデータを処理(既存キーがあれば加算、なければ新規追加)
    dataRange = wsSource2.Range("A2:B" & wsSource2.Cells(wsSource2.Rows.Count, 1).End(xlUp).Row).Value
    For i = 1 To UBound(dataRange, 1)
        key = dataRange(i, 1)
        val = dataRange(i, 2)
        If dict.Exists(key) Then
            dict(key) = dict(key) + val
        Else
            dict.Add key, val
        End If
    Next i
    
    ' 2. 結果の書き出し
    wsDest.Cells.Clear
    wsDest.Range("A1").Value = "ID"
    wsDest.Range("B1").Value = "Total"
    
    Dim outputArr() As Variant
    ReDim outputArr(1 To dict.Count, 1 To 2)
    
    Dim idx As Long: idx = 1
    For Each key In dict.Keys
        outputArr(idx, 1) = key
        outputArr(idx, 2) = dict(key)
        idx = idx + 1
    Next key
    
    wsDest.Range("A2").Resize(dict.Count, 2).Value = outputArr
    
    MsgBox "統合処理が完了しました。", vbInformation
End Sub

実務アドバイス:エンジニアとして意識すべきポイント

実務でこのプログラムを運用する際、以下の3点に留意してください。

第一に「データのクリーニング」です。外部から取り込むデータには、全角半角の混在や、前後の不要なスペースが含まれていることが多々あります。Dictionaryのキーとして利用する場合、Trim関数やStrConv関数を用いて入力を正規化する処理を必ず挟んでください。これを行わないと、”A001″と” A001 “が別々のキーとして認識され、統合が正しく行われません。

第二に「エラーハンドリング」です。数値が入るべきセルに文字列が入っている場合、加算処理で実行時エラーが発生します。IsNumeric関数による型チェックをループ内に入れるか、あるいはデータ型をVariantで受け取った後に明示的にCDbl関数で変換する等の防御策が必要です。

第三に「拡張性」です。今回は2つのシートをマージする例でしたが、実務ではフォルダ内の全CSVを統合したいという要望がよくあります。その場合は、ファイルシステムオブジェクト(FSO)を用いてディレクトリ内のファイルをループさせ、上記Dictionaryのロジックを共通関数化して呼び出す設計にすると、保守性が格段に向上します。

まとめ:VBAの真価は「設計」にある

VBA100本ノック39本目という一見単純なテーマですが、ここには「配列」「Dictionary」「型変換」「メモリ管理」という、VBAエンジニアが習得すべき必須項目が凝縮されています。

初心者は「動くコード」を書くことに注力しますが、プロフェッショナルは「読みやすく、速く、壊れにくいコード」を書くことに注力します。今回紹介したDictionaryを用いた手法は、VBAにおけるデータ処理の定石です。この手法をマスターすることで、Excelの標準機能であるピボットテーブルやPower Queryでも解決できないような、複雑な条件分岐を伴うデータ集計も自在にこなせるようになります。

コードを書く前に、データフローを整理し、計算量を意識する。この姿勢こそが、ベテランエンジニアへの第一歩です。ぜひ、ご自身のプロジェクトでこのマージロジックを最適化し、日々の業務効率を劇的に改善してください。

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