5倍速!メールマガジン
外部アカウントで登録
受講生の声
新着の講座投稿
新着の講座コメント
新着のノート投稿
投稿一覧へ新着のノートコメント
表示できる投稿はありません。
サイト運営者紹介
小川 慶一講師/教材/システム開発者紹介
この学習サイトの教材制作、サポート、システム開発をすべてやっています。
表示できる投稿はありません。
この学習サイトの教材制作、サポート、システム開発をすべてやっています。
受講生さんの投稿
(投稿ID: 4585)
解釈があっているかの確認も含め、配列について質問させていただきます。
1つのExcelに2つのsheetがあり、1つの対比sheetには左に今月、右に前月のデータを横並びにし、差異を対比できるようにしています。データはNo.で管理しています。もう1つのsheetには前月のデータのみを入れています。行はデータNo.で列は項目とし、列10列くらいは毎月変わりませんが、行は5000行以上あり毎月追加削除が発生するので変わります。
発展1を参考に対比sheetに今月データを貼り付けた後に、前月sheetからで該当するデータを転記する。該当するNo.がない場合はデータの下部に転記する。最後に横並びにしたデータを対比し差異に色をつける、というマクロを作りました。ですが、行も列も多くちょっと時間がかかるため、配列を使ってやってみようと思いました。
そこで確認ですが、配列化するのはこの事例でいうと転記元の前月のデータ(5000行10列)を1つの配列とし、該当するNo.の必要な列を、今月列=前月列として転記する。で合っていますか?
転記先の対比sheetの転記列や、検索No.列も別の配列にしたほうがいいのかなど、配列にした方が良い基準がいまいちわからないのですが‥
よろしくおねがいいたします。
小川 慶一さんのコメント
(コメントID: 6331)
こんにちは。
For Next構文を複数組み合わせることでマッチングをできるようになった。
ただし、データの件数が多いため、マクロ実行時間短縮のため、より高速にマッチングできる方法を求めている。
そこで、配列を使うとマッチングを高速化できるのではなのではないかと考えた。
ついては、この見立ては適切かどうか教えてほしい。
また、適切だということならば、より具体的な方針について相談したい。
ということかと思います。
さて、では、マッチングのパフォーマンスが配列を使うと改善するか?というと。
するにはしますが、そんなに劇的には変わらないかと思います。理由は、この回答の最後に示します。
マッチングのパフォーマンスを上げるための進化のプロセスは、以下のとおりです。
[1] For Next構文を複数組み合わせることでマッチングをできるようになる
[2] For Each構文を複数組み合わせることでマッチングをできるようになる
[3] Findメソッドを使うことでマッチングをできるようになる
[4] SQLのSELECT文を使うことでマッチングできるようになる
以下、受講生さんは[1]はもうできるという前提で話を進めます。
また、該当するNo.がない場合はデータの下部に転記するというところまでできるという前提です。
話を簡単にするため、同一シート内にある2つの表でマッチングするものとしましょう。
左の表はA-D列、2行目から201行目まで。
右の表はF-I列、2行目から251行目まで。
A列とF列で値が一致したら、G列の値をD列に転記する。
マッチするレコードは0件か1件。
'【参考】以下は、デモデータ作成マクロ
すると、[1]では、以下の要領ですね。
[2] For Each構文を使ったほうが高速化はします。
For Next構文だと、 Range("A" & hida) 等と指定する都度、該当するセルを探してメモリに格納する。
一方、For Each構文では、処理対象のセルのすべてへの参照をいったんメモリに格納し、それから処理するから。探索対象を実際にセルを見に行って割り出すか、Forループ開始時にあらかじめメモリに入れておいたリストから見つけるか?の違い。プログラム実行領域が確保しているメモリへのアクセスのほうが当然に高速です。
以下でも結果は変わりません。
変数をむやみに宣言するのを嫌うならばこういう書き方もあり。
[3] さて、次に、.Findメソッドです。
発展編1で学んだ要領で自動記録をしつつエクセル画面から検索をすると、 .Find メソッドのコードのサンプルを得られます。
以下は、B列セルのB2:B201を選択した状態で "hida1 171" をキーワードに完全一致で検索した場合に得られるコードの例。
(エクセルのバージョンや環境によって得られるコードは多少異なります)
整形して不要部分を削除してみます。
以下でも動きます。
(本当は、 LookIn:=xlFormulas もこのケースでは不要です。MSDNの公式ヘルプ等でもご自身で調べてみてください
https://docs.microsoft.com/ja-jp/office/vba/api/excel.range.find )
検索機能を使うことのメリットは、ループ内で値を拾って調べる処理を書かないでも、エクセルが一発で該当する値を見つけてくれることです。
ということで、さきほどの[2]で提案したマクロは、以下のように書き換えられます。
まずは、該当するレコードが、必ず、1件だけ存在する場合。
面倒なのは、以下のそれぞれの場合です。
[3-A] 該当するレコードがみつからない場合
[3-B] 該当するレコードが複数ある場合
[3-A] でのNG例として、以下のコードを書いてみました。
左の表と右の表を比較して、マッチングしたら左の表の値を右の表に転記します。
しかし、右の表でIDが200を超えるものについて処理しようとしたところでエラーになります。
.Findメソッドの戻り値が Nothing (つまり、当該セルは存在しない)となるからです。
このエラーの回避策は、.Findメソッドの戻り値が Nothing かどうかによって処理を分岐することです。
以下の要領。
[3-B]の検索結果が複数存在する場合の処理については、前記MSDNサイトで「検索を繰り返すには、 FindNext および FindPrevious メソッドを使用できます。」という項目あたりを調べてみてください。
ひとつのご質問への回答としてはそこまで書くとボリュームが大きくなりすぎるのでというのと、ご期待だった配列を使った解法からかなりそれた方法の提案ということもあるので、僕からの解説はこのくらいにします。
SQLを使う方法の件についても、上記と同様の理由で省略します。外部連携講座の範囲になりますし。
まずは上記[3]に習熟してもらえれば、と思います。
最後に、この課題で配列を使うと高速化するのか?という件について書きます。
配列に値なりセル範囲への参照なりを格納しても「各要素を順番に取り出して比較していく」というやり方だと[1], [2]と変わらないので、[3]の .Find メソッドには及びません。
以上です。
参考になれば、と思います。
受講生さんのコメント
(コメントID: 6333)
こんにちは。
サンプルコードまでたくさん記載いただきありがとうございます。
情報が少なくてすみませんでした。
先生の予想ですべて合っております。
・重複コードはなしです。
・発展1のForNext文で(1)と同じように作成済です。
まず、いただいたコードのとおり表を作り、(2)(3)を実行し動きがわかった上で、自分の5000行の2つのsheetにコードを置き換えて実行してみました。
(1)ForNext文→自分で以前に作成済(実行時間3分15秒)
→1セルずつなので結構かかります
(2)ForEach文→先生のコードを参考に作成(実行時間44秒)
→こんなに短くなりました!!
(3)ForEach文でFind関数で前月No→当月Noを検索し転記
→実はここで引っかかりました。。1つのsheetでテストしたときは大丈夫でしたが、2つのsheetにわけて下記のように実行すると存在するNoもすべて(見つかりません)と前月sheetに書きこまれ、当月sheetには一つも転記されませんでした。どこが悪いかわかりますでしょうか?2つのsheetですが(2)のテストでは下記のような感じでもうまくいったので、ちょっと自分ではわかりません。。なお「rM」の上にカーソルを合わせるとデータが表示されましたが、「rH」の上ではNothingと表示されました。見て頂けると助かります。よろしくお願いいたします。
--------------------------------------
● ws1→当月⇔前月対比sheet(No:C列)F列から転記とする
● ws2→前月sheet(No:C列、Noなし:A列に見つかりません)
● 転記先20行目~の rHとrMのみ左右の場所を入れ替えました
小川 慶一さんのコメント
(コメントID: 6334)
> →1セルずつなので結構かかります
>
>(2)ForEach文→先生のコードを参考に作成(実行時間44秒)
> →こんなに短くなりました!!
↑
これは、興味深いですね。
さて、いただいたさらなるご質問について。
これは、プログラムのロジックではなく、調査対象のデータの問題ではないかと思います。
というのは、以下の[1], [2]からです。
[1] 別シートにあるデータの比較も可能です
[2] いただいたマクロが動作し得るサンプルデータを作り試してみましたが、[1]と同様に問題なく動作します
以下に示すサンプルコードを検証してください。
そのうえで、手元の本番データで、前後の空白、大文字小文字、全角半角の違いが生じていないか?検証してください。
この検証の際は、いきなりループでマッチングさせるのではなく、(ハナコのやり方を踏襲し)まずは絶対にマッチングするはずの2つのセルで比較を行い .Find がセルを1つ返すのところまで成功させるのに専念されるのが堅実かと思います。
[3]を参考にしてください。
受講生さんのコメント
(コメントID: 6335)
お世話になります。
早速見ていただきありがとうございます。
サンプルコードを検証しました。
[3]でまた "失敗" となったところで、先生の「調査対象のデータの問題」の原因がわかりました。
実はもう一つ情報をお伝えしていなかったのですが、調査対象のコード列(C列)にはコードや名称など複数の列を結合する関数を入れております。
(コードのみだど重複データができてしまい正確に転記できなくなるため)
その関数の結合データの結果で当月⇔前月でマッチングさせておりました。[2]までは普通に動いたので気付かず、すみません。。。
Set rH = rHall.Find(what:=rM.Value, lookat:=xlWhole)
→Set rH = rHall.Find(what:=rM.Value,LookIn:=xlValues, lookat:=xlWhole)
とういうことで、上記のように変更しましたら無事動きました。
ですが、やはり関数だと8分くらいかかりますので、一度関数を値貼付にして実行したところ16秒でできました!全然違うのですね。。
1列増やして一度値で貼りつけ他のセルを修正するか、[2]のForEachでも44秒なのでどちらか検討いたします。とても勉強になりました。
何度もサンプルデータをいただきありがとうございました。
小川 慶一さんのコメント
(コメントID: 6336)
> Set rH = rHall.Find(what:=rM.Value, lookat:=xlWhole)
> →Set rH = rHall.Find(what:=rM.Value,LookIn:=xlValues, lookat:=xlWhole)
>
> とういうことで、上記のように変更しましたら無事動きました。
> ですが、やはり関数だと8分くらいかかりますので、一度関数を値貼付にして実行したところ16秒でできました!全然違うのですね。。
よく、気づかれましたね!
技術的には、「検索対象を、セルに埋め込まれた式ではなく式の計算結果とするには?」ということですね。
そこだけを焦点にしたサンプルコードを作りました。
また、 Application.Calculation プロパティの値を変更すると、開いているエクセルファイルでセルが編集されても、都度全セルの計算式を再計算しなくなります。
もしまだ活用されていないようでしたら、そのことによる高速化も期待できそうです。(発展編1テキスト参照)
以下を参考にしたコードを書いてみて、最終的にどんな結果になったか?またお聞かせください (^^