0

I have a VBA code that splits a large master file into several smaller sub-files, with each sub-file containing a portion of the original data. After splitting, the code is supposed to delete unwanted rows based on specific criteria.

The code runs correctly and performs the task as expected. However, it takes nearly an hour to complete the process. I'm concerned about the time it takes and would like to know if this is normal, or if there is a more efficient method for deleting rows that could speed up the code execution.

Is there an alternative approach I could use to delete rows faster, and if so, what changes can I make to optimize the performance of my VBA code?

Sub Bud_Split()

' Start timer
startTime = Timer
Application.ScreenUpdating = False
    Dim originalFile As String
    originalFile = Application.GetOpenFilename(Title:="Select the original Excel file")
    If originalFile = "" Then Exit Sub ' User canceled the selection

    Dim directoryPath As String
    directoryPath = Left(originalFile, InStrRev(originalFile, "\"))

    Dim newNames() As Variant
    newNames = Array("Ashwin.xlsx", "Avnish.xlsx", "Bernardo.xlsx", "Candice.xlsx", "Cheryl.xlsx", "Facilities.xlsx", "Gautam.xlsx", "Gerald.xlsx", "Guavus.xlsx", "Keith.xlsx", "Li.xlsx", "Miguel.xlsx", "Mike.xlsx", "Nitesh.xlsx", "Peter.xlsx", "Robert.xlsx", "Raja.xlsx", "Renato.xlsx", "Ripu.xlsx", "Rui.xlsx", "Sheila.xlsx") ' Specify your desired names

    ' Copy the original file the desired number of times
    Dim i As Long
    For i = 0 To UBound(newNames)
        FileCopy originalFile, directoryPath & newNames(i)
    Next i

   Kill originalFile
   
    ' Process each copied file
    Dim file As Object
    For Each file In CreateObject("Scripting.FileSystemObject").GetFolder(directoryPath).Files
        If file.Name Like "*.xlsx" Or file.Name Like "*.xlsb" Then
            ProcessWorkbook file.Path
        End If
    Next file

Application.ScreenUpdating = True

' End timer
endTime = Timer

' Calculate elapsed time in seconds
elapsedTime = Round(endTime - startTime, 0)

' Extract hours, minutes, and seconds
hours = elapsedTime \ 3600
minutes = (elapsedTime Mod 3600) \ 60
seconds = elapsedTime Mod 60

' Format output string in HH:MM:SS format
timeString = Format(hours, "00") & ":" & Format(minutes, "00") & ":" & Format(seconds, "00")

' Display the result in a message box (optional)
MsgBox "Execution time: " & timeString, vbInformation

End Sub



Sub ProcessWorkbook(workbookPath As String)

    Dim wb As Workbook
    Set wb = Workbooks.Open(workbookPath)

    ' Update cell B6 with filename without extension
    Dim targetSheetName As String
    targetSheetName = "Overall"

    Dim targetSheet As Worksheet
    Set targetSheet = wb.Sheets(targetSheetName)

    If targetSheet Is Nothing Then
        MsgBox "Sheet '" & targetSheetName & "' not found in file.", vbExclamation
    Else
        Dim fileBaseName As String
        fileBaseName = Left(wb.Name, InStrRev(wb.Name, ".") - 1)
        targetSheet.Range("B6").Value = fileBaseName
    End If

Dim sheetNames As Variant
    sheetNames = Array("HC by CC", "Budget HC", "HC Investments Summary", "HC Investments", "Non-HC Investments", "Cost - Planful", "Lookup")

    Dim sheetName As Variant
    For Each sheetName In sheetNames
        DeleteRowsByCriteria wb.Sheets(sheetName), 1, "Delete" ' Delete rows in column A
    Next sheetName

    DeleteTableRowsByCriteria wb.Sheets("Lookup"), "Lookup", 3, "Delete" ' Delete table rows in column C

wb.Sheets("Overall").Activate
wb.Sheets("Overall").Range("A1").Activate

    wb.Close SaveChanges:=True

End Sub



Sub DeleteRowsByCriteria(ws As Worksheet, columnIndex As Long, criteria As Variant)

    Application.Calculation = xlCalculationManual

    Dim lastRow As Long
    lastRow = ws.Cells(ws.Rows.Count, columnIndex).End(xlUp).row

    Dim deleteRange As Range
    Dim currentCell As Range

    For Each currentCell In ws.Range("A1:A" & lastRow).Cells
        If currentCell.Value = criteria Then
            If deleteRange Is Nothing Then
                Set deleteRange = currentCell
            Else
                Set deleteRange = Union(deleteRange, currentCell)
            End If
        End If
    Next currentCell

    If Not deleteRange Is Nothing Then
        deleteRange.EntireRow.Delete
    End If

    Application.Calculation = xlCalculationAutomatic
End Sub




Sub DeleteTableRowsByCriteria(ws As Worksheet, tableName As String, columnIndex As Long, criteria As Variant)

    Dim tbl As ListObject
    Set tbl = ws.ListObjects(tableName)

    If Not tbl Is Nothing Then
        Dim i As Long
        For i = tbl.ListRows.Count To 1 Step -1
            If tbl.ListRows(i).Range.Cells(1, columnIndex).Value = criteria Then
                tbl.ListRows(i).Delete
            End If
        Next i
    End If

End Sub

'Sub HideSheets(wb As Workbook, sheetNames As Variant)
'    Dim i As Long
'
'    For i = LBound(sheetNames) To UBound(sheetNames)
'        wb.Sheets(sheetNames(i)).Visible = xlSheetHidden
'    Next i
'End Sub

I know that deleting rows takes time, be it done manually or with the help of VBA. But I've tried searching for an alternate way of handling it and couldn't find any.

7
  • 5
    I had something similar in the past. I achieved great improvement by simply sorting the rows before anything. Commented Sep 17, 2024 at 13:18
  • 1
    How many rows of data (approx) and what % of rows are being deleted? If you comment out the calls to DeleteRowsByCriteria/DeleteTableRowsByCriteria what's the execution time difference? Commented Sep 17, 2024 at 16:54
  • Hi @TimWilliams, in total I have about 5K rows. And each file will have different number of rows to be deleted but it will not be more than 4K rows for any sub-file. I will try both the suggestions and post my feedback here. Commented Sep 17, 2024 at 17:50
  • 2
    Sorting the data to group all rows to be deleted and then deleting as a single block will give better results. Commented Sep 17, 2024 at 17:53
  • Thank you so much. I just ran the code after sorting the data as group, and my code took 12 minutes, which previously took 53 minutes to run. Commented Sep 17, 2024 at 18:13

1 Answer 1

0

Some trips and traps may occur around datetimes and formatting changes, but if your data is alphanumeric, you should really use arrays.

Work out your ranges in your main sub and put ranges as a parameter - will be better for tables to use the DataBodyRange as the range

Also Usedrange will temporarily be larger than it really is - until workbook is saved, but try this for speed.

Sub DeleteRows(r As Range, colIndex As Long, criteria As Variant)
Dim arr1() As Variant, arr2() As Variant
arr1 = r.Value 'assign the range values to an array

Dim rowCount As Long, colCount As Long
rowCount = UBound(arr1) - LBound(arr1) + 1
colCount = UBound(arr1, 2) - LBound(arr1, 2) + 1

'prepare an array of similar size - to hold all non-deleted rows
ReDim arr2(1 To rowCount, 1 To colCount)

Dim i As Long, j As Long, arr2Index As Long
For i = 1 To rowCount
    If arr1(i, colIndex) <> criteria Then
        'since the value is NOT the delete criteria, we need to copy to the holding array
        arr2Index = arr2Index + 1
        For j = 1 To colCount
            arr2(arr2Index, j) = arr1(i, j)
        Next j
    End If
Next i

Dim ws As Worksheet
Set ws = r.Parent
'put the holding array in place or the original range
ws.Cells(r.Row, r.Column).Resize(rowCount, colCount).Value = arr2
'delete empty rows
If rowCount > arr2Index Then ws.Range(arr2Index + r.Row & ":" & r.Row + rowCount - 1).EntireRow.Delete

End Sub
Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.