• 締切済み

エクセルVBAで月別最大値を取得したい

いつもお世話になっております。 要件がエクセルVBAで・・という事で困っています。エクセルの2010を使用しています。 今、添付画像のように、A列に日付、B列に金額が入っており、D列に月表示、E列に各月の金額MAXを入れたいのですが、どのように記述したら良いかわかりません。 調べてみたところ、月の切り替わりに空白が挿入されていれば、下記ソースで月毎のMAXが 抜けることが分かりました(D、E列への転記はわかりませんでしたが・・)。 Sub test1() Dim Rng As Range Dim c As Range Set Rng = Range(Range("B1"), Cells(Rows.Count, Range("B1").Column).End(xlUp))_ .SpecialCells(xlCellTypeConstants) For Each c In Rng.Areas c(c.Count).Offset(1).Value = WorksheetFunction.Max(c) Next End Sub データを抜きたいファイルおよび、シートが大量にあり、とりあえずsheet(1)だけでも 何とかしたいのですが、方法がわかりません。Accessならば集計クエリでグループ化、 最大値を抽出してやればいいのですが。 ご教示いただけますでしょうか。 宜しくお願い致します。

みんなの回答

  • bunjii
  • ベストアンサー率43% (3589/8248)
回答No.7

回答No.6で関数による処理を提示しましたがVBAのプログラムについても不得手ながらコードを書いてみました。 本来なら変数の定義をすべきところを省略していますので、ご容赦ください。 Sub test1() n = Cells(1, 2).End(xlDown).Row Max = 0 r = 2 Cells(r, 4) = WorksheetFunction.EoMonth(Cells(2, 1), 0) Cells(r, 4).NumberFormatLocal = "yyyy/m" For i = 2 To n If Cells(i, 1) > Cells(r, 4) Then Cells(r, 5) = Max Cells(r, 5).NumberFormatLocal = "\#,###" Max = Cells(i, 2) r = r + 1 Cells(r, 4) = WorksheetFunction.EoMonth(Cells(i, 1), 0) Cells(r, 4).NumberFormatLocal = "yyyy/m" End If Max = Application.WorksheetFunction.Max(Cells(i, 2), Max) Next i Cells(r, 5) = Max Cells(r, 5).NumberFormatLocal = "\#,###" End Sub

vesper580109
質問者

お礼

bunjiiさん お礼が遅くなり、大変申し訳ございませんでした。 ご教示頂いた内容にて得たい結果が得られております。 ありがとうございました。

  • bunjii
  • ベストアンサー率43% (3589/8248)
回答No.6

>要件がエクセルVBAで・・という事で困っています。 VBAに拘りますか? Excelの組み込み関数でも目的に合う処理が可能です。 D2セルへ次の数式を設定し、下へ必要数コピーします。 =IF(ISTEXT(D1),EOMONTH(A2,0),IF(MAX(A:A)>D1,EOMONTH(OFFSET($A$1,MATCH(D1,A:A),0),0),"")) 続いてE2セルへ次の数式を設定して下へ必要数コピーします。 =IF(D2="","",MAX(INDEX((A$2:A$34>EOMONTH(D2,-1))*(A$2:A$34<=D2)*B$2:B$34,0))) B列の最大行番号は実際のデータ数に合わせて修正してください。 尚、添付画像はExcel 2013で検証した結果ですがExcel 2010でも再現できるはずです。(11行目から24行目まで非表示にしています)

vesper580109
質問者

お礼

bunjiiさん お礼が遅くなり、大変申し訳ございませんでした。 VBAへの拘りという部分では、本件の要件定義になり、いろいろな方法で目的の事が実現できる中で 何を選択するか?の話になりますので、今回はVBAの選択になっています。VBA以外を選択する理由も なかったという状況です。 ありがとうございました。

回答No.5

マクロにこだわる余程の理由があるのであれば話は変わりますが、 > Accessならば集計クエリでグループ化、最大値を抽出 をご存じなのであればなおのことピボットテーブルをオススメします。 あくまでも私個人のイメージですが、いわゆるクロス集計のもう少し簡単な奴・・ という感覚ですので、アクセスをお使いになるのであれば難しいことは無いはずです。 手順はimogasiさんが仰る通りで滞りなく作成可能です。 一つだけ、グループ化の際に「年」も選択した方が正確かと。   ※年が違う○月(2017年1月と2018年1月など)が同一に扱われてしまいます。 グループ化のダイアログで、Ctrl+クリックで複数選択できますので、 「年」と「月」を選択してOKで「年」グループが出来ます。 そんなこんなで、一例として Dim DRange As Range   Range("D:E").Delete   Set DRange = Range(Range("A1"), Cells(Rows.Count, 2).End(xlUp))   ActiveWorkbook.PivotCaches.Create(SourceType:=xlDatabase, _           SourceData:=DRange, _           Version:=xlPivotTableVersion15).CreatePivotTable _               TableDestination:=Range("D1"), _               TableName:="ピボットテーブル", _               DefaultVersion:=xlPivotTableVersion15   With ActiveSheet.PivotTables("ピボットテーブル")     With .PivotFields("日付")       .Orientation = xlRowField       .Position = 1     End With     .ColumnGrand = False     .CompactLayoutRowHeader = "月別MAX"     .AddDataField .PivotFields("金額"), "合計 / 金額", xlMax     Range("D2").Group Start:=True, _              End:=True, _              Periods:=Array(False, _                     False, _                     False, _                     False, _                     True, _                     False, _                     True)     With .PivotFields("合計 / 金額")       .Function = xlMax       .Caption = "金額MAX"     End With   End With 「マクロの記録」を使って記録し、ちょっと手を加えただけですが、 参考までにどうぞ。

vesper580109
質問者

お礼

tsubu-yukiさん お礼が遅くなり、大変申し訳ございませんでした。 ピボットテーブルについては、私自身が、現状では何となく避けてしまっている部分でした。 ピボットテーブルを使って5分もかけずに処理完了している事を考えれば、時間の無駄が 発生しているとのご指摘も納得です。 これを機にどのような場合に有効に使えるのか掘り下げてみます。ありがとうございました。

  • imogasi
  • ベストアンサー率27% (4737/17068)
回答No.4

>エクセルVBAで・・という事で困っています 先輩に言われて、VBAの勉強中か? 普通は関数でやろうという人が、多いと思うが。 多分他の回答者と違って、ピボットテーブルの操作をして、マクロの記録を取ることを勧める。 例データ A,B列 日付 値 2017/9/11 12 2017/9/18 8 2017/9/30 45 2017/10/11 8 2017/10/18 34 2017/10/30 25 2017/11/11 8 2017/11/18 7 2017/11/30 28 ーー 操作 範囲指定(見出し+データ行) 挿入 ピボットテーブル OK 日付を「行」にD&D 値を「Σ値」へD&D ーー (ピボットテーブルで出たシートで) A列(日付)で右クリック 「グループ化」 月 OK (ピボットテーブルで出たシートで) B列で右クリック 値の集計方法 最大値 === マクロの記録では Sub Macro2() ' ' Macro2 Macro Range("A1:B10").Select Sheets.Add ActiveWorkbook.PivotCaches.Create(SourceType:=xlDatabase, SourceData:= _ "Sheet1!R1C1:R10C2", Version:=xlPivotTableVersion15).CreatePivotTable _ TableDestination:="Sheet7!R3C1", TableName:="ピボットテーブル5", DefaultVersion _ :=xlPivotTableVersion15 Sheets("Sheet7").Select Cells(3, 1).Select Sheets("Sheet7").Select With ActiveSheet.PivotTables("ピボットテーブル5").PivotFields("日付") .Orientation = xlRowField .Position = 1 End With ActiveSheet.PivotTables("ピボットテーブル5").AddDataField ActiveSheet.PivotTables( _ "ピボットテーブル5").PivotFields("価"), "合計 / 値", xlSum Range("A8").Select Selection.Group Start:=True, End:=True, Periods:=Array(False, False, False, _ False, True, False, False) Range("B5").Select ActiveSheet.PivotTables("ピボットテーブル5").PivotFields("合計 / 価").Function = xlMax End Sub となった。 ーー ピボットテーブル5  の5などは場合に応じて変わります。 データが変わった時(本番などで)は、セル番地などはどう対応して変化するか 追ってください。 ビジネスでは、本件など、私が先輩なら、「関数やVBAでなくピボットを使え!関数やVBAは時間のロスだ」といてやるがね。 ーー その他のやり方案 1行ずつデータを読んで、年と月を出して両者を文字列結合して、何月かを判別し、その前の行までの月別の最大値(月数だけ複数設ける手もある)と比べて、今回の行が大なら、その月の最大値を置き換える、を最終行まで、繰り返す。 こういうのが普通に思いつくロジックかな。

vesper580109
質問者

お礼

imogasiさん お礼が遅くなり、大変申し訳ございませんでした。 また、いつもありがとうございます。 VBAの得意な人に話を聞くと、今回のような事例の場合には、 >その他のやり方案 でご提示頂いた ロジックで処理させる・・という回答でした。 ピボットテーブルについては、私自身が、現状では何となく避けてしまっている部分ですので、 これを機にどのような場合に有効に使えるのか掘り下げてみます。 ありがとうございました。

  • watabe007
  • ベストアンサー率62% (476/760)
回答No.3

>月の切り替わりに空白が挿入されていれば 見落としていました。対策を Sub Test2()   Dim myDic1 As Object, myDic2 As Object   Dim c As Range, ym As String   Set myDic1 = CreateObject("Scripting.Dictionary")   Set myDic2 = CreateObject("Scripting.Dictionary")   For Each c In Range("A2", Cells(Rows.Count, "A").End(xlUp))     If IsDate(c.Value) Then       ym = "'" & Format(c.Value, "yyyy/mm")       myDic1(ym) = c.Offset(, 1).Value       If myDic2(ym) < myDic1(ym) Then myDic2(ym) = myDic1(ym)     End If   Next   Range("D2").Resize(myDic2.Count).Value = Application.Transpose(myDic2.keys)   Range("E2").Resize(myDic2.Count).Value = Application.Transpose(myDic2.Items) End Sub

vesper580109
質問者

お礼

watabe007さん お礼が遅くなり、大変申し訳ございませんでした。 ご教示頂いた内容にて得たい結果が得られております。 調べてみましたら、連想配列を使っているとの事で、初めて知った内容でした。 とても面白く、今後につながりそうです。 ありがとうございました。

  • watabe007
  • ベストアンサー率62% (476/760)
回答No.2

参考に Sub Test2()   Dim myDic As Object   Dim c As Range, ym As String   Set myDic1 = CreateObject("Scripting.Dictionary")   Set myDic2 = CreateObject("Scripting.Dictionary")   For Each c In Range("A2", Cells(Rows.Count, "A").End(xlUp))     ym = "'" & Format(c.Value, "yyyy/mm")     myDic1(ym) = c.Offset(, 1).Value     If myDic2(ym) < myDic1(ym) Then myDic2(ym) = myDic1(ym)   Next   Range("D2").Resize(myDic2.Count).Value = Application.Transpose(myDic2.keys)   Range("E2").Resize(myDic2.Count).Value = Application.Transpose(myDic2.Items) End Sub

  • f272
  • ベストアンサー率46% (7998/17100)
回答No.1

日付はA2から下に並んでいる。 それに対応する金額はB列の同じ行にある。 日付は昇順に並んでいる。 日付には空白行はない。 書き出しはD列とE列におこなう。 という前提で... Sub test() Set oo = Range("a2") n = oo.End(xlDown).Row k = 0 a1 = oo.Resize(n - 1, 2) y1 = Year(a1(1, 1)) m1 = Month(a1(1, 1)) m2 = a1(1, 2) For i = 2 To n - 1 If Month(a1(i, 1)) = m1 Then If m2 < a1(i, 2) Then m2 = a1(i, 2) Else oo.Offset(k, 3) = DateSerial(y1, m1, 1) oo.Offset(k, 4) = m2 k = k + 1 y1 = Year(a1(i, 1)) m1 = Month(a1(i, 1)) m2 = a1(i, 2) End If Next i oo.Offset(k, 3) = DateSerial(y1, m1, 1) oo.Offset(k, 4) = m2 End Sub

vesper580109
質問者

お礼

f272さん お礼が遅くなり、大変申し訳ございませんでした。 ご教示頂いた内容にて得たい結果が得られております。 ありがとうございました。

関連するQ&A

  • エクセル VBA の質問です。

    A2~A20までのセルに文字を入力した段階で、それぞれB2~B20に入力日時を入れるVBAを以下のように組んでいます。 しかし、同様の条件を同一シートのE2~E20・F2~F20にも入力・自動表示できるようにしなければならなくなり、困っています。 どのように記述を変えればよいのか、教えていただけたらと思います。 宜しく御願いいたします。 Sub Worksheet_change(ByVal Target As Range) Dim Rng As Range Dim c As Range Set Rng = Range("A2:A20") If Intersect(Target,Rng) Is Nothing Then Exit Sub For Each c In Intersect(Target,Rng) If Not IsEmpty(c) Then c.Offset(, 1).Value = Now Else c.Offset(, 1).ClearContents End If Next Rng.Offset(, 1).EntireColumn.AutoFit End Sub 申し訳ありませんが、何卒、宜しく御願いいたします。

  • エクセル(2003)のVBAに関する質問です。

    エクセル(2003)のVBAに関する質問です。 例えば、B列からW列のある特定の行数までの表があるとします。 表の一番下の行(B列からW列)を368行までオートフィルを行うマクロを 以下のように考えましたが実行時エラーがでてしまいます。 Private Sub Workbook_Open() Dim rng As Range Set rng = Cells(Rows.Count, "B").End(xlUp).Resize(1, 21) rng.AutoFill Destination:=Range(rng, "W368"), Type:=xlFillSeries End Sub 初歩的な質問で恐縮ですが、ぜひ解決方法を教えて下さい。

  • VBAの記録を追加したい

    エクセル2002使用です。 VBAで次のコードを使っています。 Private Sub Worksheet_Change(ByVal Target As Range) Dim Rng As Range Set Target = Intersect(Range("C:C"), Target) If Target Is Nothing Then Exit Sub For Each Rng In Target If Rng.Value <> "" Then Rng.Offset(, -2).Value = Now Else ' (*) Rng.Offset(, -2).Value = "" ' (*) End If Next Rng End Sub (C列のセルに何か入力されると、A列の同じ行にその時刻が入る。) 同じシートで、F列に何か入力されるとE列の同じ行にその時刻が入るように書き直したいのですが、どうすればいいのでしょうか? すいませんが、よろしくお願いします。

  • VBA 検索して一致したセルへジャンプさせたい

    Excelにて、シート1のA列とシート2のA列のデータにNoを入れます。 シート1のA列のNoをクリックすると、シート2のA列の同じNoにジャンプするマクロを組みたいです。 現在組んでいるマクロは、 Private Sub Worksheet_SelectionChange(ByVal Target As Range) Dim Sht As Worksheet Dim Rng1 As Range Dim Rng2 As Range Dim FindCell As Range Set Sht = Worksheets("シート2") Set Rng1 = Range("A2:A100") Set Rng2 = Sht.Range("A2:A100")If Intersect(Target, Rng1) Is Nothing Then Exit Sub If Target.Count > 1 Then Exit Sub Set FindCell = Rng2.Find(Target.Value) If Not FindCell Is Nothing Then Application.Goto Reference:=FindCell, Scroll:=False End If End Sub です。 一応マクロは実行されますが、そうすると、シート1のA列の編集(Noを追加したり変更したり・・・)できません。 編集や変更もできて、検索マクロも実行できるというマクロの組み方はありますでしょうか?

  • エクセル VBA

    A1,A2,A3→RAND()*99+1 B1→MAX(A1:A3) Sub test() Worksheets("sheet1").Calculate Dim x As Integer x = Range("B1") Range("C1").Value = x End Sub このように記述すると、B2とC2で結果が変わってしまうのですが、どうしてでしょうか?結果を同じにするにはどうすればいいですか?

  • エクセルVBAで表から行の削除

    添付画像のような表があります。 表はB列の名前でソートされています。 D列の比率をみて、100でないものは、必ず同じ名前で複数行にわかれ合計で100になります。この例では名前CとEとHがそうです。 同じ名前が複数行にわかれている場合、最大の比率の行を残し、他の行(例では、埼玉、栃木、長野、新潟の行)を削除したいのです。 複数行にわかれるのが名前CやEのように2行なら、以下のコードで出来ました。 しかし、めったにはありませんが名前Hのような3行以上に分かれるものには対応できません。 どうすればよいでしょうか? Sub test01()   Dim c As Range   Dim Rng As Range   Set Rng = Range(Cells(2, "D"), Cells(Rows.Count, "D").End(xlUp))   For Each c In Rng '2地区の分担の場合、分担比率高い方を残す。(3地区以上は未対応)2012/08/29     If c.Value <> 100 And c.Offset(1).Value <> 100 Then       If c.Offset(, -2).Value = c.Offset(1, -2).Value Then         If c.Value >= c.Offset(1).Value Then           c.Offset(1).Value = False         Else           c.Value = False         End If       End If     End If   Next   If Application.WorksheetFunction.CountIf(Rng, False) > 0 Then     Rng.SpecialCells(xlCellTypeConstants, xlLogical).EntireRow.Delete   End If End Sub

  • エクセルでの複数VBAの作業

     こんばんは。  お世話になります。  以下のコードをMicrosoft Visual Basicの「標準モジュール画面」にて記述し、作動させてみたのですが、  2つ目のSub lll()のみしか反映されないようで、”S”行のみしか値がえられませんでした。  何が問題なのか、初心者のわたくしには、わかりません。  お手数ですが、原因等をお教えいただければ、幸いでございます。 Sub hhh() Dim n As Long Dim rng As Range n = 2000 ReDim hh(1 To n, 1 To 1) Set rng = Range("C2:C31") For i = 1 To n hh(i, 1) = WorksheetFunction.Max(rng) Set rng = rng.Offset(30) Next i Range("R2").Resize(n) = hh End Sub Sub lll() Dim n As Long Dim rng As Range n = 2000 ReDim ll(1 To n, 1 To 1) Set rng = Range("D2:D31") For i = 1 To n ll(i, 1) = WorksheetFunction.Min(rng) Set rng = rng.Offset(30) Next i Range("S2").Resize(n) = ll End Sub

  • エクセルVBAで範囲内での位置取得(行&列)

    Sub test() Set Rng = Range("B2:E7") Rng.Cells(2, 2).Select End Sub これで、範囲Rng内では2行/2列目となるC3セルが選択されます。 では、C3セルが、範囲Rng内で何行/何列目であるかを取得するにはどのように記述すればよいのでしょうか? Rng.Cells(2, 2).Rowは、当たり前ですが、3になってしまいます。

  • エクセル 数値結果の値によって日付を入れたい

    シート2の2列目にOKが入ると、シート1のC列にOKが入り、更新された日がB列に表示されるようにしたいです。 C列に手入力でOKと入力すればB列に日付が表示されるのですが、C列をVLOOKで呼ぶようにしたら表示されなくなってしまいました。 どのように修正していいのか分かりません。 お教えいただければと思います。よろしくお願いいたします。 Private Sub Worksheet_Change(ByVal Target As Range) Dim Rng As Range Dim TgRng As Range Set TgRng = Intersect(Range("C1:C2000"), Target) If Not TgRng Is Nothing Then Application.EnableEvents = False For Each Rng In TgRng If Rng.Value = "OK" Then Rng.Offset(, -1).Value = Date End If Next Application.EnableEvents = True End If Set TgRng = Nothing End Sub

  • ExcelにおけるVBAで、データの振り分けをしたい

    Sub a() Dim i As Long Dim s As Long Dim b As Worksheet Dim c As Worksheet Dim maxi As Long Dim maxs As Long Application.ScreenUpdating = False With ThisWorkbook Set b = .Worksheets("bbb") Set c = .Worksheets("ccc") End With maxi = b.Range("A1").CurrentRegion.Rows.Count maxs = c.Range("A1").CurrentRegion.Rows.Count For i = maxi To 2 Step -1 For s = maxs To 2 Step -1 If c.Cells(s, 1) = b.Cells(i, 1) And c.Cells(s, 2) = b.Cells(i, 2) Then c.Cells(s, 14) = b.Cells(i, 3) End If Next s Next i Application.ScreenUpdating = True End Sub ワークシートcとワークシートbの1列目と2列目が一致したときにだけ、cの14列目にbの3列目のデータを代入したく思い、以上のようなコードを書きましたが、重くて終わる様子がありません。 ・そもそもあっているのか、あっていないならどこを修正すればよいか ・早く終わるようにするにはどうしたらいいか アドバイスをよろしくお願いします。

専門家に質問してみよう