同一フォーマットのファイルの内容を1つのシートに集約するマクロ

仕事でExcelを使っていると、ある分割単位(例えば都道府県とか)でファイルを分けて情報を管理することがよくあります。このようにファイルを複数に分割していると、フィルタ機能が使えない為にファイル間での記載方法がブレてしまいがちです。
これから紹介するマクロは、複数に分割した特定フォーマットのファイルの内容を1つのシートに集約し、フィルタをかけられるようにすることを目的としています。

### 実行画面のイメージ
マクロを登録したファイルの「INPUT」シートを実行画面とします。
「収集対象のフォルダのパス」というタイトルのついているオレンジで網掛けされたセルに集約したいファイルが格納されているフォルダのパスを入力します。
「実行」と記載されたボタンには、Startというマクロを登録しています。
![キャプチャ1.PNG](https://qiita-image-store.s3.amazonaws.com/0/122324/e1b4b162-edb6-cbb8-c500-c44ebf664e3c.png)

### 実行結果の出力画面のイメージ
マクロを登録したファイルの「OUTPUT」シートを出力画面とします。
収集対象のシートのフォーマットに合わせて、列1～列5を用意しています。
![キャプチャ2.PNG](https://qiita-image-store.s3.amazonaws.com/0/122324/b2f75dcc-80b9-300c-1605-314e1349b24d.png)

### 収集対象のファイルのイメージ
ここでは話を簡単にするために、収集対象のシート名は一律「TARGET」とします。
![キャプチャ3.PNG](https://qiita-image-store.s3.amazonaws.com/0/122324/527e57c7-4b59-0f7c-38af-ab921916da1f.png)

### VBAによる情報の集約処理
マクロを登録したファイルの「INPUT」シートの「実行」ボタンに、Startという名前のマクロを登録します。ボタン押下後は指定したフォルダ配下のファイル名をサブフォルダ配下も含めて検索し、そのファイルのTARGETシートに記載されている内容を1行ずつOUTPUTシートに出力していきます。

```vbnet 

' 定数とグローバル変数
Private Const INPUT_SHEET_NAME = "INPUT"
Private Const OUTPUT_SHEET_NAME = "OUTPUT"
Private Const TARGET_SHEET_NAME = "TARGET"
Private TARGET_FOLDER_PATH As String

' マクロの開始
'
' 引数　 : なし
' 返り値 : なし
Sub Start()

    ' INPUTシートに入力されている値をグローバル変数に代入する。
    TARGET_FOLDER_PATH = Worksheets(INPUT_SHEET_NAME).Cells(8, 2).Text

    Dim outputSheetIndex As Long: outputSheetIndex = 2
    Call GetFileDataRecursively(TARGET_FOLDER_PATH, outputSheetIndex)
    
    MsgBox ("終わりました")

End Sub


' 指定したフォルダ直下のファイル名を取得し、そのファイルの入力情報を取得する処理を実行する
'
' 引数　 : folderPath 対象フォルダのパス
' 　　   : outputSheetIndex "OUTPUT"シートの行番号
' 返り値 : なし
Function GetFileDataRecursively(ByVal folderPath As String, ByVal outputSheetIndex As Long)

    Dim fileName As String
    Dim subFolder As Object

    fileName = Dir(folderPath & "\*.*")

    Do While fileName <> ""
    
        outputSheetIndex = GetFileData(folderPath & "\" & fileName, outputSheetIndex)

        fileName = Dir()

    Loop

    With CreateObject("Scripting.FileSystemObject")

    ' サブフォルダを取得し、その数だけ再帰的に処理を実行する
    For Each subFolder In .GetFolder(folderPath).SubFolders
        Call GetFileDataRecursively(subFolder.Path, outputSheetIndex)
        Next subFolder
    End With

End Function


' ファイルのデータを1行ずつ読み込み、"OUTPUT"シートに出力する
'
' 引数　 : filePath 対象ファイルのパス
' 　　   : outputSheetIndex "OUTPUT"シートの行番号
' 返り値 : "OUTPUT"シートの行番号
Function GetFileData(ByVal filePath As String, ByVal outputSheetIndex As Long) As Long

    Workbooks.Open (filePath)
        
    Dim result As Boolean: result = True

    Dim row As collection
    Dim count As Long: count = 2

    Do While result = True

        Set row = GetRow(count)
        result = IsValid(row)
            
        ' 取得した行のいずれかのセルに入力がある場合、"OUTPUT"シートに出力する
        If result = True Then
            
            Workbooks(ThisWorkbook.Name).Worksheets(OUTPUT_SHEET_NAME).Cells(outputSheetIndex, 1) = row("列1")
            Workbooks(ThisWorkbook.Name).Worksheets(OUTPUT_SHEET_NAME).Cells(outputSheetIndex, 2) = row("列2")
            Workbooks(ThisWorkbook.Name).Worksheets(OUTPUT_SHEET_NAME).Cells(outputSheetIndex, 3) = row("列3")
            Workbooks(ThisWorkbook.Name).Worksheets(OUTPUT_SHEET_NAME).Cells(outputSheetIndex, 4) = row("列4")
            Workbooks(ThisWorkbook.Name).Worksheets(OUTPUT_SHEET_NAME).Cells(outputSheetIndex, 5) = row("列5")
            
            count = count + 1
            outputSheetIndex = outputSheetIndex + 1
            
        End If

    Loop
        
    Workbooks(ActiveWorkbook.Name).Close SaveChanges:=False

    GetFileData = outputSheetIndex

End Function


' 指定した行番号のデータを取得する
'
' 引数　 : index 行番号
' 返り値 : 1行分のデータ
Function GetRow(ByVal index As Long) As collection

    Dim row As collection
    Set row = New collection
   
    With row
        .Add Key:="列1", Item:=Worksheets(TARGET_SHEET_NAME).Cells(index, 1).Text
        .Add Key:="列2", Item:=Worksheets(TARGET_SHEET_NAME).Cells(index, 2).Text
        .Add Key:="列3", Item:=Worksheets(TARGET_SHEET_NAME).Cells(index, 3).Text
        .Add Key:="列4", Item:=Worksheets(TARGET_SHEET_NAME).Cells(index, 4).Text
        .Add Key:="列5", Item:=Worksheets(TARGET_SHEET_NAME).Cells(index, 5).Text
    End With
        
    Set GetRow = row

End Function


' 引数に指定した1行分のデータに、空文字以外のデータが入力されているか判定する
'
' 引数　 : row 1行分のデータ
' 返り値 : rowに空文字以外の入力がある場合にTrueが返却される
Function IsValid(ByRef row As collection) As Boolean

    For Each cell In row

        If cell <> "" Then
        
            IsValid = True

            Exit Function

        End If

    Next cell
        
    IsValid = False
    
End Function

```

### 実行結果のイメージ
今回は、収集対象のファイルと同じものを3ファイル用意したフォルダを対象にマクロを実行しました。画面を見ると、3ファイル分のデータが出力されていることが分かります。
![キャプチャ.PNG](https://qiita-image-store.s3.amazonaws.com/0/122324/00aeed1c-3f30-97ed-3efd-1628173700db.png)

### 最後に
今回紹介したマクロは比較的単純な内容ですが、個人的にはかなり便利だと思っています。
また、このマクロの応用としてはアンケートの集計などにも使えると思います。
この手のマクロは対象ファイルが多くなるほど便利になっていくので、よろしければ使ってみてください。

### 参考資料

■VBAの基本的な書き方について知りたい場合は・・
[Excel VBAの文法まとめ]
(http://qiita.com/yasuaki9973/items/76307da43d734dc97c66)