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
 

Excel Facts

Best way to learn Power Query?
Read M is for (Data) Monkey book by Ken Puls and Miguel Escobar. It is the complete guide to Power Query.
One way.
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
    
    ' 1st pass - Add any missing 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 end
        If foundColumn Is Nothing Then
            insertIndex = lastColumn + 1
            ws.Cells(1, insertIndex).Value = columnName
            lastColumn = lastColumn + 1
        End If
    Next i
    
    ' 2nd pass - Order the columns
    For i = LBound(requiredColumns) To UBound(requiredColumns)
        columnName = requiredColumns(i)
        Set foundColumn = ws.Rows(1).Find(columnName, LookIn:=xlValues, LookAt:=xlWhole)
        
        ' Arrange the columns
        If Not foundColumn Is Nothing Then
            ' If the column is found but not at the correct index, move it to the correct index
            foundColumn.EntireColumn.Cut ws.Columns(lastColumn + i + 1)
        End If
    Next i
    
    ' Clean up
    With ws
        .Range(.Cells(1, 1), .Cells(1, lastColumn)).EntireColumn.Delete
        .UsedRange.Columns.AutoFit
    End With
End Sub
 
Upvote 0
One way.
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
   
    ' 1st pass - Add any missing 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 end
        If foundColumn Is Nothing Then
            insertIndex = lastColumn + 1
            ws.Cells(1, insertIndex).Value = columnName
            lastColumn = lastColumn + 1
        End If
    Next i
   
    ' 2nd pass - Order the columns
    For i = LBound(requiredColumns) To UBound(requiredColumns)
        columnName = requiredColumns(i)
        Set foundColumn = ws.Rows(1).Find(columnName, LookIn:=xlValues, LookAt:=xlWhole)
       
        ' Arrange the columns
        If Not foundColumn Is Nothing Then
            ' If the column is found but not at the correct index, move it to the correct index
            foundColumn.EntireColumn.Cut ws.Columns(lastColumn + i + 1)
        End If
    Next i
   
    ' Clean up
    With ws
        .Range(.Cells(1, 1), .Cells(1, lastColumn)).EntireColumn.Delete
        .UsedRange.Columns.AutoFit
    End With
End Sub
this still puts at the end? I thought it was working, but its not now somehow. I'm copy/pasting exactly
 
Upvote 0
this still puts at the end? I thought it was working, but its not now somehow.

It works when I test it. I doubt we can move forward unless you can provide testable data (via xL2bb, example below)
Book2
ABCDEFGHIJKLM
1ClientJobJob descriptionJob PhasePhase descriptionJob StatusBooked ChargeTicketed HoursTime Actual ChargePO Actual ChargeBilling Plan - Recognise ValueBilling Plan - Notional Costs (Disbursements)Billing Plan - Profit Forecast (Fee Revenue)
2upeyganpolleniteechiurusgyrodyneextruderssenhorasbeslobberacliniclithesomellanerooutlearnsjoewoodwhitens
3ruboutshelduckkrantzbesaucenumeracymorningsmoodilygambolledscrimpittooledcleanserquisquoustrump
4vellumsperchemechanismdoubtersnagglyinsanityojibwacedroninfectanttippetambsacesbaguettesexocone
5ripcordsgonioniacaseatesarcinasbobolinkgauzygeesthypostomeflunkiestabliergoldangedscroungerruntish
6drokpamurthersbranchinggonioniavarietiesmarathiwiccaresumesvoiturieraffydavyunplumedperilleddisallow
7eprosyenrollineallytranshaperelongdessousfiltratedfascicleunpermitknellkeratoticunseatedaotes
8rouncevalcaprylicdiaplexalripcordsyenslarcenisherlkingsnitratingunsalnessimbreatheolegdichternomadisms
9regressesfrictitericareerismlamiidmescaldispraisealleyhighermoneywortsavarineyelasheselkhorn
10glimpsedagistedfootfallbogmanammoniateherdessagistedindowingisosterescandiumstrachytedobrawangrace
11pegboxesquerendimelanilinmonroeismtriosteumagendumsforficulamulkunleftagriastoxonescaumdescend
12lintenriveretbearerspirnermarmosetsesperantofaulklandkissablyhowevernaticinetrumprewallowequisetic
13recubateepeisodiahencotecaravansoxlipyenselapidselectingotididaedobracitrullinmindmitigate
14
Sheet1

The free XL2BB tool (link below) to post your data in a way that makes it accessible to others.

 
Upvote 0
The following code is about as simple as I can make it for you to do what you have asked for:

VBA Code:
Sub AddMissingColumnsThenCustomSortColumns()
'
'   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
'
    Dim DesiredColumnPosition       As Long
    Dim CurrentHeaderDictionary     As Object
    Dim cel                         As Range
    Dim foundColumn                 As Range
    Dim HeaderNumber                As Variant
    Dim requiredColumns             As Variant
    Dim ws                          As Worksheet
'
    Set ws = ActiveSheet
    Set CurrentHeaderDictionary = CreateObject("scripting.dictionary")
'
    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
'
'-------------------------------------------------------------------------------------------------------------------
'
' 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
'
'-------------------------------------------------------------------------------------------------------------------
'
' Sort the header columns into the order that we want
    For HeaderNumber = LBound(requiredColumns) To UBound(requiredColumns)                                           ' Loop through header names in requiredColumns
        Set foundColumn = ws.Rows(1).Find(requiredColumns(HeaderNumber), LookIn:=xlValues, LookAt:=xlWhole)         '   Search the header row for the header name that is beaing checked for
'
        If Not foundColumn Is Nothing Then                                                                          '   If we found the header name being checked in the header row then ...
            DesiredColumnPosition = DesiredColumnPosition + 1                                                       '       Increment DesiredColumnPosition
'
            If foundColumn.Column <> DesiredColumnPosition Then                                                     '       If column position of header name is not in the DesiredColumnPosition then ...
                foundColumn.EntireColumn.Cut                                                                        '           Cut the header column
                ws.Columns(DesiredColumnPosition).Insert                                                            '           Insert the cut column into the DesiredColumnPosition
            End If
        End If
    Next                                                                                                            ' Loop back
End Sub
 
Upvote 0
@rlv01 Your code moves every single column of data, regardless if it needs to be moved or not, that could be expensive. ;)
 
Upvote 0
@rlv01 Your code moves every single column of data, regardless if it needs to be moved or not, that could be expensive. ;)
On the other hand, with my sample data it was twice as fast as yours. ;)

Sometimes when we export a report from our financial tool
Given that, I am assuming no formulas or special formatting. On that basis, here is another approach which with my small sample data took 0.008 seconds compared to
0.270 for post #2 and 0.582 for post #5

VBA Code:
Sub Rearrange_Columns()
  Dim requiredColumns As Variant, reqCols As Variant, aRws As Variant, m As Variant
  Dim cols As Long, i As Long

  requiredColumns = Split("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)", "|")
  cols = UBound(requiredColumns) + 1
  ReDim reqCols(1 To cols)
  aRws = Evaluate("row(1:" & Columns("A").Resize(, cols).Find(What:="*", LookIn:=xlValues, SearchOrder:=xlByRows, SearchDirection:=xlPrevious).Row & ")")
  For i = 1 To cols
    m = Application.Match(requiredColumns(i - 1), Rows(1), 0)
    reqCols(i) = IIf(IsNumeric(m), m, cols)
  Next i
  With Range("A1").Resize(UBound(aRws), cols)
    .Value = Application.Index(Cells, aRws, reqCols)
    .Rows(1).Value = requiredColumns
    .Columns.AutoFit
  End With
End Sub
 
Upvote 0
Genius! Now if only it kept the columns of data that were not included in the requiredColumns array like my code did.
 
Upvote 0
This has been an interesting post to play with. Thought I'd share my 2 cents worth - it's a different approach using a custom sort. It's no faster than some of the other offerings, but it does achieve the objective, as long as I've understood the requirement properly. My understanding is to add columns where they don't exist, in the preferred order? Posting just for fun :)

VBA Code:
Option Explicit
Sub test()
    Dim t As Double: t = Timer
    Application.ScreenUpdating = False
    Dim ar1, ar2, i As Long, j As Long, LCol As Long, rng As Range
    
    LCol = Cells(1, Columns.Count).End(xlToLeft).Column
    ar1 = 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)")
    ar1 = Application.Transpose(ar1)
    ar2 = Range(Cells(1, 1), Cells(1, LCol))
    ReDim ar3(1 To UBound(ar1) - UBound(ar2), 1 To 1)
    
    If UBound(ar2) < UBound(ar1) Then
        j = 1
        For i = LBound(ar1, 1) To UBound(ar1, 1)
            If IsError(Application.Match(ar1(i, 1), ar2, 0)) Then
                ar3(j, 1) = ar1(i, 1)
                j = j + 1
            End If
        Next i
        Cells(1, LCol + 1).Resize(1, UBound(ar3, 1)) = Application.Transpose(ar3)
    End If
    
    LCol = Cells(1, Columns.Count).End(xlToLeft).Column
    Set rng = Range(Cells(1, 1), Cells(Cells(Rows.Count, 1).End(xlUp).Row, Cells(1, Columns.Count).End(xlToLeft).Column))
    Application.AddCustomList ListArray:=ar1
    With ActiveSheet.Sort
        .SortFields.Clear
        .SortFields.Add2 Key:=Range(Cells(1, 1), Cells(1, LCol)), _
        CustomOrder:=Application.CustomListCount
        .SetRange rng
        .Orientation = xlLeftToRight
        .Apply
    End With
    
    With rng
        .EntireColumn.AutoFit
        .WrapText = False
        .Range(.Cells(1, 1), .Cells(1, LCol)).Font.Bold = True
    End With
    Application.ScreenUpdating = True
    MsgBox "Completed in " & Timer - t & " seconds."
End Sub
 
Upvote 0

Forum statistics

Threads
1,223,898
Messages
6,175,272
Members
452,628
Latest member
dd2

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