Search code examples
excelvba

Split data into new workbooks based on data in a specific column, but exclude that specific column from the output files


I am very new to VBA and I currently have built a code by putting together and amending different codes I've found online and adding a bit of my own code. I have the existing working code below but need help making further tweaks.

Right now after filtering out any #N'A's in column I and renaming some headers, the code will create a new worksheet for each new item listed in column D. Instead I need it to create a new workbook for every item listed in column I, but the data that is copied over to each new workbook should only be from columns A to H and should still include the same entire heading from rows 1 to 15.

A huge bonus would be if we can also get the new workbooks to then Save AS under the same name as the data in column I.

Attached is an image of what the file would look like, so that you can get a visual idea.

enter image description here

the existing code is below:

Sub SplitData()
Set asheet = ActiveSheet
    asheet.Range("A15:I1048574").AutoFilter Field:=9, Criteria1:="#N/A"
    asheet.Range("A16:I1048574").SpecialCells _
    (xlCellTypeVisible).EntireRow.Delete
    asheet.ShowAllData
  Range("B1").Value = "MAIN HEADER"
    Range("D15").Value = "SUBHEADING1"
    Range("E15").Value = "SUBHEADING2"
    Range("F15").Value = "SUBHEADING3"
    Range("G15").Value = "SUBHEADING4"
    Range("H15").Value = "SUBHEADING5"
    
lastrow = asheet.Range("D" & Rows.Count).End(xlUp).Row
myarray = uniqueValues(asheet.Range("D16:D" & lastrow))
For i = LBound(myarray) To UBound(myarray)
 Sheets.Add.Name = myarray(i)
 asheet.Range("A15:H" & lastrow).AutoFilter Field:=4, Criteria1:=myarray(i)
asheet.Range("A1:H" & lastrow).SpecialCells(xlCellTypeVisible).Copy _
        Sheets(myarray(i)).Range("A1")
 asheet.Range("A15:H" & lastrow).AutoFilter
Next i
End Sub

Private Function uniqueValues(InputRange As Range)
    Dim cell As Range
    Dim tempList As Variant: tempList = ""
    For Each cell In InputRange
        If cell.Value <> "" Then
            If InStr(1, tempList, cell.Value) = 0 Then
                If tempList = "" Then tempList = Trim(CStr(cell.Value)) Else tempList = tempList & "|" & Trim(CStr(cell.Value))
            End If
        End If
    Next cell
    uniqueValues = Split(tempList, "|")
End Function

I tried to change instances in the code where it specified column D to column I but I would get the error code mismatch.

I don't completely understand the Private function part of the code, as that it entirely copied from someone else's example so I am having trouble figuring out how to rewrite it.

Any help is appreciated.


Solution

  • Application oriented learning can definitely be frustrating as you work your way into new realms...been there before, but once you get your solution working, it's extremely rewarding.

    You are definitely moving in the right direction. Going off your example photo, when you switched from column D to column I, the different forms of "#N/A" most likely threw off the uniqueValues function. In column D, the "#N/A" values are text strings (no green error triangle), but in column I, they are errors. VBA will handle them differently because the error value isn't the same as the displayed text in Excel.

    I've taken what you have started and organized it to follow the logical flow that you described. When I started out, this helped me better picture what is going on. Even if it isn't the most efficient process or minimal code, hopefully it helps you understand the process.

    In regards to the uniqueValues function, the function is taking your range that is passed as an input and looking for all the unique values. As it loops through the values, it searches a temp text string for the value. If it finds it, it moves to the next value; if not, it adds the value to the text string in a pattern. At the end, it splits the string on that pattern and returns an array of values. In the code below, this is handled by the collection object. The benefit here is that the object, by nature, can only hold one record for each unique key so there is no need to compare. All you have to do is add the value and handle the errors, which also handles the #N/A items.

    Sub SplitDataToNewWorkbook()
    
    '// Dimension your variables
      Dim wb, newWB As Workbook
      Dim ws, newWS As Worksheet
      Dim DataRange, FullRange, HeaderRange As Range
      Dim wbArr As New Collection '// collections store items with unique keys
    
    '// Declare your starting values
      Set wb = ThisWorkbook
      Set ws = wb.Worksheets("Sheet1")
    
      '// Get your last row
      LastRow = ws.Range("I" & Rows.Count).End(xlUp).Row
    
      '// Defines the ranges we want to copy and work with
      Set HeaderRange = ws.Range("1:14") '// above data
      Set DataRange = ws.Range("A15:H" & LastRow) '// data table to copy
      Set FullRange = ws.Range("A15:I" & LastRow) '// data table with all data
    
    '// Get the Unique Values in vLookup Column
    '// Tries to add the name to the collection if it is new else moves on...skips #N/A
      On Error Resume Next
      For Each wbName In ws.Range("I16:I" & LastRow)
        wbArr.Add wbName.Value2, wbName.Value2
      Next
      On Error GoTo 0
    
    '// Enables AutoFilter to the full data range, resumes if already on
      On Error Resume Next
      FullRange.AutoFilter
    
    '// Loop through each wb Name to create new workbooks
      For Each wbName In wbArr
        
        '// Sets new workbook and worksheet
        Set newWB = Workbooks.Add
        Set newWS = newWB.Worksheets("Sheet1")
        
        '// Applies the filter for the wbName on column I
        FullRange.AutoFilter Field:=9, Criteria1:=wbName
        
        '// Copies and applies the header section to the new file
        HeaderRange.Copy
        newWS.Range("1:14").PasteSpecial
        Application.CutCopyMode = False '// Clears clipboard
        
        '// Copies and applies visible data to the new file
        DataRange.SpecialCells(xlCellTypeVisible).Copy
        newWS.Range("A15").PasteSpecial
        Application.CutCopyMode = False '// Clear clipboard
        
        '// Updates data headers and other cells as needed
        newWS.Range("D15").Value2 = "Subheading 1"
        '// Repeat to update other headers
        
        '// Saves the workbook to the same folder as the current workbook with value from column I
        '// Replace "wb.Path" as needed to save the file to your desired location
        newWB.SaveAs wb.Path & "\" & wbName & ".xlsx"
        
        '// Closes the file (remove if you want it to stay open)
        newWB.Close
        
        '// Resets the newWB variables and shows all source data
        Set newWB = Nothing
        On Error Resume Next
        ws.ShowAllData
        
      Next
    
    End Sub