VBA Code help - inserting columns in a specific order

lichldo

Board Regular
Joined
Apr 19, 2022
Messages
65
Office Version
  1. 365
Platform
  1. MacOS
Hello! I have a code that is almost working, but I need some help getting the last bit. I am essentially trying to ensure that I have a consistent set of columns, in a specified order. Sometimes when we export a report from our financial tool, it doesn't give us all the columns we need, so this is our work around. We need the exact same columns in this specific order.

The code below works, except it is inserting the missing columns at the end, rather than inserting them in the correct order. Any ideas as to how I can modify this so that it inserts the missing column in the correct order, and not just at the end? Thank you!!


VBA Code:
Sub AddMissingColumns()
    Dim ws As Worksheet
    Dim requiredColumns As Variant
    Dim columnName As Variant
    Dim foundColumn As Range
    Dim insertIndex As Long
    Dim lastColumn As Long
    Dim i As Long
    
    ' Set the worksheet to work with (Active Sheet)
    Set ws = ActiveSheet
    
    ' Define the required columns in the desired order
    requiredColumns = Array("Client", "Job", "Job description", "Job Phase", "Phase description", "Job Status", "Booked Charge", "Ticketed Hours", "Time Actual Charge", "PO Actual Charge", "PO Estimate Charge", "Exp Actual Charge", "Exp Estimate Charge", "Billing Plan - Planned Value (Invoicing)", "Billing Plan - Recognise Value", "Billing Plan - Notional Costs (Disbursements)", "Billing Plan - Profit Forecast (Fee Revenue)") ' Replace with the column names you expect
    
    ' Find the last column in the worksheet
    lastColumn = ws.Cells(1, ws.Columns.Count).End(xlToLeft).column
    
    ' Loop through the required columns
    For i = LBound(requiredColumns) To UBound(requiredColumns)
        columnName = requiredColumns(i)
        
        ' Check if the column exists
        Set foundColumn = Nothing
        On Error Resume Next
        Set foundColumn = ws.Rows(1).Find(columnName, LookIn:=xlValues, LookAt:=xlWhole)
        On Error GoTo 0
        
        ' If the column doesn't exist, insert it at the specified index
        If foundColumn Is Nothing Then
            insertIndex = lastColumn + 1
            ws.Columns(insertIndex).Insert Shift:=xlToRight
            ws.Cells(1, insertIndex).Value = columnName
            lastColumn = lastColumn + 1
        ElseIf foundColumn.column > i + 1 Then
            ' If the column is found but not at the correct index, move it to the correct index
            foundColumn.EntireColumn.Cut ws.Columns(i + 1)
            lastColumn = lastColumn - 1
        End If
    Next i
End Sub
 
Now if only it kept the columns of data that were not included in the requiredColumns array like my code did.
I assume that you are referring to this in your code?
Any column headers that exist and are not listed in requiredColumns will be moved to the end of the header columns that are custom sorted
Could be wrong, but my interpretation of the original post is that situation would not arise. If the OP says it can, then I will post the modified version of my approach.
 
Upvote 0

Excel Facts

Repeat Last Command
Pressing F4 adds dollar signs when editing a formula. When not editing, F4 repeats last command.
Book1
ABCDEFGHIJKLMNOP
1Billing Plan - Planned Value (Invoicing)Billing Plan - Profit Forecast (Fee Revenue)Billing Plan - Recognise ValueBooked ChargeClientExp Actual ChargeExp Estimate ChargeJobJob descriptionJob PhaseJob StatusPhase descriptionPO Actual ChargePO Estimate ChargeTicketed HoursTime Actual Charge
2lintenchimbleyvavasorsunsalnessovotestisbeknotstarriersfossorialantiroyalsavarinmescaltreadabdomensfingentunheavyunvetoed
3titlistsbouvieradeembacillicliftsunmindfullapelshawseholemechanismmagboterakitoographklvaindictersbitstonelysogenic
4upholdingacrodontentenderforestpollenitepodosnazariteovernamefideleexampleduppointtaxationmarybudharthotbedsrakit
5kickbacksrouncevalretrainanatumdisceptsmelopianoboarsiguaniaoutbarksexosseoussignificsantrocelemenageriecanicideenfantsseminars
Save

.
.
.
Book1
ABCDEFGHIJKLMNOP
9997keglerterraneanaleuritestrameledbraidingpaginateswhaleheadhyocholicbonallytahkhanagordiidaeunstateslotionsetypiceuryaleansabrebill
9998olegbepaidhandelianflavicantadraddatelineseventypiemagcapacifygushestragiclyoblatelylevechemetricizetorsadeslackerer
9999kestrelnoninjurylaicizesupbyschulnhardykokoonoriasformylateupswallowanelasticnuaduquisquousgailyupbytumidily
10000itoistfondantsrefireulstermanalthionicmenschestahkhanafurfuralbackpackschivareesrepollutenicotinspaintrootwieldersdoylegodling
Save

Elaped time for post #2 = 0.3984
Elaped time for post #5 = 0.6504
Elaped time for post #7 = 0.3301
Elaped time for post #10 = 0.1016
 
Upvote 0
Since we don't have any data from the OP, I think I will wait for the OP to respond back for now.
 
Upvote 0
Since we don't have any data from the OP, I think I will wait for the OP to respond back for now.
Kind of my thinking. Though it's fun to play 'how fast is my code', the reality is that unless the OP's data set is enormous, any of the posted solutions will serve.
 
Last edited:
Upvote 0
Agreed, without actual data to test with, we are just spinning our wheels.

You mentioned a file that boasted 10k rows that you did timings on, care to post that file?
 
Upvote 0
You mentioned a file that boasted 10k rows that you did timings on, care to post that file?
Just use the XL2BB bit I posted in post #12 and fill out the rows to 10000. Only the header row is important. The cell contents don't matter so you can put in any static data.
 
Upvote 0
The following should be faster than anything seen in this thread, given the supplied data:

VBA Code:
Sub reorderColumnsByRanking()                                                                                       ' FaneDuru inspired code, Keeps formatting, Keeps unlisted columns
'                                                                                                                   ' Average time for 50k rows = 0.177266 seconds
    Dim StartTime   As Double
'
    Dim lastColumn                  As Long
    Dim LastUsedHeaderColumn        As Long, NextFreeHeaderColumn   As Long
    Dim MaxTimedLoops               As Long
    Dim TimedLoopCounter            As Long
    Dim TimerResultColumn           As Long
    Dim fC                          As Range
    Dim TimerResultColumnLetter     As String
    Dim TimingsArray()              As Variant
'
    Dim HeaderMatchFound                As Boolean
    Dim ColumnNumber                    As Long
    Dim CurrentHeaderDictionary         As Object
    Dim cel                             As Range
    Dim HeaderNumber                    As Variant
    Dim requiredColumns                 As Variant
    Dim HeaderName_DesiredPosition      As Variant
    Dim ws                              As Worksheet
'
    Set CurrentHeaderDictionary = CreateObject("scripting.dictionary")
    Set ws = ActiveSheet                                                                                            ' <--- Set the name of the sheet that contains the data
'
    LastUsedHeaderColumn = ws.Cells(1, Columns.Count).End(xlToLeft).Column                                          ' Get LastUsedHeaderColumn
    NextFreeHeaderColumn = LastUsedHeaderColumn                                                                     '
'
    requiredColumns = Split("Client|1,Job|2,Job description|3,Job Phase|4,Phase description|5,Job Status|6," & _
            "Booked Charge|7,Ticketed Hours|8,Time Actual Charge|9,PO Actual Charge|10,PO Estimate Charge|11," & _
            "Exp Actual Charge|12,Exp Estimate Charge|13,Billing Plan - Planned Value (Invoicing)|14," & _
            "Billing Plan - Recognise Value|15,Billing Plan - Notional Costs (Disbursements)|16," & _
            "Billing Plan - Profit Forecast (Fee Revenue)|17", ",")                                                 '
'
    LastUsedHeaderColumn = ws.Cells(1, Columns.Count).End(xlToLeft).Column                                          ' Get LastUsedHeaderColumn
    NextFreeHeaderColumn = LastUsedHeaderColumn                                                                     '
    MaxTimedLoops = 102
'
    ReDim TimingsArray(1 To MaxTimedLoops, 1 To 1)
'
    For TimedLoopCounter = 1 To MaxTimedLoops
        Call RestoreOriginalData
'
        StartTime = Timer
'
        Application.ScreenUpdating = False
        Application.EnableEvents = False
        Application.Calculation = xlCalculationManual
'
        ws.Range("A1").EntireRow.Insert                                                                                 ' Insert helper row
'
'-------------------------------------------------------------------------------------------------------------------
'
' Append the missing column headers to the end of the current headers
        For Each cel In ws.Rows(1).Cells                                                                                ' Loop through the cells in the header row
            If Len(cel) > 0 Then CurrentHeaderDictionary(cel.Value) = True                                              '   If Header name is found then save it to CurrentHeaderDictionary
            If cel.End(xlToRight).Column = ws.Columns.Count Then Exit For                                               '   If last column header has been checked then exit this loop
        Next                                                                                                            ' Loop back
'
        For HeaderNumber = LBound(requiredColumns) To UBound(requiredColumns)                                           ' Loop through header names in requiredColumns
            If Not CurrentHeaderDictionary(requiredColumns(HeaderNumber)) Then                                          '   If header name being checked is not in the CurrentHeaderDictionary then ...
                If ws.Cells(1) = "" Then                                                                                '       If the first header cell is blank then ...
                    ws.Cells(1) = requiredColumns(HeaderNumber)                                                         '           Save the header name being checked into the first header cell
                ElseIf ws.Cells(1).Offset(, 1) = "" Then                                                                '       Else If the second header cell is blank then ...
                    ws.Cells(1).Offset(, 1) = requiredColumns(HeaderNumber)                                             '           Save the header name being checked into the second header cell
                Else                                                                                                    '       Else ...
                    ws.Cells(1).End(xlToRight).Offset(, 1) = requiredColumns(HeaderNumber)                              '           Save the header name being checked into the next blank header cell
                End If
            End If
        Next                                                                                                            ' Loop back
'
'-------------------------------------------------------------------------------------------------------------------
'
        For ColumnNumber = 1 To LastUsedHeaderColumn
            For Each HeaderName_DesiredPosition In requiredColumns
'                If IsFound(ws.Cells(2, ColumnNumber), CStr(Split(HeaderName_DesiredPosition, "|")(0))) Then
                Set fC = ws.Cells(2, ColumnNumber).Find(CStr(Split(HeaderName_DesiredPosition, "|")(0)))
'
                If Not fC Is Nothing Then
                    ws.Cells(1, ColumnNumber).value2 = Split(HeaderName_DesiredPosition, "|")(1)
                    HeaderMatchFound = True
                    Exit For
                End If
            Next
'
'        If Not HeaderMatchFound Then ws.Cells(1, ColumnNumber).Value = 16000
            If Not HeaderMatchFound Then
                NextFreeHeaderColumn = NextFreeHeaderColumn + 1                                                         '       Increment NextFreeHeaderColumn
                ws.Cells(1, ColumnNumber).value2 = NextFreeHeaderColumn                                                 '       Save NextFreeHeaderColumn to ws.Cells(1, ColumnNumber)
            End If
'
            HeaderMatchFound = False
        Next
'
        ws.Sort.SortFields.Add Key:=ws.Range(ws.Cells(1, 1), ws.Cells(1, LastUsedHeaderColumn)), SortOn:=xlSortOnValues, _
                Order:=xlAscending, DataOption:=xlSortNormal                                                            'Sort LeftToRight
'
        With ws.Sort
            .SetRange ws.Range(ws.Cells(1, 1), ws.Cells(1, LastUsedHeaderColumn)).EntireColumn
            .Header = xlYes
            .Orientation = xlLeftToRight
            .Apply
        End With
'
        ws.Rows(1).Delete                                                                                               'Delete helper row
'
        ws.UsedRange.Columns.AutoFit                                                                                    '   Autofit the column widths
'
        Application.Calculation = xlCalculationAutomatic
        Application.EnableEvents = True
        Application.ScreenUpdating = True
'
        TimingsArray(TimedLoopCounter, 1) = Timer - StartTime
'
        Debug.Print "Time to complete reorderColumnsByRanking = " & Timer - StartTime & " seconds."  '   Display the Timing result to the VBE 'Immediate' window ... CTRL+G in the VBE
    Next
'
' Find the last column in the worksheet
    lastColumn = ws.Cells(1, ws.Columns.Count).End(xlToLeft).Column

    TimerResultColumn = lastColumn + 1
'
    TimerResultColumnLetter = Split(Cells(1, TimerResultColumn).Address, "$")(1)
'
' Display the array of times now ***
    Range(TimerResultColumnLetter & "2").Resize(UBound(TimingsArray, 1), UBound(TimingsArray, 2)) = TimingsArray
'
    Range(TimerResultColumnLetter & MaxTimedLoops + 3) = "Average Time"                                           '
'
    Range(TimerResultColumnLetter & MaxTimedLoops + 4).Formula = "=(SUM(" & TimerResultColumnLetter & "2:" & _
            TimerResultColumnLetter & MaxTimedLoops + 1 & ")-(SUM(MAX(" & TimerResultColumnLetter & "2:" & _
            TimerResultColumnLetter & MaxTimedLoops + 1 & ")+MIN(" & TimerResultColumnLetter & "2:" & _
            TimerResultColumnLetter & MaxTimedLoops + 1 & "))))/(COUNT(" & TimerResultColumnLetter & "2:" & _
            TimerResultColumnLetter & MaxTimedLoops + 1 & ")-2)"                                                  ' Display the average time for all the timings, not counting the highest & lowest value
'
    MsgBox "Done."                                                                                          ' Notify the user that all of the loops chosen to perform have completed
End Sub
 
Last edited:
Upvote 0
My apologies for my previous post that should be disregarded. I have since realized that I posted some code that I was testing with that was not complete.

The code that should have been posted is:

VBA Code:
Sub reorderColumnsByRanking_Regular()                                                                               ' FaneDuru inspired code, Keeps formatting, Keeps unlisted columns
'                                                                                                                   ' Average time for 50k rows = 0.177266 seconds
    Dim StartTime   As Double
'
    Dim HeaderMatchFound                As Boolean
    Dim ColumnNumber                    As Long
    Dim LastUsedHeaderColumn            As Long, NextFreeHeaderColumn   As Long
    Dim CurrentHeaderDictionary         As Object
    Dim cel                             As Range
    Dim fC                              As Range
    Dim HeaderNumber                    As Variant
    Dim HeaderName_DesiredPosition      As Variant
    Dim requiredColumns                 As Variant
    Dim ws                              As Worksheet
'
    Set CurrentHeaderDictionary = CreateObject("scripting.dictionary")
    Set ws = ActiveSheet                                                                                            ' <--- Set the name of the sheet that contains the data
'
    requiredColumns = Split("Client|1,Job|2,Job description|3,Job Phase|4,Phase description|5,Job Status|6," & _
            "Booked Charge|7,Ticketed Hours|8,Time Actual Charge|9,PO Actual Charge|10,PO Estimate Charge|11," & _
            "Exp Actual Charge|12,Exp Estimate Charge|13,Billing Plan - Planned Value (Invoicing)|14," & _
            "Billing Plan - Recognise Value|15,Billing Plan - Notional Costs (Disbursements)|16," & _
            "Billing Plan - Profit Forecast (Fee Revenue)|17", ",")                                                 '
'
    StartTime = Timer
'
    Application.ScreenUpdating = False                                                                              ' Turn ScreenUpdating off
      Application.EnableEvents = False                                                                              ' Turn EnableEvents off
       Application.Calculation = xlCalculationManual                                                                ' Turn Auto calculation off
'
'-------------------------------------------------------------------------------------------------------------------
'
' Append the missing column headers to the end of the current headers
    For Each cel In ws.Rows(1).Cells                                                                                ' Loop through the cells in the header row
        If Len(cel) > 0 Then CurrentHeaderDictionary(cel.Value) = True                                              '   If Header name is found then save it to CurrentHeaderDictionary
        If cel.End(xlToRight).Column = ws.Columns.Count Then Exit For                                               '   If last column header has been checked then exit this loop
    Next                                                                                                            ' Loop back
'
    For HeaderNumber = LBound(requiredColumns) To UBound(requiredColumns)                                           ' Loop through header names in requiredColumns
        If Not CurrentHeaderDictionary(CStr(Split(requiredColumns(HeaderNumber), "|")(0))) Then                     '   If header name being checked is not in the CurrentHeaderDictionary then ...
            If ws.Cells(1) = "" Then                                                                                '       If the first header cell is blank then ...
                ws.Cells(1) = CStr(Split(requiredColumns(HeaderNumber), "|")(0))                                    '           Save the header name being checked into the first header cell
            ElseIf ws.Cells(1).Offset(, 1) = "" Then                                                                '       Else If the second header cell is blank then ...
                ws.Cells(1).Offset(, 1) = CStr(Split(requiredColumns(HeaderNumber), "|")(0))                        '           Save the header name being checked into the second header cell
            Else                                                                                                    '       Else ...
                ws.Cells(1).End(xlToRight).Offset(, 1) = CStr(Split(requiredColumns(HeaderNumber), "|")(0))         '           Save the header name being checked into the next blank header cell
            End If
        End If
    Next                                                                                                            ' Loop back
'
'-------------------------------------------------------------------------------------------------------------------
'
'
    LastUsedHeaderColumn = ws.Cells(1, Columns.Count).End(xlToLeft).Column                                          ' Get LastUsedHeaderColumn
    NextFreeHeaderColumn = LastUsedHeaderColumn                                                                     '
    ws.Range("A1").EntireRow.Insert                                                                                 ' Insert helper row for sorting the columns
'
    For ColumnNumber = 1 To LastUsedHeaderColumn                                                                    ' Loop through Header columns
        For Each HeaderName_DesiredPosition In requiredColumns                                                      '   Loop through header names in requiredColumns
            If Right$(HeaderName_DesiredPosition, 1) <> Chr(2) Then                                                 '       If This header name has not been been previously found then ...
                Set fC = ws.Cells(2, ColumnNumber).Find(CStr(Split(HeaderName_DesiredPosition, "|")(0)))            '
'
                If Not fC Is Nothing Then                                                                           '       If the header name was found then ...
                    ws.Cells(1, ColumnNumber).value2 = Val(Split(HeaderName_DesiredPosition, "|")(1))               '           Save the desired column position of the column to the helper row
                    requiredColumns(Val(Split(HeaderName_DesiredPosition, "|")(1)) - 1) = _
                            requiredColumns(Val(Split(HeaderName_DesiredPosition, "|")(1)) - 1) & Chr(2)            '           Append chr(2) to requiredColumns slot to indicate that it has been found
                    HeaderMatchFound = True                                                                         '           Set HeaderMatchFound flag to True
                    Exit For                                                                                        '           Exit this For loop
                End If
            End If
        Next                                                                                                        '   Loop back
'
        If Not HeaderMatchFound Then                                                                                '   If HeaderMatchFound flag has not been set to True then
            NextFreeHeaderColumn = NextFreeHeaderColumn + 1                                                         '       Increment NextFreeHeaderColumn
            ws.Cells(1, ColumnNumber).value2 = NextFreeHeaderColumn                                                 '       Save NextFreeHeaderColumn to ws.Cells(1, ColumnNumber)
        End If
'
        HeaderMatchFound = False                                                                                    '   Set HeaderMatchFound flag to False
    Next                                                                                                            ' Loop back
'
    With ws.Sort
        .SortFields.Clear                                                                                           '   Clear any previous sorts on the sheet
'
        .SortFields.Add Key:=Range("A1:" & Split(Cells(1, LastUsedHeaderColumn).Address, "$")(1) & "1"), _
                SortOn:=xlSortOnValues, Order:=xlAscending, DataOption:=xlSortNormal                                '   Set the sort Range columns to A1:LastUsedHeaderColumn & 1
'
        .SetRange Range("A1:" & Split(Cells(1, LastUsedHeaderColumn).Address, "$")(1) & Range("A" & _
                Rows.Count).End(xlUp).Row)                                                                          '   Set the sort Range to A1:LastUsedHeaderColumn & LastRow of sheet
        .Header = xlNo                                                                                              '
        .Orientation = xlLeftToRight                                                                                '
        .Apply                                                                                                      '   Do the sort
    End With
'
    ws.Rows(1).Delete                                                                                               ' Delete the helper row that we used for sorting columns
'
    ws.UsedRange.Columns.AutoFit                                                                                    ' Autofit the column widths
'
    Application.Calculation = xlCalculationAutomatic                                                                ' Turn Auto calculation back on
    Application.EnableEvents = True                                                                                 ' Turn EnableEvents back on
    Application.ScreenUpdating = True                                                                               ' Turn ScreenUpdating back on
'
    Debug.Print "Time to complete reorderColumnsByRanking_Regular = " & Timer - StartTime & " seconds."             ' Display the Timing result to the VBE 'Immediate' window ... CTRL+G in the VBE
'
    MsgBox "Done."                                                                                                  ' Notify the user that all of the loops chosen to perform have completed
End Sub
 
Upvote 0

Forum statistics

Threads
1,215,045
Messages
6,122,840
Members
449,096
Latest member
Erald

We've detected that you are using an adblocker.

We have a great community of people providing Excel help here, but the hosting costs are enormous. You can help keep this site running by allowing ads on MrExcel.com.
Allow Ads at MrExcel

Which adblocker are you using?

Disable AdBlock

Follow these easy steps to disable AdBlock

1)Click on the icon in the browser’s toolbar.
2)Click on the icon in the browser’s toolbar.
2)Click on the "Pause on this site" option.
Go back

Disable AdBlock Plus

Follow these easy steps to disable AdBlock Plus

1)Click on the icon in the browser’s toolbar.
2)Click on the toggle to disable it for "mrexcel.com".
Go back

Disable uBlock Origin

Follow these easy steps to disable uBlock Origin

1)Click on the icon in the browser’s toolbar.
2)Click on the "Power" button.
3)Click on the "Refresh" button.
Go back

Disable uBlock

Follow these easy steps to disable uBlock

1)Click on the icon in the browser’s toolbar.
2)Click on the "Power" button.
3)Click on the "Refresh" button.
Go back
Back
Top