【Excel VBA】CSVデータを条件抽出して別ファイルに出力する方法:業務効率化マクロ徹底解説

Excel・VBA

はじめに

日々の業務でCSVファイルを扱う機会は非常に多いですよね。膨大なデータの中から特定の条件に合う行だけを抽出し、新しいファイルとして出力する作業は、手作業で行うと時間も手間もかかります。そんなデータ整理の悩みを、Excel VBAを使って劇的に解決しましょう!

この記事では、Excel VBAを使ってCSVデータから特定の条件に合致する行を抽出し、新しいCSVファイルとして出力する方法を、初心者の方でも理解しやすいように具体的なコード例と詳細な解説を交えてご紹介します。このマクロを導入すれば、データ処理の業務効率が格段に向上すること間違いなしです。

この記事で学べること

  • VBAでCSVファイルを読み込む方法
  • CSVデータを行単位で処理する方法
  • 特定の条件でデータを抽出する方法
  • 抽出したデータを新しいCSVファイルとして出力する方法

CSVデータ処理の基本とVBAでのアプローチ

CSV (Comma Separated Values) ファイルは、テキスト形式でデータを保存する際の最も一般的な形式の一つです。各行がレコードを表し、各値がカンマで区切られています。VBAを使えば、このCSVファイルをプログラム的に読み込み、加工し、出力するといった一連の作業を自動化できます。

なぜVBAでCSV処理をするのか?

  • 自動化による効率向上: 繰り返し発生する定型作業をマクロで自動化し、手作業によるミスを削減します。
  • 柔軟なデータ操作: Excelの標準機能では難しい複雑な条件での抽出や、特定の形式での出力が可能です。
  • Excelとの連携: Excelシート上のデータと連携させたり、結果をシートに表示したりと、Excel環境での親和性が高いです。

今回のシナリオ

今回は、「商品名が『りんご』で、かつ、数量が『100』以上の行」を元のCSVファイルから抽出し、別のCSVファイルとして出力するシナリオを想定して解説を進めます。

Excel VBAでCSVを条件抽出・ファイル出力する手順

それでは、具体的な手順を見ていきましょう。

1. サンプルCSVファイルの準備

まず、処理対象となるCSVファイルを用意します。以下の内容をテキストエディタにコピーし、「sample_data.csv」という名前でExcelファイルと同じフォルダに保存してください。

商品ID,商品名,価格,数量,販売店
001,りんご,150,50,A店
002,みかん,100,120,B店
003,りんご,160,150,C店
004,ぶどう,300,80,A店
005,りんご,140,200,B店
006,バナナ,90,70,C店
007,みかん,110,90,A店
008,りんご,170,90,D店

2. VBAコードの記述

Excelを開き、Alt + F11を押してVBAエディタを開きます。「挿入」メニューから「標準モジュール」を選択し、以下のコードをコピー&ペーストしてください。

Option Explicit

Sub ExtractAndOutputCSV()
    
    ' --- 設定値 --- 
    Const TARGET_CSV_FILE As String = "sample_data.csv" ' 処理対象のCSVファイル名
    Const OUTPUT_CSV_FILE As String = "extracted_data.csv" ' 出力するCSVファイル名
    Const CONDITION_COLUMN_NAME1 As String = "商品名" ' 抽出条件1の列名
    Const CONDITION_VALUE1 As String = "りんご" ' 抽出条件1の値
    Const CONDITION_COLUMN_NAME2 As String = "数量" ' 抽出条件2の列名
    Const CONDITION_VALUE2 As Long = 100 ' 抽出条件2の値 (数値型)
    
    Dim fso As Object ' FileSystemObject
    Dim tsInput As Object ' Input TextStream
    Dim tsOutput As Object ' Output TextStream
    Dim strLine As String ' 読み込んだ1行のデータ
    Dim vHeader As Variant ' ヘッダー行の配列
    Dim vData As Variant ' データ行の配列
    Dim dicHeader As Object ' ヘッダーの列番号を保持するDictionary
    Dim i As Long ' ループカウンタ
    Dim strOutputPath As String ' 出力ファイルのフルパス
    Dim strInputPath As String ' 入力ファイルのフルパス
    Dim lQuantity As Long ' 数量を一時的に格納する変数
    
    ' 現在のExcelファイルのパスを取得
    strInputPath = ThisWorkbook.Path & "\" & TARGET_CSV_FILE
    strOutputPath = ThisWorkbook.Path & "\" & OUTPUT_CSV_FILE
    
    ' FileSystemObjectを生成
    Set fso = CreateObject("Scripting.FileSystemObject")
    
    ' 入力ファイルが存在するかチェック
    If Not fso.FileExists(strInputPath) Then
        MsgBox "入力ファイルが見つかりません: " & strInputPath, vbCritical
        Set fso = Nothing
        Exit Sub
    End If
    
    On Error GoTo ErrorHandler
    
    ' 入力CSVファイルを開く (読み取り専用)
    Set tsInput = fso.OpenTextFile(strInputPath, 1) ' ForReading = 1
    
    ' 出力CSVファイルを作成または開く (書き込み専用)
    ' Trueを指定すると既存ファイルは上書きされる
    Set tsOutput = fso.CreateTextFile(strOutputPath, True)
    
    ' --- ヘッダー行の処理 --- 
    If Not tsInput.AtEndOfStream Then
        strLine = tsInput.ReadLine ' ヘッダー行を読み込む
        tsOutput.WriteLine strLine ' ヘッダー行を出力ファイルに書き込む
        
        ' ヘッダーをカンマで分割し、列名とインデックスを関連付けるDictionaryを作成
        vHeader = Split(strLine, ",")
        Set dicHeader = CreateObject("Scripting.Dictionary")
        For i = LBound(vHeader) To UBound(vHeader)
            dicHeader.Add vHeader(i), i ' 列名 -> インデックス
        Next i
    Else
        MsgBox "入力ファイルが空です。", vbExclamation
        GoTo CleanUp
    End If
    
    ' --- データ行の処理 --- 
    ' 抽出条件1の列インデックスを取得
    If Not dicHeader.Exists(CONDITION_COLUMN_NAME1) Then
        MsgBox "指定された列名が見つかりません: " & CONDITION_COLUMN_NAME1, vbCritical
        GoTo CleanUp
    End If
    Dim colIndex1 As Long
    colIndex1 = dicHeader.Item(CONDITION_COLUMN_NAME1)
    
    ' 抽出条件2の列インデックスを取得
    If Not dicHeader.Exists(CONDITION_COLUMN_NAME2) Then
        MsgBox "指定された列名が見つかりません: " & CONDITION_COLUMN_NAME2, vbCritical
        GoTo CleanUp
    End If
    Dim colIndex2 As Long
    colIndex2 = dicHeader.Item(CONDITION_COLUMN_NAME2)
    
    ' ファイルの最後までループ
    Do While Not tsInput.AtEndOfStream
        strLine = tsInput.ReadLine ' 1行読み込む
        vData = Split(strLine, ",") ' カンマで分割して配列に入れる
        
        ' データがヘッダーの列数より少ない場合はスキップ(不正な行の可能性)
        If UBound(vData) < UBound(vHeader) Then
            ' Debug.Print "Warning: Skipping malformed line: " & strLine
            GoTo NextLine
        End If
        
        ' 数量の値を数値に変換 (エラーが発生する可能性があるのでOn Error Resume Next)
        On Error Resume Next
        lQuantity = CLng(vData(colIndex2))
        On Error GoTo ErrorHandler ' エラーハンドラを元に戻す
        
        ' --- 条件抽出ロジック --- 
        ' 条件1: 商品名が「りんご」 AND 条件2: 数量が100以上
        If vData(colIndex1) = CONDITION_VALUE1 And lQuantity >= CONDITION_VALUE2 Then
            tsOutput.WriteLine strLine ' 条件に合致した行を出力ファイルに書き込む
        End If
NextLine:
    Loop
    
    MsgBox "CSVデータの抽出と出力が完了しました。" & vbCrLf & _
           "出力ファイル: " & strOutputPath, vbInformation
    
CleanUp:
    ' ファイルを閉じる
    If Not tsInput Is Nothing Then tsInput.Close
    If Not tsOutput Is Nothing Then tsOutput.Close
    
    ' オブジェクトを解放
    Set tsInput = Nothing
    Set tsOutput = Nothing
    Set fso = Nothing
    Set dicHeader = Nothing
    Exit Sub
    
ErrorHandler:
    MsgBox "エラーが発生しました: " & Err.Description, vbCritical
    GoTo CleanUp
    
End Sub

3. コードの解説

上記のコードの主要な部分を解説します。

① FileSystemObject (FSO) の利用

  • Set fso = CreateObject("Scripting.FileSystemObject")
    FSOは、ファイルの読み書き、フォルダの作成、ファイルの削除など、ファイルシステムに関する操作を行うためのオブジェクトです。VBAでテキストファイルを扱う際の基本となります。
  • fso.OpenTextFile(パス, モード)
    指定したパスのテキストファイルを開きます。モードには1 (ForReading)で読み込み、2 (ForWriting)で書き込み、8 (ForAppending)で追記を指定します。
  • fso.CreateTextFile(パス, 上書きフラグ)
    新しいテキストファイルを作成します。上書きフラグをTrueにすると、既存のファイルがあれば上書きされます。

② CSVファイルの読み込みとヘッダー処理

  • tsInput.ReadLine
    開いたテキストファイルから1行ずつデータを読み込みます。ファイルの終端に達するとtsInput.AtEndOfStreamがTrueになります。
  • vHeader = Split(strLine, ",")
    読み込んだ行(文字列)をカンマ,で分割し、文字列の配列に格納します。
  • Set dicHeader = CreateObject("Scripting.Dictionary")
    ヘッダー行から列名とそのインデックス(列番号)を対応付けるために、Dictionaryオブジェクトを使用しています。これにより、列の並び順が変わっても、列名を指定して正しいデータを取得できるようになります。

③ 条件抽出ロジック

  • vData = Split(strLine, ",")
    ヘッダーと同様に、データ行もカンマで分割して配列に格納します。
  • lQuantity = CLng(vData(colIndex2))
    抽出条件に数値を使う場合、CSVから読み込んだデータは文字列なので、CLng(長整数型に変換)などの関数を使って数値型に変換する必要があります。変換エラーを避けるため、On Error Resume Next を一時的に使用しています。
  • If vData(colIndex1) = CONDITION_VALUE1 And lQuantity >= CONDITION_VALUE2 Then
    ここでは、「商品名が『りんご』」かつ「数量が『100』以上」という2つの条件をAndで結合しています。条件に合致した場合のみ、その行をtsOutput.WriteLine strLineで出力ファイルに書き込みます。

④ ファイルのクローズとオブジェクトの解放

  • tsInput.Close, tsOutput.Close
    ファイルの読み書きが終わったら、必ずファイルを閉じます。
  • Set fso = Nothing など
    使用したオブジェクトは、メモリを解放するためにNothingを設定して明示的に解放することが推奨されます。

応用と注意点

複数条件での抽出

コード内のIf vData(colIndex1) = CONDITION_VALUE1 And lQuantity >= CONDITION_VALUE2 Thenの部分を変更することで、様々な条件に対応できます。

  • OR条件: If 条件1 Or 条件2 Then
  • 部分一致: If InStr(vData(colIndex1), "りんご") > 0 Then (商品名に「りんご」が含まれる場合)
  • 日付条件: CDate(vData(colIndex3)) >= CDate("2023/01/01") (日付型に変換して比較)

エラーハンドリング

本コードではOn Error GoTo ErrorHandlerを使って基本的なエラー処理を実装しています。ファイルが存在しない場合や、数値変換に失敗した場合など、予期せぬエラーが発生した際にプログラムが異常終了しないように、適切なエラーハンドリングを実装することは重要です。

処理速度の最適化

非常に大きなCSVファイル(数万行以上)を扱う場合、行ごとの処理が遅くなることがあります。その場合、以下のような最適化を検討してください。

  • 配列への一括読み込み: ファイル全体を一度に配列に読み込んでから処理することで、I/O(入出力)の回数を減らせます。
  • 画面更新の停止: Application.ScreenUpdating = False をコードの先頭に、Application.ScreenUpdating = True を終了時に記述することで、Excelの画面更新による処理遅延を防ぎます。

まとめ

Excel VBAを使ってCSVデータから特定の条件でデータを抽出し、新しいCSVファイルとして出力する方法を解説しました。FileSystemObjectを用いたファイルの読み書き、Split関数でのデータ分割、Dictionaryでのヘッダー管理、そして条件分岐によるデータ抽出という一連のプロセスを理解できたでしょうか。

このマクロを応用することで、日々のデータ集計、レポート作成、データクリーニングなど、多岐にわたる業務を自動化し、作業時間を大幅に短縮することが可能です。ぜひご自身の業務に合わせてカスタマイズし、VBAによる業務効率化を実感してください。

さらに複雑なデータ処理や、データベースとの連携など、VBAの可能性は無限大です。これからも「とあるシステム屋のネタログ」では、現場で役立つ実践的なVBAテクニックをご紹介していきますので、ぜひご期待ください!

コメント

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