和風スパゲティのレシピ

日本語でコーディングするExcelVBA

オートフィルターで抽出中の件数をカウントする

オートフィルターで抽出したレコード件数のカウントを、コピペして使えるユーティリティ関数にしたものを紹介します。

お忙しい方は、ソースコードをコピーして、使い方のように呼び出すだけでOKです。
コードをしっかり読みたい方は、解説をどうぞ。

ソースコード

' 抽出件数のカウント
Function Countフィルター抽出中の件数(指定シート As Worksheet) As Long
    
    ' ◇ もともとデータが空のときは0(フィルター設置範囲が▼見出し1行だけのとき)
    If 指定シート.AutoFilter.Range.Rows.Count = 1 Then
        Countフィルター抽出中の件数 = 0
        Exit Function
    End If
    
    ' オートフィルター範囲1列目の可視セル数をカウント
    Countフィルター抽出中の件数 = 指定シート.AutoFilter.Range.Columns(1) _
                                                .SpecialCells(xlCellTypeVisible).Count - 1
End Function

使い方

Call フィルターで抽出する("みかん", くだもの売上表の列.品物, ActiveSheet)
Call フィルターで抽出する(">=10", くだもの売上表の列.個数, ActiveSheet)

If Countフィルター抽出中の件数(ActiveSheet) > 0 Then
    Call みかん箱を用意する
End If

※ 一緒に使われている関数:オートフィルターで抽出する
※ くだもの売上表の列:列番号を定数で定義したものです(B列なら2などの数値です)

コードの解説

絞り込んだレコードを数える部分の解説

最初のIf部分は例外処理なので、ひとまず置いておきます。
まずは絞り込み件数をカウントしている、メイン処理の解説から。

レコードが抽出されているかを知るには、その行が表示されているかを調べます。
よって絞り込まれたレコードは、見えている行の数 = どこか1列の見えているセルの数を数えることで計算できます。

' メインのカウント部分
指定シート.AutoFilter.Range.Columns(1).SpecialCells(xlCellTypeVisible).Count - 1

' 日本語訳
指定シート.オートフィルターエリアの第1.見えているセル.- 1

日本語訳を読めば、計算の流れは大体つかめると思います。細かい解説は↓の通り。

.AutoFilter.Range

オートフィルターがかかっている表全体(▼のついている見出しの行を含む)を、Rangeオブジェクトで取得します。

.Columns(1)

なじみのある「ワークシート.Columns(1)」だと列全体を取得しますが、
「Rangeオブジェクト.Columns(1)」だと、範囲内の第1列部分を取得できます。

例えばRange("B1:D10").Columns(1)は、
この範囲の1列目である、Range("B1:B10")を返します。

.SpecialCells(xlCellTypeVisible)

見えているセル(可視セル)をRangeオブジェクトで取得します。
行列の表示設定と、オートフィルターでの表示状況が、どちらも反映されます。

.Count - 1

.Countで見えているセルの数を数え、見出し行(▼のついている行)の分を1引いて計算終了です。

オートフィルターが空っぽのデータに設置されているときは注意!

マクロを実際の業務で使っていると、いろいろな表に出くわします。

中には、空のデータ(見出ししかないデータ)が混じることもあるでしょう。
みかんを探す以前に、そもそもくだものが一つも売れなかった日があるかもしれません。

このとき、この関数は0を返すのが自然かと思われますが、
さてこの時、上のメインの処理がどうなっているかを追ってみましょう。

① AutoFilter.Range.Columns(1).SpecialCells(xlCellTypeVisible).Count - 1
② Range("A1:C1").Columns(1).SpecialCells(xlCellTypeVisible).Count - 1
③ Range("A1").SpecialCells(xlCellTypeVisible).Count - 1
④ Range("A1").Count - 11 - 1 = 0

一見すると「見出しだけの時 = 0」になってるから、Ifの条件分岐はいらないように見えますね。


しかし残念ながら、とんでもない罠が仕掛けられていて、↑の計算の通りになりません。
③から④に進んだときの

Range("A1").SpecialCells(xlCellTypeVisible) ⇒⇒ Range("A1")

こいつが罠です。

「A1の中で見えているセル=A1」と思いたいですが、なんとこれは「シート全体で見えているセルすべて」に変換されます。
そのせいで、抽出中のレコード数に、0ではなくて532とか謎の数字が返ってきてバグります。

原因は、「SpecialCellsを単独のセルから呼び出した場合は、シート全体から呼び出されたものとみなす」というトラップです。

いつかとんでもない地雷を踏む可能性があるので、SpecialCellsを使うならこの記事も読んでおくことをおすすめします。

www.limecode.jp

もう一つの方法:SUBTOTAL関数を使う

オートフィルターで抽出中の件数を数えるやりかたとして、ワークシート関数の「SUBTOTAL関数」を使う方法もあります。

    ' セルに書くとき
    =SUBTOTAL(3,B2:B10)

    ' VBAで書くとき
    WorksheetFunction.Subtotal(3, Range("B2:B10"))

これでも抽出件数を求めることができます。


SUBTOTAL関数は、オートフィルターでの抽出が反映されるさまざまな集計を行える関数です。↑の例における「3」の部分でやりたい集計を選択します。

「3」はCOUNTAなので、今回やりたい「件数のカウント」が行えます。

「9」のSUM=合計や、「4,5」のMAX,MINなども便利ですね。
抽出データの合計や、抽出した中での最大値などを求めることができます。

SUBTOTALとSpecialCellsの違い

さて、「SUBTOTALのCOUNTA」と「SpecialCellsの可視セル」の比較ですが、
とても分かりやすく、お互い短所があります。

・SUBTOTAL

まず、SUBTOTALさんですが、「抽出中のCOUNTA」という説明を聞いて、怪しさを感じ取った方もいると思います。

SUBTOTALさんは「COUNTA=データのあるセルを数える」ことしかできません。
よって、空白セルのある列では使うことができません

データが埋まっている列を選んで実行する必要があります。

・SpecialCells

次はSpecialCellsさんですが、こちらは「表示されているセル」を数えるため、手動で非表示にした行や列が反映されてしまいます

行を非表示にすると、レコードも減って計算されますし、
フィルターの一番左の列を非表示にすると、↑のユーティリティ関数は常に「0」を返すようになってしまいます。

どう使い分けるのが良いか

↑で挙げた両方の短所の「発生率」や「発生して仕方ないか」を考えましょう。

  • 「データの中に空白セルが混じる」のは普通です。別に変な使い方ではありません。
  • 「フィルター付データを、さらに手で非表示にする」はちょっと変な使い方です。そもそも好ましい操作ではなく、件数以外の問題も起きそうです。

※ 「途中の列を非表示すること」は変な使い方ではないですが、この操作では↑の関数は正常に動きます。一番左の列でのみ問題が起こります。


こう見ると、「Excelを正しく使ったときにエラーの起きにくい」SpecialCellsさんがよさそうですね。なので、ユーティリティ関数はSpecialCellsで作りました。


さて敗れてしまったSUBTOTALさんですが、こちらにも大事な役目があります。
それはもちろん、セルに関数を入れるときです。

「○件を抽出中」や「現在の合計:○」などをセルに表示させて、
ユーザーの操作をリアルタイムで結果に反映させるのは、シート関数のお役目です。
マクロは「実行」するには便利ですが、「自動計算」には不向きですからね。

ということで、結論は
「マクロに組むならSpecialCells」「セルに打ち込むならSUBTOTAL」
です。使い分けていきましょう。

SUBTOTALをセルに入れるときは、「空白のない列」を参照するのを忘れずに。

おまけ:ワンライナー(1行で処理するコード)

' 抽出件数のカウント
Function  Countフィルター抽出中の件数(指定シート As Worksheet) As Long
     フィルター抽出中の件数 = 指定シート.AutoFilter.Range.Columns(1).Resize(, 2).SpecialCells(xlCellTypeVisible).Count / 2 - 1
End Function

この記事を書くために、自分のユーティリティ関数を見たらこうなってました。
.Columns(1).Resize(, 2) で2列分の可視セルを数えそのあと2で割ることで、単独セルになる罠を回避しています。

なるほど~とは思いますが、こういうパズル的な書き方はやってはいけません。

コードはとにかく読みやすさが大事です。
コードを5秒早く書いて、処理速度が1秒早くなっても、
後で見返してコードの意味を30秒考えたら、そのプログラムは遅いプログラムです。

このブログを書くために書き直す羽目になったので、とても遅いプログラムだったといえるでしょう。