EXCEL のワークシートに挿入されたテーブルにはデフォルトでオートフィルターが設定されています.このテーブルに対してオートフィルターをかけた結果を VBA で取得する方法は難解で,従来の考え方とは少し異なります.
より抽象度の高い考え方をする必要があります.リレーショナルデータベースの概念である集合論を理解する必要があります.
テーブル=ListObjectオブジェクト
テーブルのオートフィルターの結果を取得するには ListObject オブジェクトを理解する必要があります.セル範囲をテーブルにする(OfficeTanaka)に詳細がありますが,ListObject オブジェクトはテーブルを表しており,親オブジェクトはワークシートです.また作成したテーブルには名前が自動的につきます.
テーブルの挿入
ワークシートにテーブルを挿入するには「挿入」タブの「テーブル」をクリックします.
ActiveSheet.ListObjects.Add xlSrcRange, Range("A1").CurrentRegion
ワークシートをコピーしてマスターテーブルを作成する
EXCEL で作業していると気がつかないこともあるのですが,データベースを使っているとマスターテーブルというものが存在することに気がつきます.
今回は実作業に入る前に,作業用データの入ったワークシートをコピーしてマスターテーブルを作成しましょう.
ワークシートをコピーします.「テーブルデザイン」タブの「ツール」の「重複の削除」をクリックします.
ダイアログの初期状態です.全てのタイトルにチェックが入っています.
主キーである City_Code のみチェックしてOKします.
不要な列を削除し,City_Code, City_Type, Prefecture_Code, Prefectures, City の5列を残します.
ActiveSheet.Range("テーブル22[#すべて]").RemoveDuplicates Columns:=3, Header:=xlYes
マスターテーブルの入っているワークシート名を CITY_CODE とします.また,元のデータの入っているワークシート名を DATA とします.
散布図のデータ系列には「系列名」「系列Xの値」「系列Yの値」が必要
図を見ると分かりますが,散布図のデータソースの選択においてはデータ系列を指定する必要があります.
データ系列に必要なのは「系列名」「系列Xの値」「系列Yの値」です.
「系列名」はデータ系列が一個の場合はグラフのタイトルになり,複数の場合には凡例になります.
「系列Xの値」と「系列Yの値」はそれぞれ散布図上の座標の (X, Y) の値に相当し,通常ペアをなしています.通常は対応する同じ大きさのセル範囲,つまり配列を指定します.
また,データ系列は複数指定することができます.
ここから,「系列Xの値」と「系列Yの値」には VBA 上で配列を直接指定することが可能なのではないか?という推測が成り立ちます.今回はこの仮設を検証しましょう.
処理の流れ
マスターテーブルのキーをループする
20 行目から 21 行目でマスターテーブルのセル範囲を取得しています.この範囲を一個ずつループ(24 行目と 47 行目)してフィルターのキーとします.
データテーブルをキーでオートフィルターする
ワークシート DATA にあるテーブル(25 行目と 43 行目)に対してオートフィルターをかけます(26 行目).41 行目でいったんオートフィルターを解除します.
オートフィルターの結果を取得する
オートフィルターの結果を取得する方法は従来とは異なり,ListObject オブジェクトから取得する必要があります.
トリッキーな方法ですが, .SpecialCells (xlCellTypeVisible) プロパティを用いるのが確実です.タイトル行も取得されるため,.DataBodyRange との論理積を取ってデータだけを取得しています(27 行目).
取得した値を配列に格納する
動的配列をループの中で初期化し,配列 myXValue(), myValue() に格納します(30 行目から 36 行目).この配列は使い回すため(29 行目,39 行目),値の保持は行いません.
Debug.Print でオブジェクト取得や配列への格納を確認する
コメントアウトした Debug.Print は Range オブジェクトが取得できているか,配列に値が格納されているかの確認用です(28 行目,37 行目).
配列をデータ系列に渡す処理
動的配列に格納された値を散布図のデータ系列に渡す処理は 40 行目に書きます.今回はそこまで行いません.後日確認してから記事を書きます.
Sub GetDataSeries() Dim mySht1 As Worksheet Dim mySht2 As Worksheet Dim myRng1 As Range Dim myRng2 As Range Dim myRng3 As Range Dim myName As String Dim myXValue() As Double Dim myValue() As Long Dim i As Integer Dim j As Integer Set mySht1 = Worksheets("CITY_CODE") Set mySht2 = Worksheets("DATA") Set myRng1 = Intersect(mySht1.UsedRange, mySht1.Range("A:A")) Set myRng1 = myRng1.Resize(myRng1.Rows.Count - 1).Offset(1) i = 1 For Each myRng2 In myRng1 With mySht2.ListObjects("テーブル2").Range .AutoFilter Field:=3, Criteria1:=myRng2.Value Set myRng3 = Intersect(.SpecialCells(xlCellTypeVisible), mySht2.ListObjects("テーブル2").DataBodyRange) 'Debug.Print i, myRng3.Address For j = 0 To myRng3.Rows.Count - 1 ReDim myXValue(j) ReDim myValue(j) 'Debug.Print i, j myName = myRng3.Cells(j + 1, 6) myXValue(j) = myRng3.Cells(j + 1, 10) myValue(j) = myRng3.Cells(j + 1, 7) 'Debug.Print myName, myXValue(j), myValue(j) Next j .AutoFilter Field:=3 End With i = i + 1 Next myRng2 End Sub
まとめ
EXCEL VBA でテーブルのオートフィルター結果を取得する方法について書きました.
従来の手続き型ベースの考え方ではなく,集合ベースの考え方が要求されています.特に ListObject オブジェクトのメンバは興味深い内容です.
最近の EXCEL は PowerQuery なるデータベースソフトなどとの連携を重視しているようで,SQL Server チームとの共同作業をしているようです.
テーブルを扱うオブジェクトに関してもその影響が出ているのでしょう.