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

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.

Related

Add Now() -1 in Column A When Adjacent Columns Have Data Added to It

I'm updating a report everyday and adding data from yesterdays runs. I would like to insert a code in the macro to add the date into column A next to the newly added data without changing the previous dates already in column A.
Sub datedd()
Dim lastRow As Long
lastRow = Range("B" & Rows.Count).End(xlUp).Row
With Range("A2:A" & lastRow)
.Value = Now -1
.NumberFormat = "mm/dd/yy"
End With
End Sub
But this changes all the dates in column A
not sure why pasting in the code breaks apart like this, sorry im new here!
Sub datedd()
Dim lastRow As Long
Dim firstRow As Long
lastRow = Range("B" & Rows.Count).End(xlUp).Row
firstRow = Range("A" & Rows.Count).End(xlUp).Row + 1
With Range("A" & CStr(firstRow) & ":A" & CStr(lastRow))
.Value = Now -1
.NumberFormat = "mm/dd/yy"
End With
End Sub

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

Copy specific columns in all rows from sheet 1 to sheet 2 based on condition

I tried using the code below but it display the entire row in the new sheet. Is there a way that i can move only specific columns to the new sheet by modifying the vba macro code below?
Thanks in advance!
Sub CopyExpired()
Dim bottomB As Integer
bottomB = Sheets("sheet1").Range("B" & Rows.Count).End(xlUp).Row
Dim c As Range
For Each c In Sheets("sheet1").Range("B1:B" & bottomB)
If c.Value = "expired" Then
c.EntireRow.Copy Worksheets("sheet2").Range("A" & Rows.Count).End(xlUp).Offset(1)
End If
Next c
End Sub
Try to use an .AutoFilter.
Sub CopyExpired()
With Worksheets("sheet1")
If .AutoFilterMode Then .AutoFilterMode = False
With .Cells(1, "A").CurrentRegion
.AutoFilter field:=2, Criteria1:="expired"
With .Resize(.Rows.Count - 1, 1).Offset(1, 0)
If CBool(Application.Subtotal(103, .Cells)) Then
.SpecialCells(xlCellTypeVisible).Copy _
Destination:=Worksheets("sheet2").Range("A" & Rows.Count).End(xlUp).Offset(1)
End If
End With
End With
If .AutoFilterMode Then .AutoFilterMode = False
End With
End Sub
Replace
c.EntireRow.Copy
with
Range("C" & c.Row & ",E" & c.Row & ",H" & c.Row).Copy
and you can select what columns to use
if I didnt do the syntax 100% right then sorry. Dont have excel near me.
1st: please dont use A1 codes in your macros. Rather use the R1C1 method as follows:
dim sh as worksheet
set sh =activeworkbook.activesheet
sh.cells(1,2) = "Test worked!!"
'this will put the text into row 1 column 2 of your sheet.
2nd: you could copy each cell via a subroutine.
for example:
dim rw1 as integer, lastRw as integer, cellsToRight as integer
lastRw = sh.UsedRange.SpecialCells(xlCellTypeLastCell).Row
for rw1 = 1 to lrw
for col=1 to 10
sh.cells(rw1,col + cellstoright)=sh.cells(rw1,col)
next col
next rw1
or you could just do the colums yourself manualls, eg... array of integer with the values 1,3,4,6,7,8 ... and then loop over the array with your integers as columns, same thing as the above loops.

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

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.

Count if formula with VBA till last row

My below code only gives output for 2 rows, rest it is not getting applied don't know why? formula needs to applied from K20 till last row of adjacent column(J) . Can someone help me in correcting it. Thanks!
Sub SortS()
Range("K19").Select
ActiveCell.FormulaR1C1 = "Sort"
With Sheets("Sheet1")
rowlast = .Range("K" & .Rows.Count).End(xlUp).Row
With .Range("K20:K" & rowlast)
.Formula = "=IF(COUNTIF(RC[-6]:RC[-2],""S"")>0,1,0)"
.Value = .Value
End With
End With
End Sub
You're not looking at column J for the last row - try changing this
rowlast = .Range("K" & .Rows.Count).End(xlUp).Row
to this
rowlast = .Range("J" & .Rows.Count).End(xlUp).Row