大量のデータを絞り込むには、オートフィルタが便利です。ここでは、VBAからオートフィルタを使い倒すテクニックをご紹介します。なお、ボリュームがありますので、以下の項目にページを分けて解説します。なお、ここで解説するオートフィルタは、通常のワークシートに設定されているとします。テーブルでオートフィルタを操作するときは、また別の考え方が必要です。VBAでテーブルを扱う方法に関しては「VBAでテーブルの操作」をご覧ください。
オートフィルタは、絞り込んで終わりではありません。絞り込むことがゴールではなく、絞り込んだ後に、必ず何かの操作をします。たとえば、絞り込んだ結果をコピーしたり編集したり削除したりと。しかし、そうした操作のとき「オートフィルタで絞り込んだ結果"全体"に対して何かの操作をすると、Excelは"表示されているセル"だけを対象にする」という特性を理解していると、簡単に実現できます。たとえば、絞り込んだ結果を別のシートにコピーするやり方は「絞り込んだ結果をコピーする」を参照してください。ほかにも、いくつかやってみましょう。なお、ここでは、絞り込んだ結果の実データだけを操作します。そのやり方は「絞り込んだ結果をコピーする」の"自分でResizeしちゃう法"で記述します。もし"Excelおまかせ法"の方が分かりやすいのでしたら、そちらを使ってください。
A列を"田中"で絞り込みます。
Sub Macro1() Range("A1").AutoFilter 1, "田中" End Sub
絞り込んだ結果の背景色を赤にします。
Sub Macro1() Range("A1").AutoFilter 1, "田中" With Range("A1").CurrentRegion.Offset(1, 0) .Resize(.Rows.Count - 1).Interior.ColorIndex = 3 End With End Sub
オートフィルタを解除します。
ね?うまくいくでしょ。今度は、実務でよくやる「"田中"の行だけ削除」をやってみます。
A列を"田中"で絞り込みます。
Sub Macro2() Range("A1").AutoFilter 1, "田中" End Sub
絞り込んだ結果の行全体を削除します。
Sub Macro2() Range("A1").AutoFilter 1, "田中" With Range("A1").CurrentRegion.Offset(1, 0) .Resize(.Rows.Count - 1).EntireRow.Delete End With End Sub
オートフィルタを解除します。
ね?うまくいくでしょ。可視セルなんて選択しなくても、Excelは「オートフィルタで絞り込んだ結果"全体"に対して何かの操作をすると、"表示されているセル"だけを対象にする」んです。
とはいえ、実務って一筋縄ではいきません。いろんなことを無茶ぶりされますw ときには、絞り込んだ"全体"に対してではなく、絞り込んだ結果を"セル単位"で何かしたいこともあります。ここでは、そのやり方を解説します。
まずは、表示されている行だけを個別に特定する方法です。A列を"田中"で絞り込み、その実データだけを特定します。
Sub Macro3() Range("A1").AutoFilter 1, "田中" With Range("A1").CurrentRegion.Offset(1, 0) .Resize(.Rows.Count - 1).Select End With End Sub
このままでは、非表示のセル(行)も含まれています。ここに何かの操作をすると、Excelが"見えているセルだけ"うまくやってくれるのですが、今回はそうしたExcelの"おまかせ"ではありません。VBAのコードで明示的に1行ずつ特定したいんです。なので、まずはこの範囲の表示されているセルだけを特定します。それには、SpecialCellsプロパティを使います。
Sub Macro3() Range("A1").AutoFilter 1, "田中" With Range("A1").CurrentRegion.Offset(1, 0) .Resize(.Rows.Count - 1).SpecialCells(xlCellTypeVisible).Select End With End Sub
違いが分かりますか?今度は選択範囲が非連続になっています。この範囲で、行を個別に操作します。ここはFor Eachを使いましょう。For Eachの考え方は次のとおりです。
「行」というのはFor Eachで使う制御変数です。特に深い意味はありませんが、行ですから「Row」の頭文字である「R」にしましょうか。
さて「表示されているすべてのセル」が
With Range("A1").CurrentRegion.Offset(1, 0) .Resize(.Rows.Count - 1).SpecialCells(xlCellTypeVisible) End With
でしたよね。今回欲しいのは「すべてのセル」じゃなく、その中にある「すべての行」です。そこで、次のようにします。
最後の「Rows」がポイントです。なお、ここで使う制御変数「R」はRange型で宣言します。Row型というオブジェクト型はありません。ここまでを確認してみましょう。
Sub Macro4() Dim R As Range Range("A1").AutoFilter 1, "田中" With Range("A1").CurrentRegion.Offset(1, 0) For Each R In .Resize(.Rows.Count - 1).SpecialCells(xlCellTypeVisible).Rows Debug.Print R.Address Next R End With End Sub
うまくいきました。あとは、この行内のセルを個別に操作してやればいいです。
行を特定することはできました。あとは、行内のセルをひとつずつ指定する方法です。それには、RangeとCellsの、ちょっと変わった使い方がポイントです。
ワークシート全体に対して、RangeやCellsを使うと、次のようになります。
Sub Macro5() ActiveSheet.Range("B2") = "○" ActiveSheet.Cells(4, 3) = "×" End Sub
これはいいですね。では、RangeやCellsを特定のセル範囲に対して使ってみましょう。
上図で選択しているのは、セル範囲C4:F9です。ここに対してRangeとCellsを使ってみます。
Sub Macro6() Range("C4:F9").Range("B2") = "○" Range("C4:F9").Cells(4, 3) = "×" End Sub
分かりますか?RangeとCellsは、特定のセル範囲に対して使うと、そのセル範囲での位置を表します。
これは、ここで指定したセル範囲C4:F9を、ひとつのワークシートに見立てたようなイメージです。
さあ、よろしいですか?では、いよいよ、オートフィルタで絞り込んだ行内のセルを操作する考え方です。オートフィルタによって表示されているセル範囲から、行をひとつずつ特定します。さっきのFor Eachによれば、その行は、制御変数「R」で表されるんでしたよね。
これは、すべての行について同じことです。
それでは、いくつかの例をお見せしましょう。まずは、下図のような表があったとします。
この表で、A列が"田中"の行だけ、E列に数値1~数値3の合計を代入したいです。こんな感じに。
この場合、すべての行で、次のように考えます。
Sub Macro7() Dim R As Range Range("A1").AutoFilter 1, "田中" With Range("A1").CurrentRegion.Offset(1, 0) For Each R In .Resize(.Rows.Count - 1).SpecialCells(xlCellTypeVisible).Rows R.Range("E1") = WorksheetFunction.Sum(R.Range("B1:D1")) Next R End With Range("A1").AutoFilter End Sub
これ、VBAのセミナーでだいたい半年に1回くらいのペースで質問されますw たとえば下図のような表で
A列が"田中"の行にだけ連番を振りたいと。下図みたいに。
ここまでの解説を、よ~く読んで、ちゃ~んと理解してくださいね。そしたら、何でもできます。もう詳細な解説はしません。
Sub Macro8() Dim R As Range, N As Long Range("A1").AutoFilter 1, "田中" With Range("A1").CurrentRegion.Offset(1, 0) For Each R In .Resize(.Rows.Count - 1).SpecialCells(xlCellTypeVisible).Rows N = N + 1 R.Range("C1") = N Next R End With Range("A1").AutoFilter End Sub
上図を下図のようにします。
Sub Macro9() Dim R As Range Range("A1").AutoFilter 1, "田中" With Range("A1").CurrentRegion.Offset(1, 0) For Each R In .Resize(.Rows.Count - 1).SpecialCells(xlCellTypeVisible).Rows R.Range("B1") = R.Range("B1") & "-" & R.Range("C1") Next R End With Range("A1").AutoFilter End Sub