使用Excel VBA查找工作簿中的所有匹配项

时间:2013-10-21 21:13:26

标签: excel vba excel-vba

我正在尝试编写一个VBA例程,该例程将获取一个字符串,搜索给定的Excel工作簿,并将所有可能的匹配返回给我。

我目前有一个可行的实现,但它非常慢,因为它是一个双循环。当然,内置的Excel Find函数被“优化”以找到单个匹配,但我希望它返回一个初始匹配数组,然后我可以应用其他方法。

我将发布一些已经存在的伪代码

For all sheets in workbook
    For all used rows in worksheet
        If cell matches search string
            do some stuff
        end
    end
end

如前所述,这个双循环使得运行速度非常慢,所以我希望尽可能摆脱这种情况。有什么建议吗?

更新

虽然下面的答案会改进我的方法,但我最终会遇到一些略有不同的事情,因为我需要一遍又一遍地进行多次查询。

我决定循环遍历文档中的所有行,并创建一个包含每个唯一行的键的字典。然后,它指向的值将是可能匹配的列表,因此当我稍后查询时,我只需检查它是否存在,如果是,则只需获取匹配的快速列表。

基本上只做一次初始扫描,将所有内容存储在可管理的结构中,然后查询可以在O(1)时间内完成的结构

8 个答案:

答案 0 :(得分:21)

如上所述,使用Range.Find方法以及工作簿中每个工作表的循环是最快的方法。例如,下面的字符串“Question?”在每个工作表中,并用字符串“已回答!”替换它。

Sub FindAndExecute()

Dim Sh As Worksheet
Dim Loc As Range

For Each Sh In ThisWorkbook.Worksheets
    With Sh.UsedRange
        Set Loc = .Cells.Find(What:="Question?")
        If Not Loc Is Nothing Then
            Do Until Loc Is Nothing
                Loc.Value = "Answered!"
                Set Loc = .FindNext(Loc)
            Loop
        End If
    End With
    Set Loc = Nothing
Next

End Sub

答案 1 :(得分:4)

Function GetSearchArray(strSearch)
Dim strResults As String
Dim SHT As Worksheet
Dim rFND As Range
Dim sFirstAddress
For Each SHT In ThisWorkbook.Worksheets
    Set rFND = Nothing
    With SHT.UsedRange
        Set rFND = .Cells.Find(What:=strSearch, LookIn:=xlValues, LookAt:=xlPart, SearchOrder:=xlRows, SearchDirection:=xlNext, MatchCase:=False)
        If Not rFND Is Nothing Then
            sFirstAddress = rFND.Address
            Do
                If strResults = vbNullString Then
                    strResults = "Worksheet(" & SHT.Index & ").Range(" & Chr(34) & rFND.Address & Chr(34) & ")"
                Else
                    strResults = strResults & "|" & "Worksheet(" & SHT.Index & ").Range(" & Chr(34) & rFND.Address & Chr(34) & ")"
                End If
                Set rFND = .FindNext(rFND)
            Loop While Not rFND Is Nothing And rFND.Address <> sFirstAddress
        End If
    End With
Next
If strResults = vbNullString Then
    GetSearchArray = Null
ElseIf InStr(1, strResults, "|", 1) = 0 Then
    GetSearchArray = Array(strResults)
Else
    GetSearchArray = Split(strResults, "|")
End If
End Function

Sub test2()
For Each X In GetSearchArray("1")
    Debug.Print X
Next
End Sub

在执行Find循环时要小心,不要让自己进入无限循环...引用第一个找到的单元格地址并在每个“FindNext”语句后进行比较,以确保它没有返回到第一个找到了细胞。

答案 2 :(得分:2)

您可以使用Range.Find方法:

http://msdn.microsoft.com/en-us/library/office/ff839746.aspx

这将为您提供包含搜索字符串的第一个单元格。通过将“After”参数设置为下一个单元格来重复此操作,您将获得所有其他出现,直到您第一次出现为止。

这可能会快得多。

答案 3 :(得分:1)

基于B Hart答案的想法,这是我的函数版本,它搜索范围内的值,并返回所有找到的范围(单元格):

Function FindAll(ByVal rng As Range, ByVal searchTxt As String) As Range
    Dim foundCell As Range
    Dim firstAddress
    Dim rResult As Range
    With rng
        Set foundCell = .Find(What:=searchTxt, _
                              After:=.Cells(.Cells.Count), _
                              LookIn:=xlValues, _
                              LookAt:=xlWhole, _
                              SearchOrder:=xlByRows, _
                              SearchDirection:=xlNext, _
                              MatchCase:=False)
        If Not foundCell Is Nothing Then
            firstAddress = foundCell.Address
            Do
                If rResult Is Nothing Then
                    Set rResult = foundCell
                Else
                    Set rResult = Union(rResult, foundCell)
                End If
                Set foundCell = .FindNext(foundCell)
            Loop While Not foundCell Is Nothing And foundCell.Address <> firstAddress
        End If
    End With

    Set FindAll = rResult
End Function

要在整个工作簿中搜索值:

Dim wSh As Worksheet
Dim foundCells As Range
For Each wSh In ThisWorkbook.Worksheets
    Set foundCells = FindAll(wSh.UsedRange, "YourSearchString")
    If Not foundCells Is Nothing Then
        Debug.Print ("Results in sheet '" & wSh.Name & "':")
        Dim cell As Range
        For Each cell In foundCells
            Debug.Print ("The value has been found in cell: " & cell.Address)
        Next
    End If
Next

答案 4 :(得分:1)

根据艾哈迈德(Ahmed)的回答,在进行了一些整理和归纳之后,包括其他“查找”(Find)参数,因此我们可以在任何情况下使用此功能:

'Uses Range.Find to get a range of all find results within a worksheet
' Same as Find All from search dialog box
'
Function FindAll(rng As Range, What As Variant, Optional LookIn As XlFindLookIn = xlValues, Optional LookAt As XlLookAt = xlWhole, Optional SearchOrder As XlSearchOrder = xlByColumns, Optional SearchDirection As XlSearchDirection = xlNext, Optional MatchCase As Boolean = False, Optional MatchByte As Boolean = False, Optional SearchFormat As Boolean = False) As Range
    Dim SearchResult As Range
    Dim firstMatch As String
    With rng
        Set SearchResult = .Find(What, , LookIn, LookAt, SearchOrder, SearchDirection, MatchCase, MatchByte, SearchFormat)
        If Not SearchResult Is Nothing Then
            firstMatch = SearchResult.Address
            Do
                If FindAll Is Nothing Then
                    Set FindAll = SearchResult
                Else
                    Set FindAll = Union(FindAll, SearchResult)
                End If
                Set SearchResult = .FindNext(SearchResult)
            Loop While Not SearchResult Is Nothing And SearchResult.Address <> firstMatch
        End If
    End With
End Function

答案 5 :(得分:0)

您可以将数据读入数组。从那里你可以在内存中进行匹配,而不是一次读取一个单元格。

Pass cell contents into VBA Array

答案 6 :(得分:0)

下面的代码避免了创建无限循环。假设XYZ是我们在工作簿中寻找的字符串。

   Private Sub CommandButton1_Click()
   Dim Sh As Worksheet, myCounter
   Dim Loc As Range

   For Each Sh In ThisWorkbook.Worksheets
   With Sh.UsedRange
   Set Loc = .Cells.Find(What:="XYZ")

    If Not Loc Is Nothing Then

           MsgBox ("Value is found  in " & Sh.Name)
           myCounter = 1
            Set Loc = .FindNext(Loc)

    End If
End With
Next
If myCounter = 0 Then
MsgBox ("Value not present in this worrkbook")
End If

End Sub

答案 7 :(得分:0)

在我的Scenario中,我必须在A列中查找值,并且需要找出B列中的匹配。所以我创建了一个for循环,在其中将查找整个列A并从B列获得完全匹配。

Sub Type3()

Dim loc As String
Dim k As Integer
Dim i As Integer
Dim j As Integer
Dim findpage As String
Dim methodlist As String    

findpage = "benefits" 'We can change this values as  dynamic
k = Sheet1.Range("A1048576").End(xlUp).Row

For i = 1 To k
         loc = Sheet1.Cells(i, 1).Value           
        If StrComp(findpage, loc) = 0 Then                   
                 method = Cells(i, 2).Value
                 methodlist = methodlist + "," + method   'We can use string array as well                                   
        End If         
Next i            
End Sub