VBA: Merging columns based on header

ruinedelf

New Member
Joined
Dec 6, 2023
Messages
35
Office Version
  1. 365
  2. 2021
Platform
  1. Windows
  2. MacOS
Good morrow! Got a question, I would like a VBA script that can merge columns based on the header. For instance:

Analyte 1Analyte 2Analyte 1
Sample 12342344
Sample 2665543

becomes

Analyte 1Analyte 2
Sample 12342344
Sample 2543665

How this is done isn't a major factor, but there may be more than 1 column that needs to be merged, and more than 1 row that needs merging.

I actually had a working script for this made up by someone else that did exactly what I needed, but as it uses dictionaries, it requires the Microsoft Scripting Runtime Reference to be enabled and I will be deploying this to people who won't and are unable to add it. See below.

VBA Code:
Option Explicit

Public Sub MergeColumns()
    Const HDR As Long = 1      'header row
    Const HDC As Long = 3       '(first) header column

    Dim ws As Worksheet, lRow As Long, lCol As Long, hRow As Variant, i As Long
    Dim ac As Object, dc As Object, c1 As Variant, c2 As Variant
    Set ac = CreateObject("Scripting.Dictionary")
    Set dc = CreateObject("Scripting.Dictionary")
    Dim itm As Variant, dCols As Range, d As Range, tr As String
    Set ws = ThisWorkbook.Worksheets("ALS Import")
    lRow = ws.Cells(ws.Rows.Count, HDC).End(xlUp).Row
    lCol = ws.Cells(HDR, ws.Columns.Count).End(xlToLeft).Column

    If lRow >= HDR And lCol > HDC Then
        hRow = ws.Range(ws.Cells(HDR, HDC), ws.Cells(HDR, lCol)).Value2

        Application.ScreenUpdating = False

        For i = 1 To lCol - HDC + 1 'find dupes ---------------------------------------------
            tr = Trim(hRow(1, i))
            If Len(tr) > 0 Then
                If Not ac.Exists(tr) Then
                    ac.Add tr, i + HDC - 1
                Else
                    ' If the key exists in the 'ac' dictionary, but not in 'dc', add to 'dc'
                    If Not dc.Exists(ac(tr)) Then
                        dc.Add ac(tr), i + HDC - 1
                    End If
                End If
            End If
        Next
      Application.ScreenUpdating = False
      For Each itm In dc 'merge columns ---------------------------------------------------
         c1 = ws.Range(ws.Cells(HDR, itm), ws.Cells(lRow, itm)).Value2
         c2 = ws.Range(ws.Cells(HDR, dc(itm)), ws.Cells(lRow, dc(itm))).Value2
         For i = 1 To lRow - HDR + 1
            If Len(Trim(c1(i, 1))) = 0 Then c1(i, 1) = c2(i, 1) 'trimms blanks
         Next
         ws.Range(ws.Cells(HDR, itm), ws.Cells(lRow, itm)).Value2 = c1
      Next
      For Each itm In dc 'delete duplicate columns ----------------------------------------
         Set d = ws.Cells(HDR, dc(itm))
         If dCols Is Nothing Then Set dCols = d Else Set dCols = Union(dCols, d)
      Next
      If Not dCols Is Nothing Then dCols.EntireColumn.Delete
      Application.ScreenUpdating = True
   End If
End Sub

Any ideas what I'd be able to dance with?
 

Excel Facts

Who is Mr Spreadsheet?
Author John Walkenbach was Mr Spreadsheet until his retirement in June 2019.
Good morrow! Got a question, I would like a VBA script that can merge columns based on the header. For instance:

Analyte 1Analyte 2Analyte 1
Sample 12342344
Sample 2665543

becomes

Analyte 1Analyte 2
Sample 12342344
Sample 2543665

How this is done isn't a major factor, but there may be more than 1 column that needs to be merged, and more than 1 row that needs merging.

I actually had a working script for this made up by someone else that did exactly what I needed, but as it uses dictionaries, it requires the Microsoft Scripting Runtime Reference to be enabled and I will be deploying this to people who won't and are unable to add it. See below.

VBA Code:
Option Explicit

Public Sub MergeColumns()
    Const HDR As Long = 1      'header row
    Const HDC As Long = 3       '(first) header column

    Dim ws As Worksheet, lRow As Long, lCol As Long, hRow As Variant, i As Long
    Dim ac As Object, dc As Object, c1 As Variant, c2 As Variant
    Set ac = CreateObject("Scripting.Dictionary")
    Set dc = CreateObject("Scripting.Dictionary")
    Dim itm As Variant, dCols As Range, d As Range, tr As String
    Set ws = ThisWorkbook.Worksheets("ALS Import")
    lRow = ws.Cells(ws.Rows.Count, HDC).End(xlUp).Row
    lCol = ws.Cells(HDR, ws.Columns.Count).End(xlToLeft).Column

    If lRow >= HDR And lCol > HDC Then
        hRow = ws.Range(ws.Cells(HDR, HDC), ws.Cells(HDR, lCol)).Value2

        Application.ScreenUpdating = False

        For i = 1 To lCol - HDC + 1 'find dupes ---------------------------------------------
            tr = Trim(hRow(1, i))
            If Len(tr) > 0 Then
                If Not ac.Exists(tr) Then
                    ac.Add tr, i + HDC - 1
                Else
                    ' If the key exists in the 'ac' dictionary, but not in 'dc', add to 'dc'
                    If Not dc.Exists(ac(tr)) Then
                        dc.Add ac(tr), i + HDC - 1
                    End If
                End If
            End If
        Next
      Application.ScreenUpdating = False
      For Each itm In dc 'merge columns ---------------------------------------------------
         c1 = ws.Range(ws.Cells(HDR, itm), ws.Cells(lRow, itm)).Value2
         c2 = ws.Range(ws.Cells(HDR, dc(itm)), ws.Cells(lRow, dc(itm))).Value2
         For i = 1 To lRow - HDR + 1
            If Len(Trim(c1(i, 1))) = 0 Then c1(i, 1) = c2(i, 1) 'trimms blanks
         Next
         ws.Range(ws.Cells(HDR, itm), ws.Cells(lRow, itm)).Value2 = c1
      Next
      For Each itm In dc 'delete duplicate columns ----------------------------------------
         Set d = ws.Cells(HDR, dc(itm))
         If dCols Is Nothing Then Set dCols = d Else Set dCols = Union(dCols, d)
      Next
      If Not dCols Is Nothing Then dCols.EntireColumn.Delete
      Application.ScreenUpdating = True
   End If
End Sub

Any ideas what I'd be able to dance with?
try this:
VBA Code:
Sub MergeColumns()
    Application.ScreenUpdating = False
    Application.DisplayAlerts = False
    Dim cll As Range, mcll As Range, ccll As Range, title As Range
    If Selection.Rows.Count > 1 Then Exit Sub
    Set title = Selection
    For Each cll In title
        If Not IsEmpty(cll) Then
            For Each mcll In title
                If Intersect(mcll, cll) Is Nothing Then
                    If cll.Value = mcll.Value Then
                        For Each ccll In Range(mcll.Offset(1), mcll.Offset(Lr(mcll.Column) - 1))
                            If Not IsEmpty(ccll) Then cll.Offset(Lr(cll.Column)).Value = ccll.Value
                        Next ccll
                        Range(mcll, mcll.Offset(Lr(mcll.Column) - 1)).Delete Shift:=xlToLeft
                    End If
                End If
            Next mcll
        End If
    Next cll
    Application.ScreenUpdating = True
    Application.DisplayAlerts = True
End Sub

Private Function Lr(ByVal col As Integer) As Long
    Lr = Cells(Rows.Count, col).End(xlUp).Row
End Function
 
Upvote 0
try this:
VBA Code:
Sub MergeColumns()
    Application.ScreenUpdating = False
    Application.DisplayAlerts = False
    Dim cll As Range, mcll As Range, ccll As Range, title As Range
    If Selection.Rows.Count > 1 Then Exit Sub
    Set title = Selection
    For Each cll In title
        If Not IsEmpty(cll) Then
            For Each mcll In title
                If Intersect(mcll, cll) Is Nothing Then
                    If cll.Value = mcll.Value Then
                        For Each ccll In Range(mcll.Offset(1), mcll.Offset(Lr(mcll.Column) - 1))
                            If Not IsEmpty(ccll) Then cll.Offset(Lr(cll.Column)).Value = ccll.Value
                        Next ccll
                        Range(mcll, mcll.Offset(Lr(mcll.Column) - 1)).Delete Shift:=xlToLeft
                    End If
                End If
            Next mcll
        End If
    Next cll
    Application.ScreenUpdating = True
    Application.DisplayAlerts = True
End Sub

Private Function Lr(ByVal col As Integer) As Long
    Lr = Cells(Rows.Count, col).End(xlUp).Row
End Function
oh i forgot that you need to select table title then run macro to merge data, after that your original table will be replace with merge table
 
Upvote 0
Thanks for the reply! Unfortunately, I didn't completely disclose the whole scenario, so the script doesn't quite work the way I had planned.

There's actually a buffer row in row 2, which serves a function in a different script. This causes the script to do something funky:

Before:
AnalyteAnalyte 1Analyte 2Analyte 1Analyte 2Analyte 3Analyte 4Analyte 5Analyte 6Analyte 7Analyte 8Analyte 9Analyte 10Analyte 11Analyte 12
SampleTest 1Test 2Test 3Test 4Test 5Test 6Test 7Test 8Test 9Test 10Test 11Test 12Test 13Test 14
Sample 1
2344​
752​
785​
4475​
4175​
74​
55​
Sample 2
665​
543​
757​
2745​
5​
754745​
52142​
Sample 3
24354​
72552​
578​
1​
4757​
7574​
4274557​
Sample 4
53​
545​
757​
85​
74​
785757​
757​
Sample 5
52​
5​
547​
5​
757545​
45754​
45​
Sample 6
4​
45​
742​
52875​
74​
474745​
4575​
Sample 7
234​
77856​
547​
57​
745​
5757​
14245​

After:
AnalyteAnalyte 1Analyte 2Analyte 3Analyte 4Analyte 5Analyte 6Analyte 7Analyte 8Analyte 9Analyte 10Analyte 11Analyte 12
SampleTest 1Test 2Test 5Test 6Test 7Test 8Test 9Test 10Test 11Test 12Test 13Test 14
Sample 1Test 3
2344​
785​
4475​
4175​
74​
55​
Sample 2
752​
665​
757​
2745​
5​
754745​
52142​
Sample 3
543​
24354​
578​
1​
4757​
7574​
4274557​
Sample 4
72552​
53​
757​
85​
74​
785757​
757​
Sample 5
545​
52​
547​
5​
757545​
45754​
45​
Sample 6
5​
4​
742​
52875​
74​
474745​
4575​
Sample 7
45​
234​
547​
57​
745​
5757​
14245​
77856​
Test 4

I tried removing the buffer row and running the macro, but it also looks like the script isn't a fan of empty spaces (Note how the second column of Analyte 2 is empty but it moves the column header down to the end of the first column):

AnalyteAnalyte 1Analyte 2Analyte 3Analyte 4Analyte 5Analyte 6Analyte 7Analyte 8Analyte 9Analyte 10Analyte 11Analyte 12
Sample 1
752​
2344​
785​
4475​
4175​
74​
55​
Sample 2
543​
665​
757​
2745​
5​
754745​
52142​
Sample 3
72552​
24354​
578​
1​
4757​
7574​
4274557​
Sample 4
545​
53​
757​
85​
74​
785757​
757​
Sample 5
5​
52​
547​
5​
757545​
45754​
45​
Sample 6
45​
4​
742​
52875​
74​
474745​
4575​
Sample 7
77856​
234​
547​
57​
745​
5757​
14245​
Analyte 2

To explain, I'm trying to reorganize a whole bunch of data for importing into another program. To do so, I need to change the headers of the columns to match the importing template, as well as try to get them in a specific order. How I've got it set up at the moment is a lot of macros that process the data, with two rows of column headers (Rows 1 and 2). The data is manually pasted into a section of the workbook, then the macro is triggered. After processing the data to meet a more general formatting, the data is then moved based on their header matching the headers in Row 2. For instance, if cell J61 in the pasted section corresponds to column B2 in the target location, the contents of J61 downwards are cut and pasted into B2 and downwards.

It is after this movement that I would like to merge the columns. Row 1 contains the headers that get imported into the other program, while Row 2 contains the headers from the initial data. Sometimes, the data can have different names or spellings for the same type of data (for example, "Solar" and "PV"), so I have duplicated the import headers with the different variations of initial headers. But when importing, it only takes the first column, so I'm looking for a script that I can insert into the list of macros I have that would merge the columns.

Hope that made sense!
 
Upvote 0
i don't really understand what you want, but i can fix that macro delete wrong cell that make wrong column and change title to select more rows as header of table with row 1 of selection is title:
VBA Code:
Sub MergeColumns()
    Application.ScreenUpdating = False
    Application.DisplayAlerts = False
    Dim cll As Range, mcll As Range, ccll As Range, title As Range
    Dim i As Integer
    i = Selection.Rows.Count
    Set title = Range(Selection.Cells(1), Selection.Cells(Selection.Cells.Count).Offset(-i + 1))
    For Each cll In title
        If Not IsEmpty(cll) Then
            For Each mcll In title
                If Intersect(mcll, cll) Is Nothing Then
                    If cll.Value = mcll.Value Then
                        If Lr(mcll.Column) > i Then
                            For Each ccll In Range(mcll.Offset(i), mcll.Offset(Lr(mcll.Column) - 1))
                                If Not IsEmpty(ccll) Then cll.Offset(Lr(cll.Column)).Value = ccll.Value
                            Next ccll
                        End If
                        Columns(mcll.Column).Delete Shift:=xlToLeft
                    End If
                End If
            Next mcll
        End If
    Next cll
    Application.ScreenUpdating = True
    Application.DisplayAlerts = True
End Sub

Private Function Lr(ByVal col As Integer) As Long
    Lr = Cells(Rows.Count, col).End(xlUp).Row
End Function
 
Upvote 0
Thanks eiloken, that works! I'm happy with that, thanks a lot! I have an additional request, so I'll be opening up another thread for that. Thanks again!
 
Upvote 0
Thanks eiloken, that works! I'm happy with that, thanks a lot! I have an additional request, so I'll be opening up another thread for that. Thanks again!
if your header of table is constant, you can change all "Selection" to address of your table (include buffer row with row 1 of address is title).
 
Upvote 0
At the moment, I just have the script include a Worksheet.Row(1:2).Select or something, can't remember off the top of my head.

I forgot to ask, will this script work if there are more than 2 duplicates? For example

Analyte 1 | Analyte 1 | Analyte 2 | Analyte 1

Will all 3 Analyte 1's be merged into the one column? I'm away from my computer now so I can't check, I'll have to give it a try tomorrow and see!
 
Upvote 0
At the moment, I just have the script include a Worksheet.Row(1:2).Select or something, can't remember off the top of my head.

I forgot to ask, will this script work if there are more than 2 duplicates? For example

Analyte 1 | Analyte 1 | Analyte 2 | Analyte 1

Will all 3 Analyte 1's be merged into the one column? I'm away from my computer now so I can't check, I'll have to give it a try tomorrow and see!
yes, no matter how many duplicates
 
Upvote 1

Forum statistics

Threads
1,224,823
Messages
6,181,176
Members
453,021
Latest member
Justyna P

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