Excel (一般機能)

Excelの一般機能に関するフォーラムです。
  • 解決済みのトピックにはコメントできません。
このトピックは解決済みです。
質問

 
(指定なし : Microsoft 365)
データが膨大なため処理が遅い
投稿日時: 23/08/04 00:04:19
投稿者: yama1006
メールを送信

お世話になります。
以下のコードとマクロのファイルのリンクを添付します。
 
やりたいことは、受入データと元データの比較なのですが、データが膨大なためか、処理が遅く困っております。
 
受入データと元データの社員番号が一致
 
各コードの列を比較して異なっているところに色付け
 
どのようにすればもっと早く出来るでしょうか。
 
https://drive.google.com/file/d/1tHpT1VwMWujyzl0WWX2ohNHGaC5NNqXC/view?usp=sharing
 
 
 
Application.ScreenUpdating = False
 
Application.Calculation = xlManual
 
For i = r1 To maxrow2
 
    For l = r2 To maxrow2
 
        For o2 = 2 To maxcol
         
             For o3 = 2 To maxcol
             
                 If ws1.Cells(i, 2) = ws2.Cells(l, 1) Then '社員コードが一致
                                                                  
 
                        If ws1.Cells(6, o2) = ws2.Cells(1, o3) Then
                 
 
                                If ws1.Cells(i, o2) <> ws2.Cells(l, o3) Then
 
 
                                    ws2.Cells(l, o3).Interior.Color = RGB(255, 0, 0)
   
   
                            End If
   
                                End If
         
                                   End If
        Next
         
      Next
      
    Next
Next
     
 
Application.ScreenUpdating = True
Application.Calculation = xlAutomatic
 
 
End Sub

回答
投稿日時: 23/08/04 01:23:22
投稿者: hatena
投稿者のウェブサイトに移動

この掲示板では、ファイルを直接ダウンロードさせることは禁止されています。(下記リンク参照)
 
Q&A掲示板ご利用上のお願い 禁止事項
https://www.moug.net/faq/kiyaku.html#link4
 
シートレイアウト、データ例を提示して、やりたいことを説明するようににしてください。
 
ということで、ファイルは見てませんので、提示のコードをざっとみて高速化の一般論をヒントとして提示しておきます。
 
1.画面更新の停止、自動計算の停止、これは既に実装してますね。
 
2.セルの色付け(背景色の変更)は1セルずつするのではなく、Unionメソッドで該当セルをまとめて、一気に背景色の変更する。
 
3.セルを直接参照するのは遅いので、セル範囲を配列に格納して、配列を参照するようにする。
 
4.受入データと元データの比較をループで行うのは比較回数が多くなるので、Dictionaryオブジェクトを使って比較するようにする。
 
今回の要件なら、4.が一番高速化に貢献しそうな感じですね。
 
VBA処理を高速化する場合、Dictionaryと配列処理は非常によく使うものなので、習得するようにするといいでしょう。
WEB検索すると解説ページが多数見つかりますので、そちらで学習しましょう。
 
 

回答
投稿日時: 23/08/04 09:01:03
投稿者: 半平太

> If ws1.Cells(i, 2) = ws2.Cells(l, 1) Then '社員コードが一致
 
そのIF判定のタイミングは遅すぎないですか?
 
「i」と「l」があれば判定できるので、この直下に入れて、ループ回数を減らした方がいいと思います。
                   ↓
               For l = r2 To maxrow2
 
※その場合、対応するEnd Ifの挿入箇所は下方へずらす必要があります。
  つまり、For o2 = 2 To maxcolに対応するNextの下。

回答
投稿日時: 23/08/04 09:37:32
投稿者: 半平太

あれ? ここは一般機能板ですけども・・

回答
投稿日時: 23/08/04 12:25:40
投稿者: Suzu

If ws1.Cells(i, 2) = ws2.Cells(l, 1) Then '社員コードが一致
   If ws1.Cells(6, o2) = ws2.Cells(1, o3) Then
      If ws1.Cells(i, o2) <> ws2.Cells(l, o3) Then
         ws2.Cells(l, o3).Interior.Color = RGB(255, 0, 0)
 
If ws1.Cells(6, o2) = ws2.Cells(1, o3) Then
各 社員コード関係なく、ws1 6行目 の各値 と、ws2 1行目 の値の比較を行っている
これが本当なら、社員コードも関係ないと思われるので、先に、判定を行う。
 
その後で、
操作対象が、ws2 なので、ws2 の 各行の 社員コード の値 を、ws1の各行で探す
 そこで見つかった ws1 の各列の値と、ws2 の各列の値を比較 し、色付け
 
 
多分私なら
ws2 の 2列目 以降に、条件付き書式を設定し、色付けしてしまうかな。。

回答
投稿日時: 23/08/04 15:02:40
投稿者: hatena
投稿者のウェブサイトに移動

シートレイアウトの提示がないので、今回だけダウンロードして内容を確認してみました。
 
元データ(ws1)は
 6行目が見出し行、7行目からデータ
 2列目が社員番号、3列目からデータ
 
受入データ(ws2)は
 1行目が見出し行、2行目からデータ
 1列目が社員番号、2列目からデータ
 
 
提示のコードで気になった点
 
最終行をws1とws2の大きい方の値をとってループしているが、データのない部分までループすることになり無駄です。
ws1、ws2 それぞれ別に最終行を取得してそれを元にループすればいいでしょう。
最終列も同様にする。
 
ループ処理は下記の4重ループになっている。
ws1の社員番号の最終行まで
 ws2の社員番号の最終行まで
  ws1の行見出しの最終列まで
   ws2の行見出しの最終列まで
これはいいのだが、
社員コードの一致チェックは4階層目内でしているが、これは無駄です。
2階層目内でやって、
一致したときのみ列方向のループをすればいいでしょう。
 
各シート内で社員データ、列見出しに重複はないと思われるので、
社員データ、列見出しが一致したら、それ以降ループを続けるのは無駄なので、
そこでループを抜ける(Exit For)
 
上記を考慮して修正すると下記のようになります。

Sub 勤怠比較()
    Dim ws1 As Worksheet, ws2 As Worksheet
    Set ws1 = Sheets("元データ")
    Set ws2 = Sheets("受入データ")
    ws2.Cells.ClearFormats

    Dim i As Long
    Dim l As Long
    Dim o2 As Long
    Dim o3 As Long
    Dim maxRow1 As Long
    Dim maxRow2 As Long
    Dim maxCol1 As Long
    Dim maxCol2 As Long
    maxRow1 = ws1.Cells(Rows.Count, 2).End(xlUp).Row
    maxRow2 = ws2.Cells(Rows.Count, 1).End(xlUp).Row
    maxCol1 = ws1.Cells(6, Columns.Count).End(xlToLeft).Column
    maxCol2 = ws2.Cells(1, Columns.Count).End(xlToLeft).Column

    Application.ScreenUpdating = False
    Application.Calculation = xlManual


    For i = 7 To maxRow1
        For l = 2 To ws2.Cells(Rows.Count, 1).End(xlUp).Row
            If ws1.Cells(i, 2) = ws2.Cells(l, 1) Then '社員コードが一致
                For o2 = 3 To maxCol1
                    For o3 = 2 To maxCol2
                        If ws1.Cells(6, o2) = ws2.Cells(1, o3) Then '列見出しが一致
                            If ws1.Cells(i, o2) <> ws2.Cells(l, o3) Then
                                ws2.Cells(l, o3).Interior.Color = RGB(255, 0, 0)
                            End If
                            Exit For
                        End If
                    Next
                Next
                Exit For
            End If
        Next
    Next

    Application.ScreenUpdating = True
    Application.Calculation = xlAutomatic
End Sub

 
修正前のコードだと処理に数分かかっていたのが、上記のコードだと一瞬で終わりました。
 

回答
投稿日時: 23/08/04 15:09:47
投稿者: hatena
投稿者のウェブサイトに移動

ダウンロードしたデータ量なら一瞬で終わるので、
これ以上の高速化は必要と思いますが、
もし、今後、もっとデータ量が増えるなら、
前回の回答で提案したDictionaryや配列を使う方法も検討するといいでしょう。
 
作成してみましたので、ご参考に。
 

Public Sub Sample()
    Sheets("受入データ").Cells.ClearFormats

    Dim rng1 As Range, rng2 As Range
    Set rng1 = Sheets("元データ").Range("A6").CurrentRegion
    Set rng2 = Sheets("受入データ").Range("A1").CurrentRegion
    
    Dim ary1() As Variant, ary2() As Variant
    ary1 = rng1.Value
    ary2 = rng2.Value
    
    Dim dic As Object
    Set dic = CreateObject("Scripting.Dictionary")
    
    Dim r As Long, c As Long
    For r = 2 To UBound(ary1)
        For c = 3 To UBound(ary1, 2)
            dic(ary1(r, 2) & " " & ary1(1, c)) = ary1(r, c)
        Next
    Next
    
    Dim rng3 As Range
    For r = 2 To UBound(ary2)
        For c = 2 To UBound(ary2, 2)
            If dic.Exists(ary2(r, 1) & " " & ary2(1, c)) Then
                If dic(ary2(r, 1) & " " & ary2(1, c)) <> ary2(r, c) Then
                    If rng3 Is Nothing Then
                        Set rng3 = rng2.Cells(r, c)
                    Else
                        Set rng3 = Union(rng3, rng2.Cells(r, c))
                    End If
                End If
            Else
                Set rng3 = Union(rng3, rng2.Cells(r, c))
            End If
        Next
    Next
    
    rng3.Interior.Color = vbRed
End Sub

 

投稿日時: 23/08/05 16:02:55
投稿者: yama1006
メールを送信

hatena さんの引用:
シートレイアウトの提示がないので、今回だけダウンロードして内容を確認してみました。
 
元データ(ws1)は
 6行目が見出し行、7行目からデータ
 2列目が社員番号、3列目からデータ
 
受入データ(ws2)は
 1行目が見出し行、2行目からデータ
 1列目が社員番号、2列目からデータ
 
 
提示のコードで気になった点
 
最終行をws1とws2の大きい方の値をとってループしているが、データのない部分までループすることになり無駄です。
ws1、ws2 それぞれ別に最終行を取得してそれを元にループすればいいでしょう。
最終列も同様にする。
 
ループ処理は下記の4重ループになっている。
ws1の社員番号の最終行まで
 ws2の社員番号の最終行まで
  ws1の行見出しの最終列まで
   ws2の行見出しの最終列まで
これはいいのだが、
社員コードの一致チェックは4階層目内でしているが、これは無駄です。
2階層目内でやって、
一致したときのみ列方向のループをすればいいでしょう。
 
各シート内で社員データ、列見出しに重複はないと思われるので、
社員データ、列見出しが一致したら、それ以降ループを続けるのは無駄なので、
そこでループを抜ける(Exit For)
 
上記を考慮して修正すると下記のようになります。
Sub 勤怠比較()
    Dim ws1 As Worksheet, ws2 As Worksheet
    Set ws1 = Sheets("元データ")
    Set ws2 = Sheets("受入データ")
    ws2.Cells.ClearFormats

    Dim i As Long
    Dim l As Long
    Dim o2 As Long
    Dim o3 As Long
    Dim maxRow1 As Long
    Dim maxRow2 As Long
    Dim maxCol1 As Long
    Dim maxCol2 As Long
    maxRow1 = ws1.Cells(Rows.Count, 2).End(xlUp).Row
    maxRow2 = ws2.Cells(Rows.Count, 1).End(xlUp).Row
    maxCol1 = ws1.Cells(6, Columns.Count).End(xlToLeft).Column
    maxCol2 = ws2.Cells(1, Columns.Count).End(xlToLeft).Column

    Application.ScreenUpdating = False
    Application.Calculation = xlManual


    For i = 7 To maxRow1
        For l = 2 To ws2.Cells(Rows.Count, 1).End(xlUp).Row
            If ws1.Cells(i, 2) = ws2.Cells(l, 1) Then '社員コードが一致
                For o2 = 3 To maxCol1
                    For o3 = 2 To maxCol2
                        If ws1.Cells(6, o2) = ws2.Cells(1, o3) Then '列見出しが一致
                            If ws1.Cells(i, o2) <> ws2.Cells(l, o3) Then
                                ws2.Cells(l, o3).Interior.Color = RGB(255, 0, 0)
                            End If
                            [color=yellow]Exit For[/color]
                        End If
                    Next
                Next
             [color=yellow]   Exit For[/color]
            End If
        Next
    Next

    Application.ScreenUpdating = True
    Application.Calculation = xlAutomatic
End Sub

 
修正前のコードだと処理に数分かかっていたのが、上記のコードだと一瞬で終わりました。
 

 
exit for についてなのですが、こちらは
 
 For o3 = 2 To maxCol2
 
 For o2 = 3 To maxCol1
 
それぞれこちらのループを抜けるためのものでしょうか?

投稿日時: 23/08/05 19:24:49
投稿者: yama1006
メールを送信

 
Public Sub Sample()
    Sheets("受入データ").Cells.ClearFormats
 
    Dim rng1 As Range, rng2 As Range
    Set rng1 = Sheets("元データ").Range("A6").CurrentRegion
    Set rng2 = Sheets("受入データ").Range("A1").CurrentRegion
     
    Dim ary1() As Variant, ary2() As Variant
    ary1 = rng1.Value
    ary2 = rng2.Value
     
    Dim dic As Object
    Set dic = CreateObject("Scripting.Dictionary")
     
    Dim r As Long, c As Long
    For r = 2 To UBound(ary1)
        For c = 3 To UBound(ary1, 2)
            dic(ary1(r, 2) & " " & ary1(1, c)) = ary1(r, c) ←こちらはdicのkeyに社員番号、itemに社員番号と紐づいた各列の数字を格納しているという認識で合っておりますか?
        Next
    Next
     
    Dim rng3 As Range
    For r = 2 To UBound(ary2)
        For c = 2 To UBound(ary2, 2)
            If dic.Exists(ary2(r, 1) & " " & ary2(1, c)) Then ←こちらは元データのdicと重複しているかを確認しているということでしょうか?
                If dic(ary2(r, 1) & " " & ary2(1, c)) <> ary2(r, c) Then←こちらは何を格納しているのでしょうか? dicで受入データのkey社員番号とitemで紐づいた列番号に対して違っている数字を格納?
                    If rng3 Is Nothing Then ← rng3が存在しない場合は rng3に対して何をしているのでしょうか。
                        Set rng3 = rng2.Cells(r, c)
                    Else
                        Set rng3 = Union(rng3, rng2.Cells(r, c))
                    End If
                End If
            Else
                Set rng3 = Union(rng3, rng2.Cells(r, c))
            End If
        Next
    Next
     
    rng3.Interior.Color = vbRed
End Sub
 
 
元データの受入コードが受入データに存在しない列に対しては、色付しないようにしたいのですが、どうしたらよいでしょうか?
 
質問ばかりで申し訳ありません。

Dictionaryに関して調べてみたのですが、あまり理解が出来ておりません。。。。

回答
投稿日時: 23/08/05 22:10:24
投稿者: hatena
投稿者のウェブサイトに移動

yama1006 さんの引用:
exit for についてなのですが、こちらは
 
 For o3 = 2 To maxCol2
 
 For o2 = 3 To maxCol1
 
それぞれこちらのループを抜けるためのものでしょうか?

 
Exit For は記述してある階層のForを抜けます。
 
最初の Exit For は、
 For o3 = 2 To maxCol2
を抜けます。
 
次の Exit For は、
 For l = 2 To ws2.Cells(Rows.Count, 1).End(xlUp).Row
を抜けます。
 
今、気づきましたが、ここは、下記の方がいいですね。
 For l = 2 To maxRow2
 

回答
投稿日時: 23/08/05 23:06:22
投稿者: hatena
投稿者のウェブサイトに移動

引用:

           dic(ary1(r, 2) & " " & ary1(1, c)) = ary1(r, c) ←こちらはdicのkeyに社員番号、itemに社員番号と紐づいた各列の数字を格納しているという認識で合っておりますか?

だいたいあってますが、正確には、
Keyには社員番号と列見出し(何かのコード?)を連結したものを、
Itemには社員番号と列見出しに対応した数値を格納してます。
 
引用:

            If dic.Exists(ary2(r, 1) & " " & ary2(1, c)) Then ←こちらは元データのdicと重複しているかを確認しているということでしょうか?

受入データの社員番号&列見出しがdicのkeyに存在するかどうか確認しています。
存在したら、次のコードでそれぞれの対応する値が一致するか判定してます。
 
 
引用:

                If dic(ary2(r, 1) & " " & ary2(1, c)) <> ary2(r, c) Then←こちらは何を格納しているのでしょうか? dicで受入データのkey社員番号とitemで紐づいた列番号に対して違っている数字を格納?

VBAでは、代入と比較が同じ記号(=)を使うの紛らわしいのですが、
A = B
はAにBを代入という意味ですが(代入演算子)、
If A = B Then
とIf文に記述したときは、AとBを比較するという意味です(比較演算子)。一致したらTrueを、一致しなかったらFalseを返します。なにも代入(格納)はしません。
 
引用:

                    If rng3 Is Nothing Then ← rng3が存在しない場合は rng3に対して何をしているのでしょうか。
                        Set rng3 = rng2.Cells(r, c)
                    Else
                        Set rng3 = Union(rng3, rng2.Cells(r, c))
                    End If

この部分は、rng3 に不一致のセルをまとめて格納していくコードです。
ループを抜けてから、最後に該当の複数セルを一気に背景色を変更します。
その都度、1セルずつ変更するより高速化できます。
  
複数セルをUnionメソッドを使ってまとめることができます。
rng3が空(Nothing)のとき、 Union(rng3, rng2.Cells(r, c)) とするとエラーになるので、
Unionを使わずに不一致セルのみをrng3に代入してます。
 
引用:
元データの受入コードが受入データに存在しない列に対しては、色付しないようにしたいのですが、どうしたらよいでしょうか?

 
下記の2行を削除してください。
 
        For c = 2 To UBound(ary2, 2)
            If dic.Exists(ary2(r, 1) & " " & ary2(1, c)) Then
                If dic(ary2(r, 1) & " " & ary2(1, c)) <> ary2(r, c) Then
                    If rng3 Is Nothing Then
                        Set rng3 = rng2.Cells(r, c)
                    Else
                        Set rng3 = Union(rng3, rng2.Cells(r, c))
                    End If
                End If
            Else   '←この行削除
                Set rng3 = Union(rng3, rng2.Cells(r, c))'←この行削除
            End If

 
 

投稿日時: 23/08/06 12:13:03
投稿者: yama1006
メールを送信

 
If dic(ary2(r, 1) & " " & ary2(1, c)) <> ary2(r, c) Then
 
 
詳細にご教示いただき大変助かっております。
 
こちらについては比較されているとのことなのですが、一見、受入データの社員番号と紐づいた各受入コードの数値を比較しているように見受けられるのですが、元データの数値と比較しているのでしょうか?

回答
投稿日時: 23/08/06 16:30:01
投稿者: hatena
投稿者のウェブサイトに移動

yama1006 さんの引用:

If dic(ary2(r, 1) & " " & ary2(1, c)) <> ary2(r, c) Then
 
 
詳細にご教示いただき大変助かっております。
 
こちらについては比較されているとのことなのですが、一見、受入データの社員番号と紐づいた各受入コードの数値を比較しているように見受けられるのですが、元データの数値と比較しているのでしょうか?

 
dicには、元データの 社員コード&行見出し(Key) とそれに対応する数値(Item) が格納されています。
 
提示のコードの直前のIf文の下記で
dic.Exists(ary2(r, 1) & " " & ary2(1, c))
で、受入データの 社員コード&行見出し と同一のKeyが存在するか確認して、
存在するなら、
dic(ary2(r, 1) & " " & ary2(1, c))
で、そのキーに対応する元データの数値(Item)を取り出します。
それと、このキーに対応する受入データの数値
ary2(r, c)
とを比較しています。

投稿日時: 23/08/06 23:48:09
投稿者: yama1006
メールを送信

 
 大変分かりやすい解説ありがとうございます。
 
 dictionaryについては、私の理解が足りていない部分もありますがこちらで解決とさせていただきます。
 
itemとして引き出す部分が少々理解できておりませんが、こちらは引き続き勉強していきたいと思います。
 
ありがとうございました。