Advertisement
vhCoder

Excel VBA - separate data on 'Month' worksheet onto new worksheets for each unique name in column H

Jul 9th, 2023 (edited)
1,020
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
VisualBasic 11.66 KB | Source Code | 0 0
  1. ' This solution assumes that:
  2.  
  3.     ' 1. You have Excel 2021/Microsoft 365 so your version of Excel includes the FILTER, SORT and UNIQUE functions.
  4.  
  5.     ' 2. There are no other worksheets in your workbook except the first, named 'Month'.
  6.  
  7.     ' 3. Neither the 'Month' worksheet, nor the structure of the workbook itself, are protected.
  8.  
  9. ' Demo workbook available to download at: https://drive.google.com/file/d/16T16RLWx2vNsyIArDGJ1-ft2iY8VOiE2/view?usp=sharing
  10.  
  11. ' ============
  12. ' Setup steps:
  13. ' ============
  14.  
  15. ' 1. In the Project Explorer in the VB Editor, expand the Microsoft Excel Objects section, select the ThisWorkbook object, then in the Properties Window, change the 'Name' property to 'wbThis'.
  16.  
  17. ' 2. In the Project Explorer in the VB Editor, expand the Microsoft Excel Objects section, select the Sheet1 object, then in the Properties Window, change the 'Name' property to 'wsMonth'.
  18.  
  19. ' 3. Select the cells on row 1 of your 'Month' worksheet which contain your column headers - in the example workbook, that is the range A1:J1.
  20.  
  21. ' 4. Type 'DataHeaders' into the Name box (in the top left of the Excel screen, just under the ribbon menu), and press Enter. This defines a named range for those cells.
  22.  
  23. ' 5. Choose the Formulas section of the ribbon menu, and click Define Name. In the dialog box that is displayed enter:
  24.  
  25.     ' Name:     UniqueNames
  26.     ' Scope:        Workbook
  27.     ' Refers To:    =IF(NOT(ISBLANK(Month!$H$2)),SORT(UNIQUE((Month!$H$2:INDEX(Month!$H$2:$H$1001,COUNTA(Month!$H$2:$H$1001),0)))),"No data")
  28.  
  29.     ' ...and click OK.
  30.  
  31. ' 6. Right-click anywhere in the tree structure under your workbook's VBAProject in the Project Explorer, then choose Insert > Module.
  32.  
  33. ' 7. With the new module selected in the Project Explorer, in the Properties window, change the 'Name' property to 'modDataManagement.
  34.  
  35. ' 8. Add 2 more code modules in this same way, and change their names to modPublicFunctions and modUtilities respectively.
  36.  
  37. ' 9. Paste the code for the three VBA modules listed below into the three code modules you've created in your VBA project.
  38.  
  39. ' 10. In the VB Editor, ensure that under View > Toolbars, the Debug option is ticked; if it isn't, tick it.
  40.  
  41. ' 11. Then under the Debug menu, click Compile VBAProject.
  42.  
  43.     ' If nothing seems to happen, that's good - the VBA project compiled successfully.
  44.     ' If an error is displayed, you've made an error/typo in the code you put into the modules.
  45.  
  46. ' 12. Assuming that you have the Developer menu visible in the ribbon menu, select it, then in the 'Controls' group, click the 'Insert' dropdown, and in the 'Form Controls' section, click the small white rectangle (hovering the mouse over it will pop up 'Button (Form Control)').
  47.  
  48. ' 13. Hold down the Alt key, and click-and-drag the crosshair mouse pointer over some blank cells to the top-right of your source data on the 'Month' worksheet. This will create a clickable button on the worksheet.
  49.  
  50. ' 14. The 'Assign Macro' dialog box will then appear - make sure 'Macros in' is set to 'This Workbook', and select 'SeparateDataToSheets' from the list, then click OK.
  51.  
  52. ' =================
  53. ' modDataManagement
  54. ' =================
  55.  
  56. Option Explicit
  57.  
  58. Public Sub SeparateDataToSheets()
  59.  
  60.     Application.EnableCancelKey = xlDisabled
  61.    
  62.     Dim wsEachDataSheet As Worksheet
  63.     Dim rngUniqueNames As Range, rngColumnHeaders As Range, rngDataFilteredByName As Range
  64.     Dim arrUniqueNames As Variant, arrDataForEachName As Variant
  65.     Dim i As Integer
  66.     Dim strNoDataMsg As String, strNoDataMsg_Title As String, _
  67.         strFilterFormula As String
  68.     Dim lngNoDataMsg_Style As VbMsgBoxStyle
  69.    
  70.     ' Remove all previous data sheets from previous runs
  71.    Call RemoveDataSheets
  72.    
  73.     ' Initialise MsgBox variables
  74.    strNoDataMsg_Title = "No data found"
  75.     strNoDataMsg = "There are no data records to separate onto worksheets by name."
  76.     lngNoDataMsg_Style = vbInformation + vbOKOnly
  77.    
  78.     ' Choose cell in which to calculate dynamic range
  79.    With wsMonth
  80.    
  81.         Set rngUniqueNames = .Range("A" & .Rows.Count).End(xlUp).Offset(RowOffset:=2)
  82.    
  83.     End With
  84.    
  85.     ' Avoid screen flicker
  86.    Application.ScreenUpdating = False
  87.    
  88.     ' Calculate dynamic range
  89.    rngUniqueNames.Formula2 = "=UniqueNames"
  90.    
  91.     ' If no unique names found, notify user
  92.    If rngUniqueNames.Value2 = "No data" Then
  93.    
  94.         MsgBox strNoDataMsg, lngNoDataMsg_Style, strNoDataMsg_Title
  95.    
  96.     Else
  97.        
  98.         ' Check whether evaluation of dynamic range resulted in a spilled range
  99.        If rngUniqueNames.HasSpill Then
  100.        
  101.             ' Populate the array of unique names from the spilled range
  102.            arrUniqueNames = rngUniqueNames.SpillingToRange.Value2
  103.        
  104.         Else
  105.        
  106.             ' Populate the array with the single unique name
  107.            ReDim arrUniqueNames(1 To 1, 1 To 1)
  108.             arrUniqueNames(1, 1) = rngUniqueNames.Value2
  109.        
  110.         End If
  111.        
  112.         ' Convert the 2D array into a 1D array
  113.        arrUniqueNames = GetInnerDimensionOnly(arrUniqueNames)
  114.        
  115.         ' Unique names now captured in array, dynamic array cell no longer required
  116.        rngUniqueNames.ClearContents
  117.        
  118.         ' Iterate over array of unique names
  119.        For i = LBound(arrUniqueNames) To UBound(arrUniqueNames)
  120.        
  121.             Set wsEachDataSheet = Nothing
  122.             With wbThis
  123.            
  124.                 If Not WorksheetExists(arrUniqueNames(i)) Then
  125.                    
  126.                     ' Add a worksheet with the unique name
  127.                    Set wsEachDataSheet = .Worksheets.Add(After:=.Worksheets(.Worksheets.Count))
  128.                    
  129.                     ' Ensure name does not exceed max sheet name length
  130.                    If Len(arrUniqueNames(i)) >= 32 Then
  131.                    
  132.                         wsEachDataSheet.Name = Left$(arrUniqueNames(i), 32)
  133.                    
  134.                     Else
  135.                    
  136.                         wsEachDataSheet.Name = arrUniqueNames(i)
  137.                    
  138.                     End If
  139.                
  140.                 Else
  141.                
  142.                     ' Use existing worksheet with this unique name
  143.                    Set wsEachDataSheet = .Worksheets(arrUniqueNames(i))
  144.                
  145.                 End If
  146.            
  147.             End With
  148.            
  149.             With wbThis.Names("DataHeaders").RefersToRange
  150.            
  151.                 ' Copy column headers to data sheet
  152.                Set rngColumnHeaders = wsEachDataSheet.Range(.Address)
  153.                 rngColumnHeaders.Value2 = .Value2
  154.                 rngColumnHeaders.Font.Bold = True
  155.  
  156.             End With
  157.  
  158.             With wsMonth
  159.            
  160.                 ' Choose cell in which to calculate filtered data
  161.                Set rngDataFilteredByName = .Range("C" & .Rows.Count).End(xlUp).Offset(RowOffset:=2)
  162.                
  163.                 ' Build the FILTER formula
  164.                strFilterFormula = "=FILTER("
  165.                 strFilterFormula = strFilterFormula & .Range("A1").CurrentRegion.Offset(RowOffset:=1).Resize(RowSize:=.Range("A1").CurrentRegion.Rows.Count - 1).Address(RowAbsolute:=True, ColumnAbsolute:=True)
  166.                 strFilterFormula = strFilterFormula & "," & .Range("$H$2:$H$" & .Range("H" & .Rows.Count).End(xlUp).Row).Address(RowAbsolute:=True, ColumnAbsolute:=True)
  167.                 strFilterFormula = strFilterFormula & "=""" & arrUniqueNames(i) & ""","""")"
  168.                 rngDataFilteredByName.Formula2 = strFilterFormula
  169.  
  170.             End With
  171.            
  172.             If rngDataFilteredByName.HasSpill Then
  173.  
  174.                 ' Remove any existing filtered data
  175.                With wsEachDataSheet.Range("A2").CurrentRegion
  176.                     .Offset(RowOffset:=1).Resize(RowSize:=.Rows.Count - 1).ClearContents
  177.                 End With
  178.                
  179.                 ' Copy filtered data to sheet for this name
  180.                With rngDataFilteredByName
  181.                
  182.                     wsEachDataSheet.Range("A2").Resize( _
  183.                         RowSize:=.SpillingToRange.Rows.Count, _
  184.                         ColumnSize:=.SpillingToRange.Columns.Count) _
  185.                             .Value2 = .SpillingToRange.Value2
  186.                
  187.                 End With
  188.            
  189.             End If
  190.            
  191.             ' Reset the cell used to calculate the filtered data
  192.            rngDataFilteredByName.ClearContents
  193.            
  194.             ' Apply text alignment, borders and auto column widths
  195.            With wsEachDataSheet.UsedRange
  196.                 .HorizontalAlignment = xlCenter
  197.                 .VerticalAlignment = xlCenter
  198.                 .Borders(xlEdgeBottom).LineStyle = xlContinuous
  199.                 .Borders(xlEdgeLeft).LineStyle = xlContinuous
  200.                 .Borders(xlEdgeRight).LineStyle = xlContinuous
  201.                 .Borders(xlEdgeTop).LineStyle = xlContinuous
  202.                 .Borders(xlInsideHorizontal).LineStyle = xlContinuous
  203.                 .Borders(xlInsideVertical).LineStyle = xlContinuous
  204.                 .EntireColumn.AutoFit
  205.             End With
  206.            
  207.         Next i
  208.        
  209.     End If
  210.    
  211.     Application.ScreenUpdating = True
  212.    
  213.     ' Memory management / assist garbage collection
  214.    If Not rngUniqueNames Is Nothing Then Set rngUniqueNames = Nothing
  215.     If Not rngColumnHeaders Is Nothing Then Set rngColumnHeaders = Nothing
  216.     If Not rngDataFilteredByName Is Nothing Then Set rngDataFilteredByName = Nothing
  217.     If Not wsEachDataSheet Is Nothing Then Set wsEachDataSheet = Nothing
  218.     If ArrayInitialized(arrUniqueNames) Then Erase arrUniqueNames
  219.     If ArrayInitialized(arrDataForEachName) Then Erase arrDataForEachName
  220.  
  221. End Sub
  222.                                    
  223. ' ==================
  224. ' modPublicFunctions
  225. ' ==================
  226.  
  227. Option Explicit
  228. Option Private Module
  229.  
  230. Public Function WorksheetExists(ByVal strSheetName As String) As Boolean
  231.  
  232.     Application.EnableCancelKey = xlDisabled
  233.    
  234.     Dim wsCheck As Worksheet
  235.      
  236.     On Error Resume Next
  237.     Set wsCheck = wbThis.Worksheets(strSheetName)
  238.     On Error GoTo 0
  239.    
  240.     WorksheetExists = Not wsCheck Is Nothing
  241.    
  242.     ' Memory management / assist garbage collection
  243.    If Not wsCheck Is Nothing Then Set wsCheck = Nothing
  244.      
  245. End Function
  246.  
  247. Public Function GetInnerDimensionOnly(ByRef arrConvert As Variant) As Variant()
  248.  
  249.     With Application
  250.  
  251.         .EnableCancelKey = xlDisabled
  252.    
  253.         Dim blHasSecondDimension As Boolean
  254.         Dim arrReturn() As Variant
  255.        
  256.         On Error Resume Next
  257.         blHasSecondDimension = CBool(UBound(arrConvert, 2) > -1)
  258.         On Error GoTo 0
  259.        
  260.         If blHasSecondDimension Then
  261.            
  262.             arrReturn = .Transpose(.Index(arrConvert, 0, 1))
  263.             GetInnerDimensionOnly = arrReturn
  264.        
  265.         End If
  266.    
  267.     End With
  268.  
  269. End Function
  270.  
  271. Public Function ArrayInitialized(arrTest As Variant) As Boolean
  272.  
  273.     Application.EnableCancelKey = xlDisabled
  274.    
  275.     On Error Resume Next
  276.     ArrayInitialized = LBound(arrTest) <= UBound(arrTest)
  277.     On Error GoTo 0
  278.  
  279. End Function
  280.                                        
  281. ' ============
  282. ' modUtilities
  283. ' ============
  284.  
  285. Option Explicit
  286. Option Private Module
  287.  
  288. Public Sub RemoveDataSheets()
  289.  
  290.     With Application
  291.         .EnableCancelKey = xlDisabled
  292.         .ScreenUpdating = False
  293.         .DisplayAlerts = False
  294.     End With
  295.    
  296.     Dim wsEach As Worksheet
  297.    
  298.     For Each wsEach In wbThis.Worksheets
  299.    
  300.         If wsEach.Name <> wsMonth.Name Then wsEach.Delete
  301.    
  302.     Next wsEach
  303.    
  304.     With Application
  305.         .ScreenUpdating = True
  306.         .DisplayAlerts = True
  307.     End With
  308.  
  309. End Sub
  310.  
  311.                                            
Tags: excel vba
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement