10

I have a subroutine that deletes rows in a range containing around 1000 rows. Rows are deleted on a critera. The code below works.

However, when I run the macro I usually have to run it 4 times before all rows containing the removal criteria are removed.

I guess this is because the for loop misses its index when a row suddenly dissapears when deleting a row.

My first code looks like this.

Set StatusRange = Range("B2", Range("B2").End(xlDown))

For Each StatusCell In StatusRange
    If StatusCell = "FG" Then
        StatusCell.EntireRow.Delete
    ElseIf StatusCell = "QC" Then
        StatusCell.EntireRow.Delete
    ElseIf StatusCell = "CS" Then
        StatusCell.EntireRow.Delete
    Else
    End If
Next StatusCell

When I try to update the range each loop, it still doesn't work.

Set StatusRange = Range("B2", Range("B2").End(xlDown))

For Each StatusCell In StatusRange
    If StatusCell = "FG" Then
        StatusCell.EntireRow.Delete
    ElseIf StatusCell = "QC" Then
        StatusCell.EntireRow.Delete
    ElseIf StatusCell = "CS" Then
        StatusCell.EntireRow.Delete
    Else
    End If
                
    Set StatusRange = Range("B2", Range("B2").End(xlDown))
Next StatusCell
        

Is there anyone who know a sloution to this?

Pᴇʜ
  • 56,719
  • 10
  • 49
  • 73
Energizer1
  • 285
  • 1
  • 5
  • 15
  • 3
    Work from teh bottom up. If you delete a row, everything moves up and yuou skip that row on teh next iteration. –  Apr 17 '17 at 15:11
  • 1
    This is almost certainly a duplicate question and I'll mark it as such when I find the suitable duplicate. Whenever you delete items from a collection, you *must* delete from `Rows.Count to 1 Step -1` otherwise you will skip rows. – David Zemens Apr 17 '17 at 15:12
  • Go ahead and dupe this @DavidZemens - I've added some value by showing the Case statement for multiple comparisons but this is still a dupe. –  Apr 17 '17 at 15:16
  • Here's a good explanation: http://stackoverflow.com/questions/23689196/understanding-vba-code-using-to-and-step/23697899#23697899 – David Zemens Apr 17 '17 at 15:17
  • And another with an accepted answer which actually appears to be VB.NET code but the principle is the same: http://stackoverflow.com/questions/10315120/visual-basic-excel-macro-to-delete-row/10316029#10316029 – David Zemens Apr 17 '17 at 15:17
  • btw, re-evaluating the range to iterate through in a `For ... Next` has no effect. It is considered 'static' upon entering the For ... Next and cannot be changed. While you *can* change the iteration point (in your code as `StatusCell`), it is not recommended. –  Apr 17 '17 at 15:31

4 Answers4

16

Work from the bottom up. If you delete a row, everything moves up and you skip that row on the next iteration.

Here is the 'guts' of the code to work up from the bottom.

With Worksheets("Sheet1")
    For rw = .Cells(.Rows.Count, "B").End(xlUp).Row To 2 Step -1
        Select Case UCase(.Cells(rw, "B").Value2)
            Case "FG", "QC", "CS"
                .Rows(rw).EntireRow.Delete
        End Select
    Next rw
End With
4

Since there's no Reverse loop for For Each you need to use a slightly different approach.

Also, your code with multiple Ifs and OR is "screaming for the use of Select Case.

Dim StatusRange As Range
Dim i As Long

Set StatusRange = Range("B2", Range("B2").End(xlDown))

' loop backward when deleting Ranges, Rows, Cells
For i = StatusRange.Rows.Count To 1 Step -1
    Select Case StatusRange(i, 1).Value
        Case "FG", "QC", "CS"
            StatusRange(i, 1).EntireRow.Delete
        Case Else ' for the future if you need it

    End Select
Next i
Shai Rado
  • 33,032
  • 6
  • 29
  • 51
4

Wait til the For Each loops ends before deleting the rows:

Option Explicit

Sub mySub()

    Dim myRange As Range
    Set myRange = ActiveSheet.UsedRange

    Dim myCell As Range
    Dim myTrash As Range

    For Each myCell In myRange
        Select Case myCell.Value
            Case "my value"
                If myTrash Is Nothing _
                    Then
                        Set myTrash = myCell.EntireRow
                    Else
                        Set myTrash = Union(myTrash, myCell.EntireRow)
                End If
        End Select
    Next

    If Not myTrash Is Nothing Then myTrash.Delete

End Sub
  • For me this was actually the best solution. I just added `If rngToBeDeleted Is Nothing Then Set rngToBeDeleted = xlRow.EntireRow` before the Union() to avoid an error when the range is not set initially. – snibbo Jan 29 '20 at 07:15
-1
start_over: s = 0  
    for each cell  
        if cell = "FG" then   
           s = 1            'Marked to start over if ifstmt was true
           [other code]
        end if  
    next cell  
   If s = 1 Then GoTo start_over  

'your code:

Set StatusRange = Range("B2", Range("B2").End(xlDown))  
    start_over: s = 0  
    For Each StatusCell In StatusRange  
        If StatusCell = "FG" or StatusCell = "QC" or StatusCell = "CS" Then StatusCell.EntireRow.Delete: s=1  
    Next StatusCell  
   If s = 1 Then GoTo start_over
Machiel
  • 1
  • 1