エクセルVBAで指定範囲を高速検索する方法を教えてください

このQ&Aのポイント
  • エクセルVBAで指定範囲を高速に検索する方法を教えてください。
  • 現在のコードでは5000行以上のデータに対して実用的な速度が出ないため、より高速な検索方法を知りたいです。
  • 逆順に検索してループを抜けるなどの方法も検討しましたが、それほど効果的ではありませんでした。他に効率的な方法があれば教えてください。
回答を見る
  • ベストアンサー

エクセルVBAで引き数に指定した範囲を高速に検索

いつもお世話になります。 エクセル(2003)で指定したデータと同じデータを指定範囲から探し、その行数を返す関数を作りたいのですが、何か良い方法を教えていただけませんでしょうか。 正順で調べて返すことはできるようになったのですが、5000行に貼り付けると実用的な速度が出なくて、、。実際には10000行以上になりそうです。せめて逆順に検索してループを抜けるか、とも思うのですが、それどころではない方法でもあるとうれしいです。 ちなみに、現在書いてあるコードは以下の通りです。 Function LasteData(tg As Object, ar As Range) As Integer ' ------- ' ' ar の中にある tgと同じデータの最後の位置を、その行番号で返す。 ' 見つからない時は、0 を返す。 ' ' --------------------------------------------------------------- Dim r As Range, p As Integer p = 0 For Each r In ar If Not IsEmpty(r.Value) Then If r.Value = tg.Value Then p = r.Row End If End If Next r LastData = p End Function

質問者が選んだベストアンサー

  • ベストアンサー
  • end-u
  • ベストアンサー率79% (496/625)
回答No.4

Findメソッドのヘルプを参照してください。 引数SearchDirectionをxlPreviousで使うとReverseFind的です。 Function LastData(tg As Range, ar As Range) As Long   Dim r As Range   Dim p As Long      Set r = ar.Find(What:=tg.Value, _           LookIn:=xlValues, _           LookAt:=xlWhole, _           SearchOrder:=xlByRows, _           SearchDirection:=xlPrevious, _           MatchByte:=True)   If Not r Is Nothing Then     p = r.Row     Set r = Nothing   End If   LastData = p End Function

kaznhi
質問者

お礼

FindRiverseが可能である事に気がつきませんでした。 先ほど使ってみたところ、かろうじて許容範囲内におさまりそうなので、この方法で行ってみるつもりです。ありがとうございました。

その他の回答 (4)

  • end-u
  • ベストアンサー率79% (496/625)
回答No.5

>5000行に貼り付けると実用的な速度が出なくて、、。実際には10000行以上になりそうです。 ワークシートで使うユーザー定義関数を作りたいという事でしたか。 読み違えてました。すみません。orz 例えば検索範囲が数万件で、数式として貼り付ける範囲が10,000件程になるなら Findを使うのは実用的ではありません。 No.3のki-aaaさんのdictionaryオブジェクトを使った手法をお勧めします。 http://www.eurus.dti.ne.jp/~yoneyama/Excel/vba/vba_dictionary.html または、ワークシートユーザー定義関数で処理する量では無いように思います。 素直にSubプロシージャで処理したほうが良くないですか? Sub test()   Dim dic As Object   Dim r(2) As Range   Dim ri  As Range   Dim mx  As Long   Dim i  As Long   Dim v, w      On Error Resume Next   With Application     Set r(0) = .InputBox("検索対象範囲(ar)を選択してください。", Type:=8)     Set r(1) = .InputBox("検索値の範囲(tg)を選択してください。", Type:=8)     Set r(2) = .InputBox("結果を書き出す先頭セルを選択してください。", Type:=8)   End With   If Err.Number <> 0 Then     MsgBox "cancel"     Exit Sub   End If   On Error GoTo 0      Set dic = CreateObject("scripting.dictionary")   For Each ri In Intersect(r(0).Worksheet.UsedRange, r(0))     dic(ri.Value) = ri.Row   Next   v = r(1).Value   mx = UBound(v)   ReDim w(1 To mx, 0) As Long   For i = 1 To mx     w(i, 0) = dic(v(i, 1))   Next   r(2).Resize(mx, 1).Value = w   Set dic = Nothing   Erase r, w End Sub

kaznhi
質問者

お礼

早く締め切り過ぎたようで、失礼しました。ご回答ありがとうございます。 仕事で使い始めに間に合わせるためにとりあえずでFindを使っていますが、動いている間に配列系のコードを書いてみるつもりです。そのため、関数にして組んでいます。 何せ、もともとvba の素養もなく、参考書もなく、ネット上のコードを参考にしているだけなので、自分でも「だいじょうぶかな?」状態です。きっとまたわけがわからなくてお聞きする事が出てくると思いますので、その時はまたお願いします。

  • ki-aaa
  • ベストアンサー率49% (105/213)
回答No.3

こんにちわ、 まず前提として、検索範囲(ar)が一定とします。 この回答例では、sheet1のA列です。 標準モジュール Option Explicit Public myDic As Object 'Scripting.Dictionary Private Sub myDec_set() Dim i As Long Dim v As Variant With Sheets("Sheet1") v = .Range("A1", .Cells(.Rows.Count, "A").End(xlUp)).Value End With Set myDic = CreateObject("Scripting.Dictionary") For i = 1 To UBound(v) myDic(v(i, 1)) = i Next i Erase v End Sub Function LastData(tg As Object) As Long If myDic Is Nothing Then Call myDec_set End If LastData = myDic.Item(tg.Value) End Function Sub Auto_Close() Set myDic = Nothing End Sub

kaznhi
質問者

お礼

配列に取り込む例ですね。先の方とともに、大切なコード列として勉強させていただきます。ありがとうございました。

  • mitarashi
  • ベストアンサー率59% (574/965)
回答No.2

指定範囲のサイズが大きい場合は、Rangeオブジェクトを操作せず、一旦配列に受けて操作する事で速度アップが図れます。 下記コードで、APIを使用しているのは時間を測定するためだけですので、気になさらない様に。 当方の、PentiumM,1.3G Hz,xl2000の環境で、 Rangeオブジェクトを扱う場合、200msec強、 配列に受けて扱う場合8~3msecといった速度差があります。 下記コードでは、実験用にとにかく10000回ループを回しておりますが、実際にはFor i = 1 To maxRowのところを、For i = maxRow to 1 step -1にして、見つかったらExit forすれば、更に時間短縮が図れると思います。ご参考まで。 Private Declare Function timeGetTime Lib "winmm.dll" () As Long Private Declare Function timeBeginPeriod Lib "winmm.dll" (ByVal uPeriod As Long) As Long Private Declare Function timeEndPeriod Lib "winmm.dll" (ByVal uPeriod As Long) As Long Private Declare Sub Sleep Lib "kernel32" (ByVal dwMilliseconds As Long) Sub test() Dim i As Long, startTime As Long Dim rng As Range, myCell As Range Dim buf As Variant Const maxRow As Long = 10000 timeBeginPeriod 1 Sleep 100 'タイマー安定待ち Set rng = Range(Cells(1, 1), Cells(maxRow, 1)) buf = rng 'Rangeオブジェクトにアクセスする場合 startTime = timeGetTime For Each myCell In rng.Cells If myCell.Value = "test" Then End If Next myCell Debug.Print timeGetTime - startTime 'Rangeの内容を代入した配列をアクセスする場合 startTime = timeGetTime For i = 1 To maxRow If buf(i, 1) = "test" Then End If Next i Debug.Print timeGetTime - startTime timeEndPeriod 1 End Sub

kaznhi
質問者

お礼

一度配列に取り込むというのも調べているうちに見ていたのですが、よく解からず放っておりました。 このくらい効果があるのでしたら試す価値ありですね。試してみます。 ありがとうございました。

  • imogasi
  • ベストアンサー率27% (4737/17068)
回答No.1

実行速度の速い襲いを、使用ロジックから、云々するほど、小生には力はない。普通のレベルではそうだろうと思う。 しかし全セル総なめ法は一般には、速くない。 Findメソッドを使って試してみてはどうでしょう。 どういうコードになるかは、簡単な適当行数のデータを良いし、マクロの記録で、編集ー検索ーで1セル見つかっても、検索を続け、最初の該当に帰るまで操作をして、コードを見たり、WEBで「エクセルVBA Find」で照会すれば、コードは作れるだろう。 ちなみにReverseFind的なものはVBAには無いようだ。 昇順云々に拘っても2分法などが使えないと、検索に意味が無い。 意外に、ソートして、2分法などで検索したら早いかもしれないが、データ列が複数のようだし、セルの順序を壊すとダメなのだろう。 また、同様質問で http://okwave.jp/qa/q4704609.html ScreenUpdatingの問題で満足している例ケースもある。 ーー 参考 A1:D5でbを探す例。その見つかったセルの内最下行数 Sub test1() Set 範囲 = Range("A1:D5") 検索情報 = "b" Set 変数 = 範囲.Find(検索情報, LookIn:=xlValues) If Not 変数 Is Nothing Then 変数2 = 変数.Address Do MsgBox 変数.Address Set 変数 = 範囲.FindNext(変数) If Not 変数 Is Nothing Then m = IIf(m < 変数.Row, 変数.Row, m) End If Loop While Not 変数 Is Nothing And 変数.Address <> 変数2 MsgBox m End If End Sub 多数行で速くなるかどうか、やってみてください。

kaznhi
質問者

お礼

早速の回答ありがとうございます。 DOSの時代にPascal系の言語でさんざプログラムを書いていた身としては、vbaの融通の効き加減にめんくらっております。 もとのデータが数値なので、使い難いと感じて避けていました。 ちょっと勉強して、トライしてみます。

関連するQ&A

  • EXCEL VBA で,プログラムが動かない.

    EXCEL2000のVBAでプログラムを組みました.(下に記す) 数千行に及ぶ数字のデータがあるのですが,20行に1回だけ,いらないデータが3行出てきます.その3行を削除していくプログラムです.Rangeのところで行を選んで欲しいのに,p列とq列を選んでしまうようです.RangeをRowsに変えたらエラーが出ました.こういう場合はどのように書けばいいのでしょうか.誰か教えてください.お願いします. Sub 削除() '20行ごとに入っている3行を削除していく. Dim i As Integer Dim p As Integer Dim q As Integer p = 21           'pの初期値は21 For i = 1 To 500 q = p + 2 r = "p:q" Range(r).Select Selection.Delete shift:=xlUp p = p + 20 Next i End Sub

  • Excel VBA テキストボックスを検索

    テキストボックス3に数値を入力し ExcelのA列にあるか検索をかける。 ある場合は、B列の同じ行に 「みーつけた!」と入力。 その設定で組んでみたのですが、 テキストボックス3にデータを6桁入力しようとすると 6桁目にオーバーフローエラーが出ます。 このプログラムの何処がおかしいのでしょうか? Private Sub TextBox3_Change() Dim Number As Integer If TextBox3.Value <> "" Then '空じゃない場合 Number = TextBox3.Value Call 検索(Number) MsgBox TextBox3.Value End If End Sub Sub 検索(ByVal Number As Variant) Dim FoundCell As Range Set FoundCell = Range("A:A").Cells.Find(What:=Number, lookat:=xlPart) If FoundCell Is Nothing Then Else FoundCell.Activate Range("O" & ActiveCell.Row).Value = "みーつけた!" End If End Sub

  • VBAの検索について

    Excelシートに表の一覧があり、項目(5行目)のところでウィンドウの固定をしています。 検索したいNo.をセル(G2)に入力し、コマンドボタンをクリックします。 セル(G2)に入力されたNo.とA列に入力されているNo.が一致する行を検索し、一致した行(複数はない)を項目の下までスクロールさせた状態で表示したいと思っています。 検索までは下記プログラムでできているのですが、一致した行を項目の下までスクロールさせた状態で表示するのはどうしたらよいのでしょうか。 ************************************************************* Private Sub CommandButton1_Click()   Dim myClm As Integer, myFind As Integer, myRow As Integer   myClm = 1 'A列   If Sheet1.Range("G2") = "" Then Exit Sub   myFind = Sheet1.Range("G2")   For myRow = Cells(Rows.Count, myClm).End(xlUp).Row To 1 Step -1    With Cells(myRow, myClm)     If .Value Like myFind Then       .Activate       Exit For     End If    End With    Next End Sub ************************************************************

  • エクセルVBA

    A列を基準にBC列が空白ならAの数値を入れて、A>BならB列を更新、A<CならC列を更新 A列が数値以外ならその行をスキップ、という処理をしたいのですが Option Explicit Dim A As Range, B As Range, C As Range Dim i As Long Sub test() For i = 1 To 10 Set A = Cells(i, 1) Set B = A.Offset(0, 1) Set C = A.Offset(0, 2) If IsNumeric(A) Then Else Exit Sub End If If B.Value = "" Then B.Value = A.Value If C.Value = "" Then C.Value = A.Value If A.Value > B.Value Then B.Value = A.Value If A.Value < C.Value Then C.Value = A.Value Next i End Sub とすると数値以外の行の時点で停止してしまいます。 その行を飛ばして次の行に進むにはどうしたらいいのでしょうか?

  • エクセルのVBAで質問があります。

    現在自分はカレンダーを作成していて、カレンダーに祝日を入れたいと思っています そこで これを if文もしくはselectcase どっちを使ったら楽か教えてもらいたいです Sub MakeCalendar(hi As Date) Dim i As Integer 'カレンダーにセットする日付 Dim g As Integer '日付をセットする行番号 Dim r As Integer '日付をセットする列番号 Dim lastDay As Integer '作成するカレンダーの月末 g = 2 'その月の開始曜日を算出 r = Weekday(DateSerial(Year(hi), Month(hi), 1), vbSunday) '指定された日付の翌月1日をもとて、日付の前の日を計算する lastDay = Day(DateSerial(Year(hi), Month(hi) + 1, 1) - 1) For i = 1 To lastDay 'iに日付の設定 Cells(g, r).Value = i If r = 7 Then r = 1 '戻して g = g + 1 '1行下に書く Else r = r + 1 '1列→に移動する End If Next End Sub Sub test() Range("A2:G7").Value = "" 'A2~G2にカレンダー表示 MakeCalendar Range("I2").Value 'I2に日付を入力する End Sub Sub test() Range("A2:G7").Value = "" 'A2~G2にカレンダー表示 MakeCalendar Range("I2").Value 'I2に日付を入力する End Sub このようなマクロを組んでいまして、祝日(ゴールデンウィークなど)を赤で塗りつぶすような物を考えています。 VBAは初心者でまだあまりわかっていないので、ご教授ください

  • エクセルVBAについて

    現在、エクセル2010を使用し文字色が黒だったら1と加算しそれ以外は0というVBAを VBA素人ながら、コピペしながら組んでいました。 以下 'ColorIndex = 1 は、黒です 3は赤 黄色は7 青は 5 '============================================ Function fcolor(a As Range, b As Integer) Dim c As Range, cu As Integer, frg As String Application.Volatile For Each c In a With c.Font If b = 1 Then If .Color = vbBlack Then cu = cu + 1 Else If .ColorIndex = b Then cu = cu + 1 End If End With Next fcolor = cu End Function という風にし、範囲を=fcolor(D3:E37,1)としていますが、 本当なら”0”と表記されるべきなのですが”66”となってしまいます。 VBAど素人なのでよろしくお願いします。

  • VBAで範囲を指定するには

    いつもお世話になります。 WINDOWS7 EXCELL2010 です。 下記のマクロを採用していますがその中に適用する範囲を指定したいのです。 指定する範囲は  I13~AM27 です。 どんな方法で追加すればいいかご教授を願えませんか。 Private Sub Worksheet_Change(ByVal Target As Range) Application.EnableEvents = False If Target.Value = 0 Then Target.Value = " " If Target.Value = 1 Then Target.Value = "日" If Target.Value = 2 Then Target.Value = "△" If Target.Value = 3 Then Target.Value = "▼" If Target.Value = 4 Then Target.Value = "前" If Target.Value = 5 Then Target.Value = "夜" If Target.Value = 6 Then Target.Value = "明" If Target.Value = 7 Then Target.Value = "有" Application.EnableEvents = True End Sub

  • ExcelのVBAがオーバーフローに!?

    読んで頂きありがとうございます。 以前に質問しました時に回答を頂き凄く助かっていたのですが、何故か「オーバーフロー」と表示されてしまいます。 ひょっとしたら桁なのかも知れません。 下記のVBAを教えて頂きしようしていました。 Option Explicit Sub Test() Dim c(), p(), q(), i, j, l, r, k As Long r = Range("B2").End(xlDown).Row l = Range("D2").End(xlDown).Row ReDim c(r - 2), p(r - 2), q(r - 2) For i = 1 To r - 2 c(i) = Cells(i + 2, 2).Value p(i) = Cells(i + 2, 3).Value q(i) = i + 2 Next i For i = 1 To r - 3 For j = i + 1 To r - 2 If c(i) > c(j) Then k = c(i) c(i) = c(j) c(j) = k k = p(i) p(i) = p(j) p(j) = k k = q(i) q(i) = q(j) q(j) = k End If Next j Next i For i = 1 To r - 2 Cells(i + 2, 7).Value = c(i) For j = 3 To r If Cells(j, 4).Value = c(i) Then Cells(i + 2, 8).Value = p(i) - Cells(j, 5).Value If p(i) - Cells(j, 5).Value <> 0 Then Range("B" & q(i)).Interior.ColorIndex = 6 Range("D" & j).Interior.ColorIndex = 6 End If Exit For End If Next j Next i For i = 3 To r k = 0 For j = 3 To l If Cells(i, 2).Value = Cells(j, 4).Value Then k = 1 Exit For End If Next j If k = 0 Then Range("B" & i).Interior.ColorIndex = 35 End If Next i For i = 3 To l k = 0 For j = 3 To r If Cells(i, 4).Value = Cells(j, 2).Value Then k = 1 Exit For End If Next j If k = 0 Then Range("D" & i).Interior.ColorIndex = 35 End If Next i End Sub デパックのClickすると15行目辺りの「k = c(i)」が黄色くなります。 正直言ってマクロとか全く解りませんが、コピーして使っていました。 どこをどう変更すれば良いのか教えて頂ければ助かります。 よろしくお願いします。

  • エクセルVBAなぜ実行時エラーが?

    エクセル2000です。 DATAと名づけた表の値を変換し、最大値から端数をプラマイするマクロなのですが、途中で「実行時エラー91 オブジェクト変数またはWithブロック変数が設定されていません。」となってしまいます。 なぜ出るかわからないので別なBookに同じ名前のRange をつくり同様の表でためしたらエラーにならずちゃんと作動します。 本番用のBookでのみエラーがでます。なぜなのでしょうか? 実行時エラーのでる With Range("DATA").Find(mx, LookIn:=xlValues) .Value = .Value + dff ' End With を、Withブロックをつかわず Range("DATA").Find(mx, LookIn:=xlValues).Select で試しても本番のBookではエラーになります。ほんとに困っています。 Sub 調整() Dim r As Double Dim c As Range Dim dff As Integer, mx As Long r = 25000 / Range("初期").Value With Sheets("内訳") Range("DATA").Value = .Range("F57:L73").Value '初期値複写 'MsgBox "初期値転写完了" For Each c In Range("DATA") If c.Value <> "" Then c.Value = Application.WorksheetFunction.Round(c.Value * r, -1) End If Next 'MsgBox "初期変換完了" dff = 25000 - Range("変換後") If dff <> 0 Then 'MsgBox dff mx = Application.WorksheetFunction.Max(Range("DATA")) 'MsgBox mx With Range("DATA").Find(mx, LookIn:=xlValues) .Value = .Value + dff 'ここで実行時エラー! End With End If End With End Sub

  • Excel VBA

    初心者で済みません 少し困っています。よろしくお願いします。 Range("J17") = Range("F17") * Range("H17") If Range("J17").Value = "0" Then Range("J17").Value = "" End If Range("J18") = Range("F18") * Range("H18") If Range("J18").Value = "0" Then Range("J18").Value = "" End If Range("J19") = Range("F19") * Range("H19") If Range("J19").Value = "0" Then Range("J19").Value = "" End If 上記のコードを簡単にしたいのですが、どうすればいいのか、わかりません。どうか教えていただけませんでしょうか?