10

I'm creating a fast sub to do a validity check for emails. I want to delete entire rows of contact data that do not contain a '@' in the 'E' Column. I used the below macro, but it operates too slowly because Excel moves all the rows after deleting.

I've tried another technique like this: set rng = union(rng,c.EntireRow), and afterwards deleting the entire range, but I couldn't prevent error messages.

I've also experimented with just adding each row to a selection, and after everything was selected (as in ctrl+select), subsequently deleting it, but I could not find the appropriate syntax for that.

Any ideas?

Sub Deleteit()
    Application.ScreenUpdating = False

    Dim pos As Integer
    Dim c As Range

    For Each c In Range("E:E")

        pos = InStr(c.Value, "@")
        If pos = 0 Then
            c.EntireRow.Delete
        End If
    Next

    Application.ScreenUpdating = True
End Sub
Jon Crowell
  • 21,695
  • 14
  • 89
  • 110
Parseltongue
  • 11,157
  • 30
  • 95
  • 160
  • First, limit the number of cells to traverse. i.e. instead of `range(E:E)`, use a range with data in it – shahkalpesh Jun 03 '13 at 16:35
  • I've always wondered how to do that-- how do I select a range that includes the first cell until the last cell with data in it? – Parseltongue Jun 03 '13 at 16:36
  • 1
    http://www.rondebruin.nl/win/s4/win001.htm - Take a look at this. I am sure, it will answer it for you. Regd your question, say you are on cell A1 containing data, now press ctrl + down arrow. This will select all the cells starting from A1 till the last cell containing data (Note: there shouldn't be blank cells in the middle). Using VBA, you can `lastCell = Range("A1").End(xlDown)` – shahkalpesh Jun 03 '13 at 16:40
  • 2
    Also - don't delete. Clear the row then resort the range at the end – fnostro Jun 03 '13 at 16:52
  • @Fnostro- any references on how to do this? – Parseltongue Jun 03 '13 at 16:53

5 Answers5

24

You don't need a loop to do this. An autofilter is much more efficient. (similar to cursor vs. where clause in SQL)

Autofilter all rows that don't contain "@" and then delete them like this:

Sub KeepOnlyAtSymbolRows()
    Dim ws As Worksheet
    Dim rng As Range
    Dim lastRow As Long

    Set ws = ActiveWorkbook.Sheets("Sheet1")

    lastRow = ws.Range("E" & ws.Rows.Count).End(xlUp).Row

    Set rng = ws.Range("E1:E" & lastRow)

    ' filter and delete all but header row
    With rng
        .AutoFilter Field:=1, Criteria1:="<>*@*"
        .Offset(1, 0).SpecialCells(xlCellTypeVisible).EntireRow.Delete
    End With

    ' turn off the filters
    ws.AutoFilterMode = False
End Sub

NOTES:

  • .Offset(1,0) prevents us from deleting the title row
  • .SpecialCells(xlCellTypeVisible) specifies the rows that remain after the autofilter has been applied
  • .EntireRow.Delete deletes all visible rows except for the title row

Step through the code and you can see what each line does. Use F8 in the VBA Editor.

SarahS
  • 25
  • 4
Jon Crowell
  • 21,695
  • 14
  • 89
  • 110
  • I get a 'subscript out of range' error. Could you explain two things? What does 'Set rng = ws.Range("A1:A" & lastRow)? Why the "A1:A"? and what does ".Offset(1, 0).SpecialCells(xlCellTypeVisible).EntireRow.Delete" do? – Parseltongue Jun 03 '13 at 17:02
  • I just realized the column you're working with is E. The error is because I'm searching the wrong column. Change "A" to "E", and it should work. Setting the range specifies the range we're going to autofilter (A1:A and whatever the last row with a value is). The .Offset(1,0) keeps us from deleting the title row. – Jon Crowell Jun 03 '13 at 17:04
  • I saw another answer that copied the filtered answers to a temp sheet and then deleted and copied back, but using `<>` is MUCH cleaner! – FreeSoftwareServers Nov 16 '19 at 01:48
3

Have you tried a simple auto filter using "@" as the criteria then use

specialcells(xlcelltypevisible).entirerow.delete

note: there are asterisks before and after the @ but I don't know how to stop them being parsed out!

Kazimierz Jawor
  • 18,861
  • 7
  • 35
  • 55
JosieP
  • 3,360
  • 1
  • 13
  • 16
  • Apologies-your answer wasn't there when I posted originally. I did mess up the criterion though! – JosieP Jun 03 '13 at 17:11
2

Using an example provided by user shahkalpesh, I created the following macro successfully. I'm still curious to learn other techniques (like the one referenced by Fnostro in which you clear content, sort, and then delete). I'm new to VBA so any examples would be very helpful.

   Sub Delete_It()
    Dim Firstrow As Long
    Dim Lastrow As Long
    Dim Lrow As Long
    Dim CalcMode As Long
    Dim ViewMode As Long

    With Application
        CalcMode = .Calculation
        .Calculation = xlCalculationManual
        .ScreenUpdating = False
    End With

    With ActiveSheet
        .Select
        ViewMode = ActiveWindow.View
        ActiveWindow.View = xlNormalView
        .DisplayPageBreaks = False

        'Firstrow = .UsedRange.Cells(1).Row
        Firstrow = 2
        Lastrow = .Cells(.Rows.Count, "E").End(xlUp).Row

        For Lrow = Lastrow To Firstrow Step -1
            With .Cells(Lrow, "E")
                If Not IsError(.Value) Then
                    If InStr(.Value, "@") = 0 Then .EntireRow.Delete
                End If
            End With
         Next Lrow
        End With

    ActiveWindow.View = ViewMode
    With Application
        .ScreenUpdating = True
        .Calculation = CalcMode
    End With

End Sub
Parseltongue
  • 11,157
  • 30
  • 95
  • 160
  • Well done for getting the code working but where possible avoid range loops - they can be very slow on larger datasets. Use `AutoFilter`,`SpecialCells` or variant arrays instead where possible. – brettdj Jun 29 '13 at 12:27
2

When you are working with many rows and many conditions, you better off using this method of row deletion

Option Explicit

Sub DeleteEmptyRows()
    Application.ScreenUpdating = False

    Dim ws As Worksheet
    Dim i&, lr&, rowsToDelete$, lookFor$

    '*!!!* set the condition for row deletion
    lookFor = "@"

    Set ws = ThisWorkbook.Sheets("Sheet1")
    lr = ws.Range("E" & Rows.Count).End(xlUp).Row

    ReDim arr(0)

    For i = 1 To lr
     If StrComp(CStr(ws.Range("E" & i).Text), lookFor, vbTextCompare) = 0 then
       ' nothing
     Else
        ReDim Preserve arr(UBound(arr) + 1)
        arr(UBound(arr) - 1) = i
     End If
    Next i

    If UBound(arr) > 0 Then
        ReDim Preserve arr(UBound(arr) - 1)
        For i = LBound(arr) To UBound(arr)
            rowsToDelete = rowsToDelete & arr(i) & ":" & arr(i) & ","
        Next i

        ws.Range(Left(rowsToDelete, Len(rowsToDelete) - 1)).Delete Shift:=xlUp
    Else
        Application.ScreenUpdating = True
        MsgBox "No more rows contain: " & lookFor & "or" & lookFor2 & ", therefore exiting"
        Exit Sub
    End If

    If Not Application.ScreenUpdating Then Application.ScreenUpdating = True
    Set ws = Nothing
End Sub
  • `Select` slows any code and should always be avoided. I doubt that this could approach Filter efficiency. – brettdj Jun 29 '13 at 12:16
0

Instead of looping and referencing each cell 1 by 1, grab everything and put it into a variant array; Then loop the variant array.

Starter:

Sub Sample()
    ' Look in Column D, starting at row 2
    DeleteRowsWithValue "@", 4, 2
End Sub

The Real worker:

Sub DeleteRowsWithValue(Value As String, Column As Long, StartingRow As Long, Optional Sheet)
Dim i As Long, LastRow As Long
Dim vData() As Variant
Dim DeleteAddress As String

    ' Sheet is a Variant, so we test if it was passed or not.
    If IsMissing(Sheet) Then Set Sheet = ActiveSheet
    ' Get the last row
    LastRow = Sheet.Cells(Sheet.Rows.Count, Column).End(xlUp).Row
    ' Make sure that there is work to be done
    If LastRow < StartingRow Then Exit Sub

    ' The Key to speeding up the function is only reading the cells once 
    ' and dumping the values to a variant array, vData
    vData = Sheet.Cells(StartingRow, Column) _
                 .Resize(LastRow - StartingRow + 1, 1).Value
    ' vData will look like vData(1 to nRows, 1 to 1)
    For i = LBound(vData) To UBound(vData)
        ' Find the value inside of the cell
        If InStr(vData(i, 1), Value) > 0 Then
            ' Adding the StartingRow so that everything lines up properly
            DeleteAddress = DeleteAddress & ",A" & (StartingRow + i - 1)
        End If
    Next
    If DeleteAddress <> vbNullString Then
        ' remove the first ","
        DeleteAddress = Mid(DeleteAddress, 2)
        ' Delete all the Rows
        Sheet.Range(DeleteAddress).EntireRow.Delete
    End If
End Sub
Profex
  • 1,370
  • 8
  • 20