I have two columns which have delimiters. Both columns will have the same number of delimiters. e.g a;b;c in column A and d;e;f in column B. In some columns there might not be any which is fine.
I want to be able split these columns into the exact number of rows and copy data from other columns as well. Therefore, the example above would have a total of 3 rows and result as below:
Col A Col B
a d
b e
c f
I have found the code below which i modified and works for a specified column but I want to apply this to multiple columns if possible.
Option Explicit
Sub splitcells()
Dim InxSplit As Long
Dim SplitCell() As String
Dim RowCrnt As Long
With Worksheets("Sheet1")
RowCrnt = 1
Do While True
If .Cells(RowCrnt, "L").Value = "" Then
Exit Do
End If
SplitCell = Split(.Cells(RowCrnt, "L").Value, "*")
If UBound(SplitCell) > 0 Then
.Cells(RowCrnt, "L").Value = SplitCell(0)
For InxSplit = 1 To UBound(SplitCell)
RowCrnt = RowCrnt + 1
.Rows(RowCrnt).EntireRow.Insert
.Cells(RowCrnt, "L").Value = SplitCell(InxSplit)
.Cells(RowCrnt, "A").Value = .Cells(RowCrnt - 1, "A").Value
.Cells(RowCrnt, "B").Value = .Cells(RowCrnt - 1, "B").Value
.Cells(RowCrnt, "C").Value = .Cells(RowCrnt - 1, "C").Value
.Cells(RowCrnt, "D").Value = .Cells(RowCrnt - 1, "D").Value
.Cells(RowCrnt, "E").Value = .Cells(RowCrnt - 1, "E").Value
.Cells(RowCrnt, "F").Value = .Cells(RowCrnt - 1, "F").Value
.Cells(RowCrnt, "G").Value = .Cells(RowCrnt - 1, "G").Value
.Cells(RowCrnt, "H").Value = .Cells(RowCrnt - 1, "H").Value
.Cells(RowCrnt, "I").Value = .Cells(RowCrnt - 1, "I").Value
.Cells(RowCrnt, "J").Value = .Cells(RowCrnt - 1, "J").Value
.Cells(RowCrnt, "K").Value = .Cells(RowCrnt - 1, "K").Value
Next
End If
RowCrnt = RowCrnt + 1
Loop
End With
End Sub
Is this possible? Any help is greatly appreciated.
Hello, took me a while, but I actually found a pretty fascinating / useful little procedure in this, so i was playing around a bit.
I've created a little procedure which, where you can specify from which column you want to take the data, and into which column you want to paste the data. With the following invocation:
The parse_column procedure is coded with the following way:
' parses all the values into an array
Private Sub parse_column(columnID As Integer, toColumn As Integer)
Dim totalstring As String
Dim lastrow As Integer
Dim ws As Worksheet: Set ws = Sheets("Sheet1") 'change to whatever sheet you are working with
Dim startingrow As Integer
startingrow = 2 'change to whatever row you want the procedure to start from _
(i skipped first row, because it acts as a header)
With ws
lastrow = .Cells(.Rows.Count, columnID).End(xlUp).Row
End With
Dim columnrange As Range: Set columnrange = Range(Cells(startingrow, columnID), Cells(lastrow, columnID))
For Each Rng In columnrange
totalstring = totalstring + Trim(Rng) ' we'll concatenate all the column values into a one string _
(if you wish to take spaces into accoumt, don't use trim)
Next Rng
Dim buffer() As String
ReDim buffer(Len(totalstring) - 1) '(avoid indexation by 0)
For i = 1 To Len(totalstring)
buffer(i - 1) = Mid(totalstring, i, 1) 'we fill in buffer with values
Next i
' we paste values to specified column
For i = (LBound(buffer)) To UBound(buffer)
ws.Cells((i + startingrow), toColumn).Value2 = buffer(i)
Next i
End Sub
So for example, if you wanted to parse all data from Column 1 (A) to Column 4 (D) you would invoke it in your procedure the following way
Private Sub splitcells()
Call parse_column(1, 4)
End Sub
Beauty of this all is, you can simply loop this for all the columns you have in your sheet by a simple static for loop increment. So for example, if we had 3 columns:
Let's presume we have the following data:
^ Note how Column C doesn't even have to be limited to 3 characters
We could use a simple for loop to loop through all 3 columns and paste them to the 4th next column to the right.
Private Sub splitcells()
Dim i As Integer
For i = 1 To 3
Call parse_column(i, (i + 4))
Next i
End Sub
Would produce the following result:
Related
I'm writing a code to loop through an excel sheet and changing the text (in column B) to uppercase/lowercase, depending on the value of cell in column N on the same row.
Macros purpose:
loop through cells in column B starting at row 2 and changing the string from upper to lowercase or vice versa, depending on the value of the cell in column N (lowercase if value = 5, other cases text should be uppercase)
Code I've got so far:
Sub CAPS()
'
' CAPS Macro
'
Dim Rang As Integer
Dim j As Integer
j = 2
For Each N In Source.Range("N2:N10000") ' Do 10000 rows
Rang = Cells(j, 14)
If Rang = 5 Then
Cells(j, 2).Range("A1").Select
ActiveCell.Value = LCase$(ActiveCell.Text)
Else
ActiveCell.Value = UCase$(ActiveCell.Text)
j = j + 1
End If
Next N
End Sub
I'm a little bit stuck in the looping part, not really a clue how to fix the error(s) in the current code.
Thanks in advance :)
Sub CAPS()
'
' CAPS Macro
'
Dim N as long 'use long here as integer is limite to a 32b character
For N Is 2 to 10000 ' Do 10000 rows
If Cells(N, 14) = 5 Then
Cells(N, 2) = LCase(Cells(N,2)
Else
Cells(N, 2) = UCase(Cells(N,2)
EndIf
Next N
End Sub
This should do the trick, untested though.
You currently have a fixed number of rows you want to test. To optimize your code you could first check how many rows are filled with data. To do so you can use:
DIM lastrow as long
lastrow = Cells(Rows.Count, "B").End(xlUp).Row
And then make the loop with For N Is 2 to lastrow
Also it is good practice to explicitly reference your worksheets, as this prevents undesired results. For example you click on another worksheet whilst the code is running it will continue formatting on that sheet. To do so declare a variable as your worksheet:
DIM ws as worksheet
And set a value to your variable, in this case Sheet1.
Set ws as ThisWorkbook.Worksheets("Sheet1")
Now every time you reference a Cells(), you explicitly say on what sheet that has to be by adding ws. in front of it like such: ws.Cells()
To summarize all that into your code:
Sub CAPS()
'
' CAPS Macro
'
Dim N as long 'use long here as integer is limite to a 32b character
Dim lastrow as long
Dim ws as worksheet
Set ws = ThisWorkbook.Worksheets("Sheet1") 'Set the code to run on Sheet 1 of your current workbook.
lastrow = ws.Cells(Rows.Count, "B").End(xlUp).Row
For N Is 2 to lastrow ' Do all rows that have data in column B
If ws.Cells(N, 14) = 5 Then
ws.Cells(N, 2) = LCase(ws.Cells(N,2)
Else
ws.Cells(N, 2) = UCase(ws.Cells(N,2)
EndIf
Next N
End Sub
Try processing in an array,
Sub CAPS()
'
' CAPS Macro
'
Dim arr As variant, j As Integer
with worksheets("sheet1")
arr = .range(.cells(2, "B"), .cells(.rows.count, "B").end(xlup).offset(0, 12)).value2
for j= lbound(arr, 1) to ubound(arr, 1)
if arr(j, 13) = 5 then
arr(j, 1) = lcase(arr(j, 1))
else
arr(j, 1) = ucase(arr(j, 1))
end if
next j
redim preserve arr(lbound(arr, 1) to ubound(arr, 1), 1 to 1)
.cells(2, "B").resize(ubound(arr, 1), ubound(arr, 2)) = arr
end with
End Sub
You may try something like this...
Sub CAPS()
Dim ws As Worksheet
Dim lr As Long, i As Long
Application.ScreenUpdating = False
Set ws = Sheets("Sheet1") 'Sheet where you have to change the letter case
lr = ws.Cells(Rows.Count, "B").End(xlUp).Row
For i = 2 To lr
Select Case ws.Cells(i, "N")
Case 5
ws.Cells(i, "B") = LCase(ws.Cells(i, "B"))
Case Else
ws.Cells(i, "B") = UCase(ws.Cells(i, "B"))
End Select
Next i
Application.ScreenUpdating = True
End Sub
Another approach using for each loop with Range:
Sub UCaseLCase()
Dim rng, cell As Range
Dim Test As Integer
Test = 5
Set rng = Range(Cells(2, 14), Cells(10000, 14))
For Each cell In rng.Cells
If cell.Value = Test Then
cell.Offset(0, -12) = LCase(cell.Offset(0, -12))
Else
cell.Offset(0, -12) = UCase(cell.Offset(0, -12))
End If
Next cell
End Sub
I know you said in your question starting at row 2 but it's easier just going from last row until row 2.
Hope this can help or at least, learn something new about Loops :)
Sub CAPS()
Dim j As Integer
For j = Range("B2").End(xlDown).Row To 2 Step -1
If Range("N" & j).Value = 5 Then
'uppercase
Range("B" & j).Value = UCase(Range("B" & j).Value)
Else
'lowercase
Range("B" & j).Value = LCase(Range("B" & j).Value)
End If
Next j
End Sub
My code works below works but I'd like to add one function. I've got a large data sheet with each line repeated three times. Within each set of three I've added a month twice. The purpose is to smooth out forecasted sales into one month and two months beyond the estimated shipping date. Now I'd like to multiply my the values in column E by factors into column F. The original line in each set of three will =50%*E:E in column F, the second line will have =30%*E:E in column F, and the third line will have =20%*E:E in Column F. This process should be repeated continually for every set of three lines. Problem: My current code does give me the correct value, however the values are two cells lower than they need to be. Thanks for any help in advance! My current code is below:
Public Sub DateAdd()
Dim r As Long
Dim l As Long
Dim Quant As Long
Dim dttTemp As Date
Dim ws As Worksheet
Set ws = ThisWorkbook.Worksheets("SalesForce Projects")
Application.ScreenUpdating = False
For r = ws.Range("A" & ws.Rows.Count).End(xlUp).Row To 1 Step -1
With ws.Cells(r, 1).EntireRow
.Copy
.Resize(2).Offset(1, 0).Insert Shift:=xlDown
End With
dttTemp = ws.Cells(r, "S").Value
ws.Cells(r + 1, "S").Value = DateSerial(Year(dttTemp), Month(dttTemp) + 1,
Day(dttTemp))
ws.Cells(r + 2, "S").Value = DateSerial(Year(dttTemp), Month(dttTemp) + 2,
Day(dttTemp))
Next r
Application.ScreenUpdating = True
' This is where my code is bad
For l = ws.Range("A" & ws.Rows.Count).End(xlUp).Row To 1 Step -3
Quant = ws.Cells(l, "E").Value
ws.Cells(l, "F").Value = Cells(l, "E") * 0.5
ws.Cells(l + 1, "F").Value = Cells(l, "E") * 0.3
ws.Cells(l + 2, "F").Value = Cells(l, "E") * 0.2
Next l
End Sub
Why not do it in the first loop like below?
Public Sub DateAdd()
Dim r As Long
Dim l As Long
Dim Quant As Long
Dim dttTemp As Date
Dim ws As Worksheet
Set ws = ThisWorkbook.Worksheets("SalesForce Projects")
Application.ScreenUpdating = False
For r = ws.Range("A" & ws.Rows.Count).End(xlUp).Row To 1 Step -1
With ws.Cells(r, 1).EntireRow
.Copy
.Resize(2).Offset(1, 0).Insert Shift:=xlDown
End With
dttTemp = ws.Cells(r, "S").Value
ws.Cells(r, "F").Value = Cells(r, "E") * 0.5 '\\ First line
ws.Cells(r + 1, "S").Value = DateSerial(Year(dttTemp), Month(dttTemp) + 1, Day(dttTemp))
ws.Cells(r + 1, "F").Value = Cells(r, "E") * 0.3 '\\ Second line
ws.Cells(r + 2, "S").Value = DateSerial(Year(dttTemp), Month(dttTemp) + 2, Day(dttTemp))
ws.Cells(r + 2, "F").Value = Cells(r, "E") * 0.2 '\\ Third line
Next r
Application.ScreenUpdating = True
End Sub
When you say "50% of the value in column E," you could mean the entire value of all 90 in column E or just the value of the three cells in a set. If you mean the first then (I assume that row 1 is headings, so your values start at row 2.) In cell F2 enter the formula
=Sum(E:E)*.5
In cell F3 enter
=SUM(E:E)*.3
In cell F4 enter
=SUM(E:E)*.2
If you mean the other option then enter:
In F2 =Sum(E2:E4)*.5
in F3 =Sum(E2:E4)*.3
In F4 =Sum(E2:e4)*.2
Now select f2:f4. Place your mouse on the bottom right corner and you should see the cursor change to a small black cross. Double-click and the formulas will be replicated down the sheet. If you have more than one sheet to fill then control click on the tab names before starting this process to similtainoiusly copy to all selected sheets.
In this case there is no need to loop backwards. Update the For statement to the following:
For l = 1 to ws.Range("A" & ws.Rows.Count).End(xlUp).Row Step 3
Then it should produce desired results.
I need to create a sub to consolidate some data. I have several rows (anywhere from 4k to 20k) that I need to consolidate and sum the values from each column (from C to N).
The input data looks like this:
input
For the output, it should sum the columns for each SKU (Column A) and delete the rest.
Like this:
output
It should be simple enough, but I can’t seem to come up with an appropriate solution. I tried using an array with a scripting dictionary but I can’t figure out how to store more than a single value for each key. Sample (unfinished) code:
Dim sArray As Variant
Dim dict As Object
Set dict = CreateObject("Scripting.Dictionary")
sArray = Range(currentRange).Value
For i = 1 To UBound(sArray, 1)
For j = 3 To UBound(sArray, 2)
If dict.exists(sArray(i, 1)) = False Then
dict.Add sArray(i, 1), sArray(i, j)
Else
'this part is very wrong:
dict(sArray(i, 1)) = dict(sArray(i, j)) + sArray(i, j)
End If
Next
Next
Thank you very much in advance!
Try this, It sums the values in Column Q:AB then paste them back and removes the duplicates.
Sub dupremove()
Dim ws As Worksheet
Dim lastrow As Long
Set ws = Sheets("Sheet12") ' Change to your sheet
With ws
lastrow = .Range("A" & .Rows.Count).End(xlUp).Row
With .Range("C2:N" & lastrow)
.Offset(, 14).FormulaR1C1 = "=SUMIF(C1,RC1,C[-14])"
.Value = .Offset(, 14).Value
.Offset(, 14).ClearContents
End With
With .Range("A1:N" & lastrow)
.Value = .Value
.RemoveDuplicates 1, xlYes
End With
End With
Before:
After:
I came up with the following solution instead and it took 30 seconds to run it (not entirely my own idea, borrowed some code from someplace else):
Sub dupes()
Dim MyRange As Range
Dim RowNum As Long
RowNum = 1
With Application
.ScreenUpdating = False
.EnableEvents = False
End With
Set MyRange = Range("A2:N14200") 'for test only, on the real sub it's dynamic
MyRange.Sort key1:=Range("A2"), order1:=xlAscending
For Each Row In MyRange
With Cells
While Cells(RowNum, 1) = Cells(RowNum + 1, 1) And Cells(RowNum + 1, 1) <> "" 'very important the second condition or it will continue to loop forever
For i = 3 To 14
Cells(RowNum, i) = Cells(RowNum, i) + Cells(RowNum + 1, i)
Next
Rows(RowNum + 1).EntireRow.Delete
Wend
End With
RowNum = RowNum + 1
Next
With Application
.ScreenUpdating = True
.EnableEvents = True
End With
End Sub
It's kinda messy but it does the trick. Thanks to everyone!
I have an Excel sheet with cells in some columns merged:
I need to normalize it, such that the cells in the first column are unmerged (those should be considered the true "rows"), but such that unmerged groups of cells (in these "rows") are put into a single cell with newlines to retain the list-like content:
Note that in some columns besides the first, there may also be some merged cells, but in any case the first column should determine what a "row" in the output sheet should look like.
Does such a VBA script exist to do this?
UPDATE: Here's a little pseudo-code for what I was thinking:
foreach row:
determine height of merged cell in column A
unmerge cell in column A (content is in top cell of range?)
for each column after A:
if cell is merged, unmerge (content is in top cell of range?)
else concatenate cell contents with newline separator in top cell of row range
cleanup excess rows from the unmerging
Unfortunately I think there's a bit of complexity in some of these steps.
UPDATE#2: Based on the accepted answer, I created some new code to accomplish my goals:
Sub dlo()
Dim LastRow&, r&, c&, rowheight&, n&, Content$, NewText$
Application.DisplayAlerts = False
LastRow = Cells(Rows.Count, 1).End(xlUp).Row
LastCol = Cells(1, Columns.Count).End(xlToLeft).Column
For r = 1 To LastRow
If Cells(r, 1).MergeCells Then
rowheight = Cells(r, 1).MergeArea.Cells.Count
For c = 1 To LastCol
NewText = vbNullString
For rr = r To (r + rowheight - 1)
Content = Cells(rr, c)
Cells(rr, c) = vbNullString
NewText = NewText & vbCrLf & Content
Next
Cells(r, c).UnMerge
Cells(r, c) = NewText
Next
'Cells(i + 1, 1).Resize(k - 1, 2).Delete Shift:=xlUp
'LastRow = LastRow - rowheight + 1
End If
DoEvents
Next
Application.DisplayAlerts = True
End Sub
The only thing I didn't finish was the deletion of resulting blank rows (I ended up just commenting those out since I knew a could just sort the table to eliminate the blanks).
If anyone has better ideas for how to describe this, please let me know so I can edit the title... I have a feeling this is not a rare need, so I'd like to help other find this.
Is this what you asking for?
Sub dlo()
Dim LastRow&, i&, j&, k&, n&, Content$, Text$
Application.DisplayAlerts = False
LastRow = Cells(Rows.Count, 1).End(xlUp).Row
Do
i = i + 1
Text = vbNullString
If Cells(i, 1).MergeCells Then
k = Cells(i, 1).MergeArea.Cells.Count
n = Cells(i, 1).RowHeight
For j = 1 To k
Content = Cells(j + i - 1, 2)
Cells(j + i - 1, 2) = vbNullString
Text = Text & vbCrLf & Content
Next
Cells(i, 1).UnMerge
Cells(i, 2) = Mid(Text, 3)
Cells(i + 1, 1).Resize(k - 1, 2).Delete Shift:=xlUp
Rows(i).RowHeight = n * k
NewLastRow = LastRow - k + 1
End If
DoEvents
Loop Until i = NewLastRow
Application.DisplayAlerts = True
End Sub
The above code works fine to my dummy data.
I have similar question to
[combine Rows with Duplicate Values][1]
Excel VBA - Combine rows with duplicate values in one cell and merge values in other cell
I have data in this format (rows are sorted)
Pub ID CH Ref
no 15 1 t2
no 15 1 t88
yes 15 2 t3
yes 15 2 t3
yes 15 2 t6
compare adjacent rows (say row 4 and 5) , if col 2 and 3 match then if col 4 different merge col4, delete row. if col 2,3,4 match then delete row, don't merge col 4
Desired Output
key ID CH Text
no 15 1 t2 t88
yes 15 2 t3 t6
This first code section doesn't work right
Sub mergeCategoryValues()
Dim lngRow As Long
With ActiveSheet
Dim columnToMatch1 As Integer: columnToMatch1 = 2
Dim columnToMatch2 As Integer: columnToMatch2 = 3
Dim columnToConcatenate As Integer: columnToConcatenate = 4
lngRow = .Cells(65536, columnToMatch1).End(xlUp).row
.Cells(columnToMatch1).CurrentRegion.Sort key1:=.Cells(columnToMatch1), Header:=xlYes
.Cells(columnToMatch2).CurrentRegion.Sort key1:=.Cells(columnToMatch2), Header:=xlYes
Do
If .Cells(lngRow, columnToMatch1) = .Cells(lngRow - 1, columnToMatch1) Then 'check col 2 row lngRow, lngRow-1
If .Cells(lngRow, columnToMatch2) = .Cells(lngRow - 1, columnToMatch2) Then 'check col 3 row lngRow, lngRow-1
If .Cells(lngRow - 1, columnToConcatenate) = .Cells(lngRow, columnToConcatenate) Then
Else
.Cells(lngRow - 1, columnToConcatenate) = .Cells(lngRow - 1, columnToConcatenate) & "; " & .Cells(lngRow, columnToConcatenate)
End If
.Rows(lngRow).Delete
End If
End If
lngRow = lngRow - 1
Loop Until lngRow = 1
End With
Actual Output incorrect because when cells merge t3 will not match t3;t6, my comparison on col 4 will only work in very simple case only.
Actual Output
key ID CH Text
no 15 1 t2; t88
yes 15 2 t3; t3; t6
Therefore, I had to add these two sections to split the Concatenate cells and then remove duplicates
'split cell in Col d to col e+ delimited by ;
With Range("D2:D6", Range("D" & Rows.Count).End(xlUp))
.Replace ";", " ", xlPart
.TextToColumns other:=True
End With
'remove duplicates in each row
Dim x, y(), i&, j&, k&, s$
With ActiveSheet.UsedRange
x = .Value: ReDim y(1 To UBound(x, 1), 1 To UBound(x, 2))
For i = 1 To UBound(x)
For j = 1 To UBound(x, 2)
If Len(x(i, j)) Then
If InStr(s & "|", "|" & x(i, j) & "|") = 0 Then _
s = s & "|" & x(i, j): k = k + 1: y(i, k) = x(i, j)
End If
Next j: s = vbNullString: k = 0
Next i
.Value = y()
End With
End Sub
With additional code output is
Pub ID CH Ref
no 15 1 t2 t88
yes 15 2 t3 t6
Question: There must be much easier way to do this right than use three different methods? How about inserting new columns 5+ if col 4 items don't match?
Note: Remove duplicates code was found from user nilem at excelforum.
Edit: Col 1 will always be same if Col 2 and 3 match. If solution is much easier we can assume Col 1 is blank and ignore data.
I have printed book lookup table and need to convert to a simple format that will be used in equipment that use a 1960's language which has very limited commands. I am trying to preformat this data so I only need to search for one row that has all info.
Col D final output can be in col D with delimiter or into col D-K (only 8 max Ref) because I will parse to use on other machine. Whatever method is easier.
The canonical practise for deleting rows is to start at the bottom and work toward the top. In this manner, rows are not skipped. The trick here is to find rows above the current position that match columns B and C and concatenate the strings from column D before removing the row. There are several good worksheet formulas that can acquire the row number of a two-column-match. Putting one of them into practise with application.Evaluate would seem to be the most expedient method of collecting the values from column D.
Sub dedupe_and_collect()
Dim rw As Long, mr As Long, wsn As String
With ActiveSheet '<- set this worksheet reference properly!
wsn = .Name
With .Cells(1, 1).CurrentRegion
.RemoveDuplicates Columns:=Array(2, 3, 4), Header:=xlYes
End With
With .Cells(1, 1).CurrentRegion 'redefinition after duplicate removal
For rw = .Rows.Count To 2 Step -1 'walk backwards when deleting rows
If Application.CountIfs(.Columns(2), .Cells(rw, 2).Value, .Columns(3), .Cells(rw, 3).Value) > 1 Then
mr = Application.Evaluate("MIN(INDEX(ROW(1:" & rw & ")+(('" & wsn & "'!B1:B" & rw & "<>'" & wsn & "'!B" & rw & ")+('" & wsn & "'!C1:C" & rw & "<>'" & wsn & "'!C" & rw & "))*1E+99, , ))")
'concatenate column D
'.Cells(mr, 4) = .Cells(mr, 4).Value & "; " & .Cells(rw, 4).Value
'next free column from column D
.Cells(mr, Columns.Count).End(xlToLeft).Offset(0, 1) = .Cells(rw, 4).Value
.Rows(rw).EntireRow.Delete
End If
Next rw
End With
End With
End Sub
The removal of records on a three-column-match is done with the VBA equivalent of the Date ► Data Tools ► Remove Duplicates command. This only considers columns B, C and D and deletes the lower duplicates (keeping the ones closest to row 1). If Column A is important in this respect, additional coding would have to be added.
It's unclear to me whether you wanted column D as delimited string or separate cells as an end result. Could you clarify?
As I wrote above, I would iterate through the data and collect things into the User Defined Object. There is no need for the data to be sorted in this method; and duplicate REF's will be omitted.
One advantage of a User Defined Object is that it makes debugging easier as you can see more clearly what you have done.
We combine every line where ID and CH are the same, by using the property of the Collection object to raise an error if identical keys are used.
So far as combining the Ref's in a single cell with a delimiter, vs individual cells in columns D:K, either can be done simply. I chose to separate into columns, but changing it to combine into a single column would be trivial.
After Inserting the Class Module, you must rename it: cID_CH
You will note I placed the results on a separate worksheets. You could overwrite the original data, but I would advise against that.
Class Module
Option Explicit
Private pID As Long
Private pCH As Long
Private pPUB As String
Private pREF As String
Private pcolREF As Collection
Public Property Get ID() As Long
ID = pID
End Property
Public Property Let ID(Value As Long)
pID = Value
End Property
Public Property Get CH() As Long
CH = pCH
End Property
Public Property Let CH(Value As Long)
pCH = Value
End Property
Public Property Get PUB() As String
PUB = pPUB
End Property
Public Property Let PUB(Value As String)
pPUB = Value
End Property
Public Property Get REF() As String
REF = pREF
End Property
Public Property Let REF(Value As String)
pREF = Value
End Property
Public Property Get colREF() As Collection
Set colREF = pcolREF
End Property
Public Sub ADD(refVAL As String)
On Error Resume Next
pcolREF.ADD refVAL, refVAL
On Error GoTo 0
End Sub
Private Sub Class_Initialize()
Set pcolREF = New Collection
End Sub
Regular Module
Option Explicit
Sub CombineDUPS()
Dim wsSRC As Worksheet, wsRES As Worksheet
Dim vSRC As Variant, vRES() As Variant, rRES As Range
Dim cI As cID_CH, colI As Collection
Dim I As Long, J As Long
Dim S As String
'Set source and results worksheets and results range
Set wsSRC = Worksheets("sheet1")
Set wsRES = Worksheets("sheet2")
Set rRES = wsRES.Cells(1, 1)
'Get Source data
With wsSRC
vSRC = .Range("A2", .Cells(.Rows.Count, "D").End(xlUp))
End With
'Collect and combine data
Set colI = New Collection
On Error Resume Next
For I = 1 To UBound(vSRC, 1)
Set cI = New cID_CH
With cI
.PUB = vSRC(I, 1)
.ID = vSRC(I, 2)
.CH = vSRC(I, 3)
.REF = vSRC(I, 4)
.ADD .REF
S = CStr(.ID & "|" & .CH)
colI.ADD cI, S
If Err.Number = 457 Then
Err.Clear
colI(S).ADD .REF
ElseIf Err.Number <> 0 Then
Debug.Print Err.Number, Err.Description
Stop
End If
End With
Next I
On Error GoTo 0
'Create and populate Results Array
ReDim vRES(0 To colI.Count, 1 To 11)
'Header row
vRES(0, 1) = "Pub"
vRES(0, 2) = "ID"
vRES(0, 3) = "CH"
vRES(0, 4) = "Ref"
'populate array
For I = 1 To colI.Count
With colI(I)
vRES(I, 1) = .PUB
vRES(I, 2) = .ID
vRES(I, 3) = .CH
For J = 1 To .colREF.Count
vRES(I, J + 3) = .colREF(J)
Next J
End With
Next I
'Write the results to the worksheet
Set rRES = rRES.Resize(UBound(vRES, 1) + 1, UBound(vRES, 2))
With rRES
.EntireColumn.Clear
.Value = vRES
With .Rows(1)
.Font.Bold = True
.HorizontalAlignment = xlCenter
Range(.Cells(4), .Cells(11)).HorizontalAlignment = xlCenterAcrossSelection
End With
.EntireColumn.AutoFit
End With
End Sub
Original
Processed Results
variant using dictionary below
Sub test()
Dim Dic As Object: Set Dic = CreateObject("Scripting.Dictionary")
Dic.Comparemode = vbTextCompare
Dim Cl As Range, x$, y$, i&, Key As Variant
For Each Cl In Range("B2:B" & Cells(Rows.Count, "B").End(xlUp).Row)
x = Cl.Value & "|" & Cl.Offset(, 1).Value
y = Cl.Offset(, 2).Value
If Not Dic.exists(x) Then
Dic.Add x, Cl.Offset(, -1).Value & "|" & y & "|"
ElseIf Dic.exists(x) And Not LCase(Dic(x)) Like "*|" & LCase(y) & "|*" Then
Dic(x) = Dic(x) & "|" & y & "|"
End If
Next Cl
Range("A2:D" & Cells(Rows.Count, "B").End(xlUp).Row).ClearContents
i = 2
For Each Key In Dic
Cells(i, "A") = Split(Dic(Key), "|")(0)
Range(Cells(i, "B"), Cells(i, "C")) = Split(Key, "|")
Cells(i, "D") = Replace(Split(Replace(Dic(Key), "||", ";"), "|")(1), ":", ";")
i = i + 1
Next Key
Set Dic = Nothing
End Sub
before
after