VBA100本ノック93本目:複数ブックの連結と再分割を極める
Excel VBAのスキルを一段上のステージへ引き上げるための登竜門として知られる「VBA100本ノック」。その中でも93本目に位置する「複数ブックの連結と再分割」は、実務において極めて頻繁に遭遇する「データ集約と配布」という業務フローを自動化する、非常に実践的なテーマです。
本稿では、単に課題をクリアするだけでなく、メモリ管理、ファイルパスの動的生成、そしてエラーハンドリングまでを考慮した、プロフェッショナルな実装手法を詳細に解説します。
課題の概要と技術的アプローチ
この課題の核心は、以下の2つのフェーズに大別されます。
1. 連結フェーズ:指定フォルダ内の全Excelブックから特定のシートデータを抽出し、1つのマスターブックに集約する。
2. 再分割フェーズ:集約されたマスターデータを、特定の列(例:支店名や担当者名)の値に基づいて条件ごとにフィルタリングし、個別のブックとして書き出す。
これらを実現するためには、FileSystemObject(FSO)を用いたファイル操作、Rangeオブジェクトの効率的な転記、そしてDictionaryオブジェクトを用いたユニークなキーの抽出が不可欠です。
詳細解説:連結処理の最適化
まず、連結処理において陥りやすい罠が「ループ内でのファイル開閉によるパフォーマンス低下」です。ブックを一つずつ開き、コピー&ペーストを行う際、画面更新(ScreenUpdating)や自動計算(Calculation)を制御しないと、処理時間は指数関数的に増大します。
また、再分割フェーズでは、データが何種類存在するかを動的に判定する必要があります。ここでDictionaryオブジェクトを使用し、条件列の値をキーとして登録することで、重複のないリストを瞬時に作成できます。この手法は、VBAにおけるデータ処理の定石であり、高度な集計処理のベースとなります。
サンプルコード:実務レベルの実装
以下に、堅牢性を意識した実装コードを提示します。
Option Explicit
' 必要なライブラリ:Microsoft Scripting Runtime
Sub ConsolidateAndSplit()
Dim fso As Object
Dim folderPath As String
Dim targetFolder As Object
Dim file As Object
Dim wbSource As Workbook, wbMaster As Workbook
Dim wsMaster As Worksheet, wsTemp As Worksheet
Dim lastRow As Long, dict As Object
Dim key As Variant, filterRange As Range
' 初期設定
Set fso = CreateObject("Scripting.FileSystemObject")
folderPath = ThisWorkbook.Path & "\DataFiles\"
Set wbMaster = Workbooks.Add
Set wsMaster = wbMaster.Sheets(1)
Application.ScreenUpdating = False
' 1. 連結処理
Set targetFolder = fso.GetFolder(folderPath)
For Each file In targetFolder.Files
If fso.GetExtensionName(file.Path) Like "xls*" Then
Set wbSource = Workbooks.Open(file.Path)
' ここでは1シート目を想定
Set wsTemp = wbSource.Sheets(1)
lastRow = wsTemp.Cells(wsTemp.Rows.Count, 1).End(xlUp).Row
' ヘッダーは初回のみ転記する等の条件分岐を入れるのがベター
wsTemp.Range("A2:D" & lastRow).Copy wsMaster.Cells(wsMaster.Rows.Count, 1).End(xlUp).Offset(1)
wbSource.Close False
End If
Next
' 2. 再分割処理(例:A列の値をキーに分割)
Set dict = CreateObject("Scripting.Dictionary")
lastRow = wsMaster.Cells(wsMaster.Rows.Count, 1).End(xlUp).Row
' キーの抽出
For Each key In wsMaster.Range("A2:A" & lastRow)
If Not dict.Exists(key.Value) Then dict.Add key.Value, Nothing
Next
' 分割保存
For Each key In dict.Keys
Dim wbNew As Workbook
Set wbNew = Workbooks.Add
wsMaster.AutoFilterMode = False
wsMaster.Range("A1:D" & lastRow).AutoFilter Field:=1, Criteria1:=key
wsMaster.Range("A1:D" & lastRow).SpecialCells(xlCellTypeVisible).Copy wbNew.Sheets(1).Range("A1")
wbNew.SaveAs Filename:=ThisWorkbook.Path & "\Output\" & key & ".xlsx"
wbNew.Close True
Next
Application.ScreenUpdating = True
MsgBox "処理が完了しました。"
End Sub
実務アドバイス:プロの現場で求められる品質
このコードを実務で運用する場合、以下の3点に注意を払う必要があります。
1. メモリの解放:DictionaryやFSOといった外部オブジェクトを多用する場合、処理終了後に適切にNothingを代入し、メモリを解放する習慣をつけましょう。特に大量のファイルを扱う場合、メモリリークはExcelのクラッシュに直結します。
2. エラーハンドリングの徹底:ファイルが既に開かれている場合や、保存先に同名ファイルが存在する場合に備え、On Error GoTo構文を用いたエラートラップが必須です。特に「Dir関数」と「FSO」を使い分け、パスの整合性を厳格にチェックしてください。
3. 柔軟な設計:列の固定配置は修正コストを跳ね上げます。定数定義(Const)を用いて、列番号やフォルダパスを一元管理できるようにしておくことが、メンテナンス性を高める秘訣です。
まとめ
VBA100本ノック93本目は、単なる「ファイルの操作」を超え、「データのライフサイクル管理」を学ぶ絶好の機会です。連結して終わりではなく、そのデータをどのように再利用可能な形へ変換するかという視点は、データエンジニアリングの基礎そのものです。
このコードを自身の環境で動作させ、挙動を理解した後は、ぜひ「ヘッダー行の自動判定」や「サブフォルダの再帰的探索」といった機能を追加してみてください。VBAのスキルは、こうした「少しの機能追加」を繰り返すことで、飛躍的に向上していきます。
プロフェッショナルなVBAエンジニアへの道は、このような地道な実装と最適化の積み重ねの先にあります。ぜひ、この課題を通じて、自身のコードをより洗練されたものへと昇華させてください。
