Deleting rows in a filtered set with For Each

James Snyder

Well-known Member
Joined
Jan 11, 2013
Messages
618
I am working on solving a problem where I get every other row deleted instead of every row. The situation is that I am using Autofilter to find empty cells in a column that shouldn't have empty cells. Before I can delete them, I need to add them to an array for exceptions handling. This means I need to step through the filtered range and record each unique ID for followup, then delete them. If I delete as I go, the "For Each" doesn't work on the original rows, but works on an increment. This means a deleted row causes the range to shift up one, and I end up deleting every other row.

I know I can solve this by deleting separately from recording them, but am searching for a way to work with the "For Each" maybe using a "Step -1" or something similar. Does anyone have any ideas?
 

Excel Facts

What do {} around a formula in the formula bar mean?
{Formula} means the formula was entered using Ctrl+Shift+Enter signifying an old-style array formula.
Hi James

Try:-
Code:
With Range("A37:A" & LR).SpecialCells(xlCellTypeVisible)
    .EntireRow.Delete
End With

adjust your range accordingly.

hth
 
Upvote 0
Thanks for the effort, but this is a filtered range I am talking about. Just to give a setting for the question, here is code on either side of the issue:
Code:
    If Not wkBook Is Nothing Then
        Set wkSheet = wkBook.Sheets(1)
        If Not wkSheet Is Nothing Then
            rowMax = wkSheet.Range("A65535").End(xlUp).Row  ' Assign last row
            wkSheet.Range("A1:E" & rowMax).Sort _
                key1:=wkSheet.Range("B1"), _
                key2:=wkSheet.Range("A1")
            With wkSheet
                .AutoFilterMode = False
                Set origRange = .Range("A1:" & param1 & rowMax)
                With origRange
    
                    ' Set AutoFilter to screen out non-compared objects
                    .AutoFilter Field:=wkColumn, Criteria1:=Array( _
                        param2, _
                        param3, _
                        param4), _
                        Operator:=xlFilterValues
                        
                    Set filteredRange = .SpecialCells(xlCellTypeVisible)
                    With filteredRange
                        areaCount = .Areas.count   ' 1 for headers, 1 for CG, 1 for Moved/Obstructed
                        If areaCount > 2 Then
                            For i = 2 To areaCount
                                If i = 2 Then
                                    unionStr = ".Areas(i)"
                                Else:
                                    unionStr = unionStr & ", .Areas(i)"
                                End If
                            Next i
                        Else
                            unionStr = filteredRange & ", vbNullString"
                        End If
                        filterMax = CountFilteredRows(filteredRange, areaCount, sendDate)
                        For i = 2 To areaCount  ' Area1 is column headers
                            For Each record In .Areas(i)
                                If Application.WorksheetFunction.CountIf(filteredRange.Range("A1:A" & filterMax), _
                                    record.Value) > 1 Then

                                    If Len(Join(exceptArray)) <= 0 Then
                                        ReDim Preserve exceptArray(0)    ' Adjust array size up by one row
                                        exceptArray(0) = record.Value   ' Record in array
                                    Else
                                        ReDim Preserve exceptArray(UBound(exceptArray) + 1)   ' Adjust array size up by one row
                                        exceptArray(UBound(exceptArray)) = record.Value  ' Record in array
                                    End If

                                ' Once entered into array, write to Exceptions and delete
'                                errString = exceptArray(dupeRow - 1) & "              Duplicate PSID record"
'                                failReturn = ProblemReport(errString, sendDate)
'                                filteredRange.Range("A" & i).EntireRow.Delete ' Delete the dupeValue with the count of 2
                                End If
                            Next record
                        Next i
                    End With    ' filteredRange
                End With    ' origRange
                .ShowAllData
            End With    ' wkSheet
            Application.DisplayAlerts = False       ' Suppress "SaveAs" dialog box
            Application.EnableEvents = False        ' Suppress BeforeSave event
            wkBook.Save
            Application.EnableEvents = True
            Application.DisplayAlerts = True
            PSIDDupeCheck = "Success"
        Else
            errString = errorSrc & "Failed to open worksheet get dupes"
            failReturn = ProblemReport(errString, sendDate)
            Err.Clear
            PSIDDupeCheck = errString
        End If
    Else
        errString = errorSrc & "Failed to open workbook get dupes"
        failReturn = ProblemReport(errString, sendDate)
        Err.Clear
        PSIDDupeCheck = errString
    End If
    wkSheet.AutoFilterMode = False
End Function
 
Last edited:
Upvote 0
Thanks for the effort, but this is a filtered range I am talking about.

James

I was talking about a filtered range otherwise why would I use "SpecialCells(xlCellTypeVisible)"?

However, after a bit of searching and thanks to AlphaFrog for a test for Hidden rows, something like this :-
Code:
For I = LR To 37 Step -1  ' LR = Last Row
      If Rows(I).Hidden = False Then
           J = J + 1
'    save the code 
           ArrayA(J) = Range("B" & I).Value
'     delete the row
           Rows(I).EntireRow.Delete
       End If
Next I

could be the answer to your problem.

hth
 
Upvote 0
Thanks, Mike, but you cannot address a filtered range using ("A" & I) addressing. That gets you the unfiltered row at that address. The filtered rows still retain their original address making that useless. That is why the dilemma: you cannot address the rows except using a "For Each", but the "For Each" doesn't allow a "Step" parameter. If you loop forward, the rows get shifted upward and you delete every other row.

I think the only solution is to make it a contiguous range by copying to a new sheet, thereby giving them actual references again and using any loop with the "Step -1" parameter. I cannot for the life of me figure out how to do it in place. For any who run into the same dilemma in the future, here is the (incomplete) code I ended up with:

Declarations:
Code:
    Dim wkOrdInWkBk As Workbook         ' Input XLSX file for WorkOrders
    Dim wkOrdInWkSht As Worksheet      ' Input XLSX file for WorkOrders
    Dim endColumn As String                 ' Input end of range column
    Dim filterColumn As Long                 ' Input column for filtering range
    Dim InspString As String                ' Input parameter for Filter value
    Dim cGasString As String               ' Input parameter for Filter value
    Dim obstString As String               ' Input parameter for Filter value
    Dim srcString As String                ' Input type of range being filtered (for error report)

Call:
Code:
    ' TEST ONLY
    filterColumn = 2
    endColumn = "E"
    srcString = Space(18 - Len("WorkOrders"))
    srcString = "WorkOrders" & srcString
    cGasString = "Columbia Gas Completed"
    movedString = "Meter Moved Outside"
    obstString = "Obstructed Meter"
    
    funcReturn = DupeTest(wkOrdInWkBk, wkOrdInWkSht, filterColumn, exceptArray, sendDate, _
        srcString, endColumn, cGasString, movedString, obstString)
    If funcReturn <> "Success" Then
        errString = "DupeTest fail:    Error while testing for CG/Moved/Obstructed duplicates"
        failReturn = ProblemReport(errString, sendDate)
        Err.Clear
        GoTo ExitPoint
    End If
    Stop ' Debug stop to prevent continuing to code relying on this working

Function:
Code:
Private Function DupeTest(ByRef wkBook As Workbook, _
    ByRef wkSheet As Worksheet, _
    ByVal wkColumn As Long, _
    ByRef exceptArray() As String, _
    ByRef sendDate As String, _
    ByVal errorSrc As String, _
    Optional ByVal param1 As String, _
    Optional ByVal param2 As String, _
    Optional ByVal param3 As String, _
    Optional ByVal param4 As String) As String
    
    Dim origRange As Range
    Dim rowMax As Long
    Dim filteredRange As Range
    Dim filtSheet As Worksheet
    Dim filterMax As Long
    Dim i As Long
    Dim j As Long
    Dim k As Long
    Dim fltShtName As String
    Dim failReturn As String
    Dim errString As String
    Dim enteredVal As Boolean
    
    fltShtName = "filtered"
    enteredVal = False
    wkBook.Activate ' Current active workbook is the last one prepared
    
    If Not wkBook Is Nothing Then
        Set wkSheet = wkBook.Sheets(1)
        If Not wkSheet Is Nothing Then
            rowMax = wkSheet.Range("A65535").End(xlUp).Row  ' Assign last row
            wkSheet.Range("A1:E" & rowMax).Sort _
                key1:=wkSheet.Range("B1"), _
                key2:=wkSheet.Range("A1")
            With wkSheet
                .AutoFilterMode = False
                Set origRange = .Range("A1:" & param1 & rowMax)
                With origRange
    
                    ' Set AutoFilter to screen out non-compared objects
                    .AutoFilter Field:=wkColumn, Criteria1:=Array( _
                        param2, _
                        param3, _
                        param4), _
                        Operator:=xlFilterValues
                        
                    .SpecialCells(xlCellTypeVisible).Copy
                    Set filtSheet = Sheets.Add(After:=Sheets(1))
                    filtSheet.Name = fltShtName
                    filtSheet.Paste
                      
                    With filtSheet
                        Set filteredRange = filtSheet.UsedRange.SpecialCells(xlCellTypeVisible)
                        filterMax = filteredRange.Rows.count
                        .Range("A1:A" & filterMax).NumberFormat = "0"
                        
                        With filteredRange
                            For i = 2 To filterMax
                                If Application.WorksheetFunction.CountIf(.Cells.SpecialCells(xlCellTypeVisible), _
                                    .Range("A" & i).Value) > 1 Then
                                    If Len(Join(exceptArray)) <= 0 Then
                                        ReDim Preserve exceptArray(0)    ' Adjust array size up by one row
                                        exceptArray(0) = .Range("A" & i).Value   ' Record in array
                                    Else
                                        ' Check for existence in array before adding again
                                        For k = 1 To UBound(exceptArray)
                                            If .Range("A" & i).Value = exceptArray(k) Then
                                                enteredVal = True
                                            End If
                                        Next i
                                        If Not enteredVal Then
                                            ReDim Preserve exceptArray(UBound(exceptArray) + 1)   ' Adjust array size up by one row
                                            exceptArray(UBound(exceptArray)) = .Range("A" & i).Value  ' Record in array
                                        End If
                                    End If
                                    errString = exceptArray(UBound(exceptArray)) & "              Duplicate PSID record"
                                    failReturn = ProblemReport(errString, sendDate)
                                End If
                                enteredVal = False
                            Next i
                        End With    ' filteredRange
                    End With    ' filtSheet
                    Application.DisplayAlerts = False       ' Suppress "Delete" dialog box
                    Application.EnableEvents = False        ' Suppress BeforeDelete event
                    filtSheet.Delete
                    Application.EnableEvents = True
                    Application.DisplayAlerts = True
                    wkSheet.ShowAllData
                    
                    ' Clear duplicate rows from original range
                    For j = UBound(exceptArray) To LBound(exceptArray) Step -1
                        .AutoFilter
                        .AutoFilter Field:=1, Criteria1:=exceptArray(j) & ".00", Operator:=xlFilterValues
                        .SpecialCells(xlCellTypeVisible).EntireRow.Delete
                    Next j
                    wkSheet.ShowAllData
                End With    ' origRange
            End With    ' wkSheet
            Application.DisplayAlerts = False       ' Suppress "SaveAs" dialog box
            Application.EnableEvents = False        ' Suppress BeforeSave event
            wkBook.Save
            Application.EnableEvents = True
            Application.DisplayAlerts = True
            DupeTest = "Success"
        Else
            errString = errorSrc & "Failed to open worksheet get dupes"
            failReturn = ProblemReport(errString, sendDate)
            Err.Clear
            DupeTest = errString
        End If
    Else
        errString = errorSrc & "Failed to open workbook get dupes"
        failReturn = ProblemReport(errString, sendDate)
        Err.Clear
        DupeTest = errString
    End If
    wkSheet.AutoFilterMode = False
End Function

I am still debugging, but the dupe removal works. Problems I have finished working on include an extraneous row at the beginning of the range, code clean up, documentation, and making sure both error checking and state of the filters is correct.
 
Last edited:
Upvote 0
Did you actually test Mike's original code? It works perfectly to delete the visible rows only.
 
Upvote 0
Thanks, Rory! I did not and owe him an apology. What I missed:
Code:
Rows(I).Hidden = False

EDIT: I have other duplicate checking yet to write and will definitely use Mike's research.
 
Last edited:
Upvote 0
Mike's original code was:
Rich (BB code):
With Range("A37:A" & LR).SpecialCells(xlCellTypeVisible)
    .EntireRow.Delete
End With

No need to loop. :)
 
Upvote 0
Mike's original code was:
Rich (BB code):
With Range("A37:A" & LR).SpecialCells(xlCellTypeVisible)
    .EntireRow.Delete
End With

No need to loop. :)

RoryA

Yes, my original above does work but the OP has the requirement to capture information from the deleted rows which negates this method.

However, as James continues to have concerns about the information he wants to capture before deletion the following code :-
Rich (BB code):
Dim rngFilt As Range
 
'assume Activesheet is already filtered and you want to extract all filtered rows from column B
 
With ActiveSheet
  Set rngFilt = Application.Intersect(.AutoFilter.Range.Offset(1).SpecialCells(xlCellTypeVisible), .Range("B:B"))
End With
 
rngFilt.Copy
 
Range("AA2").PasteSpecial xlPasteValues

will copy the cells in column B and paste into Column AA of the same sheet as a contiguous range. (Adjust columns accordingly)
Then, James would be able to use the code in my first post.

Acknowledgements and many thanks to Richard Scholar for the above snippet of code.

hth
 
Upvote 0

Forum statistics

Threads
1,215,098
Messages
6,123,082
Members
449,094
Latest member
mystic19

We've detected that you are using an adblocker.

We have a great community of people providing Excel help here, but the hosting costs are enormous. You can help keep this site running by allowing ads on MrExcel.com.
Allow Ads at MrExcel

Which adblocker are you using?

Disable AdBlock

Follow these easy steps to disable AdBlock

1)Click on the icon in the browser’s toolbar.
2)Click on the icon in the browser’s toolbar.
2)Click on the "Pause on this site" option.
Go back

Disable AdBlock Plus

Follow these easy steps to disable AdBlock Plus

1)Click on the icon in the browser’s toolbar.
2)Click on the toggle to disable it for "mrexcel.com".
Go back

Disable uBlock Origin

Follow these easy steps to disable uBlock Origin

1)Click on the icon in the browser’s toolbar.
2)Click on the "Power" button.
3)Click on the "Refresh" button.
Go back

Disable uBlock

Follow these easy steps to disable uBlock

1)Click on the icon in the browser’s toolbar.
2)Click on the "Power" button.
3)Click on the "Refresh" button.
Go back
Back
Top