Fastest way to delete rows which cannot be grabbed with SpecialCells
One efficient solution is to tag all the rows to keep and move all the rows to delete at the end by sorting the tags. This way, the complexity doesn't increase with the number of rows to delete.
This example deletes in less than a second, for 50000 rows, all the rows where column I
is equal to 2
:
Sub DeleteMatchingRows()
Dim rgTable As Range, rgTags As Range, data(), tags(), count&, r&
' load the data in an array
Set rgTable = ActiveSheet.UsedRange
data = rgTable.Value
' tag all the rows to keep with the row number. Leave empty otherwise.
ReDim tags(1 To UBound(data), 1 To 1)
tags(1, 1) = 1 ' keep the header
For r = 2 To UBound(data)
If data(r, 9) <> 2 Then tags(r, 1) = r ' if column I <> 2 keep the row
Next
' insert the tags in the last column on the right
Set rgTags = rgTable.Columns(rgTable.Columns.count + 1)
rgTags.Value = tags
' sort the rows on the tags which will move the rows to delete at the end
Union(rgTable, rgTags).Sort key1:=rgTags, Orientation:=xlTopToBottom, Header:=xlYes
count = rgTags.End(xlDown).Row
' delete the tags on the right and the rows that weren't tagged
rgTags.EntireColumn.Delete
rgTable.Resize(UBound(data) - count + 1).Offset(count).EntireRow.Delete
End Sub
Note that it doesn't alter the order of the rows.
Here are all the possible options I could think of with an "average time" to complete the tasks:
Option Base 1
Option Explicit
Sub FixWithArraysAndDeleteRange()
Dim lngItem As Long
Dim varArray() As Variant
Dim wksItem As Worksheet
Dim rngRangeToDelete As Range
Dim dttStart As Date
Worksheets(1).Range("I25:I50000").Formula = "=RandBetween(1, 5)"
Worksheets(1).Range("I25:I50000").Value2 = Worksheets(1).Range("I25:I50000").Value2
dttStart = Now()
Application.ScreenUpdating = False
Application.Calculation = xlCalculationManual
Application.EnableEvents = False
Set wksItem = Worksheets(1)
varArray() = wksItem.Range("I25:I50000").Value2
For lngItem = LBound(varArray) To UBound(varArray)
If IsNumeric(varArray(lngItem, 1)) Then
If Int(varArray(lngItem, 1)) = 2 Then
If rngRangeToDelete Is Nothing Then
Set rngRangeToDelete = wksItem.Rows(lngItem + 24)
Else
Set rngRangeToDelete = Intersect(rngRangeToDelete, wksItem.Rows(lngItem + 24))
End If
End If
End If
Next lngItem
rngRangeToDelete.EntireRow.Delete
Application.ScreenUpdating = True
Application.Calculation = xlCalculationAutomatic
Application.EnableEvents = True
Debug.Print Format(Now() - dttStart, "HH:MM:SS")
'Average time around 0 seconds
End Sub
Sub FixWithLoop()
Dim lngRow As Long
Dim lngLastRow As Long
Dim wksItem As Worksheet
Dim dttStart As Date
Worksheets(1).Range("I25:I50000").Formula = "=RandBetween(1, 5)"
Worksheets(1).Range("I25:I50000").Value2 = Worksheets(1).Range("I25:I50000").Value2
dttStart = Now()
Application.ScreenUpdating = False
Application.Calculation = xlCalculationManual
Application.EnableEvents = False
Set wksItem = Worksheets(1)
lngLastRow = wksItem.Cells(wksItem.Rows.Count, "I").End(xlUp).Row
For lngRow = lngLastRow To 25 Step -1
If Int(wksItem.Cells(lngRow, "I").Value) = 2 Then wksItem.Rows(lngRow).Delete
Next lngRow
Application.ScreenUpdating = True
Application.Calculation = xlCalculationAutomatic
Application.EnableEvents = True
Debug.Print Format(Now() - dttStart, "HH:MM:SS")
'Average time ~3 seconds
End Sub
Sub FixWithLoopInChunks()
Dim lngRow As Long
Dim lngLastRow As Long
Dim wksItem As Worksheet
Dim strRowsToDelete As String
Dim intDeleteCount As Integer
Dim dttStart As Date
Worksheets(1).Range("I25:I50000").Formula = "=RandBetween(1, 5)"
Worksheets(1).Range("I25:I50000").Value2 = Worksheets(1).Range("I25:I50000").Value2
dttStart = Now()
Application.ScreenUpdating = False
Application.Calculation = xlCalculationManual
Application.EnableEvents = False
Set wksItem = Worksheets(1)
lngLastRow = wksItem.Cells(wksItem.Rows.Count, "I").End(xlUp).Row
For lngRow = lngLastRow To 25 Step -1
If Int(wksItem.Cells(lngRow, "I").Value) = 2 Then
intDeleteCount = intDeleteCount + 1
strRowsToDelete = strRowsToDelete & ",I" & lngRow
End If
If intDeleteCount >= 30 Then
strRowsToDelete = Mid(strRowsToDelete, 2)
wksItem.Range(strRowsToDelete).EntireRow.Delete
intDeleteCount = 0
strRowsToDelete = ""
End If
Next lngRow
If intDeleteCount > 0 Then
strRowsToDelete = Mid(strRowsToDelete, 2)
wksItem.Range(strRowsToDelete).EntireRow.Delete
End If
Application.ScreenUpdating = True
Application.Calculation = xlCalculationAutomatic
Application.EnableEvents = True
Debug.Print Format(Now() - dttStart, "HH:MM:SS")
'Average time ~3 seconds
End Sub
Sub FixWithArraysAndDeleteChunks()
Dim lngItem As Long
Dim varArray() As Variant
Dim wksItem As Worksheet
Dim strRowsToDelete As String
Dim intDeleteCount As Integer
Dim dttStart As Date
Worksheets(1).Range("I25:I50000").Formula = "=RandBetween(1, 5)"
Worksheets(1).Range("I25:I50000").Value2 = Worksheets(1).Range("I25:I50000").Value2
dttStart = Now()
Application.ScreenUpdating = False
Application.Calculation = xlCalculationManual
Application.EnableEvents = False
Set wksItem = Worksheets(1)
varArray() = wksItem.Range("I25:I50000").Value2
For lngItem = UBound(varArray) To LBound(varArray) Step -1
If IsNumeric(varArray(lngItem, 1)) Then
If Int(varArray(lngItem, 1)) = 2 Then
intDeleteCount = intDeleteCount + 1
strRowsToDelete = strRowsToDelete & ",I" & lngItem + 24
End If
If intDeleteCount >= 30 Then
strRowsToDelete = Mid(strRowsToDelete, 2)
wksItem.Range(strRowsToDelete).EntireRow.Delete
intDeleteCount = 0
strRowsToDelete = ""
End If
End If
Next lngItem
If intDeleteCount > 0 Then
strRowsToDelete = Mid(strRowsToDelete, 2)
wksItem.Range(strRowsToDelete).EntireRow.Delete
End If
Application.ScreenUpdating = True
Application.Calculation = xlCalculationAutomatic
Application.EnableEvents = True
Debug.Print Format(Now() - dttStart, "HH:MM:SS")
'Average time ~2 seconds
End Sub
Based on the above tests the "fastest" route is to use an array, save the range of rows to be deleted using Intersect
and then delete all rows together.
Note, if you are using Application.Union
instead of Intersect
then the time of that approach drops significantly and the sub will run for almost 30 seconds.
Yet, the time difference is very small and negligible (for 50.000 rows).
Please do let me know if my speed-test-setup has any flaws which might bias the results or if I am missing another approach you would like to see.
Update:
Here is another approach offered by @SiddharthRout. I do not wish to plagiarise. Yet, I wanted to compare time results. Hence, here is the sub rewritten to compare to the others with the average time recorded on my system.
Sub DeleteFilteredRows_SiddharthRout()
Dim wksItem As Worksheet
Dim rngRowsToDelete As Range
Dim dttStart As Date
Worksheets(1).Range("I25:I50000").Formula = "=RandBetween(1, 5)"
Worksheets(1).Range("I25:I50000").Value2 = Worksheets(1).Range("I25:I50000").Value2
dttStart = Now()
Application.ScreenUpdating = False
Application.Calculation = xlCalculationManual
Application.EnableEvents = False
Set wksItem = Worksheets(1)
wksItem.AutoFilterMode = False
wksItem.Range("I25:I50000").AutoFilter Field:=1, Criteria1:=2
Set rngRowsToDelete = wksItem.Range("I25:I50000").SpecialCells(xlCellTypeVisible)
wksItem.AutoFilterMode = False
wksItem.Rows.Hidden = False
rngRowsToDelete.EntireRow.Delete
Application.ScreenUpdating = True
Application.Calculation = xlCalculationAutomatic
Application.EnableEvents = True
Debug.Print Format(Now() - dttStart, "HH:MM:SS")
'Average time around 5 seconds
End Sub
It seems that this approach is slightly slower compared to all the others.
edited
after some more testings it seems that Sort
&Delete
is a little faster than RemoveDuplicates
so I put in the following solution (keeping the first one for reference by the end of the answer)
Sub FixWithSort()
Dim testRng As Range
Dim dttStart As Date
Set testRng = Worksheets("Test").Range("I25:I50000")
With testRng
.Formula = "=RandBetween(1, 5)"
.Value2 = .Value2
End With
dttStart = Now()
With testRng
With .Offset(, 1)
.FormulaR1C1 = "=IF(RC[-1]=2,"""",row())"
.Value2 = .Value2
End With
.Resize(, 2).Sort key1:=.Columns(2), Orientation:=xlTopToBottom, Header:=xlYes
Range(.Cells(1, 2).End(xlDown).Offset(1, -1), .Cells(1, 1).End(xlDown)).EntireRow.Delete
.Columns(2).ClearContents
End With
Debug.Print Format(Now() - dttStart, "HH:MM:SS")
dttStartGlobal = dttStartGlobal + Now() - dttStart
End Sub
previous (and a little slower) solution with RemoveDuplicates
Option Explicit
Sub FixWithRemoveDuplicates()
Dim testRng As Range
Dim dttStart As Date
Set testRng = Worksheets("Test").Range("I25:I50000")
With testRng
.Formula = "=RandBetween(1, 5)"
.Value2 = .Value2
End With
dttStart = Now()
Application.ScreenUpdating = False
Application.Calculation = xlCalculationManual
Application.EnableEvents = False
With testRng
With .Offset(, 1)
.FormulaR1C1 = "=IF(RC[-1]=2,""a"",row())"
.Value2 = .Value2
End With
.EntireRow.RemoveDuplicates Columns:=Array(.Columns(2).Column), Header:=xlNo
.Offset(, 1).Find(what:="a", LookIn:=xlValues, LookAt:=xlWhole).EntireRow.Delete
.Columns(2).ClearContents
End With
Application.ScreenUpdating = True
Application.Calculation = xlCalculationAutomatic
Application.EnableEvents = True
Debug.Print Format(Now() - dttStart, "HH:MM:SS")
'Average time around 0 seconds
End Sub