[[20230310111257]] 『表を比較して差分をチェックして表示』(コッペ) ページの最後に飛ぶ

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

 

『表を比較して差分をチェックして表示』(コッペ)

以下の仕様にそったVBAを作成したいと思います。
参考になるweb記事等有りましたら紹介ください。

まだコードは、まだ手つかずな状態なので
 以下の仕様ではなくこうした方が良いと言うアドバイスもあれば紹介下さい。

「やりたい事」
 両者に存在する場合は、「○」を無い項目には「X」を表記したい。

「具体例の簡単なサンプル」
例えば、Sheet1のB列とSheet2のB列の「品名」を比較して
Sheet3に両者にある場合は、「○」、Sheet2に合ってSheet1には無い場合は「X」を記入

Sheet1

    |[A]  |[B]   |[C] 
 [1]|Check|品名  |単価
 [2]|     |いちご| 250
 [3]|     |なし  |  85
 [4]|     |りんご|  60
 [5]|     |みかん|  40
 [6]|     |メロン| 120

 Sheet2
     |[B]   
 [1]|品名  
 [2]|なし  
 [3]|みかん
 [4]|もも  

 Sheet3
     |[A]  |[B]   |[C] |[D]|[E]  |[F] 
 [1]|Check|品名  |単価|   |Check|品名
 [2]|     |いちご| 250|   |X    |もも
 [3]|○   |なし  |  85|   |     |    
 [4]|     |りんご|  60|   |     |    
 [5]|○   |みかん|  40|   |     |    
 [6]|     |メロン| 120|   |     |    
 [7]|     |      |    |   |     

< 使用 Excel:Excel2021、使用 OS:Windows11 >


 1)Sheet1にあって、Sheet2に無い場合はどうするんですか?
 2)Sheet1またはSheet2に重複がある場合はどうしますか?
(稲葉) 2023/03/10(金) 11:21:58

 仕様について(アドバイスという訳ではないのですが)個人的には↓こんなイメージを持ってます。

 Sheet1                      Sheet2
 _|___A____|___B____|        _|___A____|
 1|品名    |単価    |        1|品名    |
 2|いちご  |     250|        2|なし    |
 3|なし    |      85|        3|みかん  |
 4|りんご  |      60|        4|もも    |
 5|みかん  |      40|
 6|メロン  |     120|

 Sheet3(照合結果)
 _|___A____|___B____|___C____|___D____|
 1|        |Sheet1  |Sheet1  |Sheet2  |←照合後の業務を考慮すると
 2|chk     |品名    |単価    |品名    | 両方のシートの全カラムを出力しておいた方がいい気がする
 3|       1|いちご  |     250|        |
 4|       3|なし    |      85|なし    |
 5|       1|りんご  |      60|        |
 6|       3|みかん  |      40|みかん  |
 7|       1|メロン  |     120|        |
 8|       2|        |        |もも    |
     ↑
   1:Sheet1にだけ存在
   2:Sheet2にだけ存在
   3:両方に存在

 またn対n照合が必要な場合はもう1列用意して、そこで照合番号を管理するとか。

(白茶) 2023/03/10(金) 11:43:12


お世話になります。

>Sheet1にあって、Sheet2に無い場合はどうするんですか?

その場合は、無視して下さい。
  なのでSheet3のCheckには、「○」も「X」も記入されません。
  
>Sheet1またはSheet2に重複がある場合はどうしますか?

重複は無い前提です。
表はそれぞれソートして同名が無いことを確認しています
(コッペ) 2023/03/10(金) 11:56:17


白茶さん、お世話になります。

確かに提唱された形式の表形式の方が見やすいですね。

但し、後出し条件で気が重いのですが項目が、品名、単価の他に後3個ほどあるので
提唱された形式では少し横長の形式になりますが
列を一部非表示にすれば違いは見えやすくなるので対処できそうです。
(コッペ) 2023/03/10(金) 11:56:43


シート構成が少し違いますが、

両方に存在する場合は、以下で「3」が記入されますが
片方だけに存在する場合の「1」、「2」のVBAで思考停止しています。
アドバイスお願いします。

Sub test()

    Dim L As Long
    Dim R As Long

    Dim ws1 As Worksheet, ws2 As Worksheet, ws3 As Worksheet

    Set ws1 = Worksheets("sheet1")
    Set ws2 = Worksheets("sheet2")
    Set ws3 = Worksheets("sheet3")

    ws1.UsedRange.Copy Destination:=ws3.Range("B1")
    ws2.UsedRange.Copy Destination:=ws3.Range("D1")

    For R = 2 To ws3.Cells(Rows.Count, "D").End(xlUp).Row
        For L = 2 To ws3.Cells(Rows.Count, "B").End(xlUp).Row
            If ws3.Cells(R, "D").Value = ws3.Cells(L, "B").Value Then
                ws3.Cells(L, "A").Value = 3
                Exit For
            End If
        Next
    Next

    Set ws1 = Nothing
    Set ws2 = Nothing
    Set ws3 = Nothing
End Sub
(コッペ) 2023/03/10(金) 13:37:39

私には、難しいので両方に存在する品名のみ書き出すようにしました。

Sub test()

    Dim L As Long
    Dim R As Long

    Dim ws1 As Worksheet, ws2 As Worksheet, ws3 As Worksheet

    Set ws1 = Worksheets("sheet1")
    Set ws2 = Worksheets("sheet2")
    Set ws3 = Worksheets("sheet3")

    'オートフィルター解除(初期化)
    ws3.Range("A1").AutoFilter
    '書き出しシート全体をクリアする
    ws3.Cells.Clear

    '書き出しシートへ元DATAコピペ
    ws1.UsedRange.Copy Destination:=ws3.Range("B1")

    '表を比較 (同一ならA列に「1」を書き込み)
    For R = 2 To ws2.Cells(Rows.Count, "B").End(xlUp).Row
        For L = 2 To ws1.Cells(Rows.Count, "B").End(xlUp).Row
            If ws2.Cells(R, "B").Value = ws1.Cells(L, "B").Value Then
                ws3.Cells(L, "A").Value = 1
                Exit For
            End If
        Next
    Next

    'オートフィルターで同一の項目のみ表示
    ws3.Range("A1").AutoFilter , field:=1, Criteria1:="1"

    Set ws1 = Nothing
    Set ws2 = Nothing
    Set ws3 = Nothing

End Sub

(コッペ) 2023/03/10(金) 16:30:57


 >両方に存在する品名のみ書き出すようにしました
 では、その後
 Sheet2に残ったものを下に追加する
                        ...って段階的にやった方らいいと思います。

 (↓はDictionaryを使う例として...)

    Dim r As Long
    Dim ws1 As Worksheet, ws2 As Worksheet, ws3 As Worksheet
    Set ws1 = Worksheets("sheet1")
    Set ws2 = Worksheets("sheet2")
    Set ws3 = Worksheets("sheet3")

    Dim Dic2 As Object, Key As String
    Dim vR1 As Range, vR2 As Range, vW1(), vW2(), vW3(), vW4()
    Set Dic2 = CreateObject("Scripting.Dictionary")
    Set vR1 = ws1.UsedRange
    Set vR2 = ws2.UsedRange
    ReDim vW1(1 To vR1.Rows.Count, 1 To 1)
    vW3 = vW1
    ReDim vW2(1 To vR2.Rows.Count, 1 To 1)
    For r = 2 To vR2.Rows.Count
        Dic2(vR2.Cells(r, 1).Value) = r
    Next
    For r = 2 To vR1.Rows.Count
        vW1(r, 1) = 1
        Key = vR1.Cells(r, 1).Value
        If Dic2.Exists(Key) Then
            vW1(r, 1) = 3
            vW3(r, 1) = Key
            vW2(Dic2(Key), 1) = 3
        End If
    Next
    vW1(1, 1) = "Check"
    vW3(1, 1) = "品名(" & ws2.Name & ")"
    ws3.Cells.Clear
    ws1.UsedRange.Copy Destination:=ws3.Range("B1")
    ws3.Range("A1").Resize(vR1.Rows.Count) = vW1
    ws3.Range("D1").Resize(vR1.Rows.Count) = vW3

    Dim i As Long
    ReDim vW4(1 To vR2.Rows.Count - 1, 1 To 4)
    For r = 2 To vR2.Rows.Count
        If vW2(r, 1) = Empty Then
            i = i + 1
            vW4(i, 1) = 2
            vW4(i, 4) = vR2.Cells(r, 1).Value
        End If
    Next
    If i > 0 Then ws3.Range("A1").Offset(vR1.Rows.Count).Resize(i, 4).Value = vW4

(白茶) 2023/03/10(金) 16:59:22


 衝突したけど、最初に提示いただいたデータ通りに仕上げました・・・

    Sub test2()
        Dim ws1 As Worksheet, ws2 As Worksheet, ws3 As Worksheet
        Set ws1 = Worksheets("sheet1")
        Set ws2 = Worksheets("sheet2")
        Set ws3 = Worksheets("sheet3")
        Dim dic As Object: Set dic = CreateObject("Scripting.Dictionary")
        Dim tbl As Variant, buf As String
        Dim r As Range, i As Long
        'Sheet2の一覧表をDicに取込
        For Each r In ws2.Range("B2", ws2.Cells(Rows.Count, "B").End(xlUp))
            dic(r.Value) = 0
        Next r

        'Sheet1のデータを配列に取込
        tbl = ws1.[A1:C1].Resize(ws1.Cells(Rows.Count, "B").End(xlUp).Row).Value

        'Sheet1のB列を2行目からループしてありなしを判定
        For i = 2 To UBound(tbl, 1)
            buf = tbl(i, 2)
            If dic.exists(buf) Then
                tbl(i, 1) = "○"
                dic.Remove buf
            End If
            If dic.Count = 0 Then Exit For
        Next i

        '結果の出力
        With ws3
            .Cells.Clear
            .Range("B1").Resize(UBound(tbl, 1), UBound(tbl, 2)).Value = tbl
            'Sheet1になかったものを出力
            If dic.Count > 0 Then
                .[E1:F1].Value = [{"Check","品名"}]
                .[E2].Resize(dic.Count).Value = "X"
                .[F2].Resize(dic.Count).Value = Application.Transpose(dic.keys)
            End If
        End With
        MsgBox "出力しました"
    End Sub
(稲葉) 2023/03/10(金) 17:24:15

白茶さん、稲葉さん 他の2種類の解決方法を教えていただき感謝します。

ステップアップ実行やローカルウインドウ等を利用してコードを追いかけましたが
Dictionaryや配列を利用した方法は、現在の私の立ち位置では難しく感じます。

ただ結果だけを求めるのあればいただいたコードをそのまま利用すれば良いのですが
それではスキルアップになりません。
(現在スキル不足で3/10 16:30で上げたコード程度しか書くことができません。)

すみませんが、形式は一番最初の質問でのSheet3のような形式で
私にも理解できそうな他の方法で回答をいただけると理解が進むのですがお願いできないでしょうか?
区分は、Sheet2にだけ存在は「X」、両方に存在する場合は、「○」でSheet1にだけ存在する場合は「」(未チェック)
でお願いします。

(コッペ) 2023/03/11(土) 08:08:58


 Sub test()
     Dim ws1 As Worksheet, ws2 As Worksheet, ws3 As Worksheet
     Dim rng1 As Range, rng2 As Range, rng3 As Range, i As Long
     Set ws1 = Worksheets("Sheet1")
     Set ws2 = Worksheets("Sheet2")
     Set ws3 = Worksheets("Sheet3")

     Set rng1 = ws1.Range("B2").Resize(ws1.Cells(Rows.Count, 2).End(xlUp).Row - 1)
     Set rng2 = ws2.Range("B2").Resize(ws2.Cells(Rows.Count, 2).End(xlUp).Row - 1)

     For Each rng3 In ws3.Range("B2").Resize(ws3.Cells(Rows.Count, 2).End(xlUp).Row - 1)
         With WorksheetFunction
             If .CountIf(rng1, rng3) > 0 And .CountIf(rng2, rng3) > 0 Then rng3.Offset(0, -1) = "○"
         End With
     Next

     For i = 1 To rng2.Count
         With ws3.Cells(Rows.Count, "E").End(xlUp).Offset(1, 0)
             If WorksheetFunction.CountIf(rng1, rng2(i)) = 0 Then
                 ws3.Cells(.Row, "E") = "X"
                 ws3.Cells(.Row, "F") = rng2(i)
             End If
         End With
     Next
 End Sub
(フォーキー) 2023/03/11(土) 08:44:26

 大前提として、配列やDictionaryを学ぶことこそが、スキルアップにつながると思っています。
 それを踏まえて「私にも理解できそうな他の方法」となると、
 For〜Next をSheet2側からも行う感じになるのでは。
 出来るだけ元のコードを維持しています。

    Sub test()
        Dim L As Long
        Dim R As Long
        Dim rw As Long
        Dim flg As Boolean
        Dim ws1 As Worksheet, ws2 As Worksheet, ws3 As Worksheet
        Set ws1 = Worksheets("Sheet1")
        Set ws2 = Worksheets("Sheet2")
        Set ws3 = Worksheets("Sheet3")
        'オートフィルター解除(初期化)
    '    ws3.Range("A1").AutoFilter
        '書き出しシート全体をクリアする
        ws3.Cells.Clear
        '書き出しシートへ元DATAコピペ
        ws1.UsedRange.Copy Destination:=ws3.Range("A1")
        'Sheet1の表を上から順にSheet2の表と比較(同一ならSheet3のA列に「○」を書き込み)
        For R = 2 To ws1.Cells(Rows.Count, "B").End(xlUp).Row
            For L = 2 To ws2.Cells(Rows.Count, "B").End(xlUp).Row
                If ws1.Cells(R, "B").Value = ws2.Cells(L, "B").Value Then
                    ws3.Cells(R, "A").Value = "○"
                    Exit For
                End If
            Next
        Next
        'Sheet2の表を上から順にSheet1の表と比較
        rw = 2
        ws3.Range("E1").Resize(, 2) = Array("Check", "品名")    '見出し書込み
        For R = 2 To ws2.Cells(Rows.Count, "B").End(xlUp).Row
            flg = False
            For L = 2 To ws1.Cells(Rows.Count, "B").End(xlUp).Row
                If ws2.Cells(R, "B").Value = ws1.Cells(L, "B").Value Then
                    flg = True  'Sheet1に有ったのでフラグを立てる
                    Exit For
                End If
            Next
            If flg = False Then
                'フラグが立ってない=Sheet2にしか存在しない
                ws3.Cells(rw, "E").Value = "X"
                ws3.Cells(rw, "F").Value = ws2.Cells(R, "B").Value
                rw = rw + 1
            End If
        Next
        'オートフィルターで同一の項目のみ表示
    '    ws3.Range("A1").AutoFilter , field:=1, Criteria1:="○"
        Set ws1 = Nothing
        Set ws2 = Nothing
        Set ws3 = Nothing
    End Sub
(ポリタンク) 2023/03/11(土) 09:51:43

シート1とシート3って同じ内容だったんですね。
完全に読み間違えてました。

 Sub test()
     Dim ws1 As Worksheet, ws2 As Worksheet, ws3 As Worksheet
     Dim rng1 As Range, rng2 As Range, rng3 As Range, i As Long
     Set ws1 = Worksheets("Sheet1")
     Set ws2 = Worksheets("Sheet2")
     Set ws3 = Worksheets("Sheet3")

     ws1.Range("A1").CurrentRegion.Copy ws3.Range("A1")
     ws3.Range("A1:B1").Copy ws3.Range("E1")

     Set rng1 = ws1.Range("B2").Resize(ws1.Cells(Rows.Count, 2).End(xlUp).Row - 1)
     Set rng2 = ws2.Range("B2").Resize(ws2.Cells(Rows.Count, 2).End(xlUp).Row - 1)
     Set rng3 = ws3.Range("A2").Resize(ws3.Cells(Rows.Count, 2).End(xlUp).Row - 1)

     rng3.Formula = "=IF(COUNTIF(" & ws2.Name & "!" & rng2.Address & ",B2)>0,""○"","""")"
     rng3.Value = rng3.Value
     For i = 1 To rng2.Count
         With ws3.Cells(Rows.Count, "E").End(xlUp).Offset(1, 0)
             If WorksheetFunction.CountIf(rng1, rng2(i)) = 0 Then
                 ws3.Cells(.Row, "E") = "X"
                 ws3.Cells(.Row, "F") = rng2(i)
             End If
         End With
     Next
 End Sub

 ※10:36数式修正
(フォーキー) 2023/03/11(土) 10:23:56

 質問内容とは違いますが、数式案です。
 ※セル範囲を多めにとれば、変数不要でもっとスマートになるかも。

 Sub test()
     Dim ws1 As Worksheet, ws2 As Worksheet, ws3 As Worksheet
     Dim adr1 As String, adr2 As String, adr3 As String, rng As Range
     Set ws1 = Worksheets("Sheet1")
     Set ws2 = Worksheets("Sheet2")
     Set ws3 = Worksheets("Sheet3")
     Set rng = ws3.Range("A2").Resize(ws1.Cells(Rows.Count, 2).End(xlUp).Row - 1)

     ws1.Range("A1").CurrentRegion.Copy ws3.Range("A1")
     ws3.Range("A1:B1").Copy ws3.Range("E1")

     adr1 = ws1.Name & "!" & ws1.Range("B2").Resize(ws1.Cells(Rows.Count, 2).End(xlUp).Row - 1).Address(0, 0)
     adr2 = ws2.Name & "!" & ws2.Range("B2").Resize(ws2.Cells(Rows.Count, 2).End(xlUp).Row - 1).Address(0, 0)
     adr3 = ws2.Name & "!" & ws2.Range("B2").Resize(ws2.Cells(Rows.Count, 2).End(xlUp).Row - 1).Address
     rng.Formula = "=IF(COUNTIF(" & adr3 & ",B2)>0,""○"","""")"
     ws3.Range("F2").Formula2 = "=FILTER(" & adr2 & ",COUNTIF(" & adr1 & "," & adr2 & ")=0)"
     ws3.Range("A1").Resize(rng.Rows.Count, 6).Value = ws3.Range("A1").Resize(rng.Rows.Count, 6).Value
 End Sub
(フォーキー) 2023/03/11(土) 11:03:50

 Sub test()
     Dim ws1 As Worksheet, ws2 As Worksheet, ws3 As Worksheet, rng As Range
     Set ws1 = Worksheets("Sheet1")
     Set ws2 = Worksheets("Sheet2")
     Set ws3 = Worksheets("Sheet3")
     Set rng = ws3.Range("A2").Resize(ws1.Cells(Rows.Count, 2).End(xlUp).Row - 1)

     ws1.Range("A1").CurrentRegion.Copy ws3.Range("A1")
     ws3.Range("A1:B1").Copy ws3.Range("E1")

     ws3.Range("A2").Resize(ws1.Cells(Rows.Count, 2).End(xlUp).Row - 1).Formula = "=IF(COUNTIF(" & ws2.Name & "!$B$2:$B$10,B2)>0,""○"","""")"
     ws3.Range("F2").Formula2 = "=FILTER(IF(" & ws2.Name & "!B2:B10="""",""""," & ws2.Name & "!B2:B10),COUNTIF(" & ws1.Name & "!B2:B10," & ws2.Name & "!B2:B10)=0)"
     ws3.Range("$E$2:$E$10").Formula = "=IF(F2<>"""",""X"","""")"
     ws3.Range("A1").Resize(rng.Rows.Count, 6).Value = ws3.Range("A1").Resize(rng.Rows.Count, 6).Value
 End Sub
(フォーキー) 2023/03/11(土) 11:25:38

ポリタンクさん、拙い元コードを維持したコードをありがとうございます。

If ws2.Cells(R, "B").Value = ws1.Cells(L, "B").Value Then で
「○」を処理して
else で
「x」を処理しようとしてうまく行かずにいましたが
おかげさまでflagを利用する方法で処理できました。

フォーキーさん、何度もコードを書き換えていただき感謝します。
一応自前のコードが解決したのでじっくり見させて頂きます。

(コッペ) 2023/03/11(土) 12:59:39


コメント返信:

[ 一覧(最新更新順) ]


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