Search code examples
vbaexceltabsadvanced-filter

VBA Advanced AutoFilter + Create new sheets based on range


I need to create new tabs in a workbook based upon a range of cells in a worksheet template. I also want to delete rows of data that do not match the tab name. For example, from the table below I would have a new tab named "2206 - 6" and only data associated with that would remain, keeping in mind that this range of data will change each time the macro is used.

Before:

enter image description here

After:

enter image description here


Interval Number 2206 - 6 6304 - 5 4102 - 20

The table begins in row 11, but I need to retain all of the information above. I have an Advanced Filter Macro that gets close to what I want, but its doing two things I don't want: creating empty tabs and not retaining information above row 11.

Sub parse_data()
    Dim lr As Long
    Dim ws As Worksheet
    Dim vcol, i As Integer
    Dim icol As Long
    Dim myarr As Variant
    Dim title As String
    Dim titlerow As Integer

    vcol = 1
    Set ws = Sheets("Offshore Searches")
    lr = ws.Cells(ws.Rows.Count, vcol).End(xlUp).Row
    title = "A11:G20"
    titlerow = ws.Range(title).Cells(1).Row
    icol = ws.Columns.Count
    ws.Cells(1, icol) = "Unique"

    For i = 2 To lr
        On Error Resume Next
        If ws.Cells(i, vcol) <> "" And _
          Application.WorksheetFunction.Match(ws.Cells(i, vcol), ws.Columns(icol), 0) = 0 Then
            ws.Cells(ws.Rows.Count, icol).End(xlUp).Offset(1) = ws.Cells(i, vcol)
        End If
    Next

    myarr = Application.WorksheetFunction.Transpose(ws.Columns(icol).SpecialCells(xlCellTypeConstants))
    ws.Columns(icol).Clear

    For i = 2 To UBound(myarr)
        ws.Range(title).AutoFilter field:=vcol, Criteria1:=myarr(i) & ""
        If Not Evaluate("=ISREF('" & myarr(i) & "'!A1)") Then
            Sheets.Add(after:=Worksheets(Worksheets.Count)).Name = myarr(i) & ""
        Else
            Sheets(myarr(i) & "").Move after:=Worksheets(Worksheets.Count)
        End If
        ws.Range("A" & titlerow & ":A" & lr).EntireRow.Copy Sheets(myarr(i) & "").Range("A1")
        Sheets(myarr(i) & "").Columns.AutoFit
    Next
    ws.AutoFilterMode = False
    ws.Activate
End Sub

I also have a macro which creates tabs based on a range without the advanced filter, so each tab looks identical (just the tab name changes)

Sub CreateWorkSheetByRange()
    Dim WorkRng As Range
    Dim ws As Worksheet
    Dim arr As Variant

    On Error Resume Next

    Set WorkRng = Application.Selection
    Set WorkRng = Application.InputBox("Range", xTitleId, WorkRng.Address, Type:=8)
    arr = WorkRng.Value
    Sheets("Offshore Searches").Select
        Cells.Select
        Selection.Copy
    Application.ScreenUpdating = False

    For i = 1 To UBound(arr, 1)
        For j = 1 To UBound(arr, 2)
            Set ws = Worksheets.Add(after:=Application.ActiveSheet)
            ws.Name = arr(i, j)
            ActiveSheet.Paste
            Range("A1").Select
        Next
    Next
    Application.ScreenUpdating = True
End Sub

Is there a way to both create tabs based on a range while simultaneously using an advanced filter?


Solution

  • Another option (tested)

    All functions bellow, in a separate module
    It copies the main sheet, deletes the button and uses auto filter to remove unneeded rows


    This uses dictionaries and late binding is slow: CreateObject("Scripting.Dictionary")

    Early binding is fast: VBA Editor -> Tools -> References -> Add Microsoft Scripting Runtime


    Option Explicit
    
    Private Const X As String = vbNullString
    Public Sub CreateTabs()
        Const FIRST_CELL    As String = "Interval Number"
        Const LAST_CELL     As String = "Vesting Doc Number (LC/RS)"
        Dim wb As Workbook, ws As Worksheet, wsNew As Worksheet, d As Dictionary, i As Long
        Dim fr As Long, lr As Long, fc As Long, found As Range, rng As Range, val As String
    
        SetDisplay False
        Set wb = ThisWorkbook
        Set ws = wb.Worksheets("Offshore Searches")
        Set found = FindCell(ws.UsedRange, FIRST_CELL)
        If Not found Is Nothing Then
            fr = found.Row + 1
            fc = found.Column
        End If
        Set found = FindCell(ws.UsedRange, LAST_CELL)
        If Not found Is Nothing Then lr = found.Row - 1
    
        If fr > 0 And fc > 0 And lr >= fr Then
            If Not ws.AutoFilter Is Nothing Then ws.UsedRange.AutoFilter
            Set rng = ws.Range(ws.Cells(fr, fc), ws.Cells(lr, fc))
            Dim arr As Variant, r As Long
            arr = rng
            Set d = New Dictionary
            For r = 1 To UBound(arr)
                val = Trim(CStr(arr(r, 1)))
                val = CleanWsName(val)
                If Not d.Exists(val) Then d.Add r, val
            Next
            For i = 1 To d.Count
              If Not WsExists(d(i)) Then
                ws.Copy After:=wb.Worksheets(wb.Worksheets.Count)
                Set wsNew = wb.Worksheets(wb.Worksheets.Count)
                With wsNew
                 .Name = d(i): If .Shapes.Count = 1 Then wsNew.Shapes.Item(1).Delete
                 Set rng = .Range(.Cells(fr - 1, fc), .Cells(lr, fc))
             rng.AutoFilter Field:=1, Criteria1:="<>" & d(i), Operator:=xlAnd, Criteria2:="<>"
                 Set rng = rng.Offset(1).Resize(rng.Rows.Count - 1)
                 rng.SpecialCells(xlCellTypeVisible).EntireRow.Delete Shift:=xlUp
                 rng.AutoFilter
                End With
              End If
            Next
        End If
        ws.Activate
        SetDisplay True
    End Sub
    

    Public Sub SetDisplay(Optional ByVal status As Boolean = False)
        Application.ScreenUpdating = status
        Application.DisplayAlerts = status
    End Sub
    
    Public Function FindCell(ByRef rng As Range, ByVal celVal As String) As Range
        Dim found As Range
        If Not rng Is Nothing Then
            If Len(celVal) > 0 Then
                Set found = rng.Find(celVal, MatchCase:=True)
                If Not found Is Nothing Then Set FindCell = found
            End If
        End If
    End Function
    
    Public Function CleanWsName(ByVal wsName As String) As String
        Const x = vbNullString
        wsName = Trim$(wsName)    'Trim, then remove [ ] / \ < > : * ? | "
        wsName = Replace(Replace(Replace(wsName, "[", x), "]", x), " ", x)
        wsName = Replace(Replace(Replace(wsName, "/", x), "\", x), ":", x)
        wsName = Replace(Replace(Replace(wsName, "<", x), ">", x), "*", x)
        wsName = Replace(Replace(Replace(wsName, "?", x), "|", x), Chr(34), x)
        If Len(wsName) = 0 Then wsName = "DT " & Format(Now, "yyyy-mm-dd hh.mm.ss")
        CleanWsName = Left$(wsName, 31)         'Resize to max len of 31
    End Function
    
    Public Function WsExists(ByVal wsName As String) As Boolean
        Dim ws As Worksheet
        With ThisWorkbook
            For Each ws In .Worksheets
                If ws.Name = wsName Then
                    WsExists = True
                    Exit Function
                End If
            Next
        End With
    End Function
    

    Assumptions

    • Interval Numbers format is consistent: Unit & " - " & Week (=B12 & " - " & C12)
    • Interval Numbers are not longer than 31 character, and don't contain these special chars: [ ] / \ ? * .
      • If so, the sheet names will be shortened to 31 chars
      • and all special chars mentioned removed (Excel limitation for Sheet names)
    • Working row starts after cell "Interval Number" and stop before "Vesting Doc Number (LC/RS)"
    • There are no spaces before or after "Interval Number" and "Vesting Doc Number (LC/RS)"
    • Main tab name is exactly "Offshore Searches", and it contains only one button ("Create Tabs")