0

So I have the script below in Execl VB that goes through the rows and deletes the ones that don't contain a certain keyword.

Sub Main()
    RowsDeleted = 0
    Keyword = "COLA"
    For i = 2 to ActiveSheet.UsedRange.Rows.Count
        If InStr(Cells(i, 1).Value, Keyword) = 0 Then
            Rows(i).Delete
            RowsDeleted = RowsDeleted + 1
            i = i - 1
        End If
    Next i
    MsgBox("Rows Deleted: " & RowsDeleted)
End Sub

The problem is that this script takes a very long time to execute (around 8 minutes for ~73000 entries). Why is that and how would I go about improving it?

OneFineDay
  • 9,004
  • 3
  • 26
  • 37
dsynkd
  • 1,999
  • 4
  • 26
  • 40

4 Answers4

1

no offense to other answer, but this will only help with troubleshooting. what you need to do is remove the the line of code

Rows(i).Delete

inside the (potentially) long running For loop is what is causing the slow down.

you need to re-write it like this...

Sub Main()
    RowsDeleted = 0
    Keyword = "COLA"

    Dim rng As Excel.Range
    Dim arr() As Variant
    Dim str As String
    arr = ActiveSheet.UsedRange
    Dim R As Long

    For R = 1 To UBound(arr, 1) ' First array dimension is rows.
        If InStr(arr(R, 1), Keyword) = 0 Then
            If str <> "" Then
                str = "," & str
            End If
            str = str & arr(R, 1).Address
        End If
    Next R

    Set rng = ActiveSheet.Range(str)
    RowsDeleted = rng.Rows.Count
    rng.Delete

    MsgBox ("Rows Deleted: " & RowsDeleted)

End Sub
Doug Glancy
  • 27,214
  • 6
  • 67
  • 115
Anonymous Type
  • 3,051
  • 2
  • 27
  • 45
  • This doesn't run for me. I get a "Type Mismatch" error on `arr = ActiveSheet.UsedRange`. – Doug Glancy Feb 11 '15 at 04:01
  • Right idea, but a few implementation issues: concatenation puts the `,` in the wrong place (use `str = str & ","`). You need to delete entire rows, so use `rng.EntireRow.Delete`. There are limits to the length of a range reference string, so for very large data sets may need to delete several time during the loop (test `Len(str)` to decide when to do a delete). The two key ideas here (looping a variant array and doing the actual delete in one step) will make a _huge_ performance difference. – chris neilsen Feb 11 '15 at 04:06
  • @Doug just needs to be `arr = ActiveSheet.UsedRange.Value` – chris neilsen Feb 11 '15 at 04:15
  • 1
    @chrisneilsen, yes I made that change but then it errors later with an Object Required, if I remember right. – Doug Glancy Feb 11 '15 at 04:58
  • Brilliant approach by putting things on memory! Will it be even faster (use less memory) with `arr = ActiveSheet.UsedRange.Columns("A")`? – PatricK Feb 11 '15 at 06:25
  • @chrisneilsen lol wrote it in about 5 minutes. only debugged it in my head thanks for the fixes. – Anonymous Type Feb 11 '15 at 06:35
  • @PatricK doubt it, the performance benefit is once your outside of Excel's object model (typically exasperated via looping constructs). – Anonymous Type Feb 11 '15 at 06:37
0

It takes ages may due to formulas in your cells that are going to be deleted.

What you should do is to turn off auto calculation and Clear the contents of that row before delete. Also you should start from bottom up!

Try this:

Sub Main()
    Dim lMode As Long

    ' Store initial state of Calculation mode
    lMode = Application.Calculation
    ' Change to Manual Calculation
    Application.Calculation = xlCalculationManual
    ' Disable screen update
    Application.ScreenUpdating = False

    RowsDeleted = 0
    Keyword = "COLA"

    ' Start from bottom up!
    For i = ActiveSheet.UsedRange.Rows.Count To 2 Step -1
        If InStr(Cells(i, 1).Value, Keyword) = 0 Then
            Rows(i).ClearContents
            Rows(i).Delete
            RowsDeleted = RowsDeleted + 1
        End If
    Next i

    ' Restore screenupdate and calculation mode
    Application.ScreenUpdating = True
    Application.Calculation = lMode

    MsgBox ("Rows Deleted: " & RowsDeleted)
End Sub
PatricK
  • 6,375
  • 1
  • 21
  • 25
  • 3
    some nice ideas, but the overall approach is not best practise. the tips you suggest will however offer a small performance improvement. reversing the loop for row by row deletions is definately best practise, however my point is that you shouldn't be doing row by row to begin with. – Anonymous Type Feb 11 '15 at 02:30
  • 2
    Also, you need `Step - 1` in the `For i` statement. – Doug Glancy Feb 11 '15 at 03:35
  • Thanks @DougGlancy, fixed loop counter, wasn't careful enough. – PatricK Feb 11 '15 at 06:12
0

Here could be something to look at, It filters Column A for cells <>"Cola" and clears them It then sorts column A so the blank cells in column A are now at the bottom It then deletes the blank rows. Not knowing the setup of the OP's ws, there may have to be adjustments made.

On my sample sheet I use 81,000 rows, when I run the code it takes about 5 seconds.

Sub SomeDeleteCode()
    Dim Rws As Long, Rng As Range, nwRw As Long

    Rws = Cells(Rows.Count, "A").End(xlUp).Row

    Application.ScreenUpdating = 0
    Application.Calculation = xlCalculateManual

    Columns("A:A").AutoFilter Field:=1, Criteria1:="<>*Cola*"
    Set Rng = Range(Cells(2, 1), Cells(Rws, 1)).SpecialCells(xlCellTypeVisible)
    Rng.ClearContents
    ActiveSheet.AutoFilterMode = 0

    Columns(1).Sort Key1:=Range("A1"), Header:=xlYes
    nwRw = Cells(Rows.Count, "A").End(xlUp).Row

    Range(Range("B" & nwRw + 1), Range("B" & nwRw + 1).End(xlDown)).EntireRow.Delete
    Application.Calculation = xlCalculationAutomatic
End Sub
Davesexcel
  • 6,896
  • 2
  • 27
  • 42
-2

Amend your code to look like this:

Sub Main()
    On Error Goto ErrHandler
    Application.ScreenUpdating = False

    RowsDeleted = 0
    Keyword = "COLA"
    For i = ActiveSheet.UsedRange.Rows.Count to 2
        If InStr(Cells(i, 1).Value, Keyword) = 0 Then
            Rows(i).Delete
            RowsDeleted = RowsDeleted + 1
            ' i = i - 1 ' -- manually changing the loop counter is a bad idea
        End If
    Next i
    MsgBox("Rows Deleted: " & RowsDeleted)

EndSub:
    Application.ScreenUpdating = True
    exit sub
ErrHandler:
    ' Error handling here
    resume EndSub
End Sub

The error handler is required to ensure that the ScreenUpdating is restored, even in case of an error.

Pieter Geerkens
  • 11,775
  • 2
  • 32
  • 52
  • 1
    This will mIss rows. You have to step backwards. – Doug Glancy Feb 11 '15 at 03:38
  • @DougGlancy: Reversing the loop was easy enough - OP really was asking on the performance. – Pieter Geerkens Feb 11 '15 at 05:28
  • your response will help with troubleshooting performance, but doesn't address the OP's question of why performance is bad to begin with. – Anonymous Type Feb 11 '15 at 06:37
  • @AnonymousType: Not disabling Screenpdating during lengthy operations is the most common cause of VBA performance problems. – Pieter Geerkens Feb 11 '15 at 06:43
  • 1
    @PieterGeerkens agreed that it is a common known issue of performance issues per-se. Disagree that in this instance it is the cause, since the OP has provided the VBA used, and analysis reveals the main performance issue lies within the loop. Combining both our answers would yield the largest performance increase. – Anonymous Type Feb 12 '15 at 03:54