简体   繁体   English

VBA 删除不包含特定值的行的更快或最佳替代方案?

[英]Faster Or Best Alternative for VBA to delete rows not containing specific values?

Quick question to save everybody's time:快速提问以节省大家的时间:

I have the code below that works fine but is too slow for my 30,000+ lines.我有下面的代码可以正常工作,但对于我的 30,000 多行来说太慢了。

It basically deletes all the rows not containing the states TX, AR, LA and OK from column AD.它基本上从 AD 列中删除所有不包含状态 TX、AR、LA 和 OK 的行。

Sub DeleteStateExceptions()
    Dim iLastRow As Long
    Dim i As Long
    iLastRow = Cells(Rows.Count, "AD").End(xlUp).Row
    For i = iLastRow To 2 Step -1
        Select Case Cells(i, "AD").Value
            Case "TX"
            Case "OK"
            Case "AR"
            Case "LA"
            Case Else
                Rows(i).Delete
            End Select
    Next i
    'deletes row when cell in column AD is not TX, OK, AR or LA
End Sub

Any amendment to make it faster?有什么修改让它更快吗? Would you use a different logic?你会使用不同的逻辑吗?

Please, try the next updated code.请尝试下一个更新的代码。 It should be very fast:它应该非常快:

Sub DeleteStateExceptions()
    Dim iLastRow As Long, arrMark, lastEmptyCol As Long, i As Long, boolDel As Boolean
    iLastRow = cells(rows.count, "AD").End(xlUp).Row
    lastEmptyCol = ActiveSheet.UsedRange.Column + ActiveSheet.UsedRange.Columns.count + 1
    ReDim arrMark(1 To iLastRow - 1, 1 To 1)
    For i = 2 To iLastRow
        Select Case cells(i, "AD").value
            Case "TX", "OK", "AR", "LA"
            Case Else
                boolDel = True  'to delete only if at least a row has been marked
                arrMark(i - 1, 1) = "Del"
            End Select
    Next i
    If boolDel Then
        With cells(2, lastEmptyCol).Resize(UBound(arrMark), 1)
            .value = arrMark
            .SpecialCells(xlCellTypeConstants).EntireRow.Delete
        End With
    End If
End Sub

An alternative would be to create a Union range, but in case of large ranges, creating of this one slows down the speed seriously.另一种方法是创建一个Union范围,但在大范围的情况下,创建这个范围会严重减慢速度。 You can set a maximum cells limit (iterate backwards), let us say, 100, delete the rows already in the Union range and set it as Nothing .您可以设置最大单元格限制(向后迭代),假设为 100,删除已在Union范围内的行并将其设置为Nothing

But the above solution should be the fastest, in my opinion...但在我看来,上述解决方案应该是最快的......

Edited :编辑

I promised to come back and supply a solution overpassing the limitation of a specific number of arrays in a discontinuous range.我答应回来并提供一个解决方案,超越不连续范围内特定数量的数组的限制。 I knew only about the 8192 for versions up to 2007 inclusive.我只知道 8192 直到 2007 年的版本。 It looks, such a limitation also exists in the newer versions, even if bigger.看起来,这样的限制也存在于较新的版本中,即使更大。 In order to test the above (much improved) way against the Union range version, I imagined the next testing way:为了针对Union range 版本测试上述(大大改进)的方式,我设想了下一个测试方式:

  1. Place a constant declaration on top of the module keeping the testing code (in the declarations area):在保留测试代码的模块顶部放置一个常量声明(在声明区域中):
 Private Const arrRepeat As Long = 5000
  1. Copy the next code of a Sub building a similar environment to test the versions in a similar way, plus the sorting one:复制下一个Sub构建类似环境的代码,以类似的方式测试版本,加上排序:
3. Copy the improved above version, being extremely fast:
Sub DeleteStateExceptions()
    Dim iLastRow As Long, arrMark, lastEmptyCol As Long, i As Long, boolDel As Boolean
    Dim tm, arrSort
    
    buildTestingRange arrRepeat
    
    tm = Timer
    iLastRow = cells(rows.count, "AD").End(xlUp).Row
    arrSort = Evaluate("ROW(1:" & iLastRow - 1 & ")") 'create an array of necessary existing rows number
    lastEmptyCol = ActiveSheet.UsedRange.Column + ActiveSheet.UsedRange.Columns.count + 1
    cells(1, lastEmptyCol + 1).value = "InitSort"     'place a header to the initial sort column
    cells(2, lastEmptyCol + 1).Resize(UBound(arrSort), 1).value = arrSort 'drop the array content in the column
    
    ReDim arrMark(1 To iLastRow - 1, 1 To 1)
    For i = 2 To iLastRow
        Select Case cells(i, "AD").value
            Case "TX", "OK", "AR", "LA"
            Case Else
                boolDel = True
                arrMark(i - 1, 1) = "Del"
            End Select
    Next i
    If boolDel Then
        With cells(2, lastEmptyCol).Resize(UBound(arrMark), 1)
            Application.ScreenUpdating = False: Application.Calculation = xlCalculationManual 'some optimization...
            .value = arrMark            'drop the arrMark content
            'sort the area where the above array content has been dropped:
             SortByColumn Range("A1", cells(iLastRow, lastEmptyCol + 1)), Range(cells(1, lastEmptyCol), cells(iLastRow, lastEmptyCol))
             .SpecialCells(xlCellTypeConstants).EntireRow.Delete  'delete the rows containing "Del"
             'sort according to the original sheet initial sorting:
             SortByColumn Range("A1", cells(iLastRow, lastEmptyCol + 1)), Range(cells(1, lastEmptyCol + 1), cells(iLastRow, lastEmptyCol + 1)), True
             Range(cells(1, lastEmptyCol), cells(iLastRow, lastEmptyCol + 1)).Clear  'clear the helping column (the original sorting of the sheet)
            Application.ScreenUpdating = True: Application.Calculation = xlCalculationAutomatic
        End With
    End If
    Debug.Print "Markers: Delete rows in " & Round(Timer - tm, 2) & " sec (" & arrRepeat & ")"
End Sub

Sub SortByColumn(rng As Range, rngS As Range, Optional boolAscending As Boolean = False)
    rngS.cells(1).value = "LastColumn"
    ActiveSheet.Sort.SortFields.Clear
    ActiveSheet.Sort.SortFields.Add2 key:=rngS, SortOn:=xlSortOnValues, Order:=IIf(boolAscending, xlAscending, xlDescending), DataOption:= _
        xlSortNormal
    With ActiveSheet.Sort
        .SetRange rng
        .Header = xlYes
        .MatchCase = False
        .Orientation = xlTopToBottom
        .SortMethod = xlPinYin
        .Apply
    End With
End Sub

Then copy the Union range version:然后复制Union范围版本:

Sub DeleteStateExceptionsUnion()
    Dim iLastRow As Long, rngDel As Range, i As Long
    Dim tm
    
    buildTestingRange arrRepeat
    
    tm = Timer
    iLastRow = cells(rows.count, "AD").End(xlUp).Row
    ReDim arrMark(1 To iLastRow - 1, 1 To 1)
    For i = 2 To iLastRow
        Select Case cells(i, "AD").value
            Case "TX", "OK", "AR", "LA"
            Case Else
                If rngDel Is Nothing Then
                    Set rngDel = cells(i, "AD")
                Else
                    Set rngDel = Union(rngDel, cells(i, "AD"))
                End If
            End Select
    Next i
    Application.ScreenUpdating = False: Application.Calculation = xlCalculationManual
     If Not rngDel Is Nothing Then rngDel.EntireRow.Delete
    Application.ScreenUpdating = True: Application.Calculation = xlCalculationAutomatic
    
    Debug.Print "Union: Delete rows in " & Round(Timer - tm, 2) & " sec (" & arrRepeat & ")"
End Sub

And finally, the version using Union in batches, to avoid the code slowing down when such a range needs to be very large:最后,批量使用Union的版本,以避免在需要非常大的范围时代码变慢:

Sub DeleteStateExceptionsUnionBatch()
    Dim iLastRow As Long, rngDel As Range, i As Long
    Dim tm, batch As Long, count As Long
    
    buildTestingRange arrRepeat
    
    tm = Timer
    batch = 700
    iLastRow = cells(rows.count, "AD").End(xlUp).Row
    ReDim arrMark(1 To iLastRow - 1, 1 To 1)
    For i = iLastRow To 2 Step -1              'iterate backwards
        Select Case cells(i, "AD").value
            Case "TX", "OK", "AR", "LA"
            Case Else
                count = count + 1
                If rngDel Is Nothing Then
                    Set rngDel = cells(i, "AD")
                Else
                    Set rngDel = Union(rngDel, cells(i, "AD"))
                End If
                If count >= batch Then
                    Application.ScreenUpdating = False: Application.Calculation = xlCalculationManual
                     rngDel.EntireRow.Delete: Set rngDel = Nothing: count = 0
                    Application.ScreenUpdating = True: Application.Calculation = xlCalculationAutomatic
                End If
            End Select
    Next i
    Application.ScreenUpdating = False: Application.Calculation = xlCalculationManual
     If Not rngDel Is Nothing Then rngDel.EntireRow.Delete
    Application.ScreenUpdating = True: Application.Calculation = xlCalculationAutomatic
    Debug.Print "Union batch: Delete rows in " & Round(Timer - tm, 2) & " sec (" & arrRepeat & ") batch: " & batch
End Sub
  1. Now run each of the three versions for the same arrRepeat value.现在为相同的arrRepeat值运行三个版本中的每一个。 You fistly need to activate an empty sheet...您首先需要激活一张空白表...

I obtained (in Immediate Window ) the next running times:我(在Immediate Window中)获得了下一个运行时间:

Built testing range (5000 rows)
Markers: Delete rows in 0.33 sec (5000)
Built testing range (5000 rows)
Union: Delete rows in 24 sec (5000)
Built testing range (5000 rows)
Union batch: Delete rows in 18.79 sec (5000) batch: 600
Built testing range (5000 rows)
Union batch: Delete rows in 18.97 sec (5000) batch: 500
-------------------------------------------------------
Built testing range (10000 rows)
Markers: Delete rows in 0.43 sec (10000)
Built testing range (10000 rows)
Union: Delete rows in 51.23 sec (10000)
Built testing range (10000 rows)
Union batch: Delete rows in 14.57 sec (10000) batch: 500
--------------------------------------------------------
Built testing range (50000 rows)
Markers: Delete rows in 1.34 sec (50000)
Built testing range (50000 rows)
Union batch: Delete rows in 129.36 sec (50000) batch: 500
Built testing range (50000 rows)
Union batch: Delete rows in 125.47 sec (50000) batch: 600
Built testing range (50000 rows)

I tried Union range version but I had to close Excel after about 15 minutes...我尝试了 Union range 版本,但我不得不在大约 15 分钟后关闭 Excel ......

Delete Not-Criteria Rows删除非标准行

  • When the criteria column is not sorted, it may take 'forever' to delete hundreds or even tens of thousands of rows.当条件列未排序时,删除数百甚至数万行可能需要“永远”。
  • The following will insert and populate two columns, an integer sequence column, and the match column.下面将插入和填充两列,一个整数序列列和匹配列。
  • After the data is sorted by the match column, the now contiguous range of error values will be used to quickly delete the undesired rows.数据按匹配列排序后,将使用现在连续的错误值范围快速删除不需要的行。
  • The integer sequence column will be used to finally sort the data to regain the initial order.整数序列列将用于最终对数据进行排序以恢复初始顺序。
Sub DeleteNotCriteriaRowsTEST()
    
    Const CriteriaList As String = "TX,OK,AR,LA"
    Const FirstCellAddress As String = "AD2"
    
    Dim ws As Worksheet: Set ws = ActiveSheet ' improve!
    Dim FirstCell As Range: Set FirstCell = ws.Range(FirstCellAddress)
    
    DeleteNotCriteriaRows FirstCell, CriteriaList

End Sub

Sub DeleteNotCriteriaRows( _
        ByVal FirstCell As Range, _
        ByVal CriteriaList As String, _
        Optional ByVal CriteriaDelimiter As String = ",")
    Const ProcName As String = "DeleteNotCriteriaRows"
    Dim NothingToDelete As Boolean
    On Error GoTo ClearError
    
    Dim Criteria() As String: Criteria = Split(CriteriaList, CriteriaDelimiter)
    
    Dim ws As Worksheet
    Dim rgColumn As Range
    Dim rCount As Long
    
    With FirstCell.Cells(1)
        Set ws = .Worksheet
        Dim lCell As Range: Set lCell = .Resize(ws.Rows.Count _
            - .Row + 1).Find("*", , xlFormulas, , , xlPrevious)
        rCount = lCell.Row - .Row + 1
        Set rgColumn = .Resize(rCount)
    End With
    
    Dim rgTotal As Range
    Set rgTotal = Intersect(ws.UsedRange, rgColumn.EntireRow)
    
    Application.ScreenUpdating = False
    
    Dim rgInsert As Range
    Set rgInsert = rgColumn.Cells(1).Offset(, 1).Resize(, 2).EntireColumn
    rgInsert.Insert xlShiftToRight, xlFormatFromLeftOrAbove
    
    Dim rgIntegerSequence As Range: Set rgIntegerSequence = rgColumn.Offset(, 1)
    With rgIntegerSequence
        .NumberFormat = "0"
        .Formula = "=ROW()"
        .Value = .Value
    End With
    
    Dim rgMatch As Range: Set rgMatch = rgColumn.Offset(, 2)
    With rgMatch
        .NumberFormat = "General"
        .Value = Application.Match(rgColumn, Criteria, 0)
    End With
        
    rgTotal.Sort rgMatch, xlAscending, , , , , , xlNo
    
    Dim rgDelete As Range
    
    On Error Resume Next
        Set rgDelete = Intersect(ws.UsedRange, _
            rgMatch.SpecialCells(xlCellTypeConstants, xlErrors).EntireRow)
    On Error GoTo ClearError
        
    If rgDelete Is Nothing Then
        NothingToDelete = True
    Else
        rgDelete.Delete xlShiftUp
    End If
        
    rgTotal.Sort rgIntegerSequence, xlAscending, , , , , , xlNo
        
    rgInsert.Offset(, -2).Delete xlShiftToLeft

SafeExit:
    Application.ScreenUpdating = True
    
    If NothingToDelete Then
        MsgBox "Nothing deleted.", vbExclamation, ProcName
    Else
        MsgBox "Rows deleted.", vbInformation, ProcName
    End If

    Exit Sub
ClearError:
    Debug.Print "'" & ProcName & "' Run-time error '" _
        & Err.Number & "':" & vbLf & "    " & Err.Description
    NothingToDelete = True
    Resume SafeExit
End Sub

I recommend keeping "sheet" specific and "use case specific" logic directly in cell formulas - then you can create more modular functions that can be reused.我建议直接在单元格公式中保留“工作表”特定和“用例特定”逻辑 - 然后您可以创建更多可重用的模块化函数。

In this scenario, if you add another column called "DeleteRow?",then populate it with a formula that returns "#DELETEROW#" when you want to delete the row else any other value* then you could have a reusable sub called "deleteRow" that takes listobject as an input and then sorts the data by column named "DeleteRow?", then filters on value "#DELETEROW#" and then deletes all filtered rows在这种情况下,如果您添加另一个名为“DeleteRow?”的列,然后在您想删除该行时使用返回“#DELETEROW#”的公式填充它,否则任何其他值* 那么您可以有一个名为“deleteRow”的可重用子" 将 listobject 作为输入,然后按名为 "DeleteRow?" 的列对数据进行排序,然后对值 "#DELETEROW#" 进行过滤,然后删除所有过滤的行

Going forward, this approach let's you adjust the formula as needed to change which rows to delete without having to alter the vba.展望未来,这种方法让您可以根据需要调整公式以更改要删除的行,而无需更改 vba。

* not tested but i bey if "DeleteRow?" * 未经测试,但如果“DeleteRow?” formula returns row() when you want to keep the row, then the current sort will be preserved当您要保留行时,公式返回row() ,则将保留当前排序

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM