patternModerate
Macro code that removes excess rows
Viewed 0 times
removesrowsthatexcessmacrocode
Problem
I'm a business student who just started to learn VBA. I am trying to write a macro for a project but only have minimal experience actually stepping into the code.
The macro I have written is to delete all row entries which do not meet my criteria if they contain certain words, including removing those with future month's dates in them, and I'd like for this to update based on the current month. I have found that normally the code runs very quickly, but when I add the last part, which removes those with future dates, the code becomes extremely slow.
Is there a way to speed it up or rewrite it so that it is faster?
The macro I have written is to delete all row entries which do not meet my criteria if they contain certain words, including removing those with future month's dates in them, and I'd like for this to update based on the current month. I have found that normally the code runs very quickly, but when I add the last part, which removes those with future dates, the code becomes extremely slow.
Is there a way to speed it up or rewrite it so that it is faster?
Sub Remove_excess_entries()
Dim lRow As Long
Dim iCntr As Long
lRow = 10000
For iCntr = lRow To 1 Step -1
If Cells(iCntr, 12).Value = "Mule" Or Cells(iCntr, 11).Value = "*R1*" Or Cells(iCntr, 11).Value = "*R2*" Or Cells(iCntr, 7).Value = "*Mule*" Or Cells(iCntr, 6).Value = "*Unassigned*" Or Cells(iCntr, 12).Value = "PS" Or Cells(iCntr, 7).Value = "Marketing" Or Cells(iCntr, 12).Value = "V1" Or DatePart("m", Cells(iCntr, 16).Value) > DatePart("m", Date) Then
Rows(iCntr).Delete
End If
Next
End SubSolution
Here's that conditional, reformatted with line continuations for readability:
There are a number of problems here:
So how do you make it so that you only validate what needs to be validated, and that you don't access the worksheet more than you need to?
First make sure you specify
Then make it one single read:
Now, in one single worksheet operation, we've got an array that contains the values of columns 1-20 for row
The question is, what would this function do? Remember, we want it to only check what it needs to, and return as soon as it knows it should be returning
There's a little hack involving
VBA will evaluate each
Notice the column indices are off by one - that's because VBA arrays are zero-based by default. If you want to refer to column 11 with an
For even faster code, make one single read, and iterate an array instead of iterating a worksheet and making 10000 worksheet reads - this code should run orders of magnitude faster than your original code:
```
Option Base 1
Sub DoSomething()
Dim target As Worksheet
Set target = Application.ActiveSheet
Application.ScreenUpdating = False
Application.Calculation = xlCalculationManual
Dim rowValues()
Dim i As Long
rowValues = target.Range(target.Cells(1, 1), target.Cells(10000, 20)).Cells
For i = 10000 To 1 Step -1
If IsUpForDeletion(rowValues, i) Then target.Rows(i).Delete
Next
Application.ScreenUpdating = True
Application.Calculation = xlCalculationAutomatic
End Sub
Private Function IsUpForDeletion(ByRef rowValues(), ByVal i As Long) As Boolean
Dim result As Boolean
result = True
If Cells(iCntr, 12).Value = "Mule" _
Or Cells(iCntr, 11).Value = "*R1*" _
Or Cells(iCntr, 11).Value = "*R2*" _
Or Cells(iCntr, 7).Value = "*Mule*" _
Or Cells(iCntr, 6).Value = "*Unassigned*" _
Or Cells(iCntr, 12).Value = "PS" _
Or Cells(iCntr, 7).Value = "Marketing" _
Or Cells(iCntr, 12).Value = "V1" _
Or DatePart("m", Cells(iCntr, 16).Value) > DatePart("m", Date) _
Then
Rows(iCntr).Delete
End IfThere are a number of problems here:
Cells(andRows) are implicit references to the active sheet; this means you're accessing the active worksheet without explicitly saying so - so if the macro takes a long while to run and the user decides to activate another sheet while it's running, your macro will fail to do what it was written for.
- You're accessing the active sheet multiple times per row, and doing many checks that you may not need to be doing - if
Cells(iCntr,11).Valueis"R1", then every single next check is happening even though you already know you want to delete that row.
So how do you make it so that you only validate what needs to be validated, and that you don't access the worksheet more than you need to?
First make sure you specify
Option Explicit at the top of the module. Option Explicit forces you to always declare all variables you're using, and that alone can prevent many bugs, since without it VBA will happily compile a typo.Then make it one single read:
Dim target As Worksheet
Set target = Application.ActiveSheet
Dim rowValues()
Dim i As Long
For i = 10000 To 1 Step -1
rowValues = target.Range(target.Cells(i, 1), target.Cells(i, 20)).Cells
If IsUpForDeletion(rowValues) Then target.Rows(i).Delete
NextNow, in one single worksheet operation, we've got an array that contains the values of columns 1-20 for row
i; we call a function that returns a Boolean value when the row is "up for deletion", and so we remove that row when that function returns True.The question is, what would this function do? Remember, we want it to only check what it needs to, and return as soon as it knows it should be returning
True.There's a little hack involving
Select Case True that can help here:Private Function IsUpForDeletion(ByRef rowValues()) As Boolean
Dim result As Boolean
result = True
Select Case True
Case rowValues(1, 5) = "*Unassigned*"
Case rowValues(1, 6) = "*Mule*"
Case rowValues(1, 6) = "Marketing"
Case rowValues(1, 10) = "*R1*"
Case rowValues(1, 10) = "*R2*"
Case rowValues(1, 11) = "PS"
Case rowValues(1, 11) = "V1"
Case IsFutureMonth(rowValues(1, 15))
Case Else
result = False
End Select
IsUpForDeletion = result
End Function
Private Function IsFutureMonth(ByVal value As String) As Boolean
If Not IsDate(value) Then
IsFutureMonth = False
Exit Function
End If
IsFutureMonth = DatePart("m", CDate(value)) > Month(Date)
End FunctionVBA will evaluate each
Case until it finds a condition that evaluates to True, and then immediately jump out and return. Because we're working with an in-memory 2D array here, and not accessing any cells, this is going to run much faster.Notice the column indices are off by one - that's because VBA arrays are zero-based by default. If you want to refer to column 11 with an
11, then you could use Option Base 1 at the top of the module, and do this:Private Function IsUpForDeletion(ByRef rowValues()) As Boolean
Dim result As Boolean
result = True
Select Case True
Case rowValues(1, 6) = "*Unassigned*"
Case rowValues(1, 7) = "*Mule*"
Case rowValues(1, 7) = "Marketing"
Case rowValues(1, 11) = "*R1*"
Case rowValues(1, 11) = "*R2*"
Case rowValues(1, 12) = "PS"
Case rowValues(1, 12) = "V1"
Case IsFutureMonth(rowValues(1, 16))
Case Else
result = False
End Select
IsUpForDeletion = result
End FunctionFor even faster code, make one single read, and iterate an array instead of iterating a worksheet and making 10000 worksheet reads - this code should run orders of magnitude faster than your original code:
```
Option Base 1
Sub DoSomething()
Dim target As Worksheet
Set target = Application.ActiveSheet
Application.ScreenUpdating = False
Application.Calculation = xlCalculationManual
Dim rowValues()
Dim i As Long
rowValues = target.Range(target.Cells(1, 1), target.Cells(10000, 20)).Cells
For i = 10000 To 1 Step -1
If IsUpForDeletion(rowValues, i) Then target.Rows(i).Delete
Next
Application.ScreenUpdating = True
Application.Calculation = xlCalculationAutomatic
End Sub
Private Function IsUpForDeletion(ByRef rowValues(), ByVal i As Long) As Boolean
Dim result As Boolean
result = True
Code Snippets
If Cells(iCntr, 12).Value = "Mule" _
Or Cells(iCntr, 11).Value = "*R1*" _
Or Cells(iCntr, 11).Value = "*R2*" _
Or Cells(iCntr, 7).Value = "*Mule*" _
Or Cells(iCntr, 6).Value = "*Unassigned*" _
Or Cells(iCntr, 12).Value = "PS" _
Or Cells(iCntr, 7).Value = "Marketing" _
Or Cells(iCntr, 12).Value = "V1" _
Or DatePart("m", Cells(iCntr, 16).Value) > DatePart("m", Date) _
Then
Rows(iCntr).Delete
End IfDim target As Worksheet
Set target = Application.ActiveSheet
Dim rowValues()
Dim i As Long
For i = 10000 To 1 Step -1
rowValues = target.Range(target.Cells(i, 1), target.Cells(i, 20)).Cells
If IsUpForDeletion(rowValues) Then target.Rows(i).Delete
NextPrivate Function IsUpForDeletion(ByRef rowValues()) As Boolean
Dim result As Boolean
result = True
Select Case True
Case rowValues(1, 5) = "*Unassigned*"
Case rowValues(1, 6) = "*Mule*"
Case rowValues(1, 6) = "Marketing"
Case rowValues(1, 10) = "*R1*"
Case rowValues(1, 10) = "*R2*"
Case rowValues(1, 11) = "PS"
Case rowValues(1, 11) = "V1"
Case IsFutureMonth(rowValues(1, 15))
Case Else
result = False
End Select
IsUpForDeletion = result
End Function
Private Function IsFutureMonth(ByVal value As String) As Boolean
If Not IsDate(value) Then
IsFutureMonth = False
Exit Function
End If
IsFutureMonth = DatePart("m", CDate(value)) > Month(Date)
End FunctionPrivate Function IsUpForDeletion(ByRef rowValues()) As Boolean
Dim result As Boolean
result = True
Select Case True
Case rowValues(1, 6) = "*Unassigned*"
Case rowValues(1, 7) = "*Mule*"
Case rowValues(1, 7) = "Marketing"
Case rowValues(1, 11) = "*R1*"
Case rowValues(1, 11) = "*R2*"
Case rowValues(1, 12) = "PS"
Case rowValues(1, 12) = "V1"
Case IsFutureMonth(rowValues(1, 16))
Case Else
result = False
End Select
IsUpForDeletion = result
End FunctionOption Base 1
Sub DoSomething()
Dim target As Worksheet
Set target = Application.ActiveSheet
Application.ScreenUpdating = False
Application.Calculation = xlCalculationManual
Dim rowValues()
Dim i As Long
rowValues = target.Range(target.Cells(1, 1), target.Cells(10000, 20)).Cells
For i = 10000 To 1 Step -1
If IsUpForDeletion(rowValues, i) Then target.Rows(i).Delete
Next
Application.ScreenUpdating = True
Application.Calculation = xlCalculationAutomatic
End Sub
Private Function IsUpForDeletion(ByRef rowValues(), ByVal i As Long) As Boolean
Dim result As Boolean
result = True
Select Case True
Case rowValues(i, 6) = "*Unassigned*"
Case rowValues(i, 7) = "*Mule*"
Case rowValues(i, 7) = "Marketing"
Case rowValues(i, 11) = "*R1*"
Case rowValues(i, 11) = "*R2*"
Case rowValues(i, 12) = "PS"
Case rowValues(i, 12) = "V1"
Case IsFutureMonth(rowValues(i, 16))
Case Else
result = False
End Select
IsUpForDeletion = result
End Function
Private Function IsFutureMonth(ByVal value As String) As Boolean
If Not IsDate(value) Then
IsFutureMonth = False
Exit Function
End If
IsFutureMonth = DatePart("m", CDate(value)) > Month(Date)
End FunctionContext
StackExchange Code Review Q#131864, answer score: 11
Revisions (0)
No revisions yet.