Does a Do-While loop break a For Each-Next Loop? - vba

I'm trying to make sure that a range has visible rows before I act on those visible rows (delete them) because if I try to act on a filtered range when there aren't any visible rows there, I'll get an error.
Dim lastrow As Integer
Dim ws as Worksheet
For Each ws In ActiveWorkbook.Worksheets
ws.Activate
ws.Range("$A$8").Select
Selection.End(xlDown).Select
lastrow = ActiveCell.Row
'DELETE PART CLASSES ROWS
ActiveSheet.Range("$O$7:$O$" & lastrow & "").AutoFilter Field:=1, Criteria1:= _
Array("CONS", "MISC", "PFG", "PRT", "TOTE", "="), _
Operator:=xlFilterValues
Range("$A$8").Select
Do
If ActiveCell.SpecialCells(xlCellTypeVisible) Then
ActiveSheet.Range("$O$8:$O$" & lastrow).SpecialCells(xlCellTypeVisible).EntireRow.DELETE
Exit Do
End If
While ActiveCell.Row < lastrow
Range("$O$8").AutoFilter Field:=1
Next
The code block was working fine until I added the Do-If-While nested loops. I now get the Compile error: Next without For
What am I doing wrong?
Thank you.

It appears your do/while loop should be written like this:
Do
If ActiveCell.SpecialCells(xlCellTypeVisible) Then
ActiveSheet.Range("$O$8:$O$" & lastrow).SpecialCells(xlCellTypeVisible).EntireRow.DELETE
Exit Do
End If
Loop While ActiveCell.Row < lastrow
If you wish to have a plain while loop that always evaluates the condition, rather than not evaluating it the first time, you need to write it like this:
Do While ActiveCell.Row < lastrow
If ActiveCell.SpecialCells(xlCellTypeVisible) Then
ActiveSheet.Range("$O$8:$O$" & lastrow).SpecialCells(xlCellTypeVisible).EntireRow.DELETE
Exit Do
End If
Loop

Instead of deleting row by row, delete them all. You're going about it in a really complicated way. All you have to do is search for the first unfiltered row. I do this by searching for the first empty cell below the header in the very last column (which works unless you use every single column available, which is really, really unlikely.)
Sub deleteUnfiltered()
'
Dim ws As Worksheet
headerRow = 1
For Each ws In ActiveWorkbook.Worksheets
ws.Activate
lastCol = Columns.Count
firstRow = Columns(lastCol).Find(What:="", After:=Cells(headerRow, lastCol), LookIn:=xlFormulas, LookAt _
:=xlWhole, SearchOrder:=xlByRows, SearchDirection:=xlNext).Row
lastRow = Cells(Rows.Count, headerRow).End(xlUp).Row
If lastRow <= headerRow Then lastRow = firstRow
Rows(firstRow & ":" & lastRow).Delete Shift:=xlUp
Next
End Sub
I'd think that looping row by row would be much slower than doing them all in one shot.

Related

How can I copy data from multiple tabs to one tab?

I am trying to copy data from multiple tabs to one single tab. The data need to be filtered first then copied from different tabs to a new tab. Data from different tabs (has random number of lines)should be continuous within the new tab. Due to the size of the data, it is divided into multiple tabs. So merging tabs into one tab first is not an option.
I have below difficulties that need help:
From second tab, I don’t need to copy the header of data. Any command can be added to the code?
Current codes not copying all four tabs, I am not too sure what is the issue
Can my active sheet be a general command instead of specific like ActiveSheet.Range("$A$1:$U$493692")?
See below code
Sub Filter_FSI()
'
' Filter_FSI Macro
'
'
Dim lastRow As String
lastRow = Sheets("Sheet1").Cells(Rows.Count, "A").End(xlUp).Row + 1
Sheets("Train 3-8").Select
ActiveSheet.Range("$A$1:$U$493692").AutoFilter Field:=4, Criteria1:="FSI"
ActiveSheet.AutoFilter.Range.Copy
Sheets("Sheet1").Select
Range("A1").Select
Sheets("Sheet1").Paste
Sheets("Train 9-14").Select
ActiveSheet.Range("$A$1:$U$539243").AutoFilter Field:=4, Criteria1:="FSI"
ActiveSheet.AutoFilter.Range.Copy
Sheets("Sheet1").Select
Range("A" & lastRow).Select
ActiveSheet.Paste
Sheets("Train 15-25").Select
ActiveSheet.Range("$A$1:$U$528028").AutoFilter Field:=4, Criteria1:="FSI"
ActiveSheet.AutoFilter.Range.Copy
Sheets("Sheet1").Select
Range("A" & lastRow).Select
ActiveSheet.Paste
Sheets("Train 27-41").Select
ActiveSheet.Range("$A$1:$U$298055").AutoFilter Field:=4, Criteria1:="FSI"
ActiveSheet.AutoFilter.Range.Copy
Sheets("Sheet1").Select
Range("A" & lastRow).Select
ActiveSheet.Paste
Sheets("Sheet1").Copy
Windows("Train Data JULY_Sam Edit.xlsb").Activate
End Sub
So a couple things I noticed with your code - you're declaring lastrow as a string, but that should really be a long since it's representing a number.
Personally, I'm not a fan of autofiltering - and like Peh said above, you want to avoid using Select and Copy/Paste when you can. Try this solution below - it's my personal preference of doing things. We loop through all your worksheets, then loop through every cell in Column D - if it is equal to "FSI", we bring it to Sheet1:
Option Explicit
Sub Filter_FSI()
Dim sht As Worksheet, sht2 As Worksheet
Dim lastrow As Long, i As Long, j As Long, k As Long
Dim myworksheets As Variant
Set sht = ThisWorkbook.Worksheets("Sheet1")
myworksheets = Array("Train 3-8", "Train 9-14", "Train 15-25", "Train 27-41")
'Bring in headers
sht.Range("A1:U1").Value = Worksheets("Train 3-8").Range("A1:U1").Value
k = 2
For i = 0 To UBound(myworksheets)
Set sht2 = Worksheets(myworksheets(i))
lastrow = sht2.Cells(sht2.Rows.Count, 4).End(xlUp).Row
For j = 2 To lastrow
If sht2.Cells(j, 4).Value = "FSI" Then
sht.Range("A" & k & ":U" & k).Value = sht2.Range("A" & j & ":U" & j).Value
k = k + 1
End If
Next j
Next i
End Sub

Autofill won't start at desired cell and won't fill down

I'm trying to autofill this formula from AD2 down to the end of the dataset. But, instead, my macro will use the formula on AD1 (the column title) and not fill down. I've done this several times, but I can't figure out why it's acting up now. The obnoxious formula is reading the from the cell a few columns over (AB) and then declares one of three strings.
Dim lastRow As Long
lastRow = Cells(Rows.Count).End(xlUp).Row
Range("AD2").Select
Selection.FormulaR1C1 = _
"=IF(NOT(ISERROR(FIND(""iMac"",RC[-2]))),""iMac"",IF(NOT(ISERROR(FIND(""MacBook"",R[-21]C[-2]))),""MacBook"",""N/A""))"
Range("AD2").Select
Selection.AutoFill Destination:=Range("AD2:AD" & lastRow)
try to modify your var lastRow with ActiveSheet.Cells(ActiveSheet.Rows.Count, "AB").End(xlUp).Row
Sub test()
Dim lastRow As Long
lastRow = ActiveSheet.Cells(ActiveSheet.Rows.Count, "AB").End(xlUp).Row
Range("AD2").Select
Selection.FormulaR1C1 = _
"=IF(NOT(ISERROR(FIND(""iMac"",RC[-2]))),""iMac"",IF(NOT(ISERROR(FIND(""MacBook"",R[-21]C[-2]))),""MacBook"",""N/A""))"
Range("AD2").Select
Selection.AutoFill Destination:=Range("AD2:AD" & lastRow)
End Sub
Try this. You are missing a column in your Cells (I have used column A so change to suit) and you don't need to select anything. In fact you probably don't need Autofill at all, just apply to the whole range in one go.
Sub y()
Dim lastRow As Long
lastRow = Cells(Rows.Count, 1).End(xlUp).Row
With Range("AD2")
.FormulaR1C1 = _
"=IF(NOT(ISERROR(FIND(""iMac"",RC[-2]))),""iMac"",IF(NOT(ISERROR(FIND(""MacBook"",R[-21]C[-2]))),""MacBook"",""N/A""))"
.AutoFill Destination:=Range("AD2:AD" & lastRow)
End With
End Sub

VBA: When no value is found, first row is deleted

I have some code that deletes every row that doesn't contain a key string (in this case "2550"). The issue is, if I run the script twice by mistake, it will delete the top row in the worksheet.
See the code below:
Dim ws As Worksheet
Dim rng As Range
Dim lastRow As Long
Set ws = ActiveWorkbook.Sheets(1)
lastRow = ws.Range("L" & ws.Rows.Count).End(xlUp).Row
Set rng = ws.Range("L1:L" & lastRow)
With rng
.AutoFilter Field:=1, Criteria1:="<>*2550*"
.Offset(1, 0).SpecialCells(xlCellTypeVisible).EntireRow.Delete
End With
ws.AutoFilterMode = False
I thought that if there was no row with that key, the AutoFilter would show nothing and therefore nothing should be deleted, but it appears that that is not the case. Could anyone explain why that may be?
Another solution is to use the Max function in your last row declaration. Something like:
lastRow = Application.Max(2,ws.Range("L" & ws.Rows.Count).End(xlUp).Row)
Allows you to skip some nesting and IF statements.
Put in a test..
lastRow = ws.Range("L" & ws.Rows.Count).End(xlUp).Row
If lastRow = 1 Then
Set rng = ws.Range("L1:L" & lastRow)
With rng
.AutoFilter Field:=1, Criteria1:="<>*2550*"
.Offset(1, 0).SpecialCells(xlCellTypeVisible).EntireRow.Delete
End With
ws.AutoFilterMode = False
End If
If lastRow returns the top row, it won't do the rest.

Delete rows (dynamic amount) based on cell's content

Sorry if this is simple, this is my first time trying VBA.
So I want this macro to get rid of rows I don't need, and for every entity it has a total field (about every 20 records or so) and I made this script:
Dim i As Integer
Dim LastRow As Integer
LastRow = Range("A65536").End(xlUp).Row
For i = 3 To LastRow
If Range("C" & i) = "Result" Then
Rows(i & ":" & i).Select
Selection.Delete Shift:=x1Up
End If
Next
And that worked perfectly! Then I tried to a similar thing.. I tried to go through each row (record) in a data set and then if a certain field does not contain the string "INVOICE" then I don't need that row and I can delete it. So I just added into my current loop (why loop twice?) So now it looks like this:
Dim i As Integer
Dim LastRow As Integer
LastRow = Range("A65536").End(xlUp).Row
For i = 3 To LastRow
If Range("C" & i) = "Result" Then
Rows(i & ":" & i).Select
Selection.Delete Shift:=x1Up
End If
If Not InStr(1, Range("Q" & i), "INVOICE") Then
Rows(i & ":" & i).Select
Selection.Delete Shift:=x1Up
End If
Next
That second bit as far as I can tell just randomly starts deleting rows with no rhyme or reason. Rows where the Q field doesn't contain invoice sometimes stay sometimes go, and same if it does contain invoice. Any idea what I'm doing wrong?
You should OR your conditions together so that if either reason exists the line is deleted. Otherwise since you're deleting lines within a preset range, you'll end up skipping more lines than you are currently. Currently it looks like you skip a line everytime you delete one, so you're missing any consecutive cases. Tim's advice to work from the last row up is spot on.
For i = LastRow to 3 Step -1
If Range("C" & i) = "Result" OR Not InStr(1, Range("Q" & i), "INVOICE") Then
Rows(i & ":" i).Delete Shift:=x1Up
End If
Next i
There are indeed two approaches: AutoFilter and For Loop. Of the two, AutoFilter is much faster especially with large datasets, but it will often need a very good set-up. The For Loop is easy, but it has marginal returns, especially when your data start hitting 100k rows or more.
Also, Not InStr(1, Range("Q" & i), "INVOICE") might seem like the best way but IMHO it's not. InStr returns a number, so it's better if you either do further comparison like Not InStr(1, Range("Q" & i), "INVOICE") > 0 or just simply InStr(1, Range("Q" & i), "INVOICE") = 0. In any case, I used the former in my second code below.
Following are both approaches. They are tested on simple data. The code might seem a bit bulky, but the logic is sound. Refer to the comments as well for other things.
AutoFilter approach:
Sub RemoveViaFilter()
Dim WS As Worksheet: Set WS = ThisWorkbook.Sheets("ModifyMe")
Dim LastRow As Long
Application.ScreenUpdating = False
With WS
'--For condition "Result"
.AutoFilterMode = False
LastRow = .Cells(Rows.Count, 1).End(xlUp).row '--Compatible if there are more rows.
With Range("A2:Q" & LastRow) '--Assuming your header is in Row 2 and records start at Row 3.
.AutoFilter Field:=3, Criteria1:="Result" '--Field:=3 is Column C if data starts at A
.Cells.Offset(1, 0).SpecialCells(xlCellTypeVisible).EntireRow.Delete '--Delete the visible ones.
End With
'--For condition "<>*INVOICE*"
.AutoFilterMode = False
LastRow = .Cells(Rows.Count, 1).End(xlUp).row
With Range("A2:Q" & LastRow)
.AutoFilter Field:=17, Criteria1:="<>*INVOICE*" '--Field:=17 is Column Q if data starts at A
.Cells.Offset(1, 0).SpecialCells(xlCellTypeVisible).EntireRow.Delete
End With
.AutoFilterMode = False
End With
Application.ScreenUpdating = True
End Sub
For-loop approach:
Sub RemoveViaLoop()
Dim WS As Worksheet: Set WS = ThisWorkbook.Sheets("Sheet6")
Dim LastRow As Long: LastRow = WS.Cells(Rows.Count, 1).End(xlUp).row
Dim Iter As Long
Application.ScreenUpdating = False
With WS
For Iter = LastRow To 3 Step -1 '--Move through the rows from bottom to up by 1 step (row) at a time.
If .Range("C" & Iter) = "Result" Or Not InStr(1, .Range("Q" & Iter).Value, "Invoice") > 0 Then
.Rows(Iter).EntireRow.Delete
End If
Next Iter
End With
Application.ScreenUpdating = True
End Sub
Let us know if this helps.

Application-defined or object-defined error 1004

VBA is throwing the above given error on the line Sheets("Sheet1").Range("A" & i).Copy Destination:=Sheets("Sheet2").Range("A" & i & "A" & LastCol - 1)
What I am trying to do is actually to copy the "A" & i cell (in first iteration it's A2) to a range in the second worksheet named Sheet2.
Sub FindFill()
Dim DatesRange As Range
Dim i As Integer
Dim TransposeThis As Range
Dim LastCol As Integer
If WorksheetFunction.CountA(Cells) > 0 Then
LastColumn = Cells.Find(What:="*", After:=[A1], _
SearchOrder:=xlByColumns, _
SearchDirection:=xlPrevious).Column
End If
With Sheets("Sheet1")
Set DatesRange = Range("B2" & LastCol)
End With
i = 1
Do While i <= ActiveSheet.Rows.Count
Sheets("Sheet1").Range("A" & i + 1).Copy Destination:=Sheets("Sheet2").Range("A" & i & "A" & LastCol - 1)
i = i + 1
Loop
End
End Sub
You are missing a ":" before "A"
Range("A" & i & ":A" & LastCol - 1)
FOLLOWUP
After I went through your comments, I saw lot of errors in your code
1) You have dimmed i as Integer. This can give you an error in Excel 2007 onwards if your last row is beyond 32,767. Change it to Long I would recommend having a look at this link.
Topic: The Integer, Long, and Byte Data Types
Link: http://msdn.microsoft.com/en-us/library/aa164754%28v=office.10%29.aspx
Quote from the above link
Integer variables can hold values between -32,768 and 32,767, while Long variables can range from -2,147,483,648 to 2,147,483,647
2) You are finding the Last Column But in Which Sheet? You have to fully qualify the path Like this.
If WorksheetFunction.CountA(Sheets("Sheet1").Cells) > 0 Then
LastCol = Cells.Find(What:="*", After:=[A1], _
SearchOrder:=xlByColumns, _
SearchDirection:=xlPrevious).Column
End If
Same is the case with
With Sheets("Sheet1")
Set DatesRange = Range("B2" & LastCol)
End With
You are missing a DOT before Range
This is the correct way...
.Range("B2....
Also Range("B2" & LastCol) will not give you the range that you want. See the code below on how to create your range.
3) You are using a variable LastColumn but using LastCol. I would strongly advise using Option Explicit I would also recommend having a look at this link (SEE POINT 2 in the link).
Topic: To ‘Err’ is Human
Link: http://www.siddharthrout.com/2011/08/01/to-err-is-human/
4) What happens if there .CountA(Sheets("Sheet1").Cells) = 0? :) I would suggest you this code instead
If WorksheetFunction.CountA(Sheets("Sheet1").Cells) > 0 Then
LastCol = Cells.Find(What:="*", After:=[A1], _
SearchOrder:=xlByColumns, _
SearchDirection:=xlPrevious).Column
Else
MsgBox "No Data Found"
Exit Sub
End If
5) ActiveSheet.Rows.Count will not give you the last active row. It will give you the total number of rows in that sheet. I would recommend getting the last row of Col A which has data.
You can use this for that
With Sheets("Sheet")
LastRow =.Range("A" & .Rows.Count).End(xlup).row
End With
Now use LastRow instead of ActiveSheet.Rows.Count You also might want to use a For Loop so that you don't have to increment i every time. For example
For i = 1 to LastRow
6) Finally You should never use End. Reason is quite simple. It's like Switching your Computer using the POWER OFF button. The End statement stops code execution abruptly, without invoking the Unload, QueryUnload, or Terminate event, or any other Visual Basic code. Also the Object references held (if any) by other programs are invalidated.
7) Based on your image in Chat, I believe you are trying to do this? This uses a code which doesn't use any loops.
Option Explicit
Sub FindFill()
Dim wsI As Worksheet, wsO As Worksheet
Dim DatesRange As Range
Dim LastCol As Long, LastRow As Long
If Application.WorksheetFunction.CountA(Sheets("Sheet1").Cells) = 0 Then
MsgBox "No Data Found"
Exit Sub
End If
Set wsI = Sheets("Sheet1")
Set wsO = Sheets("Sheet2")
With wsI
LastCol = .Cells(1, .Columns.Count).End(xlToLeft).Column
LastRow = .Range("A" & .Rows.Count).End(xlUp).Row
Set DatesRange = .Range("B1:" & Split(Cells(, LastCol).Address, "$")(1) & 1)
.Columns(1).Copy wsO.Columns(1)
DatesRange.Copy
wsO.Range("B2").PasteSpecial xlPasteValues, _
xlPasteSpecialOperationNone, False, True
.Range("B2:" & Split(Cells(, LastCol).Address, "$")(1) & LastCol).Copy
wsO.Range("C2").PasteSpecial xlPasteValues
End With
End Sub