[[20120120124047]] 『複数シートのデータの集計』(ミッチェル) ページの最後に飛ぶ

[ 初めての方へ | 一覧(最新更新順) | 全文検索 | 過去ログ ]

 

『複数シートのデータの集計』(ミッチェル)

 ※仕様変更があったので最初の質問を書き直して投稿します

 物流関係の会社でデータの集計をしています。

 取引先から「このアイテムをこういう組み合わせで出して」という指示が来たら、商品、パンフ、
 サンプル、情報誌などをその内容に応じて組み合わせて出荷しています。

 「指示内容」(出荷番号ごとのファイル)

    A     B    C    D   E   F
 1 発送品ID 印字記号 在庫ID 商品名 数量 種別
 2 A001    ア01   AAA  あいう  200 商品
 3 A001       イ22   ABB  かきく  50 資料
 4 B001    ウ01   ABB  かきく  20 資料
 5  C001            CCC  たちつ  150 サンプル

 出荷番号は毎日ほぼ決まっていて(XXは定期コース購入者向け、YYはサンプル希望者などの規則性がある)
 これから出荷番号ごとにどの資材をどれだけ出荷しているかを日毎に集計して、月平均などを出そうとしています。

 日毎の集計表は出荷番号ごとのシートがあり(30〜40シート)、各シートにその出荷番号で使用した資材の内訳が入っています。

 例1:出荷番号「XX」のシート

    A   B      C    D     E   F   G 
 1  番号 出荷番号 発送品ID 印字記号 在庫ID 商品名 数量
 2  1   XX    A001   ア01   AAA   あいう  200
 3  2   XX    A001     イ22   ABB   かきく  50
 4  3   XX    B001   ウ01   BBB   たちつ  20
 5   4   XX    C001           CCC   さしす  150 

 例2:出荷番号「YY」のシート

    A   B      C    D     E   F   G 
 1  番号 出荷番号 発送品ID 印字記号 在庫ID 商品名 数量
 2  1   YY    A001   ア01   AAA   あいう  150
 3  2   YY    A002          BBB   たちつ  550
 4  3   YY    B001   ウ01   BBB   たちつ  120
 5   4   YY    C001           CCC   さしす   10

 A列は連番で「=ROW()-1」の式が入っています。
 同じ発送品IDで違う商品が出ることもあり、違う発送品IDで同じ商品が出ることもあります。
 D列の「印字記号」は空白のこともあります。
 また「発送品ID」は違う出荷番号で同じ発送品IDが使われます。

 シート名は「出荷XX」のようになっています。
 その日の出荷が全て終わった時点で、「集計」シートに「発送品ID」をキーにしてそれぞれの発送品IDで
 どの商品がどれだけ使われたかの集計を取りたいです。

 例:「集計」シート

    A    B     C    D    E    F   
 1  番号 発送品ID 印字記号 在庫ID 商品名  数量
 2  1   A001   ア01    AAA   あいう  250
 3  2   A001     イ22    ABB   かきく  50
 4  3   A002          BBB   たちつ  550
 5  4   B001   ウ01    BBB   たちつ  140
 6  5   C001            CCC   さしす  160

 基本は「発送品ID」での合計個数、同じ発送品IDで複数の商品があったらその内訳の合計を出したいです。

 「データ」-「統合」を試してみましたが「発送品ID」で集約した数量の合計しか出ず、在庫IDや商品名が空欄になってしまいました。
 また「統合」では30ほどのシートを一つずつ追加しなければならず、かなり面倒です。

 SUMIFS関数で何とかできるかと思いましたがどのように条件を指定していいか分からず…

 どなたか何かお知恵をお貸しください。

 Excelは2007です。

 ※追記

 各「出荷○○」のシートのデータを「集計」シートにまとめるのは下記のマクロでできました。

 Sub sh_check()
  Dim newSh As String
  Dim Sh As Worksheet, myFlag As Boolean
    newSh = "当日一覧"
    myFlag = False
    For Each Sh In ThisWorkbook.Worksheets
        If Sh.Name = newSh Then
            myFlag = True
            Worksheets(newSh).Cells.ClearContents
            Worksheets(newSh).Move before:=Sheets(1)
            Exit For
        End If
    Next Sh
    If myFlag = False Then
        ActiveWorkbook.Worksheets.Add(before:=Worksheets(1)).Name = newSh
    End If
 End Sub

 Sub matome()
  Dim i As Integer
  Dim lRow As Long, lCol As Long, lRow2 As Long
    Application.ScreenUpdating = False
    sh_check
    Worksheets(2).Range("1:1").Copy Worksheets(1).Range("A1")
    For i = 2 To Worksheets.Count - 9
  '「出荷○○」シートの後に9シート別のシートが存在するのでそれを差し引き

        With Worksheets(i)
            lRow = .Cells(Rows.Count, 1).End(xlUp).Row
            lCol = .Cells(1, Columns.Count).End(xlToLeft).Column

            If lRow >= 2 Then
                lRow2 = Worksheets(1).Cells(Rows.Count, 1).End(xlUp).Row + 1
                .Activate
                .Range(Cells(2, 1), Cells(lRow, lCol)).Copy Worksheets(1).Cells(lRow2, 1)
            End If
        End With
    Next i
    Worksheets(1).Activate
    Range("A1").Select
    Application.ScreenUpdating = True
 End Sub

 これで「出荷○○」シートと同じレイアウトで「当日一覧」というシートにデータをまとめ、
 「出荷番号」列を削除、「発送品ID」をキーに昇順で並べ替えまで出来ました。

 この後各発送品IDごとの集計がうまくいきません。

 出荷番号ごとのファイルというデータを出荷番号に応じて
 出荷番号「XX」のシートとか出荷番号「YY」のシートに振り分けて入力された
 データを今度は、統合したデータ集計をしたい ということですね!!

 VBAを知らない方なら、この出荷番号別にシートを作るという方法も頷けますし、
 実際良く見かけます。

 が、VBAを知っているなら、
 データは、同じレイアウトなのですから、
 同一シートに貯めて(すなわち、これデータベース)、ほしい集計(出荷番号別元帳を
 はじめとする各種集計)は、このデータのあるシートからVBAを使って抽出する 
 というのが基本的な手法なんです。データは、シンプルに・・・。
 もし、検討ができるなら、一考ください。

 さて、・・・・。例にあげられたデータも多く、出来ているコードも提示されていて
 私は、非常によい記述だと思います。

 一点だけ
 >例:「集計」シート
 >2  1   A001   ア01    AAA   あいう  250

 これ、合計は、入力データからすると、250ではなく、350ですか?

 合計値を集計するには、何をキーに集計すればよいのか?
 が、わからないと行えませんが・・・、

 例としてのデータだけを見ると、

発送品IDと在庫IDが同じであれば、集計できる

 ように見えますが、いかがですか?
 違うなら、このキーを特定することです。
 集計するキーは一つの列だけでなくても良いのです。

 いづれにせよ、Excelの集計を使っても結果は出ます。

 が、よく見かけるのが Dictionary というオブジェクトです。
 これを使うと、割と簡単にこの手の集計が行えます。

 例
     A    B   C    D   
  1 日付    姓   名   売上
  2 2012/1/5	田中	真紀子	15000
  3 2012/1/5	藤	圭子	2000
  4 2012/1/5	伊藤	咲	5000
  5 2012/1/5	田中	秀子	2000
  6 2012/1/5	鈴木	美穂	6000
  7 2012/1/5	鈴木	良子	2000
  8 2012/1/5	藤巻	江	3000
  9 2012/1/5	藤	巻江	3000
 10 2012/1/6	田中	真紀子	1000
 11 2012/1/6	藤	圭子	20000
 12 2012/1/6	伊藤	咲	2000
 13 2012/1/6	田中	秀子	1500
 14 2012/1/6	鈴木	美穂	4000
 15 2012/1/6	鈴木	良子	3000
 16 2012/1/6	藤巻	江	2000
 17 2012/1/6	藤	巻江	1000

 ある化粧品販売レディの個人の二日間(1/5と1/6)の売上集計を行うことを考えます。
 但し、この上記の表、個人名の 姓と名前がB列とC列に分かれています。
 ですから、B列の姓だけで集計すると、正確な情報が出ませんよね?

 では、B列とC列を連結したデータをキーに集計しよう ということになります。

 でも、ただ単に連結すると・・・、

 田中真紀子
 藤圭子
 伊藤咲
 田中秀子
 鈴木美穂
 鈴木良子
 藤巻江
 藤巻江

 あれれえ(コナン風)、
 ふじまき ごう さんと ふじ まきえ さんが同じキーになってしまう。

 ということで、間に有り得ない文字を区切り文字として挟みます。例えば _ アンダーバー

 Sub test()
    Dim rng As Range
    Dim dic As Object
    Dim crng As Range
    Dim kk As Variant
    Dim myarray As Variant
    Set rng = Range("a2", Cells(Rows.Count, "a").End(xlUp))
    Set dic = CreateObject("scripting.dictionary")
    For Each crng In rng
       kk = crng.Offset(0, 1).Value & "_" & crng.Offset(0, 2).Value
       If dic.exists(kk) Then
          myarray = dic(kk)
          myarray(3) = myarray(3) + crng.Offset(0, 3).Value
          dic(kk) = myarray
       Else
          With Application
             dic(kk) = .Transpose(.Transpose(crng.Offset(0, 1).Resize(1, 3).Value))
          End With
       End If
    Next
    Range("F1:H1").Value = Array("姓", "名", "合計")
    With Application
       Range("f2:h" & dic.Count + 1).Value = .Transpose(.Transpose(dic.items))
    End With
    Set dic = Nothing
    Set crng = Nothing
    Set rng = Nothing
    Erase myarray
 End Sub

 こんなコードで上記表のあるF列から、集計表が作成されます。

 同じような方法で考えてみてください。

 ichinose


  >例:「集計」シート
  >2  1   A001   ア01    AAA   あいう  250
 すみません!おっしゃる通り、合計は350です…

 > 同一シートに貯めて(すなわち、これデータベース)、ほしい集計(出荷番号別元帳を
 >はじめとする各種集計)は、このデータのあるシートからVBAを使って抽出する 
 >というのが基本的な手法なんです。
 ええと、つまり最初に「集計」シートを作って、それから各データのシートを作るということでしょうか。

 VBAは超簡単なものしか扱えないのですが、そちらの方がよいのでしょうか。

 >発送品IDと在庫IDが同じであれば、集計できる 
 はい、その通りです。

 教えていただいたコードを参考に下記のコードを書いたところ、同じシートのI列以降に結果を出すことができました

 Sub test2()
    Dim rng As Range
    Dim dic As Object
    Dim crng As Range
    Dim kk As Variant
    Dim myarray As Variant

        Set rng = Range("a2", Cells(Rows.Count, "a").End(xlUp))
        Set dic = CreateObject("scripting.dictionary")
        For Each crng In rng
        kk = crng.Offset(0, 1).Value & "_" & crng.Offset(0, 3).Value
            If dic.exists(kk) Then
                myarray = dic(kk)
                myarray(5) = myarray(5) + crng.Offset(0, 5).Value
                dic(kk) = myarray
            Else
                With Application
                    dic(kk) = .Transpose(.Transpose(crng.Offset(0, 1).Resize(1, 5).Value))
                End With
            End If
        Next

    Range("I1:M1").Value = Array("発送品ID", "印字記号", "在庫ID", "商品名", "数量")
    With Application
       Range("I2:M" & dic.Count + 1).Value = .Transpose(.Transpose(dic.items))
    End With
    Set dic = Nothing
    Set crng = Nothing
    Set rng = Nothing
    Erase myarray

 End Sub

 それで、今度はデータの一覧を載せた「当日一覧」シートから合計した結果を「当日集計」シートに転記しようと思い、
 コードを下記のように書き換えましたが、「当日集計」シートに結果が出ません。
 (もし集計データから各データシートへの転記が必要なら集計データを残した方がいいと思ったので)
 シートの指定の仕方が悪いのでしょうか。

 Sub test3()
    Dim rng As Range
    Dim dic As Object
    Dim crng As Range
    Dim kk As Variant
    Dim myarray As Variant

    With Sheets("当日一覧")
        Set rng = Range("a2", Cells(Rows.Count, "a").End(xlUp))
        Set dic = CreateObject("scripting.dictionary")
        For Each crng In rng
        kk = crng.Offset(0, 1).Value & "_" & crng.Offset(0, 3).Value
            If dic.exists(kk) Then
                myarray = dic(kk)
                myarray(5) = myarray(5) + crng.Offset(0, 5).Value
                dic(kk) = myarray
            Else
                With Application
                    dic(kk) = .Transpose(.Transpose(crng.Offset(0, 1).Resize(1, 5).Value))
                End With
            End If
        Next
    End With

    With Sheets("当日集計")

    Range("B1:F1").Value = Array("発送品ID", "印字記号", "在庫ID", "商品名", "数量")
    With Application
       Range("B2:F" & dic.Count + 1).Value = .Transpose(.Transpose(dic.items))
    End With
    Set dic = Nothing
    Set crng = Nothing
    Set rng = Nothing
    Erase myarray

    End With

 End Sub

 (ミッチェル)

 >つまり最初に「集計」シートを作って、それから各データのシートを作るということでしょうか。

 いえ、もっと生データです。

 データには、出荷番号や日付と言う情報も入ったような・・・、
 抽出したい情報を思い浮かべてください。その情報を取得するなら、どんなキー情報が
 あればよいか?
 それら全部の情報がふくまれたデータを一つのシートに蓄積するのです。

 決められた出荷番号のデータだけ取得するには、出荷番号情報がどこかの列になければ
 なりませんし、ある期間の集計を行いたければ、日付の情報が必要ですよね!!

 私の会社の仕事をしてもらっている新人プログラマに最初に販売管理のプログラムの
 概略設計をさせると、そのほとんどが納品書を印刷したタイミングで
 この納品書データ(売上データ)を得意先別に作成した売上ファイルの該当得意先ファイルに
 書き込むという設計書を持ってきます。
 でも、そんな設計になっている販売管理はないと思いますよ!!

 まっ、これは、ゆっくり考えてみてください。

 さて、本題です。

 test3というプロシジャー、拝見しました。

 問題は、Withステートメントの使い方です。

 > With Sheets("当日一覧")

 これで当日一覧というSheetオブジェクトは、アドレッシングされています。
 でも、せっかく、アドレッシングしてもプロシジャーの中でどこにも
 当日一覧というシートを参照している記述がありません。

 本来は、
 >Set rng = Range("a2", Cells(Rows.Count, "a").End(xlUp))

 ここで参照しなくてはならないのですが・・・・。

    With Sheets("当日一覧")
        Set rng = .Range("a2", .Cells(.Rows.Count, "a").End(xlUp))
    End With
 違いがわかりますか? 参照しているか否かは、「.」これです。
 Rangeの前で、Cellsの前で、Rows.Countの前で .が付いていて参照しています。

 当日一覧の参照は、上記の箇所だけでよいのです。rngにセル範囲が登録されますので
 ほかでは参照の必要がありません。

 もう一箇所は、 

 >With Sheets("当日集計")

 このWithステートメントも Withでアドレッシングされたシートを参照している
 コードが見当たりません。
 ここよく考えてください。

 どうすれば、アドレッシングされたSheets("当日集計")を参照できるのか?

 更に 

 >With Application

 とここでは、Applicationをアドレッシングしています。

 With ステートメントで二つ同時にアドレッシングは、出来ませんから
 どうすればよいか?

 どちらかは、Withではなく、オブジェクト変数を使いアドレッシングしなければ
 なりません。

 Withの意味から、調べてみてください。

 このWithステートメントやオブジェクトへの参照がきちんと理解できると、
 どのシートがアクティブであっても、シートを対象シートに切り替えることなく、
 この処理を遂行することが出来ます。
 又、今後のプログラミングの処理速度向上やバグの少ないプログラミングに
 大いに役立つと思いますよ!!

 ichinose

 


 ichinose様

 丁寧なご説明ありがとうございます。

 下記のコードで思うような処理ができました。

 Sub 集計()
    Dim rng As Range
    Dim dic As Object
    Dim crng As Range
    Dim kk As Variant
    Dim myarray As Variant
    Dim syukei As Range

    Set syukei = Worksheets("当日集計").Range("A1")

    With Sheets("当日一覧")
        Set rng = .Range("a2", .Cells(.Rows.Count, "a").End(xlUp))
    End With
        Set dic = CreateObject("scripting.dictionary")
        For Each crng In rng
        kk = crng.Offset(0, 1).Value & "_" & crng.Offset(0, 3).Value
            If dic.exists(kk) Then
                myarray = dic(kk)
                myarray(5) = myarray(5) + crng.Offset(0, 5).Value
                dic(kk) = myarray
            Else
                With Application
                    dic(kk) = .Transpose(.Transpose(crng.Offset(0, 1).Resize(1, 5).Value))
                End With
            End If
        Next

    syukei.Range("B1:F1").Value = Array("発送品ID", "印字記号", "在庫ID", "商品名", "数量")
    With Application
       syukei.Range("B2:F" & dic.Count + 1).Value = .Transpose(.Transpose(dic.items))
       syukei.Range("A2:A" & dic.Count + 1).Formula = "=Row()-1"
    End With
    Set dic = Nothing
    Set crng = Nothing
    Set rng = Nothing
    Erase myarray

 End Sub

 あの、お世話になりついで(?)に教えていただきたいのですが、

 1)「データベース」の件ですが、こちらには一番上に書いた「指示内容」のようなファイルが出荷番号ごとのファイルで届きます。
 そしてデータが必要なのはやはり一番上に書いたような

 例1:出荷番号「XX」のシート

    A   B      C    D     E   F   G 
 1  番号 出荷番号 発送品ID 印字記号 在庫ID 商品名 数量

 このような情報です。 

 出荷番号は規則性はありますが、その日によってある番号とない番号があります。

 おっしゃられる
 >それら全部の情報がふくまれたデータを一つのシートに蓄積するのです。
 というのは、上記のA〜Gの情報を一つのシートにまとめ、それぞれの出荷番号ごとのシートはそこからマクロで
 抽出するようなイメージでしょうか?
 (理解が悪くてすみません)
 その日にいくつの出荷番号があっていくつのシートができるかは分かりませんので、出荷番号ごとのシートの雛形を
 作っておいてそれをコピーしてその中に抽出したデータを書き込む、という感じですか?

 2)>Applicationをアドレッシングしています
 検索して Applicationオブジェクトというものがあるのは分かりましたが具体的にどのようなものか分かりません。
 上記のコードのApplicationオブジェクトはどのような働きをしているのでしょうか?

 3)ピリオドの有無ですが、Withステートメントの中で範囲の指定などをする時は先頭にピリオドを付ける、という内容で合ってますか?

 すみません、よろしくお願いします。

 (ミッチェル)

 >下記のコードで思うような処理ができました。
 一箇所だけ、

 >    Dim syukei As Range
 >    Set syukei = Worksheets("当日集計").Range("A1")

 この箇所これでも確かに間違いではありません。作動しているのですから・・・。

 が、通常は、

      Dim syukei As worksheet
      Set syukei = Worksheets("当日集計") 

 このようにWorksheetオブジェクトとして、変数を定義し、
 変数は、ワークシートオブジェクトを参照するようにします。

 >   syukei.Range("B1:F1").Value = Array("発送品ID", "印字記号", "在庫ID", "商品名", "数量")
 >    With Application
 >      syukei.Range("B2:F" & dic.Count + 1).Value = .Transpose(.Transpose(dic.items))
 >      syukei.Range("A2:A" & dic.Count + 1).Formula = "=Row()-1"
 >   End With
 >   Set dic = Nothing
 >   Set crng = Nothing
 >   Set rng = Nothing
     set syukei=nothing
 >   Erase myarray

 後半のsyukei変数を利用している箇所は、そのままでよいです。

 1について

 >上記のA〜Gの情報を一つのシートにまとめ、それぞれの出荷番号ごとのシートはそこからマクロで
 抽出するようなイメージでしょうか?

 ↑これです。前回も申し上げましたが、一日だけでは、ないなら、日付列も必要です。
 (ここは、運用の精細がわからないのでなんともいえませんが・・)

 出荷番号別のデータは、必要に応じてオンデマンドで情報をみることができたらよいのですよね?(勿論、これはVBAで行います)
 何もブック上の別シートにデータを残すことはない様に思えます。
 元情報があれば、いつだって集計できるのですから・・・。

 2 Applicationオブジェクトついて

 Applicationオブジェクトとは、

 Workbookオブジェクトがブックを操作する時に使うオブジェクト

 Worksheetオブジェクトは、ワークシートを操作する時に使うオブジェクト

 Rangeオブジェクトは、セルを操作する時に使うオブジェクト

 これと同様に Applicationは、Excel本体を操作する時に使うオブジェクトです。

 一例ですが、Application.QuitのQuitメソッドでExcelが終了します。

 今回は、ワークシート関数のTranspose関数をVBAで利用するために
 Applicationオブジェクトを使っています。
 他にもApplicationオブジェクトには、へえと思うような機能がありますから、
 HelpでApplicationオブジェクトのプロパティやメソッドを眺めてみてください。
 漢字のフリガナを連想する なんてこともできるんですよ!!

 3 ピリオドの有無

 Withステートメントについては、以前こんな投稿をしたことがありました。

http://www.vbalab.net/vbaqa/c-board.cgi?cmd=ntr;tree=45342;id=excel

 参考にしてください

 ichinose

 オブジェクトというのは、プロパティとメソッドと呼ばれるインターフェースを使って
 操作します。


 追伸
 >オブジェクトというのは、プロパティとメソッドと呼ばれるインターフェースを使って
 操作します。

 昨日は、この記述をしておきながら、私、うとうとしてしまって・・・。

 続きです。

 これを使ってオブジェクトを操作するのに

 range("a1").value=1
 range("a1").NumberFormatLocal = "#,##0.00_ "

 等とオブジェクトから、記述すると、そに度にVBAは、オブジェクトを探さなければ
 なりません。シートやブックなら、探すだけでよいですが、

 Rangeオブジェクトなどは、最初からオブジェクトがあるわけではないので、
 range("a1") という記述を実行するたびにオブジェクトを作成しているので
 何度も使うなら、無駄と言うか、処理時間がかかってしまいます
 (これがリンク先の実験でわかることです)。

 これを withステートメントやオブジェクト変数でアドレッシングすることで

 コードの記述では、
 オブジェクトの記述が省略でき、更に処理速度も向上する。

 with range("a1")
    .value=1
    .NumberFormatLocal = "#,##0.00_ "
 end with

 という一石二鳥なんです。

 以上ですが、
 私の記述から、

 range("A1").value=1    '1
 range("A1").NumberFormatLocal = "#,##0.00_ "   '2

 1と2のRange("A1")で表してるオブジェクト、同じオブジェクトではないのです!!
 同じセルを扱っているので確かにプロパティやメソッドは、同じ値を返すでしょうが、
 オブジェクトとしては、別物です。

 sub test()

 msgbox worksheets("sheet1") is worksheets("sheet1")

 msgbox range("a1") is range("a1") 

 end sub

 この結果の違いを考えて見てください。

 本当に以上です。

 ichinose


コメント返信:

[ 一覧(最新更新順) ]


YukiWiki 1.6.7 Copyright (C) 2000,2001 by Hiroshi Yuki. Modified by kazu.