Faster code to delete cells through multiple worksheets in excel - vba

i am a beginner in VB and having googled and looked through the answers here i have written the following loop to cycle through multiple excel worksheets and delete rows where the cells contain specific elements (N/A # N/A#).
The data in the xl sheet to be cleaned is financial data with DATE, OPEN. HIGH LOW CLOSE. the number of rows can be significant and the number of worksheets can be 2-300. It works but is very very slow and as I am learning - would appreciate any assistance on how i can make this code faster. Thank you.
Sub DataDeleteStage1()
ScreenUpdating = False
Dim lrow As Long
Dim ws As Worksheet
Dim icntr As Long
For Each ws In ThisWorkbook.Worksheets
lrow = ws.Cells(Rows.CountLarge, "a").End(xlUp).Row
For icntr = lrow To 1 Step -1
If ws.Name <> "HEADER" Then
If ws.Cells(icntr, "B") = "#N/A N/A" And ws.Cells(icntr, "C") = "#N/A N/A" And ws.Cells(icntr, "D") = "#N/A N/A" And ws.Cells(icntr, "E") = "#N/A N/A" Then
ws.Rows(icntr).EntireRow.Delete
End If
End If
Next icntr
Next ws
End Sub

Try merging all Ranges to be deleted to a MergeRng object, and then just delete it all at once.
Code
Sub DataDeleteStage1()
ScreenUpdating = False
Dim lrow As Long
Dim ws As Worksheet
Dim icntr As Long
Dim MergeRng As Range
For Each ws In ThisWorkbook.Worksheets
With ws
lrow = .Cells(.Rows.Count, "A").End(xlUp).Row
For icntr = lrow To 1 Step -1
If .Name <> "HEADER" Then
If .Cells(icntr, "B") = "#N/A N/A" And .Cells(icntr, "C") = "#N/A N/A" And .Cells(icntr, "D") = "#N/A N/A" And .Cells(icntr, "E") = "#N/A N/A" Then
If Not MergeRng Is Nothing Then
Set MergeRng = Application.Union(MergeRng, .Rows(icntr))
Else
Set MergeRng = .Rows(icntr)
End If
End If
End If
Next icntr
' Delete all rows at once
If Not MergeRng Is Nothing Then MergeRng.Delete
End With
Set MergeRng = Nothing ' reset range when changing worksheets
Next ws
End Sub

You can make your code delete only once and not every time.
In order to make it like this, try the following:
Sub DataDeleteStage1()
Application.ScreenUpdating = False
Dim lrow As Long
Dim ws As Worksheet
Dim icntr As Long
Dim delRange As Range
For Each ws In ThisWorkbook.Worksheets
lrow = ws.Cells(Rows.CountLarge, "a").End(xlUp).Row
For icntr = lrow To 1 Step -1
If ws.Name <> "HEADER" Then
If ws.Cells(icntr, "B") = "#N/A N/A" And _
ws.Cells(icntr, "C") = "#N/A N/A" And _
ws.Cells(icntr, "D") = "#N/A N/A" And _
ws.Cells(icntr, "E") = "#N/A N/A" Then
If Not delRange Is Nothing Then
Set delRange = ws.Rows(icntr)
Else
Set delRange = Union(delRange, ws.Rows(icntr))
End If
End If
End If
Next icntr
If Not delRange Is Nothing Then delRange.Delete
Set delRange = Nothing
Next ws
End Sub
I have not tried it, but it should work.

How about this?
Sub DeleteRows()
Dim ws As Worksheet
With Application
.Calculation = xlCalculationManual
.EnableEvents = False
.ScreenUpdating = False
End With
For Each ws In ThisWorkbook.Sheets
If ws.Name <> "HEADER" Then
On Error Resume Next
ws.Columns("B:E").Replace "#N/A N/A", "=NA()"
ws.Columns("B:E").SpecialCells(xlCellTypeFormulas, 16).EntireRow.Delete
End If
Next ws
With Application
.Calculation = xlCalculationAutomatic
.EnableEvents = True
.ScreenUpdating = True
End With
End Sub

With AutoFilter and without looping altogether:
Sub DataDeleteStage1()
Dim ws As Worksheet
Dim lr As Integer
Application.ScreenUpdating = False
For Each ws In ThisWorkbook.Worksheets
With ws
lr = .Range("A" & .Rows.Count).End(xlUp).Row
If ws.Name <> "HEADER" Then
.UsedRange.AutoFilter Field:=2, Criteria1:="#N/A"
.UsedRange.AutoFilter Field:=3, Criteria1:="#N/A"
.UsedRange.AutoFilter Field:=4, Criteria1:="#N/A"
.UsedRange.AutoFilter Field:=5, Criteria1:="#N/A"
.Range("A2:A" & lr).SpecialCells(xlCellTypeVisible).EntireRow.Delete shift:=xlUp
End If
End With
Next ws
Application.ScreenUpdating = True
End Sub
Tested this vs. the merged range approach on 300K rows - faster by minutes when doing multiple sheets.

I haven't tested but try this,
Sub DataDeleteStage1()
Application.ScreenUpdating = False
Application.DisplayAlerts = False
Dim lrow As Long
Dim ws As Worksheet
Dim icntr As Long
For Each ws In ThisWorkbook.Worksheets
lrow = ws.Cells(Rows.CountLarge, "a").End(xlUp).Row
If ws.Name <> "HEADER" Then
On Error Resume Next
Range("F1:F" & lrow).Formula = "=IF(SUMPRODUCT(--ISERROR(A1:E1))=5,NA(),"""")"
Range("F1:F" & lrow).SpecialCells(xlCellTypeFormulas, xlErrors).EntireRow.Delete shift:=xlUp
Range("F1:F" & lrow).Clear
End If
Next ws
Application.ScreenUpdating = True
Application.DisplayAlerts = True
End Sub

Related

VBA Excel Script - Runtime Error 424 (Code Specific)

By no means am I a VBA developer, but any help on why this isn't working would be greatly appreciated...
Problem:
Analyze all worksheets, except the last.
Check if a column I and J contain an X, if they do, get that row and copy it to the last worksheet.
Error Highlighted is at this line: For Each ws In Workbook.Worksheets. I'm not sure why.
Below is my code, but it's not compiling, and giving me the error code 424 - Object Required.
Sub CopyData()
Application.ScreenUpdating = False
Dim pasteSheet As Worksheet
Set pasteSheet = Worksheets("Remediation Summary")
For Each ws In ThisWorkbook.Worksheets
If ws.Index < (ThisWorkbook.Worksheets.Count - 1) Then
'check column i for x
For Each icell In ws.Range("i1:i200").Cells
If icell.Value Like ("X") Or ("x") Then
Rows(icell.RowIndex).Copy
pasteSheet.Cells(Rows.Count, 1).End(xlUp).Offset(1, 0).PasteSpecial xlPasteValues
Application.CutCopyMode = False
Application.ScreenUpdating = True
End If
Next icell
'check column j for x
For Each jcell In ws.Range("j1:j200").Cells
If jcell.Value Like ("X") Or ("x") Then
Rows(jcell.RowIndex).Copy
pasteSheet.Cells(Rows.Count, 1).End(xlUp).Offset(1, 0).PasteSpecial xlPasteValues
Application.CutCopyMode = False
Application.ScreenUpdating = True
End If
Next jcell
End If
Next ws
End Sub
Option Explicit is really a great helper - write it on the top of every module / class / worksheet. It would tell immediately, if there is some variable, which is not declared.
In your case, ws should be declared as a worksheet, as far as you are using the for-each loop to go through the Worksheets collection:
Option Explicit
Sub CopyData()
Dim ws As Worksheet
For Each ws In ThisWorkbook.Worksheets
Debug.Print ws.Name
Next ws
End Sub
Option Explicit MSDN
Concerning this part - If icell.Value Like ("X") Or ("x") Then, consider rewriting it like this:
If UCase(icell) = "X" Then. It would be more understandable and Like is not needed when the comparison is without some additional signs ?*.
Excel VBA like operator
updated codebase:
Sub CopyData()
Application.ScreenUpdating = False
Dim pasteSheet As Worksheet
Set pasteSheet = Worksheets("Remediation Summary")
For Each ws In ThisWorkbook.Worksheets
If ws.Index < (ThisWorkbook.Worksheets.Count - 1) Then
'check column i for x
For Each icell In ws.Range("i0:i200").Cells
If icell.Value Like ("X") Or ("x") Then
Rows(icell.RowIndex).Copy
pasteSheet.Cells(Rows.Count, 1).End(xlUp).Offset(1, 0).PasteSpecial xlPasteValues
Application.CutCopyMode = False
Application.ScreenUpdating = True
End If
Next icell
'check column j for x
For Each jcell In ws.Range("j0:j200").Cells
If jcell.Value Like ("X") Or ("x") Then
Rows(jcell.RowIndex).Copy
pasteSheet.Cells(Rows.Count, 1).End(xlUp).Offset(1, 0).PasteSpecial xlPasteValues
Application.CutCopyMode = False
Application.ScreenUpdating = True
End If
Next jcell
End If
Next ws
End Sub
Based on my test, please try the code below:
Option Explicit
Sub CopyData()
Application.ScreenUpdating = False
Dim pasteSheet As Worksheet
Dim ws As Worksheet
Dim icell As Range
Dim jcell As Range
Set pasteSheet = Worksheets("Remediation Summary")
For Each ws In ThisWorkbook.Worksheets
If ws.Index < (ThisWorkbook.Worksheets.Count - 1) Then
'check column i for x
For Each icell In ws.Range("i1:i200").Cells
If UCase(icell) = "X" Or UCase(icell) = "x" Then
pasteSheet.Cells(Rows.Count, 1).End(xlUp).Offset(1, 0).EntireRow.Value = icell.EntireRow.Value
Application.CutCopyMode = False
Application.ScreenUpdating = True
End If
Next icell
'check column j for x
For Each jcell In ws.Range("j1:j200").Cells
If UCase(jcell) = "X" Or UCase(jcell) = "x" Then
pasteSheet.Cells(Rows.Count, 1).End(xlUp).Offset(1, 0).EntireRow.Value = jcell.EntireRow.Value
Application.CutCopyMode = False
Application.ScreenUpdating = True
End If
Next jcell
End If
Next ws
End Sub
Sub CopyData()
Dim pasteSheet As Worksheet, ws As Worksheet, icell As Range
Set pasteSheet = Worksheets("Remediation Summary") 'ThisWorkbook?
For Each ws In ThisWorkbook.Worksheets
If ws.Index < (ThisWorkbook.Worksheets.Count - 1) Then
'check column i,j for x
For Each icell In ws.Range("i1:i200").Cells
If LCase(icell.Value) = "x" Or LCase(icell.Offset(0, 1).Value) = "x" Then
pasteSheet.Cells(Rows.Count, 1).End(xlUp).Offset(1, 0).EntireRow.Value = _
icell.EntireRow.Value
End If
Next icell
End If
Next ws
End Sub

Pasting between workbooks excel vba

i have 50 workbooks and i made a code to copy from a main one the rows in which are the corespondent names to the other 49 files. the problem is in pasting to the 49 target files - paste method doesn't work. The errors is when the filter doesn't find entries for a name. How can i include a line that if the filter doesn't find a name in the main file, it will paste "no entries this month" in the file with the name that wasn't find? Thank you.
Any help is welcomed.
Sub name1()
Dim ws As Worksheet
Dim rng As Range, rngA As Range, rngB As Range
Dim LRow As Long
Set ws = Sheets("name list")
With ws
LRow = .Range("A" & .Rows.Count).End(xlUp).Row
Set rng = .Range("A1:M" & LRow)
.AutoFilterMode = False
With rng
.AutoFilter Field:=12, Criteria1:="name1"
Set rngA = .Offset(1, 0).SpecialCells(xlCellTypeVisible)
End With
.AutoFilterMode = False
With rng
.AutoFilter Field:=13, Criteria1:="name1"
Set rngB = .Offset(1, 0).SpecialCells(xlCellTypeVisible)
End With
.AutoFilterMode = False
rng.Offset(1, 0).EntireRow.Hidden = True
Union(rngA, rngB).EntireRow.Hidden = False
End With
End Sub
Sub name11()
Dim lst As Long
Dim rng As Range
Dim i As Integer
Set rng = Application.Intersect(ActiveSheet.UsedRange, Range("A:M"))
rng.SpecialCells(xlCellTypeVisible).Select
Selection.Copy
Application.DisplayAlerts = False
Workbooks.Open Filename:= _
"\\HOFS\persons\name1.xlsm" _
, UpdateLinks:=true
With Sheets("tribute").Range("A" & Rows.Count).End(xlUp).Offset(1)
'.PasteSpecial Paste:=xlPasteColumnWidths
.PasteSpecial Paste:=xlPasteValues
End With
ActiveWorkbook.Close SaveChanges:=True
Application.DisplayAlerts = False
Windows("name list.xlsm").Activate
rng.Offset(1, 0).EntireRow.Hidden = False
End Sub
Sub TRANSFER_name1()
Call name1
Call name11
End Sub
Set the last row separately.
' Gives the first empty row in column 1 (A)
lastRow = Worksheets("tribute").Cells(Worksheets("tribute").Rows.Count, 1).End(xlUp).Row + 1
' Pastes values
Worksheets("tribute").Range("A" & lastRow).PasteSpecial Paste:=xlPasteValues
Its probably much better to avoid copy/paste situations. This can get super time consuming over time.
try somethign like this instead:
With Sheets("tribute").Range("A" & Rows.Count).End(xlUp).Offset(1).value = rng.Value
This is a bit crude but I am sure you can significantly simplify your code if you do.
Dim wbk As Workbook
Dim Filename As String
Dim path As String
Dim rCell As Range
Dim rRng As Range
Dim wsO As Worksheet
Dim StartTime As Double
Dim SecondsElapsed As Double
Dim sheet As Worksheet
Application.ScreenUpdating = False
Application.DisplayAlerts = False
Application.Calculation = xlCalculationManual
StartTime = Timer
path = "pathtofolder" & "\"
Filename = Dir(path & "*.xl??")
Set wsO = ThisWorkbook.Sheets("Sheet1")
Do While Len(Filename) > 0
DoEvents
Set wbk = Workbooks.Open(path & Filename, True, True)
Set rRng = sheet.Range("b1:b308")
For Each rCell In rRng.Cells
wsO.Cells(wsO.Rows.count, 1).End(xlUp).Offset(1, 0).Value = rCell
Next rCell
wbk.Close False
Filename = Dir
Loop
Application.ScreenUpdating = True
Application.DisplayAlerts = True
Application.Calculation = xlCalculationAutomatic
SecondsElapsed = Round(Timer - StartTime, 2)
MsgBox "This code ran successfully in " & SecondsElapsed & " seconds", vbInformation

Adding line into Excel with macro

I have this code and I am trying to get it to add a line in when copying the information accross. The issue I have is that it adds a line in between them and scrambles the information. I have a template worksheet with a total on the bottom and basicly want it pushed down as the lines are enetered.
Any help would be great
Sub SummurizeSheets()
Dim ws As Worksheet, wsSummary As Worksheet
Dim c As Range
Range("A4:D31").Select
Selection.ClearContents
Application.ScreenUpdating = False
Set wsSummary = Sheets("Summary")
' Set destination cell
Set c = wsSummary.Range("A4")
For Each ws In Worksheets
If ws.Name <> "Summary" Then
ActiveCell.EntireRow.Insert
ws.Range("D1").Copy
c.PasteSpecial (xlPasteValues)
ws.Range("E4").Copy
c.Offset(0, 1).PasteSpecial (xlPasteValues)
ws.Range("J39").Copy
c.Offset(0, 2).PasteSpecial (xlPasteValues)
' Move destination cell one row down
Set c = c.Offset(1, 0)
End If
Next ws
Application.ScreenUpdating = True
End Sub
Try this then:
Sub SummurizeSheets()
Dim ws As Worksheet, wsSummary As Worksheet
Dim c As Range
Application.ScreenUpdating = False
Set wsSummary = Sheets("Summary")
Set c = wsSummary.Range("$A$4")
For Each ws In Worksheets
If ws.Name <> "Summary" Then
c.EntireRow.Insert xlDown, xlFormatFromLeftOrAbove
Set c = c.Offset(-1, 0)
ws.Range("D1").Copy
c.PasteSpecial xlPasteValues
ws.Range("E4").Copy
c.Offset(0, 1).PasteSpecial xlPasteValues
ws.Range("J39").Copy
c.Offset(0, 2).PasteSpecial xlPasteValues
End If
Next ws
Application.ScreenUpdating = True
End Sub

Excel VBA copying rows using autofilter

Looking to copy rows from all sheets apart from my active sheet that meet a certain criteria in column J using VBA.
Not experienced in writing code in VBA so I have tried to frankenstein together the necessary parts from looking through other questions and answers;
below is the code I have written so far;
Sub CommandButton1_Click()
Dim lngLastRow As Long
Dim ws As Worksheet
Dim r As Long, c As Long
Dim wsRow As Long
Set Controlled = Sheets("Controlled") ' Set This to the Sheet name you want all Ok's going to
Worksheets("Controlled").Activate
r = ActiveSheet.Cells(Rows.Count, 2).End(x1up).Row
c = ActiveSheet.Cells(1, Columns.Count).End(x1ToLeft).Column
Range("J").AutoFilter
For Each ws In Worksheets
If ws.Name <> "Controlled" Then
ws.Activate
wsRow = ActiveSheet.Cells(Rows.Count, 2).End(x1up).Row + 1
Range("A" & r).AutoFilter Field:=10, Criteria1:="Y"
.Copy Controlled.Range("A3" & wsRow)
End If
Next ws
End If
Application.ScreenUpdating = True
Application.EnableEvents = True
Application.Calculation = xlCalculationAutomatic
End Sub
Where Controlled is the sheet I want the data to appear in from the other sheets, and all other sheets are searched to see if their column J meets the criteria="Y"
I won't need to copy over formatting as all Sheets will have the formatting exactly the same and if possible I want the rows that are copied over to start at row 3
Try this:
Option Explicit
Sub ConsolidateY()
Dim ws As Worksheet, wsCtrl As Worksheet
Dim lrow As Long, rng As Range
Set wsCtrl = Thisworkbook.Sheets("Controlled")
With Application
.ScreenUpdating = False
.DisplayAlerts = False
End With
For Each ws In Thisworkbook.Worksheets
If ws.Name = "Controlled" Then GoTo nextsheet
With ws
lrow = .Range("J" & .Rows.Count).End(xlUp).Row
.AutoFilterMode = False
Set rng = .Range("J1:J" & lrow).Find(what:="Y", after:=.Range("J" & lrow))
If rng Is Nothing Then GoTo nextsheet
.Range("J1:J" & lrow).AutoFilter Field:=1, Criteria1:="Y"
.Range("J1:J" & lrow).Offset(1,0).SpecialCells(xlCellTypeVisible).EntireRow.Copy
wsCtrl.Range("A" & wsCtrl.Rows.Count).End(xlUp).Offset(1,0).PasteSpecial xlPasteValues
.AutoFilterMode = False
Application.CutCopyMode = False
End With
nextsheet:
Next
With Application
.ScreenUpdating = True
.DisplayAlerts = True
End With
End Sub
I think this covers everything or most of your requirement.
Not tested though so I leave it to you.
If you come across with problems, let me know.

Delete rows that not contain string in my array

Pls help me modify this code but I would like to keep it 90% the same.
I want to delete the rows that does not contain the array items. So my program deletes rows with a, b in cell. How can I modify the below code so that it erases the other a, b to remain in exec.
myArr = Array("a","b")
For I = LBound(myArr) To UBound(myArr)
'Sheet with the data, you can also use Sheets("MySheet")
With ActiveSheet
'Firstly, remove the AutoFilter
.AutoFilterMode = False
'Apply the filter
.Range("E1:E" & .Rows.Count).AutoFilter Field:=1, Criteria1:=myArr(I)
Set rng = Nothing
With .AutoFilter.Range
On Error Resume Next
Set rng = .Offset(1, 0).Resize(.Rows.Count - 1, 1) _
.SpecialCells(xlCellTypeVisible)
On Error GoTo 0
If Not rng Is Nothing Then rng.EntireRow.Delete
End With
'Remove the AutoFilter
.AutoFilterMode = False
End With
Next I
This works for me... I have commented the code so you should not have a problem understanding it...
Option Explicit
Dim myArr
Sub Sample()
Dim ws As Worksheet
Dim Lrow As Long, i As Long
Dim rRange As Range, delRange As Range
myArr = Array("a", "b", "c")
Set ws = ThisWorkbook.Sheets("MySheet")
With ws
'~~> Get last row of Sheet
Lrow = .Range("A" & .Rows.Count).End(xlUp).Row
For i = 2 To Lrow
If Not DoesExists(.Range("A" & i).Value) Then
If delRange Is Nothing Then
Set delRange = .Range("A" & i)
Else
Set delRange = Union(delRange, .Range("A" & i))
End If
End If
Next i
If Not delRange Is Nothing Then delRange.EntireRow.Delete
End With
End Sub
Function DoesExists(clVal As Variant) As Boolean
Dim j As Long
For j = LBound(myArr) To UBound(myArr)
If clVal = myArr(j) Then
DoesExists = True: Exit For
End If
Next j
End Function