Macro to concatenate data

eduzs

Well-known Member
Joined
Jul 6, 2014
Messages
704
Office Version
  1. 2019
  2. 2010
Platform
  1. Windows
Hi there,

(all fictitious)

Suppose that I have a worksheet with names, date and prizes won at those dates, as they appear in the worksheet.

I need a VBA code that concatenate these data in a single row per person, listing the person name, the dates and the prizes wons on each unique date.

The list of names is in alphabetical order and then in date order, the prizes are unique.

For example:

[TABLE="class: grid, width: 50"]
<tbody>[TR]
[TD]NAME[/TD]
[TD]DATE[/TD]
[TD]PRIZE[/TD]
[/TR]
[TR]
[TD]ANN[/TD]
[TD]2018/10/1[/TD]
[TD]X[/TD]
[/TR]
[TR]
[TD]BILL[/TD]
[TD]2018/10/1[/TD]
[TD]Y[/TD]
[/TR]
[TR]
[TD]BILL[/TD]
[TD]2018/11/4[/TD]
[TD]Z[/TD]
[/TR]
[TR]
[TD]BILL[/TD]
[TD]2018/11/4[/TD]
[TD]W[/TD]
[/TR]
[TR]
[TD][/TD]
[TD][/TD]
[TD][/TD]
[/TR]
[TR]
[TD][/TD]
[TD][/TD]
[TD][/TD]
[/TR]
[TR]
[TD][/TD]
[TD][/TD]
[TD][/TD]
[/TR]
</tbody>[/TABLE]

The result rows will be:

"ANN: 2018/10/1 (X)"
"BILL: 2018/10/1 (Y), 2018/11/4 (Z,W)"

One person name will be listed once one row per person.

Each unique date that the person won prizes will be listed in the same name line, with each type of prize won between ().

Any ideas? Thanks.
 
Last edited:

Excel Facts

What is =ROMAN(40) in Excel?
The Roman numeral for 40 is XL. Bill "MrExcel" Jelen's 40th book was called MrExcel XL.
Try this for results on sheet2.
Code:
[COLOR=navy]Sub[/COLOR] MG21Aug13
[COLOR=navy]Dim[/COLOR] Dn      [COLOR=navy]As[/COLOR] Range
[COLOR=navy]Dim[/COLOR] Rng     [COLOR=navy]As[/COLOR] Range
[COLOR=navy]Dim[/COLOR] Dic     [COLOR=navy]As[/COLOR] Object
[COLOR=navy]Dim[/COLOR] Q       [COLOR=navy]As[/COLOR] Variant
   [COLOR=navy]Set[/COLOR] Rng = Range("A2", Range("A" & Rows.Count).End(xlUp))
 [COLOR=navy]Set[/COLOR] Dic = CreateObject("Scripting.Dictionary")
    Dic.CompareMode = 1
   [COLOR=navy]For[/COLOR] [COLOR=navy]Each[/COLOR] Dn [COLOR=navy]In[/COLOR] Rng
            [COLOR=navy]If[/COLOR] Not Dic.exists(Dn.Value) [COLOR=navy]Then[/COLOR]
                [COLOR=navy]Set[/COLOR] Dic(Dn.Value) = CreateObject("Scripting.Dictionary")
            [COLOR=navy]End[/COLOR] If
        
        [COLOR=navy]If[/COLOR] Not Dic(Dn.Value).exists(Dn.Offset(, 1).Value) [COLOR=navy]Then[/COLOR]
                Dic(Dn.Value).Add (Dn.Offset(, 1).Value), Dn.Offset(, 2).Value
        [COLOR=navy]Else[/COLOR]
                Dic(Dn.Value).Item(Dn.Offset(, 1).Value) = _
                Dic(Dn.Value).Item(Dn.Offset(, 1).Value) & "," & Dn.Offset(, 2).Value
        [COLOR=navy]End[/COLOR] If
    [COLOR=navy]Next[/COLOR] Dn
[COLOR=navy]Dim[/COLOR] k   [COLOR=navy]As[/COLOR] Variant
[COLOR=navy]Dim[/COLOR] p   [COLOR=navy]As[/COLOR] Variant, c [COLOR=navy]As[/COLOR] [COLOR=navy]Long,[/COLOR] nStr [COLOR=navy]As[/COLOR] [COLOR=navy]String[/COLOR]
ReDim Ray(1 To Dic.Count + 1, 1 To 2)
    Ray(1, 1) = "Name": Ray(1, 2) = "Date/Prize"
    c = 1
    [COLOR=navy]For[/COLOR] [COLOR=navy]Each[/COLOR] k [COLOR=navy]In[/COLOR] Dic.Keys
        c = c + 1
        Ray(c, 1) = k
           For Each p In Dic(k) 
               nStr = nStr & IIf(nStr = "", p & " (" & Dic(k).Item(p) & ")", ", " _
               & p & " (" & Dic(k).Item(p) & ")")
           [COLOR=navy]Next[/COLOR] p
            Ray(c, 2) = nStr
            nStr = ""
    [COLOR=navy]Next[/COLOR] k
[COLOR=navy]With[/COLOR] Sheets("Sheet2").Range("A1").Resize(c, 2)
    .Value = Ray
    .Borders.Weight = 2
    .Columns.AutoFit
 [COLOR=navy]End[/COLOR] [COLOR=navy]With[/COLOR]
[COLOR=navy]End[/COLOR] [COLOR=navy]Sub[/COLOR]
Regards Mick
 
Last edited:
Upvote 0
Ok, I've come up with a sub that should do what you want, but it assumes several things:
- the raw data is in Sheet1
- the output (formatted) data is in Sheet2
- the first row of Sheet1 are headers for each column and not part of the data set
- Names are in column A
- dates are in column B
- prizes are in column C
- dates are in a form that vba will recognize
- there is at least 1 person on the list

Sub sorting()


' Create variables
Dim DocName As String ' name of excel file
Dim ThisBook As Workbook ' Current workbook
Dim s1 As Worksheet ' Sheet with raw data
Dim s2 As Worksheet ' Sheet with formatted data
Dim Lrow As Long ' Last row of raw data
Dim TP As Long ' For finding the total number of people
Dim CD As Long ' For holding the current number of dates for a person
Dim TD As Integer ' For finding the largest number of dates for anyone (this will determine the total number of columns used in sheet2)
Dim NR As Long ' Used for holding moving data from one array into the other
Dim Tog As Integer ' Toggle for whether matches are found
Dim NH As Long ' Used for holding a row number for later use
Dim DS1() As Variant ' Array used for holding and manipulating the data set
Dim DS2() As Variant ' Array used for holding and manipulating the data set
Dim Rdata As Range ' Range variable used to pull the raw data into the an Array and for the output of the formatted data
Dim CurPer As String ' Used for storing the name of the person currently being looked at
Dim CurD As Date ' used for storing the date currently being looked at for a person
Dim LoopNum1 As Long ' Used for outer loops
Dim LoopNum2 As Long ' Used for secondary loops
Dim LoopNum3 As Long ' Used for tertiary loops
' variables for turning a number into a column letter
Dim LastColumn As Long ' last column (as a number)
Dim FirstLetterAsNumber As Integer ' the number of the first letter of the column
Dim SecondLetterAsNumber As Integer ' the number of the second letter of the column
Dim ColumnString As String ' the column letter


' Set variables
DocName = ActiveWorkbook.Name
Set ThisBook = Workbooks(DocName)
Set s1 = ThisBook.Sheets("Sheet1")
Set s2 = ThisBook.Sheets("Sheet2")
Lrow = s1.Cells(s1.Rows.Count, "A").End(xlUp).Row
'Make sure a list exists
If Lrow < 2 Then
GoTo oops
End If


Set Rdata = s1.Range("A2:C" & Lrow)
ReDim DS1(1 To Lrow - 1, 1 To 3)
ReDim DS2(1 To Lrow - 1, 1 To 3)
DS1 = Rdata
TP = 0
TD = 0
CD = 0
NR = 1


' Combine enteries with same name and date
For LoopNum1 = LBound(DS1, 1) To UBound(DS1, 1)
If DS1(LoopNum1, 1) <> "" Then ' find next person
CurPer = DS1(LoopNum1, 1)
TP = TP + 1
CD = 0
For LoopNum2 = LBound(DS1, 1) To UBound(DS1, 1)
If DS1(LoopNum2, 1) = CurPer Then ' find all instances of a person
CurD = DS1(LoopNum2, 2)
Tog = 0
For LoopNum3 = LBound(DS1, 1) To UBound(DS1, 1)
If DS2(LoopNum3, 1) = CurPer And DS2(LoopNum3, 2) = CurD Then ' if person and date already exist on a list, then combine
DS2(LoopNum3, 3) = DS2(LoopNum3, 3) & "," & DS1(LoopNum2, 3)
DS1(LoopNum2, 1) = ""
Tog = 1
Exit For
End If
Next LoopNum3
If Tog = 0 Then ' if person and date do not exist on list, then add a new entery
CD = CD + 1
DS2(NR, 1) = DS1(LoopNum2, 1)
DS2(NR, 2) = DS1(LoopNum2, 2)
DS2(NR, 3) = DS1(LoopNum2, 3)
DS1(LoopNum2, 1) = ""
NR = NR + 1
End If
End If
Next LoopNum2
If CD > TD Then
TD = CD
End If
End If
Next LoopNum1


' combine all enteries of each person into a single line
ReDim DS1(1 To TP, 1 To (TD + 1))
NR = 0
For LoopNum1 = LBound(DS2, 1) To UBound(DS2, 1)
If DS2(LoopNum1, 1) <> "" Then ' look for the next person
NR = NR + 1
CurPer = DS2(LoopNum1, 1)
CD = 0
NH = LoopNum1
DS1(NR, 1) = CurPer
For LoopNum2 = LBound(DS2, 1) To UBound(DS2, 1)
If DS2(LoopNum2, 1) = CurPer Then ' find total number of entries (dates) for the person
CD = CD + 1
End If
Next LoopNum2
For LoopNum2 = CD To 1 Step -1
CurD = "1/1/1970"
For LoopNum3 = LBound(DS2, 1) To UBound(DS2, 1)
If DS2(LoopNum3, 1) = CurPer And DS2(LoopNum3, 2) > CurD Then ' find the entery with the highest date for the person
CurD = DS2(LoopNum3, 2)
NH = LoopNum3
End If
Next LoopNum3
DS1(NR, (LoopNum2 + 1)) = DS2(NH, 2) & " (" & DS2(NH, 3) & ")"
DS2(NH, 1) = ""
Next LoopNum2
End If
Next LoopNum1


' Put lisst in alphabetical order
ReDim DS2(1 To TP, 1 To (TD + 1))
NR = 1
NH = 0
For LoopNum1 = LBound(DS1, 1) To UBound(DS1, 1) ' one loop per person
For LoopNum2 = LBound(DS1, 1) To UBound(DS1, 1) ' find the name with the lowest value
If DS1(LoopNum2, 1) <> "" Then
If NH = 0 Or LCase(DS1(LoopNum2, 1)) < LCase(CurPer) Then
NH = LoopNum2
CurPer = DS1(LoopNum2, 1)
End If
End If
Next LoopNum2
For LoopNum2 = LBound(DS1, 2) To UBound(DS1, 2)
DS2(NR, LoopNum2) = DS1(NH, LoopNum2)
Next LoopNum2
DS1(NH, 1) = ""
NR = NR + 1
NH = 0
Next LoopNum1


' find the column number that corrisponds to Total number of date columns (TD) + 1 (for name)
' this uses some old code I wrote a while back
LastColumn = TD + 1
If LastColumn > 26 Then ' check if the Last Column is larger than Z
FirstLetterAsNumber = Int(LastColumn / 26) ' divide the total number of columns by 26 and remove the remainder
SecondLetterAsNumber = LastColumn - (FirstLetterAsNumber * 26) ' subtract the first number of columns used for the first letter
ColumnString = Chr(FirstLetterAsNumber + 64) & Chr(SecondLetterAsNumber + 64) ' use ASCII to change the numbers into letters and put them together
Else ' if the Last Column is smaller the Z
ColumnString = Chr(LastColumn + 64) ' use ASCII to change the column number into a letter
End If

'output formatted data to sheet2
Set Rdata = s2.Range("A1:" & ColumnString & TP)
Rdata = DS2


oops:
End Sub

I know it's not exactly pretty, but it works on all the tests I've run.
 
Upvote 0
@ spiralrain
For the future, your code would be much easier to read if the indentations were preserved, like Mick's, with Code tags. My signature block below shows how to do that. :)
 
Last edited:
Upvote 0
@ Peter_SSs
Thank you for the info, I was wondering how that was done.

Here is a reposting of the code with the indentions intact (I hope)
Code:
Sub sorting()

' Create variables
Dim DocName As String ' name of excel file
Dim ThisBook As Workbook ' Current workbook
Dim s1 As Worksheet ' Sheet with raw data
Dim s2 As Worksheet ' Sheet with formatted data
Dim Lrow As Long ' Last row of raw data
Dim TP As Long ' For finding the total number of people
Dim CD As Long ' For holding the current number of dates for a person
Dim TD As Integer ' For finding the largest number of dates for anyone (this will determine the total number of columns used in sheet2)
Dim NR As Long ' Used for holding moving data from one array into the other
Dim Tog As Integer ' Toggle for whether matches are found
Dim NH As Long ' Used for holding a row number for later use
Dim DS1() As Variant ' Array used for holding and manipulating the data set
Dim DS2() As Variant ' Array used for holding and manipulating the data set
Dim Rdata As Range ' Range variable used to pull the raw data into the an Array and for the output of the formatted data
Dim CurPer As String ' Used for storing the name of the person currently being looked at
Dim CurD As Date ' used for storing the date currently being looked at for a person
Dim LoopNum1 As Long ' Used for outer loops
Dim LoopNum2 As Long ' Used for secondary loops
Dim LoopNum3 As Long ' Used for tertiary loops
' variables for turning a number into a column letter
Dim LastColumn As Long ' last column (as a number)
Dim FirstLetterAsNumber As Integer ' the number of the first letter of the column
Dim SecondLetterAsNumber As Integer ' the number of the second letter of the column
Dim ColumnString As String ' the column letter




' Set variables
DocName = ActiveWorkbook.Name
Set ThisBook = Workbooks(DocName)
Set s1 = ThisBook.Sheets("Sheet1")
Set s2 = ThisBook.Sheets("Sheet2")
Lrow = s1.Cells(s1.Rows.Count, "A").End(xlUp).Row
'Make sure a list exists
If Lrow < 2 Then
    GoTo oops
End If


Set Rdata = s1.Range("A2:C" & Lrow)
ReDim DS1(1 To Lrow - 1, 1 To 3)
ReDim DS2(1 To Lrow - 1, 1 To 3)
DS1 = Rdata
TP = 0
TD = 0
CD = 0
NR = 1


' Combine enteries with same name and date
For LoopNum1 = LBound(DS1, 1) To UBound(DS1, 1)
    If DS1(LoopNum1, 1) <> "" Then ' find next person
        CurPer = DS1(LoopNum1, 1)
        TP = TP + 1
        CD = 0
        For LoopNum2 = LBound(DS1, 1) To UBound(DS1, 1)
            If DS1(LoopNum2, 1) = CurPer Then ' find all instances of a person
                CurD = DS1(LoopNum2, 2)
                Tog = 0
                For LoopNum3 = LBound(DS1, 1) To UBound(DS1, 1)
                    If DS2(LoopNum3, 1) = CurPer And DS2(LoopNum3, 2) = CurD Then ' if person and date already exist on a list, then combine
                        DS2(LoopNum3, 3) = DS2(LoopNum3, 3) & "," & DS1(LoopNum2, 3)
                        DS1(LoopNum2, 1) = ""
                        Tog = 1
                        Exit For
                    End If
                Next LoopNum3
                If Tog = 0 Then ' if person and date do not exist on list, then add a new entery
                    CD = CD + 1
                    DS2(NR, 1) = DS1(LoopNum2, 1)
                    DS2(NR, 2) = DS1(LoopNum2, 2)
                    DS2(NR, 3) = DS1(LoopNum2, 3)
                    DS1(LoopNum2, 1) = ""
                    NR = NR + 1
                End If
            End If
        Next LoopNum2
        If CD > TD Then
            TD = CD
        End If
    End If
Next LoopNum1


' combine all enteries of each person into a single line
ReDim DS1(1 To TP, 1 To (TD + 1))
NR = 0
For LoopNum1 = LBound(DS2, 1) To UBound(DS2, 1)
    If DS2(LoopNum1, 1) <> "" Then ' look for the next person
        NR = NR + 1
        CurPer = DS2(LoopNum1, 1)
        CD = 0
        NH = LoopNum1
        DS1(NR, 1) = CurPer
        For LoopNum2 = LBound(DS2, 1) To UBound(DS2, 1)
            If DS2(LoopNum2, 1) = CurPer Then ' find total number of entries (dates) for the person
                CD = CD + 1
            End If
        Next LoopNum2
        For LoopNum2 = CD To 1 Step -1
            CurD = "1/1/1970"
            For LoopNum3 = LBound(DS2, 1) To UBound(DS2, 1)
                If DS2(LoopNum3, 1) = CurPer And DS2(LoopNum3, 2) > CurD Then ' find the entery with the highest date for the person
                    CurD = DS2(LoopNum3, 2)
                    NH = LoopNum3
                End If
            Next LoopNum3
            DS1(NR, (LoopNum2 + 1)) = DS2(NH, 2) & " (" & DS2(NH, 3) & ")"
            DS2(NH, 1) = ""
        Next LoopNum2
    End If
Next LoopNum1


' Put lisst in alphabetical order
ReDim DS2(1 To TP, 1 To (TD + 1))
NR = 1
NH = 0
For LoopNum1 = LBound(DS1, 1) To UBound(DS1, 1) ' one loop per person
    For LoopNum2 = LBound(DS1, 1) To UBound(DS1, 1) ' find the name with the lowest value
        If DS1(LoopNum2, 1) <> "" Then
            If NH = 0 Or LCase(DS1(LoopNum2, 1)) < LCase(CurPer) Then
                NH = LoopNum2
                CurPer = DS1(LoopNum2, 1)
            End If
        End If
    Next LoopNum2
    For LoopNum2 = LBound(DS1, 2) To UBound(DS1, 2)
        DS2(NR, LoopNum2) = DS1(NH, LoopNum2)
    Next LoopNum2
    DS1(NH, 1) = ""
    NR = NR + 1
    NH = 0
Next LoopNum1


' find the column number that corrisponds to Total number of date columns (TD) + 1 (for name)
' this uses some old code I wrote a while back
LastColumn = TD + 1
If LastColumn > 26 Then ' check if the Last Column is larger than Z
    FirstLetterAsNumber = Int(LastColumn / 26) ' divide the total number of columns by 26 and remove the remainder
    SecondLetterAsNumber = LastColumn - (FirstLetterAsNumber * 26) ' subtract the first number of columns used for the first letter
    ColumnString = Chr(FirstLetterAsNumber + 64) & Chr(SecondLetterAsNumber + 64) ' use ASCII to change the numbers into letters and put them together
Else ' if the Last Column is smaller the Z
    ColumnString = Chr(LastColumn + 64) ' use ASCII to change the column number into a letter
End If


'output formatted data to sheet2
Set Rdata = s2.Range("A1:" & ColumnString & TP)
Rdata = DS2


oops:
End Sub
 
Upvote 0
Thanks a lot all!
Spiralrain I will give a try and do only some minor adaptations, works nice! Superb.
 
Last edited:
Upvote 0
How to concatenate all the result in two columns (name in "A", result in "B"), instead using diferent columns for each unique "B" column dates? (using "; " instead of the next column).
 
Upvote 0
To simplify I change DATE to STRING, and no need to sort alphabetically after all (name in order they appear).
 
Upvote 0

Forum statistics

Threads
1,224,823
Messages
6,181,184
Members
453,020
Latest member
Mohamed Magdi Tawfiq Emam

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