Fastest way to Delete whole row based on Blank Cell - excel

There are so many ways to delete a whole row based on a blank cell in specific column. What I want to know is which is the fastest way to accomplish this task in terms of Excel speed. I have a sheet with about 39,000 original rows of data which then becomes 21,000 rows after I run the code below. The issue is the chunk of code takes almost 60 seconds to return. While I know CPU and such is a factor, but lets assume all else being equal.
I am using Column A as the total count of rows and Column F as the location of blank cells. Is this the best/ fastest way to write this code?
' Finds the last row with a file numbers and removes the remaining rows
Dim LastRow As Integer
LastRow = Range("A" & Rows.Count).End(xlUp).Row
Range("F2:F" & LastRow).SpecialCells(xlCellTypeBlanks).EntireRow.Delete

Try this (wishing it would help, although take backup of your sheet before!):
Sub FastestBlankRowTerminator()
ActiveSheet.UsedRange.Columns(6).SpecialCells(xlCellTypeBlanks).EntireRow.Delete
End Sub

One of the simplest things I can advise that should increase performance by a noticeable amount is to turn off screen updating and automatic calculation while running this procedure.
I typically turn these items off at the initial invocation of code and turn them back on after the final one. Meaning I would have one sub to contain a series of other subs and functions that it would execute in sequence. Instead of embedding this in those subs and functions individually I just set them off, execute the main sub, and then reset them.
' Speed Up
application.screenupdating = false
application.calculation = xlCalculationManual
<insert code you want to improve performance on here>
' Slow Down
application.screenupdating = true
application.calculation = xlCalculationAutomatic
I ran a test myself populating column a with a rowcount up to 39000 and then every other record would have a "1" in column f.
It still takes some time but only 46 seconds on my core2duo, if I don't turn off screen updating it takes 3 minutes and 34 seconds.
Sub Main()
' Speed Up
Application.ScreenUpdating = False
Application.Calculation = xlCalculationManual
Begin ' Main Sub
' Reset
Application.ScreenUpdating = True
Application.Calculation = xlCalculationAutomatic
End Sub
Sub Begin()
' Sub 1
' Sub 2
' Sub 3
Remove_Blanks
End Sub
Sub Remove_Blanks()
Dim dA As Date, dB As Date
Dim wb As Workbook
Dim ws As Worksheet
Dim i As Long, j As Integer
Dim r As Long, c As Integer
dA = Now
' Commented out to indicate they could be here but if you are executing multiple procedures then you should have it occur outside of this.
'Application.ScreenUpdating = False
'Application.Calculation = xlCalculationManual
Set wb = ThisWorkbook
Set ws = wb.Sheets("Sheet1")
With ws
For r = 1 To .UsedRange.Rows.Count
If .Cells(r, 6) = "" Then .Rows(r).Delete
Next r
End With
dB = Now
'Commented out for same reason above
'Application.ScreenUpdating = True
'Application.Calculation = xlCalculationAutomatic
Debug.Print "Remove_Blanks: " & Format((dB - dA), "mm/dd/yyyy hh:mm:ss")
End Sub

I set up a matrix of 50,000 rows x 12 columns. In column F I placed about 25,000 blanks randomly placed.
Read the used range into an array
Iterate through the array and read those rows with content in column F into a results array
Clear the original data
write the results array
A lot of steps, but the execution time was less than one second; it would probably be faster with screenupdating false; and longer if you have more columns.
EDIT: Screenupdating false did not significantly decrease the execution speed, which was approximately 0.36 seconds when timed with a hi-res timer.
EDIT2: After reading Tim Williams comment about preserving formatting and formulas, I present a different approach. This approach will use the Advanced Filter and, on the same made up data as above, will place the data on another worksheet minus the rows that have blanks in column F. This does require a first row of column headers in the data; or, at least, that F1 has a unique, non-blank value.
To accomplish that process takes about 0.15 seconds.
If you also want to copy it back over the original worksheet, and delete the added worksheet, that will take about another 0.3 seconds.
Here is some code to do that, but you'd have to alter it for your own specifications:
==============================================
Sub DeleteBlankFRows2()
Dim WS As Worksheet, wsTemp As Worksheet, rTemp As Range
Dim R As Range, rCrit As Range
Dim I As Long
Set WS = Worksheets("Sheet5")
Set R = WS.Range("a1").CurrentRegion
Set rCrit = R.Offset(0, R.Columns.Count + 3).Resize(2, 1)
rCrit(1) = R(1, 6)
rCrit(2) = "<>"
Application.ScreenUpdating = False
Worksheets.Add
Set wsTemp = ActiveSheet
wsTemp.Name = "Temp"
R.AdvancedFilter xlFilterCopy, rCrit, Cells(1, 1)
Set rTemp = wsTemp.Cells(1, 1).CurrentRegion
WS.Cells.Clear
rTemp.Copy WS.Cells(1, 1)
Application.DisplayAlerts = False
wsTemp.Delete
Application.DisplayAlerts = True
Application.ScreenUpdating = True
End Sub
=======================================
This was my original code using VBA arrays:
===========================
Sub foo()
Dim vSrc As Variant, vRes() As Variant
Dim rSrc As Range
Dim I As Long, J As Long, K As Long
Dim lRows As Long
'Or may need to use a different method to include everything
Set rSrc = Range("a1").CurrentRegion
vSrc = rSrc
'how many rows to retain
For I = 1 To UBound(vSrc)
If vSrc(I, 6) <> "" Then lRows = lRows + 1
Next I
ReDim vRes(1 To lRows, 1 To UBound(vSrc, 2))
K = 0
For I = 1 To UBound(vSrc)
If vSrc(I, 6) <> "" Then
K = K + 1
For J = 1 To UBound(vSrc, 2)
vRes(K, J) = vSrc(I, J)
Next J
End If
Next I
Cells.Clear
Range("a1").Resize(UBound(vRes, 1), UBound(vRes, 2)) = vRes
End Sub

Related

Match, Copy, Paste and clear takes a long time. How to speed up?

I am using below code in one workbook as the following:
(1) Match a range on SheetA against a range on SheetB.
(2) If the data found on SheetB, then some values will be inserted on SheetB and Sheet Log.
(3) The matched data (rows) on SheetB will be copied to Sheet Result and Autofit.
(4) The matched data (rows) on SheetB will be cleared. (cut & paste is not applicable).
The count of values on the first range in SheetA is normally 7 or 8 and this macro was as fast as it takes 2 seconds to finish all that steps.
I tried to put 146 values on the first range, but the macro turned to be very slow and it took 35 seconds to finish.
Please, how to speed up and optimize this macro?
Note: there is no problem at all to change match code or copy, paste, autofit and clear code.
Link for the full macro and sheet on the first comment.
Sub Match_Copy()
With Application
.Calculation = xlCalculationManual
.ScreenUpdating = False
.EnableEvents = False
End With
'----------------------------- Match, Code
Dim Cell As Object, Match_A As Variant
For Each Cell In WorkOrder
Match_A = Application.Match(Cell.value, Auto_Data, 0)
If Not IsError(Match_A) Then
Cell.Offset(, 6).Resize(1, 3).value = Array("Close", Now, ws.name) 'Put Data of Close in every Area
If ws.name = "SheetB" Then 'Put Data of Close in Log Sheet
Sheets("Log").Range("A" & Rows.Count).End(xlUp).Offset(1, 0).Resize(1, 3).value = _
Array(Application.UserName, Now, Cell)
End If
End If
Next Cell
'----------------------------- Copy, Paste, AutoFit and Clear Code
Dim StatusColumn As Range
Set StatusColumn = ws.Range("G2", ws.Cells(Rows.Count, "G").End(xlUp))
For Each Cell In StatusColumn
If Cell.value = "Close" Then
Cell.EntireRow.Copy
Dim DestRng As Range
Set DestRng = Sheets("Result").Cells(Rows.Count, "A").End(xlUp).Offset(1)
DestRng.PasteSpecial xlPasteValues
DestRng.Rows.AutoFit
If DestRng.Rows.RowHeight < 45 Then DestRng.Rows.RowHeight = 45
End If
Next Cell
For Each Cell In StatusColumn
If Cell.value = "Close" Then
Cell.EntireRow.Clear
End If
Next Cell
'-----------------------------
With Application
.Calculation = xlCalculationAutomatic
.ScreenUpdating = True
.EnableEvents = True
End With
End sub
Please, check the next adapted code. It uses arrays for faster iteration and for faster results return. Also, setting the row height for each cell consumes Excel resources. I commented some rows but no time now for everything. If something unclear, please do not hesitate to ask for clarifications:
Sub Run_Close()
Dim dStart As Double: dStart = Timer
With Application
.Calculation = xlCalculationManual
.ScreenUpdating = False
.EnableEvents = False
End With
'------------------
Dim lastR As Long: lastR = Sheets("SheetA").Cells(Rows.Count, "A").End(xlUp).Row
Dim Auto_Data As Range: Set Auto_Data = Sheets("SheetA").Range("A2:A" & lastR)
Dim Count_Auto_Data As Long: Count_Auto_Data = WorksheetFunction.CountA(Auto_Data)
If Count_Auto_Data = 0 Then Exit Sub
With Auto_Data
.NumberFormat = "General"
.Value = .Value
End With
'------------------
Sheets("Result").AutoFilter.ShowAllData
Dim ws As Worksheet, arrWsFin, arrLog, k As Long
For Each ws In Sheets(Array("SheetB")) 'There are another 3 Sheets
ws.AutoFilter.ShowAllData
Dim LastRow As Long: LastRow = ws.Cells(Rows.Count, "A").End(xlUp).Row
Dim WorkOrder As Range: Set WorkOrder = ws.Range("A3:A" & LastRow)
Dim arrWO: arrWO = WorkOrder.Value2 'place the range in an array for faster iteration
ReDim arrWsFin(1 To LastRow, 1 To 3) 'redim array to keep the modifications in ws sheet
ReDim arrLog(1 To 3, 1 To LastRow): k = 1 'redim array to keep maximum modif of ws sheet
'----------------------------- Match, Code
Dim Cell As Object, Match_A As Variant, i As Long
For i = 1 To UBound(arrWO)
Match_A = Application.Match(arrWO(i, 1), Auto_Data, 0)
If Not IsError(Match_A) Then
arrWsFin(i, 1) = "Close": arrWsFin(i, 2) = Now: arrWsFin(i, 3) = ws.name
If ws.name = "SheetB" Then 'Put Data of Close in the array for further return at once
arrLog(1, k) = Application.UserName: arrLog(2, k) = Now: arrLog(3, k) = arrWO(i, 1): k = k + 1
End If
End If
Next i
ws.Range("G2").Resize(UBound(arrWsFin), UBound(arrWsFin, 2)).Value = arrWsFin
If k > 1 Then
ReDim Preserve arrLog(1 To 3, 1 To k - 1)
Sheets("Log").Range("A" & Rows.Count).End(xlUp).Offset(1, 0).Resize(UBound(arrLog, 2), UBound(arrLog)).Value = Application.Transpose(arrLog)
End If
'----------------------------- Copy, Paste and AutoFit, Code
Dim StatusColumn As Range, totRng As Range, lastCol As Long, arrSt, arrResult, arrRow, j As Long
lastR = ws.Cells(Rows.Count, "G").End(xlUp).Row
Set StatusColumn = ws.Range("G2", ws.Cells(Rows.Count, "G").End(xlUp))
arrSt = StatusColumn.Value2 'place the range in an array for faster iteration
lastCol = ws.Cells(1, ws.Columns.Count).End(xlToLeft).Column
Set totRng = ws.Range("A2", ws.Cells(lastR, lastCol)) 'total range to extract the row slice
Dim rngClearCont As Range
ReDim arrResult(1 To lastCol, 1 To lastR): k = 1
For i = 1 To UBound(arrSt)
If arrSt(i, 1) = "Close" Then
arrRow = totRng.Rows(i).Value
'load arrResult array:
For j = 1 To lastCol
arrResult(j, k) = arrRow(1, j)
Next
k = k + 1
If rngClearCont Is Nothing Then
Set rngClearCont = StatusColumn.Cells(i) 'set the range necessary to clear rows at the end
Else
Set rngClearCont = Union(rngClearCont, StatusColumn.Cells(i))
End If
End If
Next i
If k > 1 Then
ReDim Preserve arrResult(1 To lastCol, 1 To k - 1)
With Sheets("Result").Cells(Rows.Count, "A").End(xlUp).Offset(1).Resize(UBound(arrResult, 2), _
UBound(arrResult))
.Value = Application.Transpose(arrResult)
.Rows.RowHeight = 45
End With
rngClearCont.EntireRow.ClearContents
End If
'-----------------------------
Next ws
With Application
.Calculation = xlCalculationAutomatic
.ScreenUpdating = True
.EnableEvents = True
End With
MsgBox "Time taken: " & Format(Timer - dStart, "0.00s")
End Sub
It should take less than a second...
The root of your issue is that you are making many edits to the worksheet. One of the first ways to speed up VBA code is to reduce the number of times you write data to the sheet.
Rather than writing your data to the sheet every time in a For Each loop, add all of your data to an Array and then write that entire Array to the sheet(s) at once. This way, you don't have to write multiple times for every For Each loop, but only once.
I cannot guarantee that this is the only reason your code is "sub-optimal" but it's a good place to start to improve performance times.
While writing to the sheet does take time, the main problem here is the copy/paste part.
If you, after the row
Cell.Offset(, 6).Resize(1, 3).value = Array("Close", Now, ws.name) 'Put Data of Close in every Area
Put something like:
Sheets("Result").Cells(Rows.Count, "A").End(xlUp).Offset(1).Resize(, 9).value = Array(Cell, , , , , , "Close", Now, ws.name)
And then remove the copy/paste part completely, you should be able to run it almost instantly.

Excel VBA - Delete empty columns between two used ranges

I would like to delete all empty columns between 2 used ranges, based on the screenshot:
However, these two used ranges may have varying column length, thus the empty columns are not always Columns D to K.
Here is my code:
Sub MyColumns()
Application.ScreenUpdating = False
Application.DisplayAlerts = False
Workbooks.Open ("BOOK2.xlsx")
Workbooks("BOOK2.xlsx").Activate
Workbooks("BOOK2.xlsx").Sheets(1).Activate
Workbooks("BOOK2.xlsx").Sheets(1).Cells(1, 4).Value = "NON-EMPTY"
Dim finalfilledcolumn As Long
finalfilledcolumn = Workbooks("BOOK2.xlsx").Sheets(1).Cells(1, Columns.Count).End(xlToLeft).Column
Dim iCol As Long
Dim i As Long
iCol = firstfilledcolumn + 1
'Loop to delete empty columns
For i = 1 To firstfilledcolumn + 1
Columns(iCol).EntireColumn.Delete
Next i
Workbooks("BOOK2.xlsx").Close SaveChanges:=True
MsgBox "DONE!"
Application.ScreenUpdating = True
Application.DisplayAlerts = True
End Sub
However, the empty columns still remain.
Do note that the last filled column for the first used range, Place = "USA", Price = "110" and Category = "Mechanical" may not be fixed at Column C, but could go to Column D, E, etc.
Many thanks!
Please, try the next way:
Sub deleteEmptyColumn()
Dim sh As Worksheet, lastCol As Long, rngColDel As Range, i As Long
Set sh = ActiveSheet 'use here your necessary sheet, having the workbook open
'if not open, you can handle this part...
lastCol = sh.cells(1, sh.Columns.count).End(xlToLeft).column
For i = 1 To lastCol
If WorksheetFunction.CountA(sh.Columns(i)) = 0 Then
If rngColDel Is Nothing Then
Set rngColDel = sh.cells(1, i)
Else
Set rngColDel = Union(rngColDel, sh.cells(1, i))
End If
End If
Next i
If Not rngColDel Is Nothing Then rngColDel.EntireColumn.Delete
End Sub
Try this ..
Dim rng As Range, i As Long
Set rng = Workbooks("BOOK2.xlsx").Sheets(1).UsedRange
For i = rng.Columns.Count To 1 Step -1
If WorksheetFunction.CountA(rng.Columns(i)) = 0 Then
rng.Columns(i).EntireColumn.Delete
End If
Next i

Optimize slow code that has lots of "for" and "if"

I have three main sheets: "inputs","variables" and "result". The sheet "inputs" has a list with 150 cells with inputs by the user, the sheet "variable" has a list with more than 30 000 points and "result" is the result of the code.
The code takes a point from the sheet "inputs", searches this point in the "variables" sheet, takes a bunch of information from this sheet an pastes them in the "result" sheet. However the process is really slow, it takes from 7 to 9 minutes to find the 150 inputs. There is any way I can make it faster, or at least half of the time?
The simplified version of the code is shown below, the actual code has at least 5 "for" and 4 "if", due to a lot of conditions presented in the "input" section
Application.ScreenUpdating = False
Application.DisplayAlerts = False
Dim ws1 As Worksheet
Dim ws2 As Worksheet
Dim ws3 As Worksheet
Dim tag As String
Dim var As String
Dim input As String
Dim i As Integer
Dim j As Integer
Set ws1 = Worksheets("inputs")
Set ws2 = Worksheets("variable")
Set ws3 = Worksheets("result")
For i = 2 To ws2.Range("C" & Rows.count).End(xlUp).Offset(1).Row
For j = b To ws2.Range("C" & Rows.count).End(xlUp).Offset(1).Row
var = ws2.Cells(j, 4)
input = ws1.Cells(i, 2), 12, 40)
If var = specs
DO STAFF HERE
End If
Next j
Next i
Application.ScreenUpdating = True
Application.DisplayAlerts = True
End Sub
You should try saving into an array the looping through said array! always seems to improve efficiency but a loop inside a loop is never great in terms of O(n2) and all that!
Dim varr As Variant
Dim j as long
varr = ws2.Range("C1:C" & ws2.Range("C" & Rows.count).End(xlUp).Row).value
For j = LBound(varr) To UBound(varr)
If varr(j, 1) = specs then
end if
Next j

Excel VBA Can't delete entire row when part of row is a table

I'm trying to loop through my data and Union certain row numbers that I need to delete later on. The code below stores the correct rows, but I can't delete them. I believe it's because my data is arranged in a table, since I'm able to delete the desired rows if the data is not in a table. I get the error message 'run time error 1004 - delete method of range class failed' on the line Urng.delete.
Sub DeleteRows()
Dim ws4 As Worksheet: Set ws4 = Worksheets("Sheet1")
Dim LastRow As Long
Dim CurrentRow As Long
Dim GroupValue
Dim GroupTotal As Long
Dim x As Long
Dim Urng As Range
Application.ScreenUpdating = False
ws4.Activate
GroupValue = ws4.Range("B6").Value
CurrentRow = 6 LastRow = ws4.Cells(Rows.Count, "B").End(xlUp).Row
Set Urng = Rows(LastRow + 1)
For x = 1 To LastRow
GroupTotal = Application.WorksheetFunction.CountIf(Range("B6:B" & LastRow), GroupValue)
If GroupTotal = 1 Then
Set Urng = Union(Urng, Rows(CurrentRow))
End If
CurrentRow = CurrentRow + GroupTotal
GroupValue = Range("B" & CurrentRow).Value
If GroupValue = "" Then '
Exit For
End If
Next x
Urng.Delete
Application.ScreenUpdating = True
End Sub
I've tried using .EntireRow.Delete without luck.
There's no data outside the table, so deleting just the table rows could be a solution, however, I don't know how to build the loop that Unions the row numbers if I can't use the row number in Union(Urng, Rows(CurrentRow)).
Is there a VBA-solution to delete multiple entire rows, where part of the row is a table?
This is how to delete row number 5 from a table named TableName:
Sub TestMe()
Range("TableName[#All]").ListObject.ListRows(5).Delete
End Sub
Concerning your specific problem, the case is that in Urng you are having rows, which are both in and outside the table. Thus, they cannot be deleted with .Delete. Write this before Urng.Delete to see yourself:
Urng.Select
Stop
Unrg.Delete
At the sample you may see that the row 6 is in the table and row 18 is outside the table:
Concerning deletion of two rows, which are not close to each other in a table, I guess that the only way is to loop. It is a bit slower indeed, but it works:
Sub TestMe()
Dim cnt As Long
Dim arrRows As Variant: arrRows = Array(10, 12)
Dim table As ListObject: Set table = ActiveSheet.ListObjects("SomeTable")
For cnt = UBound(arrRows) To LBound(arrRows) Step -1
table.ListRows(arrRows(cnt)).Delete
Next cnt
'This works only when the rows are after each other, e.g. 2,3,4
table.Range.Rows("2:4").Select
Stop
table.Range.Rows("2:4").Delete
End Sub

How to add a step function within a dictionary macro

I am new to VBA and I have been using the great help within this site, to create a macro to take a list of numbers from one sheet (Sheet 14), remove the duplicates and paste within another sheet (Sheet 2).
I am hoping to take this further by rather than pasting the cells one after another I am looking to have the list pasted in alternate rows i.e D10, D12, D14 etc.
I have tried various methods from within this site, however to no avail. I have used different types of "Step" functions but I am struggling to incorporate this within the below coding.
Any help is much appreciated!
Below is what I have at the moment:
Sub RUN()
Application.ScreenUpdating = False
Dim lastRow As Long
Dim i As Long
Dim dictionary As Object
Set dictionary = CreateObject("scripting.dictionary")
Sheet14.Activate
lastRow = Sheet14.Cells(Rows.Count, "F").End(xlUp).Row
On Error Resume Next
For i = 3 To lastRow
If Len(Cells(i, "F")) <> 0 Then
dictionary.Add Cells(i, "F").Value, 1
End If
Next
Sheet2.Range("d10").Resize(dictionary.Count).Value = _
Application.Transpose(dictionary.keys)
Application.ScreenUpdating = True
MsgBox dictionary.Count & " RUN TEMPLATES."
End Sub
Here's one approach (BTW, I wouldn't call a macro RUN):
Sub ListUniques()
Dim lastRow As Long
Dim i As Long
Dim dictionary As Object
Dim vKeys
Application.ScreenUpdating = False
Set dictionary = CreateObject("scripting.dictionary")
With Sheet14
lastRow = .Cells(.Rows.Count, "F").End(xlUp).Row
For i = 3 To lastRow
If Len(.Cells(i, "F")) <> 0 Then
dictionary(.Cells(i, "F").Value) = 1
End If
Next
End With
vKeys = dictionary.keys
For i = LBound(vKeys) To UBound(vKeys)
Sheet2.Range("d10").Offset(2 * i).Value = vKeys(i)
Next i
Application.ScreenUpdating = True
MsgBox dictionary.Count & " RUN TEMPLATES."
End Sub

Resources