Find Specific Value, Delete Corresponding Range. Macro agonozingly slow - vba

In my spreadsheet I have something close to 2,000 rows. I need to search through these rows, find a specific date (current date), and then delete a corresponding range. It however runs very very slowly. Any suggestions about how I can make it run faster? I was thinking that maybe I could organize my rows based on the date (current date will always be the oldest and therefore be on the top) and then delete all of the rows at once with a Range(XX:XX").Delete. But I don't know how to find where the last row with Currentdate would be as it is going to be constantly changing.
Sub ChangeandDelete
MudaDataLCA
DeleteDateLCA
End Sub
Sub MudaDataLCA()
'===Muda Data Atual ABERTURA===
Dim Affected As Workbook
Dim Dados As Worksheet
Dim LastRow As Long
Set Affected = Workbooks("Controle de Lastro LCA_FEC - Test")
Set Dados = Affected.Sheets("DADOS")
Dados.Activate
Dim CurrentDate As Date
CurrentDate = Range("AH2") + 1
Range("AH2") = CurrentDate
End Sub
Sub DeleteDateLCA()
Dim Affected As Workbook
Dim Dados As Worksheet
Dim LastRow As Long
Set Affected = Workbooks("Controle de Lastro LCA_FEC - Test")
Set Dados = Affected.Sheets("DADOS")
Dados.Activate
LastRow = Dados.Cells(Rows.Count, "P").End(xlUp).Row
For i = 5 To LastRow
Do While Range("S" & i).Value = Range("AH2")
Range("P" & i & ":AG" & i).Delete
Loop
Next i
End Sub

This method of filtering for the updated date in AH2 should speed the process up significantly.
Sub ChangeandDelete()
Dim fr As Long, lr As Long, fString As String
Application.ScreenUpdating = False
Application.Calculation = xlCalculationManual
Application.EnableEvents = False
With Workbooks("Controle de Lastro LCA_FEC - Test").Sheets("DADOS")
.Range("AH2") = CDate(.Range("AH2").Value + 1)
fr = 4: lr = .Cells(Rows.Count, "P").End(xlUp).Row
fString = Chr(61) & Format(.Range("AH2").Value, .Range("P5").NumberFormat)
With .Range(.Cells(fr, "P"), .Cells(lr, "P"))
.AutoFilter
.AutoFilter Field:=1, Criteria1:=fString
If CBool(Application.Subtotal(102, .Columns(1)) + IsNumeric(.Cells(1, 1).Value2)) Then
With .Offset(1, 0).Resize(.Rows.Count - 1, .Columns.Count)
.SpecialCells(xlCellTypeVisible).EntireRow.Delete
End With
Debug.Print Application.Count(.Columns(1))
End If
.AutoFilter
End With
End With
Application.EnableEvents = True
Application.Calculation = xlCalculationAutomatic
Application.ScreenUpdating = True
End Sub
I've assumed that at least part of the slowdown was formula recalculation every time a row was deleted and this would indicate automatic calculation. I've turned off automatic calculation and restored it once the process is complete. There are other methods of storing the current state of the workbook/worksheet calculation, turning calculation off, then restoring the original state.

So I've got two answers. I put in 39000 rows of data and did it with 7500 rows that would meet the criteria for deleting - so I could test the time (64bit windows 7)
Loops can be super slow but I'll write this one first because it's closest to your code:
Sub DeleteIT()
Dim deleteRange As Range
Dim deleteValue As Date
Dim lastRow As Long
Set affected = ThisWorkbook
Set dados = affected.Sheets("DADOS")
Dim CTtimer As CTimer
'Set CTtimer = New CTimer
'Dados.Activate
Application.ScreenUpdating = False
deleteValue = dados.Range("AH2")
lastRow = dados.Range("S" & dados.Rows.Count).End(xlUp).Row
'CTtimer.StartCounter
Do
Set deleteRange = Range("S5:S" & lastRow).Find(what:=deleteValue, LookIn:=xlValues, _
lookat:=xlPart, SearchOrder:=xlByRows, SearchDirection:=xlNext, _
MatchCase:=False, SearchFormat:=False)
If Not deleteRange Is Nothing Then deleteRange.Range(Cells(1, 1), Cells(1, 18)).Offset(0, -3).Delete
Loop While Not deleteRange Is Nothing
'MsgBox CTtimer.TimeElapsed
Application.ScreenUpdating = True
End Sub
I got throught about 500 rows and 150 deletes of matching records in 4 mins with the above code. I did a code break and stopped because nobody should have to deal with that haha..
My other idea(below) is more along the lines of your sort idea, this way only took about 25 seconds to do 30500 deletes from 31500 rows.
Sub aReader()
Dim affected As Workbook
Dim SheetName As String
Dim deleteValue As Date
Dim population As Range
Dim lastRow As Long
Dim x As Long
'Dim CTtimer As CTimer
'Set CTtimer = New CTimer
Set affected = ThisWorkbook
Application.ScreenUpdating = False
SheetName = "DADOS"
deleteValue = affected.Worksheets(SheetName).Range("AH2")
Set population = Worksheets(SheetName).Range("P5", Sheets(SheetName).Range("P5").End(xlDown))
'CTtimer.StartCounter
For x = 1 To population.Count
If population.Cells(x, 4).Value = deleteValue Then Range(population.Cells(x, 1), population.Cells(x, 18)).Value = ""
Next x
Range("P5:AG" & (population.Count + 4)).Sort key1:=Range("S5:S" & population.Count + 4), _
order1:=xlAscending, Header:=xlNo
Application.ScreenUpdating = True
'MsgBox CTtimer.TimeElapsed
End Sub

Related

Deleting rows in VBA based on a list

I have two tabs in my spreadsheets (Report and Holidays). In column A of Holidays tab there is a list of dates (updated manually) which I want to exclude from Report tab (column E contains dates).
I have found a code which does what is needed but takes some time when the number of rows is around 100-200k:
Sub Holidays()
Application.DisplayAlerts = False
Dim d As Object, e, rws&, cls&, i&, j&
Set d = CreateObject("scripting.dictionary")
For Each e In Sheets("Holidays").Range("A1").CurrentRegion
d(e.Value) = 1
Next e
Sheets("Report").Activate
rws = Cells.Find("*", After:=[a1], SearchOrder:=xlByRows, _
SearchDirection:=xlPrevious).Row
cls = Cells.Find("*", After:=[a1], SearchOrder:=xlByColumns, _
SearchDirection:=xlPrevious).Column
For i = rws To 1 Step -1
For j = 1 To cls
If d(Range("A1").Resize(rws, cls)(i, j).Value) = 1 Then _
Cells.Rows(i).Delete: Exit For
Next j, i
Application.DisplayAlerts = True
End Sub
Is there a way to speed up that macro? Ideally it should take only several seconds to run.
Thank you in advance for your help.
This should remove about 10 K rows out of 200 K, in less than 30 seconds
Code bellow assumes that UsedRange on both sheets starts in A1, and
Sheet Holidays contains only column A (in contiguous rows)
Sheet Report contains dates to be removed in column E (in contiguous rows)
Dates on both sheets are formatted as "m/d/yyyy"
Option Explicit
Public Sub RemoveHolidaysFromReportFilterUnion()
Const WS_NAME = "Report"
Dim wsH As Worksheet: Set wsH = ThisWorkbook.Worksheets("Holidays")
Dim wsR As Worksheet: Set wsR = ThisWorkbook.Worksheets(WS_NAME)
Dim del As Range, wsNew As Worksheet
Application.ScreenUpdating = False
Set del = GetRowsToDelete(wsH, wsR)
If del.Cells.Count > 1 Then
del.EntireRow.Hidden = True
Set wsNew = ThisWorkbook.Worksheets.Add(After:=wsR)
wsR.UsedRange.SpecialCells(xlCellTypeVisible).EntireRow.Copy
With wsNew.Cells(1)
.PasteSpecial xlPasteColumnWidths
.PasteSpecial xlPasteAll
.Select
End With
Application.DisplayAlerts = False
wsR.Delete
Application.DisplayAlerts = True
wsNew.Name = WS_NAME
End If
Application.ScreenUpdating = True
End Sub
Private Function GetRowsToDelete(ByRef wsH As Worksheet, ByRef wsR As Worksheet) As Range
Const HOLIDAYS_COL = "A"
Const REPORT_COL = "E"
Dim arr As Variant, i As Long, itm As Variant
ReDim arr(1 To wsH.UsedRange.Rows.Count - 1)
i = 1
For Each itm In wsH.UsedRange.Columns(HOLIDAYS_COL).Offset(1).Cells
If Len(itm) > 0 Then
arr(i) = itm.Text 'Create AutoFilter Array (dates as strings)
i = i + 1
End If
Next
Dim ur As Range, del As Range, lr As Long, fc As Range
With wsR.UsedRange
Set ur = .Resize(.Rows.Count - 1, 1).Offset(1)
Set del = wsR.Cells(.Rows.Count + 1, REPORT_COL)
End With
lr = wsR.UsedRange.Rows.Count
Set fc = wsR.Range(wsR.Cells(1, REPORT_COL), wsR.Cells(lr, REPORT_COL))
fc.AutoFilter Field:=1, Criteria1:=arr, Operator:=xlFilterValues
If fc.SpecialCells(xlCellTypeVisible).Cells.Count > 1 Then
Set del = Union(del, ur.SpecialCells(xlCellTypeVisible))
End If
fc.AutoFilter
Set GetRowsToDelete = del
End Function
Performance - Removed about 5K rows out of a total of 100K
Sheet Report - Rows: 100,011, Cols: 11 (Rows Left: 94,805 - Deleted: 5,206)
Sheet Holidays - Rows: 20, Cols: 1
Initial Sub - Holidays() - Time: 112.625 sec
RemoveHolidaysFromReportFilterUnion() - Time: 10.512 sec
Test Data
Holidays
Report - Before
Report - After
I would suggest using arrays.
Populate an Array for the list of dates. example:
arr = Array("Alpha","Bravo","Charlie")
Filter the report based on Criteria.
Sheet17.Range("E1").AutoFilter Field:=5, Criteria1:=arr, Operator:=xlFilterValues
Once the sheet is filtered, create a range of selected visible cells
set myrange = range("A1:F" &_
Cells(Rows.Count,"A").end(xlup).row).SpecialCells(xlCellTypeVisible)
Delele the Range using Range(“YourRange”).EntireRow.Delete
This will delete the range in 4 operations instead of looping through every row in the range based on the condition.
Hope that helps!

VBA script causes Excel to not respond after 15 loops

I am running a script to find and delete rows that contain data from after 2018. I am searching through around 650000 rows. Every time I run the script after 5 seconds my cursor becomes the spinning circle and the excel program becomes unresponsive. Here is the code I am using.
Option Explicit
Option Base 1 'row and column index will match array index
Sub removeWrongYear()
Dim i As Long, yearA As Long, rowsCnt As Long
Dim rowsToDelete As Range
Dim vData As Variant
With ActiveSheet
'1st to 635475 row, 20th column
vData = Range(.Cells(1, 20), .Cells(635475, 20))
For i = UBound(vData) To 2 Step -1
If Val(Right(vData(i,1),2)) > 17 Then
Debug.Print Val(Right(vData(i,1),2))
rowsCnt = rowsCnt + 1
If rowsCnt > 1 Then
Set rowsToDelete = Union(rowsToDelete, .Rows(i))
ElseIf rowsCnt = 1 Then
Set rowsToDelete = .Rows(i)
End If
End If
Next i
End With
If rowsCnt > 0 Then
Application.ScreenUpdating = False
rowsToDelete.EntireRow.Delete
Application.ScreenUpdating = True
End If
End Sub
Every time I run the script after 5 seconds my cursor becomes the spinning circle and the excel program becomes unresponsive.
That's normal. VBA is running on the single available UI thread, the same one Excel runs on. While it's busy running your loop, it's not able to respond to other stimuli, and tells you that by putting "(not responding)" in the title bar, until it completes the work and is able to resume doing everything else it needs to do (i.e. listen for mouse & keyboard messages, etc.).
You could add a little DoEvents in the body of that loop to allow Excel to breathe and process pending messages between iterations, but then there's a catch: first, your code will take even longer to complete, and second, if the user is able to select/activate another sheet in the middle of that loop, then this unqualified Range call:
vData = Range(.Cells(1, 20), .Cells(635475, 20))
...will be the source of a run-time error 1004, since you can't do Sheet1.Range(Sheet2.Cells(1,20), Sheet2.Cells(635475,20)) and expect Excel to know what to do with that (assuming Sheet2 was active when the loop started, and the user activated Sheet1 in the middle of it).
This answer provides what appears to be the most efficient approach to conditionally deleting lines when a lot of rows are involved. If you can, add a helper column to calculate your criteria (e.g. make it return TRUE for rows to keep and FALSE for rows to delete), then use Worksheet.Replace and Worksheet.SpecialCells to perform the filtering and deletion:
.Columns("Z:Z").Replace What:=False, _
Replacement:="", _
LookAt:=xlPart, _
SearchOrder:=xlByRows, _
MatchCase:=False, _
SearchFormat:=False, _
ReplaceFormat:=False
.Columns("Z:Z").SpecialCells(xlCellTypeBlanks).EntireRow.Delete
Then you don't need a loop, and it might actually complete before you get to count to 5 seconds.
Other than that, long-running operations are just that: long-running operations. Own it:
Application.StatusBar = "Please wait..."
Application.Cursor = xlWait
Application.ScreenUpdating = False
Application.Calculation = xlCalculationManual
Application.EnableEvents = False
'..code..
Application.Cursor = xlDefault
Application.ScreenUpdating = True
Application.Calculation = xlCalculationAutomatic
Application.EnableEvents = True
Application.StatusBar = False
This seems pretty quick. It puts results in U1 and down so you'd probably want to amend that. This extracts the values you want into a second array.
Sub removeWrongYear()
Dim i As Long, vData As Variant, v2(), j As Long
vData = Range(Cells(1, 20), Cells(635475, 20))
ReDim v2(1 To UBound(vData, 1), 1 To 1)
For i = UBound(vData) To 2 Step -1
If Val(Right(vData(i, 1), 2)) <= 17 Then
j = j + 1
v2(j, 1) = vData(i, 1)
End If
Next i
Range("U1").Resize(j, 1) = v2
End Sub
This uses an AutoFilter - the more rows to delete, the faster it gets
Rows: 1,048,575 (Deleted: 524,286), Cols: 21 (70 Mb xlsb file)
Time: 6.90 sec, 7.49 sec, 7.21 sec (3 tests)
Test data shown in images bellow
How it works
It generates a temporary helper column with formula "=RIGHT(T1, 2)" (first empty column)
Applies a filter for the years to keep ("<18") in the temp column
Copies all visible rows to a new sheet (not including the temp column)
Removes the initial sheet
Renames the new sheet to the initial sheet name
Option Explicit
Public Sub RemoveYearsAfter18()
Dim ws As Worksheet, wsName As String, lr As Long, lc As Long
Dim ur As Range, filterCol As Range, newWs As Worksheet
Set ws = Sheet1 'Or: Set ws = ThisWorkbook.Worksheets("Sheet1")
wsName = ws.Name
lr = ws.Cells(ws.Rows.Count, "T").End(xlUp).Row 'Last Row in col T (or 635475)
lc = ws.Cells(1, ws.Columns.Count).End(xlToLeft).Column 'Last Col in row 1
Set ur = ws.Range(ws.Cells(1, 1), ws.Cells(lr, lc))
Set filterCol = ws.Range(ws.Cells(1, lc + 1), ws.Cells(lr, lc + 1)) 'Exclude Headers
OptimizeApp True
Set newWs = ThisWorkbook.Worksheets.Add(After:=ws) 'Add new sheet
With filterCol
.Formula = "=RIGHT(T1, 2)"
.Cells(1) = "FilterCol" 'Column header
.Value2 = .Value2 'Convert formulas to values for filter
End With
filterCol.AutoFilter Field:=1, Criteria1:="<18" 'Reverse filter
ur.Copy 'Copy visible data
With newWs.Cells
.PasteSpecial xlPasteColumnWidths
.PasteSpecial xlPasteAll 'Paste data on new sheet
.Cells(1).Select
End With
ws.Delete 'Delete old sheet
newWs.Name = wsName
OptimizeApp False
End Sub
Private Sub OptimizeApp(ByVal speedUp As Boolean)
Application.Calculation = IIf(speedUp, xlCalculationManual, xlCalculationAutomatic)
Application.ScreenUpdating = Not speedUp
Application.DisplayAlerts = Not speedUp
Application.EnableEvents = Not speedUp
End Sub
Before
After
This code process 635475 Rows x 20 Columns in 12.48 seconds on my fast computer and 33.32 seconds on my old computer (0.84 and 2.06 seconds for 38k x 20).
Option Explicit
Sub removeWrongYear2()
Const DATE_COLUMN = 20
Dim StartTime As Double: StartTime = Timer
Dim data() As Variant, results() As Variant
Dim c As Long, r As Long, r2 As Long
With ActiveSheet
data = .UsedRange.Value
ReDim results(1 To UBound(data), 1 To UBound(data, 2))
For r = 2 To UBound(data)
If Val(Right(data(r, DATE_COLUMN), 2)) <= 17 Then
r2 = r2 + 1
For c = 1 To UBound(data, 2)
results(r2, c) = data(r, c)
Next
End If
Next
If r2 > 0 Then
Application.ScreenUpdating = False
Application.Calculation = xlCalculationManual
.UsedRange.Offset(1).Value = results
Application.Calculation = xlCalculationAutomatic
Application.ScreenUpdating = True
End If
End With
Debug.Print Round(Timer - StartTime, 2)
End Sub
Sub Setup()
Dim data, r, c As Long
Const LASTROW = 635475
Cells.Clear
data = Range(Cells(1, 1), Cells(LASTROW, 20)).Value
For r = 1 To UBound(data)
For c = 1 To 19
data(r, c) = Int((LASTROW * Rnd) + 100)
Next
data(r, 20) = Int((10 * Rnd) + 10)
Next
Application.ScreenUpdating = False
Range(Cells(1, 1), Cells(LASTROW, 20)).Value = data
Application.ScreenUpdating = True
End Sub
Sort() & AutoFilter() are always a good pair:
Sub nn()
Dim sortRng As Range
With ActiveSheet.UsedRange ' reference all data in active sheet
With .Offset(, .Columns.Count).Resize(, 1) ' get a helper column right outside data
.Formula = "=ROW()" ' fill it with sequential numbers from top to down
.Value = .Value ' get rid of formulas
Set sortRng = .Cells ' store the helper range
End With
With .Resize(, .Columns.Count + 1) ' consider data and the helper range
.Sort key1:=.Cells(1, 20), order1:=xlAscending, Header:=xlNo ' sort it by data in column 20
.AutoFilter Field:=20, Criteria1:=">=01/01/2018" ' filter it for data greater than 2017
.Resize(.Rows.Count - 1).Offset(1).SpecialCells(xlCellTypeVisible).EntireRow.Delete ' delete filtered data
.Parent.AutoFilterMode = False ' remove filter
.Sort key1:=sortRng(1, 1), order1:=xlAscending, Header:=xlNo ' sort things back by means of helper column
.Columns(.Columns.Count).ClearContents ' clear helper column
End With
End With
End Sub
in my test a 768k row by 21 columns data took 11 seconds

Dynamically insert rows

I want to ask couple of Qs.
1.
Code below dynamically add new rows before the cell that contain "7000"
Code works but it is not very efficient. It slows down where I used For Next loop to insert new rows. Is there better way to insert rows dynamically before cell that contain "7000".
Sub PLFinalReport()
Dim XCount As Integer
Dim YCount As Integer
Dim i As Integer
JobsPivot.Activate
XCount = JobsPivot.Range("H3", Range("H3").End(xlDown)).Count
PLJob.Activate
Range("G6", Range("G6").End(xlDown)).Find("7000").Select
YCount = Range(ActiveCell, ActiveCell.End(xlUp)).Count - 2
For i = 1 To (XCount - YCount)
ActiveCell.EntireRow.Insert
Next i
JobsPivot.Activate
JobsPivot.Range("H3", Range("H3").End(xlDown).End(xlToRight)).Copy
PLJob.Range("G6").PasteSpecial
End Sub
Also I want to copy the forumulas from cell B444 to F44 and paste them all the way down to the last row containing formulas. Same way as we do in the excel with fill handle.
Thanks
Please try this code.
Sub PLFinalReport()
' 13 Feb 2018
Dim SourceRange As Range
Dim TargetRange As Range
Dim R As Long
Dim C As Long
With Application
.ScreenUpdating = False
.Calculation = xlCalculationManual
End With
Set SourceRange = JobsPivot.Range("H3")
With SourceRange
C = .End(xlToRight).Column - .Column + 1
Set SourceRange = .Resize((.End(xlDown).Row - .Row + 1), C)
End With
C = 7 ' Column G
With PLJob
R = MatchRow("7000", .Cells(6, C)) ' = G6
If R Then
Set TargetRange = Range(.Cells(R, C), .Cells((R + SourceRange.Rows.Count - 1), C))
TargetRange.Rows.EntireRow.Insert
SourceRange.Copy .Cells(R, "H") ' column H
Else
' "7000" wasn't found
End If
End With
With Application
.Calculation = xlCalculationAutomatic
.ScreenUpdating = True
End With
End Sub
Private Function MatchRow(ByVal Crit As Variant, _
ByVal StartCell As Range) As Long
' 13 Feb 2018
Dim Rng As Range
Dim Rl As Long
Dim Fnd As Range
With StartCell.Worksheet
Rl = .Cells(.Rows.Count, 1).End(xlUp).Row ' find last used row
Set Rng = Range(.Cells(StartCell.Row, StartCell.Column), _
.Cells(Rl, StartCell.Column))
End With
With Rng
Set Fnd = .Find(What:=Crit, _
After:=.Cells(.Cells.Count), _
LookIn:=xlValues, _
LookAt:=xlPart, _
SearchOrder:=xlByRows, _
SearchDirection:=xlNext, _
MatchCase:=False)
End With
On Error Resume Next
MatchRow = Fnd.Row
End Function
I was more careful with the Find function because there are many reasons why the item might not be found causing an "unexplained" crash. One of the reasons is that Excel remembers most of the settings of your last use of Find. If your code doesn't make clear which settings to use you may not always get the same result with the same code. Consider the setting of the LookAt property in this regard.
I didn't look into your second question because - in essence - it is another question.
you can do that in one statement like:
Range("G6", Range("G6").End(xlDown)).Find("7000").Resize(XCount - YCount).EntireRow.Insert
as for your second question you can use something like follows (explanations in comments, so you can adjust it to your needs):
With PLJob 'reference PLJob
With .Range("B2", .Cells(.Rows.Count, 1).End(xlUp)) 'reference its columns A and B cells from row 2 down to column A last not empty one
.Formula = .Resize(1).Formula 'copy/paste formulas
End With
End With

Copying visible/filtered rows efficiently in excel

I am working with some very large datasets (various sheets with 65K+ rows and many columns each). I am trying to write some code to copy filtered data from one sheet to a new empty sheet as fast as possible, but have not had much success so far.
I can include the rest of the code by request, but all it does is calculates the source and destination ranges (srcRange and destRange). The time taken to calculate these is negligible. The vast majority of the time is being spent on this line (4 minutes 50 seconds to be precise):
srcRange.Rows.SpecialCells(xlCellTypeVisible).Copy Destination:=destRange
Additionally I've tried this:
destRange.Value = srcRange.Rows.SpecialCells(xlCellTypeVisible).Value
But it doesn't work properly when there's a filter.
Function FastCopy(srcSheet As String, srcCol As String, destSheet As String, destCol As String)
Application.ScreenUpdating = False
Application.Calculation = xlCalculationManual
Dim srcRange As Range
Dim destRange As Range
Set srcRange = GetColumnRangeByHeaderName(srcSheet, srcCol, -1)
Set destRange = GetColumnRangeByHeaderName(destSheet, destCol, srcRange.Rows.Count)
'destRange.Value = srcRange.Rows.SpecialCells(xlCellTypeVisible).Value
srcRange.Rows.SpecialCells(xlCellTypeVisible).Copy Destination:=destRange
Application.ScreenUpdating = True
Application.Calculation = xlCalculationManual
End Function
This is a slow, dual core machine with 2GB of RAM running excel 2010. Results will obviously vary on a faster machine.
Try something like this to work with filtered ranges. You're on the right track, the .Copy method is expensive and simply writing values from range to range should be much faster, however as you observe, this doesn't work when a range is filtered. When the range is filtered, you need to iterate the .Areas in the range's .SpecialCells:
Sub Test()
Dim rng As Range
Dim subRng As Range
Dim destRng As Range
Set destRng = Range("A10")
Set rng = Range("A1:B8").SpecialCells(xlCellTypeVisible)
For Each subRng In rng.Areas
Set destRng = destRng.Resize(subRng.Rows.Count, subRng.Columns.Count)
destRng.Value = subRng.Value
Set destRng = destRng.Cells(destRng.Rows.Count, 1).Resize(1, 1).Offset(1, 0)
Next
End Sub
Modified for your purposes, but untested:
Function FastCopy(srcSheet As String, srcCol As String, destSheet As String, destCol As String)
Application.ScreenUpdating = False
Application.Calculation = xlCalculationManual
Dim srcRange As Range
Dim destRange As Range
Dim subRng As Range
Set srcRange = GetColumnRangeByHeaderName(srcSheet, srcCol, -1)
Set destRange = GetColumnRangeByHeaderName(destSheet, destCol, srcRange.Rows.Count)
For Each subRng In srcRange.Areas
Set destRng = destRng.Resize(subRng.Rows.Count, subRng.Columns.Count)
destRng.Value = subRng.Value
Set destRng = destRng.Cells(destRng.Rows.Count, 1).Resize(1, 1).Offset(1, 0)
Next
Application.ScreenUpdating = True
Application.Calculation = xlCalculationManual
End Function
Simplest copying (no filter)
Range("F1:F53639").Value = Range("A1:A53639").Value
To expand on my comment
Sub Main()
Application.ScreenUpdating = False
' paste the Range into an array
Dim arr
arr = Range("$A$1:$A$53639").Value
' fill the range based on the array
Range("$F$1").Resize(UBound(arr, 1), UBound(arr, 2)) = arr
' apply the same filter to your copied range as the original range
'+ i don't know how you have applied your filter but just re-apply it to column F
' and delete the invisible cells
' unfortunately there is no xlCellTypeHidden or xlCelltypeInvisible hehe so you have to iterate
Dim i As Long
For i = Range("F" & Rows.Count).End(xlUp).Row To 1 Step -1
If (Range("F" & i).EntireRow.Hidden) Then Range("F" & i).Delete
' or Range("F" & i).EntireRow.Delete
Next i
Application.ScreenUpdating = True
End Sub
If you could provide the time it took you to run it that would be great I am very curious
I just ran this code on 53639 rows and it took less than 1 second
Sub Main()
Application.ScreenUpdating = False
Dim tNow As Date
tNow = Now
' paste the Range into an array
Dim arr
arr = Range("$A$1:$A$53639").Value
' fill the range based on the array
Range("$F$1").Resize(UBound(arr, 1), UBound(arr, 2)) = arr
' apply the same filter to your copied range as the original range
ActiveSheet.Range("$F$1:$F$53640").AutoFilter Field:=1, Criteria1:="a"
' and delete the invisible cells
' unfortunately there is no xlCellTypeHidden or xlCelltypeInvisible hehe so you have to iterate
Dim i As Long
For i = Range("F" & Rows.Count).End(xlUp).Row To 1 Step -1
If (Range("F" & i).EntireRow.Hidden = True) Then
Range("F" & i).Delete
End If
Next i
Debug.Print DateDiff("s", tNow, Now)
Application.ScreenUpdating = True
End Sub

Efficient way to delete entire row if cell doesn't contain '#' [duplicate]

This question already has answers here:
Delete Row based on Search Key VBA
(3 answers)
Closed 8 years ago.
I'm creating a fast sub to do a validity check for emails. I want to delete entire rows of contact data that do not contain a '#' in the 'E' Column. I used the below macro, but it operates too slowly because Excel moves all the rows after deleting.
I've tried another technique like this: set rng = union(rng,c.EntireRow), and afterwards deleting the entire range, but I couldn't prevent error messages.
I've also experimented with just adding each row to a selection, and after everything was selected (as in ctrl+select), subsequently deleting it, but I could not find the appropriate syntax for that.
Any ideas?
Sub Deleteit()
Application.ScreenUpdating = False
Dim pos As Integer
Dim c As Range
For Each c In Range("E:E")
pos = InStr(c.Value, "#")
If pos = 0 Then
c.EntireRow.Delete
End If
Next
Application.ScreenUpdating = True
End Sub
You don't need a loop to do this. An autofilter is much more efficient. (similar to cursor vs. where clause in SQL)
Autofilter all rows that don't contain "#" and then delete them like this:
Sub KeepOnlyAtSymbolRows()
Dim ws As Worksheet
Dim rng As Range
Dim lastRow As Long
Set ws = ActiveWorkbook.Sheets("Sheet1")
lastRow = ws.Range("E" & ws.Rows.Count).End(xlUp).Row
Set rng = ws.Range("E1:E" & lastRow)
' filter and delete all but header row
With rng
.AutoFilter Field:=1, Criteria1:="<>*#*"
.Offset(1, 0).SpecialCells(xlCellTypeVisible).EntireRow.Delete
End With
' turn off the filters
ws.AutoFilterMode = False
End Sub
NOTES:
.Offset(1,0) prevents us from deleting the title row
.SpecialCells(xlCellTypeVisible) specifies the rows that remain after the autofilter has been applied
.EntireRow.Delete deletes all visible rows except for the title row
Step through the code and you can see what each line does. Use F8 in the VBA Editor.
Have you tried a simple auto filter using "#" as the criteria then use
specialcells(xlcelltypevisible).entirerow.delete
note: there are asterisks before and after the # but I don't know how to stop them being parsed out!
Using an example provided by user shahkalpesh, I created the following macro successfully. I'm still curious to learn other techniques (like the one referenced by Fnostro in which you clear content, sort, and then delete). I'm new to VBA so any examples would be very helpful.
Sub Delete_It()
Dim Firstrow As Long
Dim Lastrow As Long
Dim Lrow As Long
Dim CalcMode As Long
Dim ViewMode As Long
With Application
CalcMode = .Calculation
.Calculation = xlCalculationManual
.ScreenUpdating = False
End With
With ActiveSheet
.Select
ViewMode = ActiveWindow.View
ActiveWindow.View = xlNormalView
.DisplayPageBreaks = False
'Firstrow = .UsedRange.Cells(1).Row
Firstrow = 2
Lastrow = .Cells(.Rows.Count, "E").End(xlUp).Row
For Lrow = Lastrow To Firstrow Step -1
With .Cells(Lrow, "E")
If Not IsError(.Value) Then
If InStr(.Value, "#") = 0 Then .EntireRow.Delete
End If
End With
Next Lrow
End With
ActiveWindow.View = ViewMode
With Application
.ScreenUpdating = True
.Calculation = CalcMode
End With
End Sub
When you are working with many rows and many conditions, you better off using this method of row deletion
Option Explicit
Sub DeleteEmptyRows()
Application.ScreenUpdating = False
Dim ws As Worksheet
Dim i&, lr&, rowsToDelete$, lookFor$
'*!!!* set the condition for row deletion
lookFor = "#"
Set ws = ThisWorkbook.Sheets("Sheet1")
lr = ws.Range("E" & Rows.Count).End(xlUp).Row
ReDim arr(0)
For i = 1 To lr
If StrComp(CStr(ws.Range("E" & i).Text), lookFor, vbTextCompare) = 0 then
' nothing
Else
ReDim Preserve arr(UBound(arr) + 1)
arr(UBound(arr) - 1) = i
End If
Next i
If UBound(arr) > 0 Then
ReDim Preserve arr(UBound(arr) - 1)
For i = LBound(arr) To UBound(arr)
rowsToDelete = rowsToDelete & arr(i) & ":" & arr(i) & ","
Next i
ws.Range(Left(rowsToDelete, Len(rowsToDelete) - 1)).Delete Shift:=xlUp
Else
Application.ScreenUpdating = True
MsgBox "No more rows contain: " & lookFor & "or" & lookFor2 & ", therefore exiting"
Exit Sub
End If
If Not Application.ScreenUpdating Then Application.ScreenUpdating = True
Set ws = Nothing
End Sub
Instead of looping and referencing each cell 1 by 1, grab everything and put it into a variant array; Then loop the variant array.
Starter:
Sub Sample()
' Look in Column D, starting at row 2
DeleteRowsWithValue "#", 4, 2
End Sub
The Real worker:
Sub DeleteRowsWithValue(Value As String, Column As Long, StartingRow As Long, Optional Sheet)
Dim i As Long, LastRow As Long
Dim vData() As Variant
Dim DeleteAddress As String
' Sheet is a Variant, so we test if it was passed or not.
If IsMissing(Sheet) Then Set Sheet = ActiveSheet
' Get the last row
LastRow = Sheet.Cells(Sheet.Rows.Count, Column).End(xlUp).Row
' Make sure that there is work to be done
If LastRow < StartingRow Then Exit Sub
' The Key to speeding up the function is only reading the cells once
' and dumping the values to a variant array, vData
vData = Sheet.Cells(StartingRow, Column) _
.Resize(LastRow - StartingRow + 1, 1).Value
' vData will look like vData(1 to nRows, 1 to 1)
For i = LBound(vData) To UBound(vData)
' Find the value inside of the cell
If InStr(vData(i, 1), Value) > 0 Then
' Adding the StartingRow so that everything lines up properly
DeleteAddress = DeleteAddress & ",A" & (StartingRow + i - 1)
End If
Next
If DeleteAddress <> vbNullString Then
' remove the first ","
DeleteAddress = Mid(DeleteAddress, 2)
' Delete all the Rows
Sheet.Range(DeleteAddress).EntireRow.Delete
End If
End Sub