mugaxのなんでも情報局

いろんな分野について発信していきます。

VBAでオートフィルターを使うと便利だけど、使う必要がないこともある

表から特定の行にだけ処理をしたいことがある。例えば、バスケットボールチームの選手リストがあって、ポジションがPGの選手の行にだけ色を塗りたいといった場合だ。

とりあえず、Wリーグの公式サイトに載っているシャンソンVマジックの選手リストから作成した表を例にしてコードを書いてみる。

シャンソンVマジック サマーキャンプ22のロスター
'----------------------------------------
' PGの行に色を塗る
'----------------------------------------
Sub ChansonAutoFilterTest01()

    Dim dataTable As Range
    
    ' 表の範囲
    Set dataTable = Sheet2.Range("A1").CurrentRegion

    ' ポジション列にフィルターをかける
    dataTable.AutoFilter Field:=3, Criteria1:="PG"

    ' フィルターをかけた行に色を塗る(項目行は除く)
    With dataTable
        .Offset(1, 0).Resize(.Rows.Count - 1).Interior.Color = RGB(0, 255, 0)
    End With

    ' フィルター解除
    dataTable.AutoFilter

End Sub

オートフィルターを使って書いたが、オートフィルターを使わずにループ処理で書くこともできる。どちらがいいのだろうか?

ループ処理で書けば、AutoFilterメソッドについて勉強しなくて済む。しかし、処理速度はループよりオートフィルターの方が速い。だから、オートフィルターを使った方がいいと思う。

もちろん、今回のように17行しかない表ならどちらでも差はないが、これは説明用のサンプルコードである。

次に、PGの選手の名前だけ取得するコードを書いてみる。

'----------------------------------------
' PGの名前のみ取得
'----------------------------------------
Sub ChansonAutoFilterTest02()

    Dim dataTable As Range
    Dim filteredRow As Range
    Dim nameItem As Variant
    
    Dim nameList As Collection
    Set nameList = New Collection
    
    ' 表の範囲
    Set dataTable = Sheet2.Range("A1").CurrentRegion

    ' ポジション列にフィルターをかける
    dataTable.AutoFilter Field:=3, Criteria1:="PG"

    ' フィルター後の名前列のデータをリストに追加
    For Each filteredRow In dataTable.SpecialCells(xlCellTypeVisible).Rows

        nameList.Add filteredRow.Cells(1, 2).Value

    Next
    ' 項目名を削除
    nameList.Remove 1

    ' フィルター解除
    dataTable.AutoFilter

    ' 名前を表示
    For Each nameItem In nameList
        Debug.Print nameItem
    Next
End Sub

こちらは少し面倒な処理が必要になる。フィルターをかけた後、そのまま名前列のみを取得しようとすると、すべてのデータを取得してしまう。だから、 SpecialCells(xlCellTypeVisible).Rowsを使って1行ずつ取り出さなければならない。

取り出したデータはコレクションに追加していくと便利だ。1行目の項目名も取り出してしまうので、nameList.Remove 1で項目名を削除している。追加や削除が簡単にできるからコレクションは便利なのだ。

最後に、PGの平均身長を求めるコードを書いてみる。

'----------------------------------------
' PGの平均身長を求める
'----------------------------------------
Sub ChansonAutoFilterTest03()

    Dim dataTable As Range
    Dim filteredRow As Range
    Dim heightItem As Variant
    Dim heightSum As Long
    
    Dim heightList As Collection
    Set heightList = New Collection
    
    ' 表の範囲
    Set dataTable = Sheet2.Range("A1").CurrentRegion

    ' ポジション列にフィルターをかける
    dataTable.AutoFilter Field:=3, Criteria1:="PG"

    ' フィルター後の身長列のデータをリストに追加
    For Each filteredRow In dataTable.SpecialCells(xlCellTypeVisible).Rows

        heightList.Add filteredRow.Cells(1, 4).Value

    Next
    ' 項目名を削除
    heightList.Remove 1

    ' フィルター解除
    dataTable.AutoFilter

    ' 身長の合計を求める
    For Each heightItem In heightList
        heightSum = heightSum + heightItem
    Next
    
    Debug.Print "平均身長 : " & (heightSum / heightList.Count)
End Sub

やっていることは名前を取得するコードとほぼ同じだ。コレクションの扱い方の復習にちょうどいいと思って作ってみた。しかし、平均身長を求めるだけなら、オートフィルターを使うより簡単な方法がある。ワークシート関数のSumIfとCountIfを使う方法だ。

'-------------------------------------------
' PGの平均身長を求める(SumIf,CountIfを使う)
'-------------------------------------------
Sub ChansonSumIfTest01()

    Dim dataTable As Range
    Dim heightSum As Long
    Dim dataCnt As Long
    
    ' 表の範囲
    Set dataTable = Sheet2.Range("A1").CurrentRegion

    ' 合計とデータの個数を求める
    heightSum = WorksheetFunction.SumIf(dataTable.Columns(3), "PG", dataTable.Columns(4))
    dataCnt = WorksheetFunction.CountIf(dataTable.Columns(3), "PG")
    
    Debug.Print "平均身長 : " & (heightSum / dataCnt)
End Sub

明らかにこちらの方がわかりやすいし、バグが入る余地も少ないだろう。

VBAは、このようにいろいろな書き方があるからややこしい。ちなみに、いずれのコードでも使っているSheet2というのはシートをシートコードで指定した書き方だ。

mugax.hatenablog.com

どの方法を選択するかで大いに迷う、これはVBAを使うにあたって避けては通れない道なのだ。