久々のVBA ノンプロ研中級講座の内容を活かす?
こんにちは。
派犬事務員のコロ子です。
実は去年12月にノンプロ研中級講座が終わってから、全くVBAを書いていなかった。
4月から部署を異動するので、ルーチン業務から外れ毎日雑用の日々。
最近はPython(GASにも)に気を取られてVBAはもうしばらくいいかなー、気分になっていた。
そんなダラダラした気分のところに上司から「マクロ作って」の依頼がきた。
VBAもういいや、な気分だったのに、話を聞いたらノンプロ研中級講座の内容がめちゃめちゃ使えそうだったから、少し気分が乗ってきた。
しかし、3ヶ月VBAをやってなかっただけなのに、関数の外にコード書いたり、変数を宣言するのを忘れたり、もうVBA忘れちゃったのかものひどい有り様だった。
ゆっくり良く考えてかっこいいコードが書きたかったけど、「急ぎ」だったので、とりあえず急いで作った。もっといい方法がないか今日考えたけど、時間があるからといっていいアイデア浮かぶわけでもない・・・。
でもノンプロ研中級講座の内容を盛り込めたと思う。
【内容】
下記のようなデータを番号、Lotが同じもので集計する。
データ1、データ2、データ3は各々最大値と最小値を除いて平均する。
表をテーブルにする。
表の中にカーソルを置いて「Ctrl+T」を押すとテーブルになる!
色が付いてオシャレになっただけじゃない。
テーブルにすると表をオブジェクトとして扱えるから便利。
考え方
①表の「番号」と「Lot」を繋げた文字列をキーにする。
【例】
2行目の場合は「BBBabc」がキー
②表の1行分のデータを格納するクラスを用意する。
③表を1行づつ読み込んで、Dictionaryオブジェクトに格納する
Key:①で作成したキー
Item:②のクラスを配列にして、配列のインデックス番号
Dictionaryオブジェクトに格納する際、Keyがすでに存在していたら、そのインデックス番号のクラスのデータを集計する。
コード
クラスモジュール RawDataクラス
Private no_ As String Private lot_ As String Private data1_ As Long Private data2_ As Long Private data3_ As Long Private max1_ As Long Private min1_ As Long Private max2_ As Long Private min2_ As Long Private max3_ As Long Private min3_ As Long Private cnt_ As Long '*初回の値をセット Public Sub Init(ByVal list As ListRow) no_ = list.Range(cnNo) lot_ = list.Range(cnLot) data1_ = list.Range(cnData1) max1_ = data1_ min1_ = data1_ data2_ = list.Range(cnData2) max2_ = data2_ min2_ = data2_ data3_ = list.Range(cnData3) max3_ = data3_ min3_ = data3_ cnt_ = 1 End Sub '*加算 Public Sub Add(ByVal list As ListRow) data1_ = data1_ + list.Range(cnData1) Call MaxMin(max1_, min1_, list.Range(cnData1)) data2_ = data2_ + list.Range(cnData2) Call MaxMin(max2_, min2_, list.Range(cnData2)) data3_ = data3_ + list.Range(cnData3) Call MaxMin(max3_, min3_, list.Range(cnData3)) cnt_ = cnt_ + 1 End Sub '最大値と最小値を探す '(max, minはByRefにして変数の中身を置き換える) Public Sub MaxMin(ByRef max As Long, ByRef min As Long, ByVal Data As Long) If Data > max Then max = Data ElseIf Data < min Then min = Data End If End Sub '番号 Public Property Get No() As String No = no_ End Property 'ロット Public Property Get Lot() As String Lot = lot_ End Property 'データ1 Public Property Get Data1() As Long Data1 = WorksheetFunction.Round((data1_ - max1_ - min1_) / (cnt_ - 2), 0) End Property 'データ2 Public Property Get Data2() As Long Data2 = WorksheetFunction.Round((data2_ - max2_ - min2_) / (cnt_ - 2), 0) End Property 'データ3 Public Property Get Data3() As Long Data3 = WorksheetFunction.Round((data3_ - max3_ - min3_) / (cnt_ - 2), 0) End Property
Sheet1モジュール
Public Property Get Key(list As ListRow) As String Key = list.Range(cnNo) & list.Range(cnLot) End Property
Sheet2モジュール
Public Sub Del() Me.UsedRange.Offset(1, 0).Delete End Sub Public Sub MakeStyle() '罫線 Me.Range("A2").CurrentRegion.Borders.LineStyle = xlContinuous 'ソート Me.Range("A2").CurrentRegion.Sort Key1:=Range("A1"), _ Order1:=xlAscending, _ Key2:=Range("B1"), _ Order2:=xlAscending, _ Header:=xlYes End Sub
標準モジュール
参照設定:Microsoft Scripting Runtimeを忘れずに。
Public Enum ColumnName cnNo = 1 cnLot cnData1 cnData2 cnData3 End Enum Public Sub Aggregate() Dim list As ListObject Set list = Sheet1.ListObjects(1) Dim ran As ListRow Dim Dic As Dictionary Set Dic = New Dictionary 'RawDataクラスは動的配列にする Dim Data() As RawData Dim i As Long 'データ読み込み For Each ran In list.ListRows Dim kye As String kye = Sheet1.Key(ran) If Dic.Exists(kye) Then 'すでにkeyが存在している時は、配列のインデックス番号のデータを加算 Call Data(Dic.Item(kye)).Add(ran) Else 'keyが存在しない(初めて)時は、Dicにデータを格納 ReDim Preserve Data(i) Set Data(i) = New RawData Dic.Add kye, i Call Data(i).Init(ran) i = i + 1 End If Next ran Call Sheet2.Del 'データ書き出し For i = 2 To UBound(Data) + 2 Sheet2.Cells(i, cnNo).Value = Data(i - 2).No Sheet2.Cells(i, cnLot).Value = Data(i - 2).Lot Sheet2.Cells(i, cnData1).Value = Data(i - 2).Data1 Sheet2.Cells(i, cnData2).Value = Data(i - 2).Data2 Sheet2.Cells(i, cnData3).Value = Data(i - 2).Data3 Next i Call Sheet2.MakeStyle Sheet2.Activate End Sub
集計結果
こんな感じで集計。
①シートモジュールを使う
②配列
③クラス
④テーブル
などなど、中級講座の内容。
今だから正直に言うけど、テーブルについては知らなかったし、クラスもちょっと自信なかった。TAじゃなくて受講するレベルだった疑惑があるけど、テーブルもクラスも使って作ったから許して~。