質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.48%
VBA

VBAはオブジェクト指向プログラミング言語のひとつで、マクロを作成によりExcelなどのOffice業務を自動化することができます。

コピー

元のオブジェクトを破壊することなく、オブジェクトの複製を生成することをコピーと呼びます。

マクロ

定義された処理手続きに応じて、どのような一連の処理を行うのかを特定させるルールをマクロと呼びます。

Q&A

解決済

4回答

2232閲覧

古いデータを別シートに移動させる場合の高速化

Taka1108

総合スコア32

VBA

VBAはオブジェクト指向プログラミング言語のひとつで、マクロを作成によりExcelなどのOffice業務を自動化することができます。

コピー

元のオブジェクトを破壊することなく、オブジェクトの複製を生成することをコピーと呼びます。

マクロ

定義された処理手続きに応じて、どのような一連の処理を行うのかを特定させるルールをマクロと呼びます。

0グッド

0クリップ

投稿2020/04/12 05:34

編集2020/04/12 05:40

コード
エクセルで表のような商品の表を作成しています。

A       B     C    D     E
商品コード  商品名 成分 成分比率   更新日付
aaa     商品A a   0.7     2020/1/10
aaa     商品A b   0.3     2020/1/10
bbb     商品B c   0.5     2020/1/11
bbb     商品B d   0.5     2020/1/11
ccc     商品C f   1       2020/1/20
aaa     商品A a   0.6     2020/3/5
aaa     商品A b   0.4     2020/3/5
ddd     商品D a   0.4     2020/3/12
ddd     商品D f   0.6     2020/3/12
ccc     商品C s   1     2020/3/20

成分を色々調整しており、古いデータは別のシートに移動させたく次のようなコードを作成しました。
①コード、日付でソートする。
②コードが同じで日付が異なる箇所のコード、日付を記憶させF列に旧版と記載する。
③F列が旧版の列を別シートにコピーし削除する。
④日付でソートする。

これで思い通りには動くのですが、1000以上のデーターでは20分以上掛かり困っています。初心者のためこの程度のコードしか書けないのですが、高速化できる方法があるでしょうか。よろしくお願いします。
環境:Excel2010、Windows7

Sub 移動() Application.ScreenUpdating = False Dim txt As String, txt2 As String Dim i As Long, j As Long Dim ws As Worksheet, ws2 As Worksheet Set ws = Sheets("Sheet1") Set ws2 = Sheets("Sheet2") With ws.Sort With .SortFields .Clear .Add Key:=ws.Range("A1"), Order:=xlAscending .Add Key:=ws.Range("E1"), Order:=xlAscending End With .SetRange ws.Range("A1:F" & Cells(Rows.Count, 1).End(xlUp).Row) .Header = xlYes .Apply End With With ws For i = 2 To .Cells(Rows.Count, 1).End(xlUp).Row If .Cells(i, 1) = .Cells(i + 1, 1) And .Cells(i, 5) <> .Cells(i + 1, 5) Then txt = .Cells(i, 1) txt2 = .Cells(i, 5) For j = 2 To .Cells(Rows.Count, 1).End(xlUp).Row If .Cells(j, 1) = txt And .Cells(j, 5) = txt2 Then .Cells(j, 6) = "旧版" End If Next j End If Next i End With For i = ws.Cells(Rows.Count, 1).End(xlUp).Row To 2 Step -1 If ws.Cells(i, 6) = "旧版" Then ws.Rows(i).Copy Sheets("Sheet2").Select ws2.Rows(ws2.Cells(Rows.Count, 1).End(xlUp).Row).Offset(1, 0).Insert Shift:=xlDown ws.Rows(i).Delete Shift:=xlUp End If Next i ws.Select With ws.Sort With .SortFields .Clear .Add Key:=ws.Range("E1"), Order:=xlAscending End With .SetRange ws.Range("A1:F" & Cells(Rows.Count, 1).End(xlUp).Row) .Header = xlYes .Apply End With Application.ScreenUpdating = True End Sub

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答4

0

dictionayを使用してみました。
これで2重ループが1回ですむかと。
以下のようになります。

VBA

1Sub 移動() 2 Application.ScreenUpdating = False 3 Dim txt As String, txt2 As String 4 Dim i As Long, j As Long 5 Dim ws As Worksheet, ws2 As Worksheet 6 Dim dicT As Object 'キー 商品コード 値:更新日付 7 Dim key As Variant 8 Set dicT = CreateObject("Scripting.Dictionary") ' 連想配列の定義 9 10 Set ws = Sheets("Sheet1") 11 Set ws2 = Sheets("Sheet2") 12 With ws.Sort 13 With .SortFields 14 .Clear 15 .Add key:=ws.Range("A1"), Order:=xlAscending 16 .Add key:=ws.Range("E1"), Order:=xlAscending 17 End With 18 .SetRange ws.Range("A1:F" & Cells(Rows.Count, 1).End(xlUp).row) 19 .Header = xlYes 20 .Apply 21 End With 22 With ws 23 For i = .Cells(Rows.Count, 1).End(xlUp).row To 2 Step -1 24 key = .Cells(i, 1) 25 If dicT.exists(key) = True Then 26 If dicT(key) <> .Cells(i, 5) Then 27 .Cells(i, 6) = "旧版" 28 End If 29 Else 30 dicT(key) = .Cells(i, 5) 31 End If 32 Next 33 End With 34 For i = ws.Cells(Rows.Count, 1).End(xlUp).row To 2 Step -1 35 If ws.Cells(i, 6) = "旧版" Then 36 ws.Rows(i).Copy 37 Sheets("Sheet2").Select 38 ws2.Rows(ws2.Cells(Rows.Count, 1).End(xlUp).row).Offset(1, 0).Insert Shift:=xlDown 39 ws.Rows(i).Delete Shift:=xlUp 40 End If 41 Next i 42 ws.Select 43 With ws.Sort 44 With .SortFields 45 .Clear 46 .Add key:=ws.Range("E1"), Order:=xlAscending 47 End With 48 .SetRange ws.Range("A1:F" & Cells(Rows.Count, 1).End(xlUp).row) 49 .Header = xlYes 50 .Apply 51 End With 52 Application.ScreenUpdating = True 53End Sub 54

投稿2020/04/12 10:59

tatsu99

総合スコア5438

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

Taka1108

2020/04/12 11:35

回答ありがとうございました。 Dictionaryを使った方法勉強になりました。
guest

0

ベストアンサー

①コード、日付でソートする。

②コードが同じで日付が異なる箇所のコード、日付を記憶させF列に旧版と記載する。
③F列が旧版の列を別シートにコピーし削除する。
④日付でソートする。

① と ④ はそんなに時間がかかっていないし、大幅なの高速化はないでしょう。

② と ③ で時間がかかっていると予想されます。
まずは、② の高速化のロジックを考えてみました。

日付が昇順に並んでいるなら、ループをは後ろから走査した方がシンプルかつ高速になりそうです。
Boolean型の旧版判定変数を、
商品コードがブレーク(変化したら)したら False
日付がブレークしたら True にするというロジックにしました。

vba

1 2Sub 移動() 3 '略 ① 4 5 With ws 6 Dim old As Boolean ’旧版判定 7 For i = .Cells(Rows.Count, 1).End(xlUp).Row To 3 Step -1 8 If .Cells(i, 1) <> .Cells(i - 1, 1) Then '商品コードブレーク 9 old = False 10 ElseIf .Cells(i, 5) <> .Cells(i - 1, 5) Then '更新日ブレーク 11 old = True 12 End If 13 14 If old Then .Cells(i - 1, 6) = "旧版" 15 Next i 16 End With 17 18 '略 ③ ④ 19 20End Sub

③ の旧版のコピー/削除は、1行ずつコピー/削除するのではなく、AutoFilter で旧版のデータのみに絞り込んでから、コピー/削除すれば1回で済みますので、高速化になると思います。

vba

1 With ws.Range("A2:F" & ws.Cells(Rows.Count, 1).End(xlUp).Row) 2 .AutoFilter Field:=6, Criteria1:="=旧版" 3 .Copy ws2.Range("A2") 4 .EntireRow.Delete 5 .AutoFilter 6 End With

② の旧版判定のコードを配列を使ったものにしてみました。セル参照なしでメモリー上での処理になるのでさらに高速化できると思います。

vba

1 Dim rng As Range, ary() As Variant 2 Set rng = ws.Range("A2:F" & Cells(Rows.Count, 1).End(xlUp).Row) 3 ary = rng.Value '表データを配列に格納 4 Dim old As Boolean 5 For i = UBound(ary) To 2 Step -1 6 If ary(i, 1) <> ary(i - 1, 1) Then '商品コードブレーク 7 old = False 8 ElseIf ary(i, 5) <> ary(i - 1, 5) Then '更新日ブレーク 9 old = True 10 End If 11 12 If old Then ary(i - 1, 6) = "旧版" 13 Next i 14 rng.Columns(6).Value = Application.Index(ary, 0, 6) '配列の6列目を表の6列目に代入

投稿2020/04/12 09:26

編集2020/04/12 12:35
hatena19

総合スコア33715

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

Taka1108

2020/04/12 11:47

回答ありがとうございました。 家のPCで別のデーターでトライしてみましたが 1000近いデーターで15秒近くかかっていたのが、1秒以下になりました。 会社のPCは家のPCよりも環境が悪く、実際のデーターも別ファイルに移動したりと色々あるので 明日会社でコードを修正します。
Taka1108

2020/04/17 09:37

先週回答いただいて何の問題もなく作動するのですが、一つ疑問があります。 ElseIf .Cells(i, 5) <> .Cells(i - 1, 5) Then '更新日ブレーク old = True If old Then .Cells(i - 1, 6) = "旧版" 下のデーターで20204/10から20203/3に切り替わるところはCells(i, 5)<>Cells(i - 1, 5)でTrueなのですが、それより上はすべて20203/3なのでCells(i, 5) = Cells(i - 1, 5)となりFalseにならないのでしょうか。 質問が分かりにくいかもしれませんが、どうしてもこの部分が理解できずにいます。 こちらの望む通りの動きをしているにもかかわらず、申し訳ありません。 商品コード 商品名 成分 成分比率 更新日付 aaa    商品A a 0.5    2020/3/3  旧版 aaa    商品A b 0.2    2020/3/3  旧版 aaa    商品A c 0.3    2020/3/3  旧版 aaa    商品A a 0.3    2020/4/10 aaa    商品A b 0.2    2020/4/10 aaa    商品A c 0.3    2020/4/10 aaa    商品A d 0.2    2020/4/10 ccc    商品C a 1    2020/1/10
hatena19

2020/04/17 12:40

> それより上はすべて20203/3なのでCells(i, 5) = Cells(i - 1, 5)となりFalseにならないのでしょうか。 Cells(i, 5)<>Cells(i - 1, 5) の時は、old を True に変更するが、 Cells(i, 5) = Cells(i - 1, 5) の時は、何もしない、つまり、old は True のまま変化しない、 ということです。 言葉では分かりにくい場合は、ブレークポイントをその行に設定して、ステップ実行しながら、変数の値がどうなっているか確認するというデバッグ作業をすると理解しやすいと思います。 【超初心者向け】エクセルVBAでデバッグをする方法を解説します https://www.excelspeedup.com/vbadebug/
Taka1108

2020/04/18 06:00

ありがとうございました。納得できました。
guest

0

ループ回数を減らしました。どのぐらい速くなるかは分かりませんが、どうでしょうか?

VBA

1Sub test() 2 3 Dim txt As String, txt2 As String 4 Dim i As Long, j As Long 5 Dim ws As Worksheet, ws2 As Worksheet 6 Set ws = Sheets("Sheet1") 7 Set ws2 = Sheets("Sheet2") 8 With ws.Sort 9 With .SortFields 10 .Clear 11 .Add Key:=ws.Range("A1"), Order:=xlAscending 12 .Add Key:=ws.Range("E1"), Order:=xlDescending 13 End With 14 .SetRange ws.Range("A1:F" & Cells(Rows.Count, 1).End(xlUp).Row) 15 .Header = xlYes 16 .Apply 17 End With 18 19 With ws 20 txt = .Cells(2, 1).Value 21 txt2 = .Cells(2, 5).Value 22 For i = 3 To .Cells(Rows.Count, 1).End(xlUp).Row 23 If .Cells(i, 1).Value <> txt Then 24 txt = .Cells(i, 1).Value 25 txt2 = .Cells(i, 5).Value 26 ElseIf .Cells(i, 5).Value <> txt2 Then 27 .Cells(i, 6) = "旧版" 28 ws.Rows(i).Copy 29 ws2.Rows(ws2.Cells(Rows.Count, 1).End(xlUp).Row).Offset(1, 0).Insert Shift:=xlDown 30 ws.Rows(i).Delete Shift:=xlUp 31 i = i - 1 32 End If 33 Next i 34 End With 35 36End Sub

Forループを2回しているので、1回にしてはどうでしょうか?
検証はしていませんが下記の様になるかと思います。

VBA

1If .Cells(j, 1) = txt And .Cells(j, 5) = txt2 Then 2 .Cells(j, 6) = "旧版" 3 .Rows(j).Copy 4 ws2.Rows(ws2.Cells(Rows.Count, 1).End(xlUp).Row).Offset(1, 0).Insert Shift:=xlDown 5 .Rows(j).Delete Shift:=xlUp 6End If

投稿2020/04/12 06:48

編集2020/04/12 09:04
meg_

総合スコア10580

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

Taka1108

2020/04/12 08:40 編集

回答ありがとうございます。 検証してみましたが、商品Aの成分が二つあり、1行が移動されず残ったままになります。 ループを1回省略したことで、成分が二つあるものは一つの成分しか移動されないようです。 商品コード 商品名 成分 成分比率 更新日付 aaa 商品A b 0.3 2020/1/10 bbb 商品B c 0.5 2020/1/11 bbb 商品B d 0.5 2020/1/11 aaa 商品A a 0.6 2020/3/5 aaa 商品A b 0.4 2020/3/5 ddd 商品D a 0.4 2020/3/12 ddd 商品D f 0.6 2020/3/12 ccc 商品C s 1 2020/3/20
tatsu99

2020/04/12 09:46 編集

商品コード 商品名 成分 成分比率 更新日付 aaa 商品A b 0.3 2020/1/10・・・① aaa 商品A b 0.3 2020/1/10・・・② bbb 商品B d 0.5 2020/1/11 aaa 商品A a 0.6 2020/3/5・・・③ aaa 商品A b 0.4 2020/3/5・・・④ ddd 商品D a 0.4 2020/3/12 ddd 商品D f 0.6 2020/3/12 ccc 商品C s 1 2020/3/20 商品Aについてですが、上記の場合、残す(移動しない)のは①~④のどれでしょうか?
Taka1108

2020/04/12 10:21

残すのは日付が新しい③と④です。よろしくお願いします。
Taka1108

2020/04/12 11:44

回答ありがとうございました。 今後はループを少なく作成するように勉強していきます。
guest

0

解決済みですが、すっごい気になるので。。。。

VBAで悩む前に手動でさくっとやっつけたいですね。

ExcelVBA

1Sub test() 2 Dim i As Long 3 4 For i = 1 To 2 5 Worksheets(1).Copy After:=Worksheets(i) 6 With Worksheets(i + 1).UsedRange 7 .Sort Key1:=.Cells(.Columns.Count), Order1:=i, Header:=xlYes 8 .RemoveDuplicates Columns:=Array(1, 2, 3), Header:=xlYes 9 If i = 2 Then 10 .Sort Key1:=.Cells(.Columns.Count), Order1:=xlAscending, Header:=xlYes 11 End With 12 End With 13 Next 14End Sub

投稿2020/04/17 11:39

mattuwan

総合スコア2136

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.48%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問