Optimizing VBA command to delete rows with blanks - vba

I have a macro where I use the following command to delete all rows where A = blank:
Range("A:A").SpecialCells(xlCellTypeBlanks).EntireRow.Delete
The files that I am running through this macro range from 1kb to 300mb.
Doing some pressure testing, it appears that when the file is below 15mb, the command will execute almost instantaneously, resulting in a 20 second to 6 minute run time for the macro.
The second I plug in a file that is larger than 15mb, the macro will get caught on this command for 30-40 minutes.
Does anyone have a nifty trick to optimize this command for large files? If it takes a fraction of a second for a 13mb file, there must be a way to make it take a fraction of a second for a 15mb file and hopefully make it take a fraction of a second for a 300mb file...

Use an AutoFilter Method and temporarily turn off all possible environment parameters.
Application.ScreenUpdating = False
Application.DisplayAlerts = False
Application.EnableEvents = False
Application.Calculation = xlCalculationManual
With Worksheets("Sheet1")
If .AutoFilterMode Then .AutoFilterMode = False
With .Cells(1, 1).CurrentRegion
.Cells.Sort Key1:=.Columns(1), Order1:=xlAscending, _
Orientation:=xlTopToBottom, Header:=xlYes
.AutoFilter Field:=1, Criteria1:="="
.Resize(.Rows.Count - 1, 1).Offset(1, 0).EntireRow.Delete
.AutoFilter
End With
End With
Application.Calculation = xlCalculationAutomatic
Application.EnableEvents = True
Application.DisplayAlerts = True
Application.ScreenUpdating = True
It has been my experience that even looping through the rows from bottom to top can sometimes beat a call to delete Range.SpecialCells with the xlCellTypeBlanks.

Alright, so, basically riding on Jeeped's suggestions that sorting is the best method to do it, I just decided to index the data and sort:
'Insert column for index
Columns("A:A").Select
Selection.Insert Shift:=xlToRight, CopyOrigin:=xlFormatFromLeftOrAbove
'Apply index
Range("A1").Select
ActiveCell.FormulaR1C1 = "1"
Range("A2").Select
ActiveCell.FormulaR1C1 = _
"=IF(RC[1]="""","""",R[-1]C+1)"
Range("A3:A" & Range("B" & Rows.Count).End(xlUp).Row).Formula = "=IF(RC[1]="""","""",IF(R[-1]C="""",R[-2]C+1,R[-1]C+1))"
'Sort blanks to bottom
Columns("A:Z").Select
ActiveWorkbook.Worksheets("MasterList").Sort.SortFields.Clear
ActiveWorkbook.Worksheets("MasterList").Sort.SortFields.Add Key:=Columns( _
"A:A"), SortOn:=xlSortOnValues, Order:=xlAscending, DataOption:= _
xlSortNormal
With ActiveWorkbook.Worksheets("Masterlist").Sort
.SetRange Columns("A:Z")
.Header = xlGuess
.MatchCase = False
.Orientation = xlTopToBottom
.SortMethod = xlPinYin
.Apply
End With
Columns("A").Select
Selection.Delete Shift:=xlToLeft
Which runs like butter. Leaves the non-true-blank rows at the bottom, which is going to drive up the file size a bit, which is not ideal, but id rather run the macro run fast and take a half Meg penalty in file size than sit here waiting 40 minutes when I want to run a large file.

Related

needing code to use autofilter with variable range of rows

I'm trying to autofilter data in a sheet with 7 columns before copying to another workbook. This is to be used on different data that will have a different number of rows each time.
The issue is that when it autofilters the data it records the number of rows which is different each time (see Range("B1:B124")) below, which it will then apply the next time I try to use it
ChDir "F:\Work-Macro"
Workbooks.Open Filename:="F:\Work-Macro\usage.xls"
Cells.Select
With Selection.Font
.Name = "Calibri"
.Size = 10
End With
Range("D:E,I:L").Select
Range("I1").Activate
Selection.Delete Shift:=xlToLeft
Columns("A:F").Select
Range("F1").Activate
Selection.AutoFilter
ActiveWorkbook.Worksheets("Sheet1").AutoFilter.Sort.SortFields.Clear
ActiveWorkbook.Worksheets("Sheet1").AutoFilter.Sort.SortFields.Add2 Key:= _
Range("B1:B124"), SortOn:=xlSortOnValues, Order:=xlAscending, DataOption _
:=xlSortNormal
With ActiveWorkbook.Worksheets("Sheet1").AutoFilter.Sort
.Header = xlYes
.MatchCase = False
.Orientation = xlTopToBottom
.SortMethod = xlPinYin
.Apply
End With
there must be a way to apply the autofilter in a dynamic way without it recording the number of rows filtered
Instead of using a fixed range, you want to know the last cell that is in use:
Range("B2:B" & ActiveWorkbook.sheets("Sheet1").cells(rows.count,2).end(xlup).row)
The ActiveWorkbook.sheets("Sheet1").cells(rows.count,2).end(xlup).row) part will return the last row in use in column 2.
By the way, I suggest that you clean up your code after recording. Using Select and Activate is almost never necessary in a macro. It slows your code down and is very error sensitive.

Set Range Based on Last Cell with Value Plus 1

For reasons beyond my control, I must save and upload my data as a csv and i must manually select and clear the cells below my data set, the range changes daily.
Question: How can I edit my vba script to create a range based on the cell below my data set to :J10 and clear/delete it?
EDIT: Are there any changes you would implement?
Here's what I currently have, after exporting and saving as a csv, I reopen the new data and sort and attempt to clear. I have already tried Selection.SpecialCells(CellTypeBlanks).EntireRow.ClearContents & Selection.SpecialCells(CellTypeBlanks).ClearContents
I have 5 separate csv workbooks this macro currently opens and operates on but here is the final portion of my script that is giving me errors
Sub openCSV()
Dim lRow As Long
lRow = Cells(Rows.Count, 1).End(xlUp).Row
For i = lRow To 2 Step -1
If ActiveSheet.Range("H" & i).Value = 0 Then
ActiveSheet.Rows(i).ClearContents
End If
Next i
Range("A2:J10").Select
ActiveWorkbook.Worksheets("BA").Sort.SortFields.Clear
ActiveWorkbook.Worksheets("BA").Sort.SortFields.Add Key:=Range("H2") _
, SortOn:=xlSortOnValues, Order:=xlDescending, DataOption:=xlSortNormal
With ActiveWorkbook.Worksheets("BA").Sort
.SetRange Range("A2:J10")
.Header = xlGuess
.MatchCase = False
.Orientation = xlTopToBottom
.SortMethod = xlPinYin
.Apply
End With
On Error Resume Next
Range("A2:H10").Select
Selection.SpecialCells(CellTypeBlanks).ClearContents
Range("A2:J10").Select
Selection.SpecialCells(CellTypeBlanks).EntireRow.ClearContents
ActiveWorkbook.Save
ActiveWorkbook.Close
SaveChanges = True
Thank you for your time and assistance

Sudden Runtime error '9' on a two year old macro

I'm having issues with a macro that I've been using for months with little issue. The macro is designed to reformat an excel report and insert it into a different workbook within excel. Today, I keep encountering this message:
runtime error '9': subscript out of range
When I select Debug, it highlights this line of code:
ActiveWorkbook.Worksheets("Sheet1").Sort.SortFields.Clear
I am not a coder. I've used the macro button to copy what I do to shorten tasks, but outside copy and pasting the errors and highlighted code into a search engine to see what someone else may have tried, I'm not savvy enough to troubleshoot the logic. The code has always worked, I've done nothing different, but today it's throwing that '9' error. What I have tried is renaming the worksheet to match the code, so basically "Sheet1". I have copied the previous month's worksheet, deleted the old data, and tried running the macro. I even tweaked the code as was suggested by a google find with an individual suffering a similar problem, but I just created a '1004' error because I don't totally understand the logic with xlTop vs. xlDown other than the implied direction. That didn't work so I'm back to square one.
Here is my macro code in total. It's simple enough.
Sub UserStats()
'
' UserStats Macro
'
Application.ScreenUpdating = False 'Doesn't show the macro
run on the screen, speeds up program
'
Cells.Select
With Selection
.VerticalAlignment = xlTop
.WrapText = True
.Orientation = 0
.AddIndent = False
.ShrinkToFit = False
.ReadingOrder = xlContext
.MergeCells = False
End With
Columns("A:A").Select
Selection.Delete Shift:=xlToLeft
Columns("B:C").Select
Selection.Delete Shift:=xlToLeft
Columns("C:D").Select
Selection.Delete Shift:=xlToLeft
Columns("D:I").Select
Selection.Delete Shift:=xlToLeft
Rows("1:7").Select
Selection.Delete Shift:=xlUp
Columns("A:D").Select
' SortUserStats Macro
ActiveWorkbook.Worksheets("Sheet1").Sort.SortFields.Clear
ActiveWorkbook.Worksheets("Sheet1").Sort.SortFields.Add Key:=Range("A1"), _
SortOn:=xlSortOnValues, Order:=xlAscending, DataOption:= _
xlSortTextAsNumbers
With ActiveWorkbook.Worksheets("Sheet1").Sort
.SetRange Range("A:D")
.Header = xlNo
.MatchCase = False
.Orientation = xlTopToBottom
.SortMethod = xlPinYin
.Apply
End With
' CopyUserStats Macro
Cells.Select
Selection.RowHeight = 12
Range("A2:D2").Select
Range(Selection, Selection.End(xlDown)).Select
Selection.Copy
Workbooks("User Stats Prep.xlsx").Worksheets(1).Activate
Cells(Range("A1").End(xlDown).Row + 1, 1).Select
Selection.Insert Shift:=xlDown
Cells.Select
Selection.RowHeight = 12
Cells(Range("A1").End(xlDown).Row + 1, 1).Select
Application.CutCopyMode = False
ActiveWorkbook.Close SaveChanges:=True
ActiveWorkbook.Close SaveChanges:=False
End Sub
I appreciate any suggestions, otherwise I'll be adding the new data row by row copy and paste style.
I've tried to re-write it so that it doesn't have the excess that the macro recorder generally creates. If this does not work or if it works differently than before, please describe exactly what is wrong/the error
Sub UserStats()
'
' UserStats Macro
'
Application.ScreenUpdating = False 'hides screen, speeds up program
With ActiveWorkbook.Sheets(1)
'format all sheet1 cells
With .Cells
.VerticalAlignment = xlTop
.WrapText = True
.Orientation = 0
.AddIndent = False
.ShrinkToFit = False
.ReadingOrder = xlContext
.MergeCells = False
.RowHeight = 12
End With
'delete A:D, D:I, and 1:7
.Columns("A:D").Delete Shift:=xlToLeft
.Columns("D:I").Delete Shift:=xlToLeft
.Rows("1:7").Delete Shift:=xlUp
'Sort UserStats
With .Columns("A:D").Sort
.SortFields.Clear
.SortFields.Add _
key:=Range("A1"), _
SortOn:=xlSortOnValues, _
Order:=xlAscending, _
DataOption:=xlSortTextAsNumbers
.SetRange Range("A:D")
.Header = xlNo
.MatchCase = False
.Orientation = xlTopToBottom
.SortMethod = xlPinYin
.Apply
End With
'Copy UserStats
ActiveWorkbook.Sheets(1).Range(Range("A2:D2"), Range("A2:D2").End(xlDown)).copy
End With
With Workbooks("User Stats Prep.xlsx").Worksheets(1)
.Cells.RowHeight = 12
.Cells(Range("A1").End(xlDown).row + 1, 1).Insert Shift:=xlDown
End With
Workbooks("User Stats Prep.xlsx").Close SaveChanges:=True
With Application
.CutCopyMode = False
.ScreenUpdating = True
End With
End Sub

Paste Run Time Error 1004

I am trying to copy a set of filtered data from one sheet to the bottom of another sheet. And my code works great except for the first time upon opening the file I get a:
Run Time error 1004
If I quit the debugger and re-run the macro it works great.
Here is my code: noted where the problem occurs.
Sub MoveData_Click()
'Select the filtered alarm data and paste on the master spreadsheet
Sheets("DailyGen").Select
ActiveSheet.UsedRange.Offset(5, 0).SpecialCells _
(xlCellTypeVisible).Copy
Sheets("2015 Master").Select
If ActiveWorkbook.ActiveSheet.FilterMode _
Or ActiveWorkbook.ActiveSheet.AutoFilterMode Then
ActiveWorkbook.ActiveSheet.ShowAllData
End If
Range("C4").Select
Selection.End(xlDown).Select
ActiveCell.Offset(1, -2).Range("A1").Select
ActiveSheet.Paste '~~> THIS IS WHERE IT ERRORS
'Sort newest to oldest in the date column
ActiveWorkbook.Worksheets("2015 Master").ListObjects("Table44").Sort.SortFields.Clear
ActiveWorkbook.Worksheets("2015 Master").ListObjects("Table44").Sort.SortFields.Add _
Key:=Range("Table44[[#All],[Active Time]]"), _
SortOn:=xlSortOnValues,
Order:=xlDescending, DataOption:=xlSortNormal
With ActiveWorkbook.Worksheets("2015 Master").ListObjects("Table44").Sort
.Header = xlYes
.MatchCase = False
.Orientation = xlTopToBottom
.SortMethod = xlPinYin
.Apply
End With
End Sub
When you ShowAllData (same as Data->Clear in the Filter section) you are emptying the clipboard and telling Excel to forget about the copied Range. Do it outside of VBA to confirm if you want. Excel loves to empty the clipboard when you edit a cell or do much of anything other than selecting.
To fix, do the Copy after the ShowAllData. In your case, you will have to Select the Worksheet back and forth.
You should generally work to avoid using Select and Activate for your VBA. See this post for details.
Here is the final code with the changes made:
Sub MoveData_Click()
'Select the filtered alarm data and paste on the master spreadsheet
Sheets("2015 Master").Select
If ActiveWorkbook.ActiveSheet.FilterMode Or ActiveWorkbook.ActiveSheet.AutoFilterMode Then
ActiveWorkbook.ActiveSheet.ShowAllData
End If
Sheets("DailyGen").Select
ActiveSheet.UsedRange.Offset(5, 0).SpecialCells _
(xlCellTypeVisible).Copy
Sheets("2015 Master").Select
Range("C4").Select
Selection.End(xlDown).Select
ActiveCell.Offset(1, -2).Range("A1").Select
ActiveSheet.Paste
'Sort newest to oldest in the date column
ActiveWorkbook.Worksheets("2015 Master").ListObjects("Table44").Sort.SortFields.Clear
ActiveWorkbook.Worksheets("2015 Master").ListObjects("Table44").Sort.SortFields.Add _
Key:=Range("Table44[[#All],[Active Time]]"), SortOn:=xlSortOnValues, Order _
:=xlDescending, DataOption:=xlSortNormal
With ActiveWorkbook.Worksheets("2015 Master").ListObjects("Table44").Sort
.Header = xlYes
.MatchCase = False
.Orientation = xlTopToBottom
.SortMethod = xlPinYin
.Apply
End With
End Sub

VBA Macro to compare then organize data

I'm trying to architect a macro to do the following steps:
Compare two lists of data (in this case Column A against Column C)
Output in B any cell that exists in both A and C. Line up the match next to its match in Column A.
Sort both column A and B by their values so that the corresponding cells in A and B are still next to each other after the sort.
Desired result. Notice how the matches in column A and B are still together. This enables users of this macro to quickly eliminate data that only belongs to one of the respective columns and it allows us to retain any information that may be tied to column A, e.g., Column A contains email addresses, and there is a corresponding column next to it that contains phone #'s. We don't want to split that information up. This macro would enable that:
Pastebin of excel data I used: http://pastebin.com/mYuQRMjj
This is the macro I've written, which uses a second macro:
Sub Macro()
Range(Selection, Selection.End(xlDown)).Select
Application.Run "macro4.xlsm!Find_Matches"
Range("B1:B284").Select
ActiveWorkbook.Worksheets("Sheet1").Sort.SortFields.Clear
ActiveWorkbook.Worksheets("Sheet1").Sort.SortFields.Add Key:=Range("B1:B284") _
, SortOn:=xlSortOnValues, Order:=xlAscending, DataOption:=xlSortNormal
With ActiveWorkbook.Worksheets("Sheet1").Sort
.SetRange Range("A1:B284")
.Header = xlGuess
.MatchCase = False
.Orientation = xlTopToBottom
.SortMethod = xlPinYin
.Apply
End With
End Sub
The second macro that does the comparison is literally ripped straight from Microsoft, with a little extra.
Sub Find_Matches()
Application.ScreenUpdating = False
Dim CompareRange As Variant, x As Variant, y As Variant
' Set CompareRange equal to the range to which you will
' compare the selection.
Set CompareRange = Range("C1:C500")
' NOTE: If the compare range is located on another workbook
' or worksheet, use the following syntax.
' Set CompareRange = Workbooks("Book2"). _
' Worksheets("Sheet2").Range("C1:C5")
'
' Loop through each cell in the selection and compare it to
' each cell in CompareRange.
For Each x In Selection
For Each y In CompareRange
If x = y Then x.Offset(0, 1) = x
Next y
Next x
Application.ScreenUpdating = True
End Sub
Using these two macros, I get exactly what I want, but I don't like using limited ranges. I want the macro to be smart enough to determine exactly what the range is, because the people who will be using this macro sometimes will be using a list of 200, sometimes a list of 2,000,000. I want this macro to be a "one size fits all" for range.
I looked into this and the command:
Range(Range("B1"),Range("A1").End(xlDown)).Select
gets exactly the selection I want after Find_Matches runs (I also realize that Find_Matches is using a finite compare range . . . solving my issue for this first Macro will solve that too).
The problem is that I am unsure how to plug that into my Macro. I've tried several implementations and I'm flat out stuck. I can't find an answer for something this specific, but I know I'm very close. Thank you for any help!
edit: This whole method is realllly slow on larger lists (20+ minutes on a list of 100k). If you can suggest some ways to speed it up that would be super helpful!
Sub MatchNSort()
Dim lastrow As Long
'Tell Excel to skip the calculation of all cells and the screen
Application.Calculation = xlCalculationManual
Application.ScreenUpdating = False
'Find the last row in the data
With ActiveSheet
If Application.WorksheetFunction.CountA(.Cells) <> 0 Then
lastrow = .Cells.Find(What:="*", _
After:=.Range("A1"), _
Lookat:=xlPart, _
LookIn:=xlFormulas, _
SearchOrder:=xlByRows, _
SearchDirection:=xlPrevious, _
MatchCase:=False).Row
Else
lastrow = 1
End If
End With
'Force a formula in column B to match a from c
ActiveSheet.Range("B1:B" & lastrow).Formula = _
"=IFERROR(IF(MATCH(C[-1],C[1]:C[1],0)>0,C[-1],""""),"""")"
'Force a recalculate
Application.Calculate
'Sort columns B and A
With ActiveSheet
.Range("A1:B" & lastrow).Select
.Sort.SortFields.Clear
'First key sorts column B
.Sort.SortFields.Add Key:=Range("B1:B" & lastrow) _
, SortOn:=xlSortOnValues, Order:=xlAscending _
, DataOption:=xlSortNormal
'Second key (optional) sort column A, after defering to column B
.Sort.SortFields.Add Key:=Range("A1:A" & lastrow) _
, SortOn:=xlSortOnValues, Order:=xlAscending _
, DataOption:=xlSortNormal
.Sort.SetRange Range("A1:B" & lastrow)
.Sort.Header = xlGuess
.Sort.MatchCase = False
.Sort.Orientation = xlTopToBottom
.Sort.SortMethod = xlPinYin
.Sort.Apply
End With
'Return autocalulation and screen updates
Application.Calculation = xlCalculationAutomatic
Application.ScreenUpdating = True
Application.Calculate
End Sub
See Error in finding last used cell in VBA for the best way to find the last row of data.
Find the last row and then change your range selection to:
Range("C1:C"&Trim(CStr(lastrow)))
To speed up your macro execution start your macro with:
Application.Calculation = xlCalculationManual
Application.ScreenUpdating = False
and to restore autocalc and screen updates, end your macro with:
Application.Calculation = xlCalculationAutomatic
Application.ScreenUpdating = True
Application.Calculate