[[20240120203734]] 『マクロでマトリクス条件に一致する元データを拾い』(ぱんだ) ページの最後に飛ぶ

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

 

『マクロでマトリクス条件に一致する元データを拾いたい』(ぱんだ)

マクロで下記の事をやりたいです。
A列からD列に元データがあります。1行目は項目行です。
次に、条件表(マトリクス形式)があります。F列からJ列にあります。 F1セルから表が始まります。
この条件表のF列のNo.が、元データのA列のNo.と一致し、
条件表の1行目のあ、い、う、えが、元データのC列の種別を表しています。
この条件に則って、元データから一致する行を抽出したいです。抽出結果は、ToとCcで分けたいです。
条件表にToとかいてあるものは、該当する元データ行の情報をL列からO列に転記したい。1行目に項目名は予め入力してある。
条件表にCcととかいてあるものは、該当する元データ行の情報をQ列からT列に転記したい。1行目に項目名は予め入力してある。
To or Ccと書いてあるのに、元データに一致するものがない場合もありうる。その場合、転記するものがないので何もしない。

まず、元データを2から最終行までループして、次に条件表の縦方向と横方向のマトリクスをループで一つずつみていくイメージでしょうか。
元データの行が数百行あるので、例えば配列を使うとか何か良い方法がありましたら教えて欲しいです。
よろしくお願いいたします。

<元データ>

 A     B         C       D
 No.  分類	種別	キー
 1     a	 あ	 A
 1     a	 い	 B
 1     b	 い	 C
 2     a	 あ	 D
 2     c	 い	 E
 2     d	 う	 F
 3     e	 い	 G
 3     f	 う	 G
 4     a	 あ	 A
 4     a	 あ	 H
 4     g	 い	 I
 4     h	 う	 J
 4     i	 え	 K
 4     j	 え	 L

<条件表>

 F      G      H       I     J
 No.    あ     い     う     え
 1      To		     To
 2	       To            To
 3	       Cc     To     To
 4			     Cc

<Output:To>

 L     M         N        O 
 No.   分類     種別     キー
 1     a	 あ	  A
 2     c	 い	  E
 3     f	 う	  G

<Output:Cc>

 Q   R     S        T
 No.   分類     種別     キー
 3	e	 い	  G
 4	i	 え	  K
 4      j	 え	  L

< 使用 Excel:Microsoft365、使用 OS:Windows10 >


To には「え」がないのはなぜですか。
Cc には「え」がふたつあるのはなぜですか。
(?) 2024/01/20(土) 21:29:15

?様
>To には「え」がないのはなぜですか。
元データにNo.1, 2, 3の「え」がないからです。

>Cc には「え」がふたつあるのはなぜですか。
元データの最後の2行が、条件マトリクスのNo.4 かつ 「え」に一致するからです。

よろしくお願いいたします。
(ぱんだ) 2024/01/20(土) 21:38:40


 一応例示通りになりました。

    Sub sample()
        Dim dat, cnd, arr_to(), arr_cc(), r, c
        Dim i&, n&, m&, k&

        [L1].CurrentRegion.Resize(, 9).Offset(1).ClearContents
        dat = [A1].CurrentRegion
        cnd = [F1].CurrentRegion
        ReDim arr_to(1 To UBound(dat), 1 To 4)
        ReDim arr_cc(1 To UBound(dat), 1 To 4)
        For i = 2 To UBound(dat)
            r = Application.Match(dat(i, 1), Application.Index(cnd, , 1), 0)
            c = Application.Match(dat(i, 3), Application.Index(cnd, 1), 0)
            If Not IsError(r) And Not IsError(c) Then
                Select Case cnd(r, c)
                    Case "To"
                        n = n + 1
                        For k = 1 To 4
                            arr_to(n, k) = dat(i, k)
                        Next
                    Case "Cc"
                         m = m + 1
                        For k = 1 To 4
                            arr_cc(m, k) = dat(i, k)
                        Next
                End Select
            End If
        Next
        [L2].Resize(n, 4) = arr_to
        [Q2].Resize(m, 4) = arr_cc
    End Sub
(辰) 2024/01/20(土) 21:44:47

辰様
ありがとうございます。非常に考え方の勉強になります。

r = Application.Match(dat(i, 1), Application.Index(cnd, , 1), 0)
c = Application.Match(dat(i, 3), Application.Index(cnd, 1), 0)
の部分は、Index関数の説明を読むと、 INDEX(範囲,行番号,列番号) となっているので、
感覚的にcが行番号、rが列番号という感じがするのですが、このように取得するのですね。勉強してみます。

Bcc追加版でも無事にできました↓
大変ありがとうございました。

 Sub sample_Bcc追加()
        Dim dat, cnd, arr_to(), arr_cc(), arr_Bcc(), r, c
        Dim i&, n&, m&, o&, k&
        [L1].CurrentRegion.Resize(, 14).Offset(1).ClearContents
        dat = [A1].CurrentRegion
        cnd = [F1].CurrentRegion
        ReDim arr_to(1 To UBound(dat), 1 To 4)
        ReDim arr_cc(1 To UBound(dat), 1 To 4)
        ReDim arr_Bcc(1 To UBound(dat), 1 To 4)
        For i = 2 To UBound(dat)
            r = Application.Match(dat(i, 1), Application.Index(cnd, , 1), 0)
            c = Application.Match(dat(i, 3), Application.Index(cnd, 1), 0)
            If Not IsError(r) And Not IsError(c) Then
                Select Case cnd(r, c)
                    Case "To"
                        n = n + 1
                        For k = 1 To 4
                            arr_to(n, k) = dat(i, k)
                        Next
                    Case "Cc"
                         m = m + 1
                        For k = 1 To 4
                            arr_cc(m, k) = dat(i, k)
                        Next
                    Case "Bcc"
                         o = o + 1
                        For k = 1 To 4
                            arr_Bcc(o, k) = dat(i, k)
                        Next
                End Select
            End If
        Next
        [L2].Resize(n, 4) = arr_to
        [Q2].Resize(m, 4) = arr_cc
        [V2].Resize(o, 4) = arr_Bcc
    End Sub

(ぱんだ) 2024/01/20(土) 22:29:17


 解決したようなので、別案を。

 こういうのはPower Queryが簡単です。
 1.条件表を「ピボット解除」
 2.To(またはCc)でフィルター
 3.元データと「クエリのマージ」
(マナ) 2024/01/20(土) 22:43:35

 関数でも。

      __A  __B_  __C_  __D_  __E  __F  __G  __H  __I  __J  __K  __L  __M_  __N_  __O_  __P  __Q  __R_  __S_  __T_
  1   No.  分類  種別  キー       No.  あ   い   う   え        No.  分類  種別  キー       No.  分類  種別  キー
  2     1  a     あ    A            1  To             To          1  a     あ    A            3  e     い    G   
  3     1  a     い    B            2       To        To          2  c     い    E            4  i     え    K   
  4     1  b     い    C            3       Cc   To   To          3  f     う    G            4  j     え    L   
  5     2  a     あ    D            4                 Cc                                                         
  6     2  c     い    E                                                                                         
  7     2  d     う    F                                                                                         
  8     3  e     い    G                                                                                         
  9     3  f     う    G                                                                                         
 10     4  a     あ    A                                                                                         
 11     4  a     あ    H                                                                                         
 12     4  g     い    I                                                                                         
 13     4  h     う    J                                                                                         
 14     4  i     え    K                                                                                         
 15     4  j     え    L      

 [L2]  =LET(list,$A$2:$D$500,key,take(list,,1)&"|"&take(drop(list,,2),,1),find,LET(a,tocol($G$2:$J$5&"|"&$F$2:$F$5&"|"&$G$1:$J$1),b,FILTER(a,LEFT(a,2)="To"),MID(b,4,LEN(b))),bool,map(key,LAMBDA(a,SUM(IFERROR(XMATCH(find,a),0)))),ret,FILTER(list,bool,""),ret)
 [Q2]  =LET(list,$A$2:$D$500,key,take(list,,1)&"|"&take(drop(list,,2),,1),find,LET(a,tocol($G$2:$J$5&"|"&$F$2:$F$5&"|"&$G$1:$J$1),b,FILTER(a,LEFT(a,2)="Cc"),MID(b,4,LEN(b))),bool,map(key,LAMBDA(a,SUM(IFERROR(XMATCH(find,a),0)))),ret,FILTER(list,bool,""),ret)

(まる2021) 2024/01/20(土) 22:51:28


マナ様
ありがとうございます。
挑戦してみましたが、再現できず。。パワークエリ、勉強してみます。

まる2021様
関数でも出来てしまう事に驚きました。ありがとうございます。
「その関数は正しくありません」とエラーが出てしまいますが、今使っているPCがoffice365対応でないからですかね。後日試してみます!
(ぱんだ) 2024/01/20(土) 23:08:16


お世話になります。
1月にこちらで色々教えていただきました件、その後も活用させていただいております。
下記について、追加ですみませんが教えてもらいたいです。

下記、辰様に教えていただいたコードですが、
マトリクス条件に一致する行を、「To, Cc」ごとに転記するという部分で、転記する際に
もし1〜4列目の全ての項目が重複していたら、リストへ転記するのは1行分だけにしたいです。(重複削除したい)

※実際の実例では1〜4列ではなく、50列位あるので、
例えばIf文で、「 If arr_to(j, 1) = dat(i, 1) And arr_to(j, 2) = dat(i, 2) And arr_to(j, 3) = dat(i, 3) And arr_to(j, 4) = dat(i, 4) Then
のように確認していたら大変です。
何か一気に重複削除できるような方法あれば知りたいです。
また、行削除すると周りの列がずれてしまうので、重複削除した状態で貼り付けられるとベストです。
何か良い方法ありましたら、アドバイスお願いいたします。

    Sub sample()
        Dim dat, cnd, arr_to(), arr_cc(), r, c
        Dim i&, n&, m&, k&
        [L1].CurrentRegion.Resize(, 9).Offset(1).ClearContents
        dat = [A1].CurrentRegion
        cnd = [F1].CurrentRegion
        ReDim arr_to(1 To UBound(dat), 1 To 4)
        ReDim arr_cc(1 To UBound(dat), 1 To 4)
        For i = 2 To UBound(dat)
            r = Application.Match(dat(i, 1), Application.Index(cnd, , 1), 0)
            c = Application.Match(dat(i, 3), Application.Index(cnd, 1), 0)
            If Not IsError(r) And Not IsError(c) Then
                Select Case cnd(r, c)
                    Case "To"
                        n = n + 1
                        For k = 1 To 4
                            arr_to(n, k) = dat(i, k)
                        Next
                    Case "Cc"
                         m = m + 1
                        For k = 1 To 4
                            arr_cc(m, k) = dat(i, k)
                        Next
                End Select
            End If
        Next
        [L2].Resize(n, 4) = arr_to
        [Q2].Resize(m, 4) = arr_cc
    End Sub
(ぱんだ) 2024/04/20(土) 00:26:00

 「データ」 - 「重複の排除」を使って、予め重複を排除しておくのが簡便でしょう。
 VBAで言えば、RangeオブジェクトのRemoveDuplicatesメソッドですね。

(xyz) 2024/04/20(土) 06:14:18


 被っちゃった...
 出力後に、重複削除すればいいのでは?
 具体的には、コードの最後に以下を追加。見出しがある場合は「Header:=xlYes」で

        [L2].CurrentRegion.RemoveDuplicates Columns:=Array(1, 2, 3, 4), Header:=xlNo
        [Q2].CurrentRegion.RemoveDuplicates Columns:=Array(1, 2, 3, 4), Header:=xlNo
(まる2021) 2024/04/20(土) 06:44:32

xyz様、まる2021様
ありがとうございます。
最後に削除しようとして、
[L2].CurrentRegion.RemoveDuplicates Columns:=Array(1, 2, 3, 4), Header:=xlYes ←見出しあり

としたところ、結果が全て空白になってしまいます。
どこがおかしいのか分からず、、原因分かりますでしょうか。

>予め重複を排除しておくのが簡便でしょう
これも考えましたが、元データは変えたくなく、結果のみ重複削除したいと考えています。
(ぱんだ) 2024/04/20(土) 07:41:13


 >>予め重複を排除しておくのが簡便でしょう
 >これも考えましたが、元データは変えたくなく、結果のみ重複削除したいと考えています。

 Microsoft365なのでUnique関数で重複を排除して配列に格納すればいいでしょう。

       dat = [A1].CurrentRegion
        ↓
       dat = WorksheetFunction.Unique([A1].CurrentRegion)

(hatena) 2024/04/20(土) 09:20:39


 解決済みのようですが、こんな方法もあったかもしれません。
 ・元データの最終列の右に、条件表から求めた「To,Cc,Bccの区分」列を追加(*)
 ・その追加列をもとに、フィルタオプションで結果の表にそれぞれ書き出す。
 ・「重複を排除」する組み込みの設定も利用可能です。

 (*)の部分は、条件表をもとに、No & vbTab & 種別をキーにしたdictionaryを作成しておけば簡単です。

 列を追加するスペースがありません、とか
 余分な列を追加したくありません、いうことなら残念ですけど。
 なお、元データのコピーでもとっておかれるとよいと思いました。
(xyz) 2024/04/20(土) 10:03:33

hatena様
ありがとうございます。このような格納方法もあるのですね。
手持ちPCが365対応ではないため、月曜日に試させていただきます。

xyz様
ありがとうございます。
今回はテーブルが入り組んでいるファイルのためスペース的に厳しそうですが、
とても勉強になります。
(ぱんだ) 2024/04/20(土) 12:12:49


 xyzさんの作業列を使う案と同様のことを式でやる案を考えてました。

 > xyz様
 > ありがとうございます。
 > 今回はテーブルが入り組んでいるファイルのためスペース的に厳しそうですが、
 > とても勉強になります。

 CurrentRegionでデータ表と条件表を取得出来ているということは、
 データ表と条件表の間に空列があるということですよね。
 それを作業列にすればいいかと思います。

 作業列にTo,Cc,Bccの区分を式で出力
 その列を条件にFILTER関数でデータを抽出
 という方法です。
 質問のデータなら、具体的には下記のような式になります。

 作業列E列
 E2セル: =XLOOKUP(C2,$G$1:$J$1,XLOOKUP(A2,$F$2:$F$5,$G$2:$J$5))
 下にコピー

 Toの抽出結果
 L2セル: =UNIQUE(FILTER(A2:D15,E2:E15="To"))

 Ccの抽出結果
 Q2セル: =UNIQUE(FILTER(A2:D15,E2:E15="Cc"))

 実際のデータはもっと列数や行数が多いということなので、
 このような式をVBAで設定して、抽出結果を値化して、作業列をクリアするということをコード化してみました。

 ---------------------------------------------
Public Sub SampleHatena()
    Const kubuns = "To Cc Bcc" '区分
    Dim rData As Range, rCond As Range, rWork As Range
    Set rData = Cells(1).CurrentRegion
    Set rData = Intersect(rData, rData.Offset(1)) 'データ範囲
    Set rCond = Cells(1, rData.Columns.Count + 2).CurrentRegion
    Set rCond = Intersect(rCond, rCond.Offset(1, 1)) '条件範囲
    Set rWork = rData.Columns(rData.Columns.Count + 1) '作業列
    '前の結果データ削除↓
    rData.Offset(, rData.Columns.Count + rCond.Columns.Count + 3).Resize(, (rData.Columns.Count + 1) * (UBound(Split(kubuns)) + 1)).ClearContents
    rWork.Value2 = "=XLOOKUP(C2," & rCond.Rows(0).Address & ",XLOOKUP(A2," & rCond.Columns(0).Address & "," & rCond.Address & "))"
    Dim OutputCell As Range, kubun
    Set OutputCell = rCond.Offset(, rCond.Columns.Count + 1).Cells(1)
    For Each kubun In Split(kubuns)
        OutputCell.Formula2 = "=UNIQUE(FILTER(" & rData.Address & "," & rWork.Address & "=""" & kubun & """))"
        With OutputCell.CurrentRegion
            .Value = .Value '抽出結果を値化
        End With
        Set OutputCell = OutputCell.Offset(, rData.Columns.Count + 1)
    Next
    rWork.ClearContents '作業列クリア
End Sub
 ---------------------------------------------

 データ表や条件表の列が増えてもこのコードで対応できます。

 こんな方法もあるということでご参考までに。

 ※2024/04/20 18:41 コード一部修正しました

(hatena) 2024/04/20(土) 15:16:49


hatena様
色々と検証いただき、ありがとうございます。
事例の書き方をかなり端折ってしまいましたが、実際はデータベースと条件表、はきだし先が別のファイルとなってしまうので、
それをこのコードに当てはめるのは難しそうですが、考え方非常に勉強になります。
今後の参考にさせていただきます。
(ぱんだ) 2024/04/20(土) 16:42:31

 Range.Address(External:=True) で外部参照(ブック名シート名を含む)アドレスを取得できますので別ブックでも参照できます。
 少しの修正で対応可能です。

 とりあえず下記の仕様とすると、

 データペースと条件表のあるブックにマクロがある
 一番目のシートにデータと条件はあり、レイアウトは質問と同じ

 出力先のブック名 Output.xlsx
 一番目のシートのA2セル以降に出力

 ------------------------------------------------------------------
Public Sub SampleHatena2()
    Const kubuns = "To Cc Bcc" '区分
    Dim rData As Range, rCond As Range, rWork As Range
    With ThisWorkbook.Worksheets(1)
        Set rData = .Cells(1).CurrentRegion
        Set rData = Intersect(rData, rData.Offset(1)) 'データ範囲
        Set rCond = .Cells(1, rData.Columns.Count + 2).CurrentRegion
        Set rCond = Intersect(rCond, rCond.Offset(1, 1)) '条件範囲
        Set rWork = rData.Columns(rData.Columns.Count + 1) '作業列
    End With
    rWork.Value2 = "=XLOOKUP(C2," & rCond.Rows(0).Address & ",XLOOKUP(A2," & rCond.Columns(0).Address & "," & rCond.Address & "))"
    Dim OutputCell As Range, kubun
    Set OutputCell = Workbooks("Output.xlsx").Worksheets(1).Range("A2")
    '前の結果データ削除↓
    OutputCell.Resize(rData.Rows.Count, (rData.Columns.Count + 1) * (UBound(Split(kubuns)) + 1)).ClearContents
    For Each kubun In Split(kubuns)
        OutputCell.Formula2 = "=UNIQUE(FILTER(" & rData.Address(External:=True) & "," & rWork.Address(External:=True) & "=""" & kubun & """))"
        With OutputCell.CurrentRegion
            .Value = .Value
        End With
        Set OutputCell = OutputCell.Offset(, rData.Columns.Count + 1)
    Next
    rWork.ClearContents
End Sub
 ------------------------------------------------------------------

(hatena) 2024/04/20(土) 18:46:25


hatena様
大変、参考になるアドバイス、ありがとうございます!!
外部参照のアドレス取得、とても勉強になります。
月曜日に365対応パソコンで試してみます!
(ぱんだ) 2024/04/20(土) 23:40:31

コメント返信:

[ 一覧(最新更新順) ]


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