【问题标题】:How to increase search speed for my vba userform?如何提高我的 vba 用户表单的搜索速度?
【发布时间】:2021-06-16 15:42:56
【问题描述】:

我有一个用于搜索值(字母数字)的用户表单。它运作良好。但需要很长时间,有时会挂起 excel 程序。

在我的用户表单中,有三个文本框和三个列表框。

为了搜索我的查询,我可以在任何文本框中输入任何内容。并且来自三个 Listbox 的所有数据都经过过滤并相互依赖。 例如:在用户窗体初始化时,每个列表框显示 100 个项目列表。 当我在任何搜索框(文本框)中输入任何内容时,项目将从所有列表框中过滤掉。

(对不起,我的英语和语法可能很弱。)

试着用这张图来理解。

有什么方法可以提高我的搜索速度?

我的代码如下:-

Option Explicit

Private Sub UserForm_Initialize()
    Call loadList
    Me.tbox_srch_ID.SetFocus
End Sub

Private Sub tbox_srch_ID_Keyup(ByVal KeyCode As MSForms.ReturnInteger, ByVal Shift As Integer)
    Me.tbox_srch_Word.Value = ""
    Call loadList
End Sub

Private Sub tbox_srch_Word_Keyup(ByVal KeyCode As MSForms.ReturnInteger, ByVal Shift As Integer)
    Me.tbox_srch_Party.Value = ""
    Call loadList
End Sub

Private Sub tbox_srch_Party_Keyup(ByVal KeyCode As MSForms.ReturnInteger, ByVal Shift As Integer)
    Me.tbox_srch_Word.Value = ""
    Call loadList
End Sub

Sub loadList()

    Application.ScreenUpdating = False
    Application.Calculation = xlCalculationManual
    Application.EnableEvents = False

    Dim baseArray() As Variant
    Dim resultArray() As Variant
    Dim IDArray() As Variant
    Dim wordArray() As Variant
    Dim PartyArray() As Variant
    Dim counter As Long, i As Long

    'On Error Resume Next

    'Clear current list boxes
    Me.lbox_ID.Clear
    Me.lbox_Word.Clear
    Me.lbox_Party.Clear

    'Assign source list to an array, unfiltered
    baseArray = Sheet2.Range("C2:E100001")

    'Set a default value to filter match counter
    counter = 0

    'Iterate through the source list, if search term is found add item to result array
    For i = LBound(baseArray) To UBound(baseArray)
        If ((InStr(1, baseArray(i, 1), Me.tbox_srch_ID.Value, vbTextCompare) > 0 And Me.tbox_srch_Word.Value = "" And Me.tbox_srch_Party.Value = "") Or _
    (InStr(1, baseArray(i, 2), Me.tbox_srch_Word.Value, vbTextCompare) > 0 And Me.tbox_srch_ID.Value = "" And Me.tbox_srch_Party.Value = "") Or _
    (InStr(1, baseArray(i, 3), Me.tbox_srch_Party.Value, vbTextCompare) > 0 And Me.tbox_srch_ID.Value = "" And Me.tbox_srch_Word.Value = "")) Then

            counter = counter + 1

            ReDim Preserve resultArray(1 To 3, 1 To counter)
            resultArray(1, counter) = baseArray(i, 1)
            resultArray(2, counter) = baseArray(i, 2)
            resultArray(3, counter) = baseArray(i, 3)
        End If
    Next i

    'If there is at least one match, separate result array to two arrays and load them to the listboxes
    If counter > 0 Then
        ReDim IDArray(1 To UBound(resultArray, 2), 1 To 1)
        ReDim wordArray(1 To UBound(resultArray, 2), 1 To 1)
        ReDim PartyArray(1 To UBound(resultArray, 2), 1 To 1)

        For i = LBound(resultArray, 2) To UBound(resultArray, 2)
            IDArray(i, 1) = resultArray(1, i)
            wordArray(i, 1) = resultArray(2, i)
            PartyArray(i, 1) = resultArray(3, i)
        Next i

        Me.lbox_ID.List = IDArray
        Me.lbox_Word.List = wordArray
        Me.lbox_Party.List = PartyArray

    End If

    On Error GoTo 0

    Application.Calculation = xlCalculationAutomatic
    Application.ScreenUpdating = True
    Application.EnableEvents = True

End Sub

【问题讨论】:

  • 在您的文本中您说 100 但源范围是 10 万条记录?现在搜索需要多长时间?
  • 真的有 100,000 行数据,还是只是抓取了超出需要的数据?如果你找到lastRow,你可以使用一个更小的数组来提高速度。此外,如果您将数据预加载到 Dictionary 中,您可以保存几乎所有搜索该值的迭代,至少在 Date 列中。
  • 在每个Keyup 中清除一个其他文本框 - 为什么只清除一个而不是其他两个?
  • 先生,我以 100 为例。而且我只有基本的VBA知识和英语知识。所以也许我无法澄清我的问题。
  • 您可能要考虑使用 ADODB:查询电子表格并在 recordset 中获取数据(单次读取),然后使用 recordset 的 filter 方法过滤为用户类型。这将需要使用 ADO 和使用 SQL 重新编码所有内容 - 包括读取、写入列表、构建过滤器字符串、用于连接的 ADO 样板等,但它可能会更快。

标签: vba


【解决方案1】:

Redim Preserve 占用大量资源,尤其是在运行 100K 次时。我会一步一步收集受影响的行号并将数据存储在一个数组中。

您应该使用一个变量来检查 For 循环的上限,因为 Ubound 是函数,不需要在每个循环中都对其进行评估。

您还应该修改 if 条件,因为它也需要大量资源。请注意,所有条件都被评估(即使布尔逻辑不需要它)并且您有 3 个InStr(乘以 100K)。 此外,对于 100K 行,您在每个循环中检查两次文本框是否为空。

你可以在某个地方设置一个逃生点,比如当你找到第一个空单元格时。无需检查空白区域。

我会这样重构它:

Dim RowColl as Collection
Set RowColl = New Collection

Dim boolID_Empty As Boolean, boolParty_Empty As Boolean, boolWord_Empty As Boolean
Dim boolSkip as Boolean
Dim iMax As Long

boolID_Empty = LenB(Me.tbox_srch_ID.Value) = 0  ' this is the fastest way to check for emptiness + check only once
boolParty_Empty = LenB(Me.tbox_srch_Party.Value) = 0 
boolWord_Empty = LenB(Me.tbox_srch_Word.Value) = 0
counter = 0

iMax = UBound(baseArray)
For i = LBound(baseArray) To iMax
    If LenB(baseArray(i, 1)) = 0 And LenB(baseArray(i, 2)) = 0 And LenB(baseArray(i, 3)) = 0 Then Exit For
    boolSkip = True

    If boolID_Empty And boolParty_Empty Then
       If InStr(1, baseArray(i, 2), Me.tbox_srch_Word.Value, vbTextCompare) > 0 Then boolSkip = False
    End If

    If boolSkip And boolWord_Empty And boolParty_Empty Then
       If InStr(1, baseArray(i, 1), Me.tbox_srch_ID.Value, vbTextCompare) > 0 Then boolSkip = False
    End If

    If boolSkip And boolID_Empty And boolWord_Empty Then
       If InStr(1, baseArray(i, 3), Me.tbox_srch_Party.Value, vbTextCompare) > 0 Then boolSkip = False
    End If

    If Not boolSkip Then
       RowColl.Add i    ' add row index to a collection
    End If
 Next i

 counter = RowColl.Count    

 ReDim resultArray(1 To 3, 1 To counter)

 Dim v
 i = 0
 For Each v in RowColl    
     resultArray(1, i) = baseArray(v, 1)
     resultArray(2, i) = baseArray(v, 2)
     resultArray(3, i) = baseArray(v, 3)
     i = i + 1
 Next

【讨论】:

  • For Each i in RowColl,给出编译错误。它说:对于每个控制变量必须是变体或对象。
  • 确实如此。已更正。
  • 感谢@AcsErno 先生的热情回复。但我不知道该把这段代码放在哪里,或者从我的旧代码中删除哪一行等等。
  • 我已经删除了所有旧的 Sub LoadList() 代码。只保留 Dim 和 baseArray = Sheet2.Range("C2:E100001"),然后粘贴您的代码。但它给出了下标超出范围错误,错误代码 9。
  • 先生,我已经重新发布了我的所有代码。这是有效的,但非常非常慢。
猜你喜欢
  • 2016-12-21
  • 2020-12-24
  • 1970-01-01
  • 1970-01-01
  • 2013-06-14
  • 1970-01-01
  • 2011-07-29
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多