I have the following dataset
Key ID Status 1 Status 2 Order ID
1 A1 FALSE TRUE 1234-USF-0025
1 A1 FALSE TRUE 1234-USF-0026
1 A1 FALSE TRUE 1234-USF-0027
2 A1 TRUE TRUE 1234-USF-0025
2 A1 TRUE TRUE 1234-USF-0026
2 A1 TRUE TRUE 1234-USF-0027
3 A1 FALSE TRUE 1234-USF-0025
3 A1 FALSE TRUE 1234-USF-0026
3 A1 FALSE TRUE 1234-USF-0027
4 A2 TRUE TRUE 1234-USF-0028
4 A2 TRUE TRUE 1234-USF-0029
4 A2 TRUE TRUE 1234-USF-0030
5 A3 TRUE TRUE 1234-USF-0031
5 A3 TRUE TRUE 1234-USF-0032
5 A3 TRUE TRUE 1234-USF-0033
6 A4 TRUE TRUE 1234-USF-0034
6 A4 TRUE TRUE 1234-USF-0035
6 A4 TRUE TRUE 1234-USF-0036
I need the following
Order ID ID TRUE FALSE
1234-USF-0025 A1 2 1,3
1234-USF-0026 A1 2 1,3
1234-USF-0027 A1 2 1,3
1234-USF-0028 A2 4
1234-USF-0029 A2 4
1234-USF-0030 A2 4
1234-USF-0031 A3 5
1234-USF-0032 A3 5
1234-USF-0033 A3 5
1234-USF-0034 A4 6
1234-USF-0035 A4 6
1234-USF-0036 A4 6
In the second table (the one I need), each Order ID is listed next to the corresponding ID. Although A1 is listed 9 times in the original dataset, there are only 3 unique Order IDs in total for A1. However, A1 is also associated with 3 different Keys.
The goal is to concatenate the Keys for each Order ID and ID combination, where both Status 1 and Status 2 are TRUE and list them in the TRUE column. For those Order ID and ID combinations where at least one Status is FALSE, the Keys should be listed under the FALSE column.
What I've tried
I tried starting with just the TRUE column, using INDEX-MATCH as an array formula, and although I know the below formula would not work for my desired end goal, I was trying to start small and build upon the formula. Unfortunately, my knowledge of arrays is limited, I'm not sure how to proceed because I don't understand why it returns what it does or how to reach my goal from this point.
=INDEX($C$2:$C$19,MATCH(1,($H2 = $B$2:$B$19) * ($G2 = $E$2:$E$19)))
Next I tried to break the pieces apart in the original dataset, but got stuck on how to proceed. I think this is the easier solution, but I can't figure out how to concatenate based on the required criteria.
TRUE: =IF(AND($C2=TRUE,$D2=TRUE),$A2,"")
FALSE: =IF(OR($C2<>TRUE,$D2<>TRUE),$A2,"")
Notes:
An ID is associated with at least one Key, but can have more
Order ID can repeat for the same ID but only for different Keys for that ID.
I am open to a VBA, Python or R based solution as well, but not sure how to even start a script for this task, so I've been focusing on Excel.
This is kinda a verbose solution and assumes your data is exactly as you posted (and also on sheet1), but it works (I think). You'll also need to create a second sheet for the output data. Let me know if you're not sure where to post this code/how to run it.
Sub DoStuff()
'Initialize the output sheet
Sheet2.Cells.Clear
Sheet2.Cells(1, 1) = "Order ID"
Sheet2.Cells(1, 2) = "ID"
Sheet2.Cells(1, 3) = "TRUE"
Sheet2.Cells(1, 4) = "FALSE"
newRow = 2
'Loop through the first sheet and remove duplicates
lastRow = Sheet1.Range("E:E").Cells.SpecialCells(xlCellTypeConstants).Count
For i = 2 To lastRow
exists = False
For j = 2 To newRow
If Sheet1.Cells(i, 5).Value = Sheet2.Cells(j, 1).Value Then
exists = True
Exit For
End If
Next
If exists = False Then
Sheet2.Cells(newRow, 1) = Sheet1.Cells(i, 5).Value
Sheet2.Cells(newRow, 2) = Sheet1.Cells(i, 2).Value
'Populate the true and false columns
For k = 2 To lastRow
If Sheet1.Cells(k, 5).Value = Sheet1.Cells(i, 5).Value Then
If Sheet1.Cells(k, 3).Value = True And Sheet1.Cells(k, 4).Value = True Then
Sheet2.Cells(newRow, 3) = Sheet2.Cells(newRow, 3).Value & Sheet1.Cells(k, 1).Value & ", "
Else
Sheet2.Cells(newRow, 4) = Sheet2.Cells(newRow, 4).Value & Sheet1.Cells(k, 1).Value & ", "
End If
End If
Next
'Remove extra characters, if there are any
If Sheet2.Cells(newRow, 3).Value <> "" Then
Sheet2.Cells(newRow, 3).Value = Left(Sheet2.Cells(newRow, 3).Value, Len(Sheet2.Cells(newRow, 3).Value) - 2)
End If
If Sheet2.Cells(newRow, 4).Value <> "" Then
Sheet2.Cells(newRow, 4).Value = Left(Sheet2.Cells(newRow, 4).Value, Len(Sheet2.Cells(newRow, 4).Value) - 2)
End If
newRow = newRow + 1
End If
Next
End Sub
Results using your data as posted:
I used a dictionary and a Class module to help gather and transform the data.
It also has the advantage of being a bit easier to follow and maintain since the named parameters are more or less obvious.
I also "did the work" in a VBA array as, with any sizeable database, the execution speed will be considerably faster.
It should be obvious within the code where to define the worksheets and ranges you want to use for your source data and results
Regular Module
Option Explicit
'Set reference to Microsoft Scripting Runtime
Sub orgOrders()
Dim wsSrc As Worksheet, wsRes As Worksheet, rRes As Range
Dim vSrc As Variant, vRes As Variant
Dim dOrds As Dictionary, cOrd As cOrder
Dim I As Long, V As Variant
Dim sKey As String
'set source and result worksheet and range
Set wsSrc = Worksheets("Sheet2")
Set wsRes = Worksheets("Sheet2")
Set rRes = wsRes.Cells(1, 10)
'read source data into array
With wsSrc
vSrc = .Range(.Cells(1, 1), .Cells(.Rows.Count, 5).End(xlUp))
End With
'Read into order dictionary
Set dOrds = New Dictionary
For I = 2 To UBound(vSrc, 1)
Set cOrd = New cOrder
sKey = vSrc(I, 5) 'Order ID
With cOrd
.ID = vSrc(I, 2)
.Key = vSrc(I, 1)
.Status1 = vSrc(I, 3)
.Status2 = vSrc(I, 4)
.addTrueFalse .Key, .Status1, .Status2
If Not dOrds.Exists(sKey) Then
dOrds.Add Key:=sKey, Item:=cOrd
Else
dOrds(sKey).addTrueFalse .Key, .Status1, .Status2
End If
End With
Next I
'Dim Results array
ReDim vRes(0 To dOrds.Count, 1 To 4)
'Headers
vRes(0, 1) = "Order ID"
vRes(0, 2) = "ID"
vRes(0, 3) = "TRUE"
vRes(0, 4) = "FALSE"
'Data
I = 0
For Each V In dOrds.Keys
I = I + 1
With dOrds(V)
vRes(I, 1) = V
vRes(I, 2) = .ID
vRes(I, 3) = .TrueFalse(True)
vRes(I, 4) = .TrueFalse(False)
End With
Next V
'Write results
Set rRes = rRes.Resize(UBound(vRes, 1) + 1, UBound(vRes, 2))
With rRes
.EntireColumn.Clear
.Value = vRes
.Rows(1).Font.Bold = True
With .EntireColumn
.HorizontalAlignment = xlCenter
.AutoFit
End With
End With
End Sub
Class Module
RENAME this module cOrder
Option Explicit
Private pKey As Long
Private pID As String
Private pStatus1 As Boolean
Private pStatus2 As Boolean
Private pTrueFalse As Dictionary
Public Property Get Key() As Long
Key = pKey
End Property
Public Property Let Key(Value As Long)
pKey = Value
End Property
Public Property Get ID() As String
ID = pID
End Property
Public Property Let ID(Value As String)
pID = Value
End Property
Public Property Get Status1() As Boolean
Status1 = pStatus1
End Property
Public Property Let Status1(Value As Boolean)
pStatus1 = Value
End Property
Public Property Get Status2() As Boolean
Status2 = pStatus2
End Property
Public Property Let Status2(Value As Boolean)
pStatus2 = Value
End Property
Public Function addTrueFalse(Key As Long, Status1 As Boolean, Status2 As Boolean)
If Status1 = True And Status2 = True Then
If Not pTrueFalse.Exists(True) Then
pTrueFalse.Add Key:=True, Item:=Key
Else
pTrueFalse(True) = pTrueFalse(True) & "," & Key
End If
Else
If Not pTrueFalse.Exists(False) Then
pTrueFalse.Add Key:=False, Item:=Key
Else
pTrueFalse(False) = pTrueFalse(False) & "," & Key
End If
End If
End Function
Public Property Get TrueFalse() As Dictionary
Set TrueFalse = pTrueFalse
End Property
Private Sub Class_Initialize()
Set pTrueFalse = New Dictionary
End Sub
Related
I have workbook with one sheet and 80k lines as shown below. same Client may come up 100 times in sheet1.i need to look for value under ddindex value "1" and value under tier value "2", if these condition match then pick client name and put in new sheet ( sheet2) with their value from column data size. If same client comes again using above condition while going row by row in sheet1 then add( sum it with previous value) data size in second sheet ( sheet2). And also get latest created date and expiry date for same client in second sheet. any idea how to achieve this using VBA ??
so far i come up below code
Option Explicit
Sub find()
Dim i As Long
Dim sheets As Variant
Dim sheet As Variant
Application.ScreenUpdating = False
Application.Calculation = xlCalculationManual
Application.ScreenUpdating = False
Application.EnableEvents = False
Set ws = ThisWorkbook.sheets("Sheet2")
For i = 2 To ActiveSheet.sheets("sheet1").Range("A2").End(xlDown).Row
If Cells(i, 4).Value = 1 And Cells(i, 6).Value = 2 Then
ws.Range(1 & i).Value = Cells(i, 1).Value
ws.Range("A" & i).Value = Cells(i, 1).Value
End If
Next
Application.Calculation = xlCalculationAutomatic
Application.ScreenUpdating = True
Application.ScreenUpdating = True
Application.EnableEvents = True
End Sub
Try something like this:
Sub Summarize()
Dim i As Long, ws1 As Worksheet, ws2 As Worksheet, data, m, client
Dim dict As Object, dataOut, rw As Long
Set dict = CreateObject("scripting.dictionary") 'for tracking unique client id's
Set ws1 = ThisWorkbook.sheets("Sheet1")
Set ws2 = ThisWorkbook.sheets("Sheet2")
data = ws1.Range("A2:F" & ws1.Cells(Rows.Count, 1).End(xlUp).Row).Value 'data to array
ReDim dataOut(1 To UBound(data, 1), 1 To UBound(data, 2)) 'size output array
rw = 0
For i = 1 To UBound(data, 1) 'loop over rows in the array
If data(i, 5) = 1 And data(i, 6) = 2 Then 'processing this row?
client = data(i, 1)
If Not dict.exists(client) Then 'first time for this client?
rw = rw + 1 'increment "row" counter
dict.Add client, rw 'store position in dictionary
dataOut(rw, 1) = client 'add the client id
End If
rw = dict(client) 'find the array "row" to update
If data(i, 2) > dataOut(rw, 2) Then
dataOut(rw, 2) = data(i, 2)
dataOut(rw, 3) = data(i, 3)
End If
dataOut(rw, 4) = dataOut(rw, 4) + data(i, 4)
dataOut(rw, 5) = data(i, 5)
dataOut(rw, 6) = data(i, 6)
End If
Next
'drop the summarized data on the worksheet
If rw > 0 Then ws2.Range("A2").Resize(rw, UBound(data, 2)).Value = dataOut
End Sub
Each of the cells in the “O2: O20” range is populated with numerical values. Next to each one of these cells there is a cell that is also populated with numerical values depending on the value that exists in "O2: 020". For example: If "O2" = 10.2 then the cell on its side "P2" = 1000 but then "P2" = 500, then "P2" = 600, then "P2" = 50; in short, "P2" can take any positive Natural value. I would like to calculate the difference between the previous value that "P2" takes and the current value that it can take as long as "O2" remains with the same value. If the value of "O2" changes, then the difference is not important to me: For example: if "O2" = 10.2 and "P2" = 50 and then "O2" = 10 and "P2" = 3000, in this case, no I want to know the difference, because "O2" is not the same for both cells.
I hope I could understand your problem. Please see this solution.
It is using Option Base 1.
Updated program for writing the difference into the Q column.
If the message is not needed please delete or Rem the line of the last MsgBox.
Option Base 1
Private Sub Worksheet_Change(ByVal Target As Range)
'Static declaration to safe the previous values and change status
Static vO As Variant
Static vP As Variant
Static bolOChanged() As Boolean
'Set up the ranges
Dim rngO As Range, rngP As Range, rngQ As Range
Set rngO = ThisWorkbook.ActiveSheet.Range("O2:O20")
Set rngP = ThisWorkbook.ActiveSheet.Range("P2:P20")
Set rngQ = ThisWorkbook.ActiveSheet.Range("Q2:Q20") 'Range for results
'If the change is not in the range then ignore
If Intersect(Union(rngO, rngP), Target) Is Nothing Then Exit Sub
'Prevent unhandelt multiply changes. If multiply changes required than the
'Target range shall be loop through
If Target.Cells.Count > 1 Then
Application.EnableEvents = False
rngO.Value = vO
rngP.Value = vP
Application.EnableEvents = True
MsgBox "You cannot change more the one cell in the range of: " & Union(rngO, rngP).Address
Exit Sub
End If
'At the firs occasion the current status has to be saved
If VarType(vO) < vbArray Then
vO = rngO.Value
vP = rngP.Value
ReDim bolOChanged(1 To UBound(vO))
End If
Dim iIndex As Long 'Adjust the index of the array to the Row of Target cell
iIndex = Target.Row - rngO(1).Row + 1
If Not Intersect(rngO, Target) Is Nothing Then
'Change was in O range, so next P change shall be ignored
bolOChanged(iIndex) = True
Else
'rngP changed
If bolOChanged(iIndex) Then
'There was a previous O range change, ignore
bolOChanged(iIndex) = False 'Delete the recent change flag
vP(iIndex, 1) = Target.Value 'Store the value
Else
rngQ(iIndex).Value = Target.Value - vP(iIndex, 1)
MsgBox "Value change from: " & vP(iIndex, 1) & ", to: " & Target.Value & ". Difference is: " & Target.Value - vP(iIndex, 1)
vP(iIndex, 1) = Target.Value 'Store the value
End If
End If
End Sub
UPDATE: This version is working with multiply entries.
Option Base 1
Private Sub Worksheet_Change(ByVal Target As Range)
'Static declaration to safe the previous values and change status
Static vO As Variant
Static vP As Variant
Static bolOChanged() As Boolean
'Set up the ranges
Dim rngO As Range, rngP As Range, rngQ As Range
Set rngO = ThisWorkbook.ActiveSheet.Range("O2:O20")
Set rngP = ThisWorkbook.ActiveSheet.Range("P2:P20")
Set rngQ = ThisWorkbook.ActiveSheet.Range("Q2:Q20") 'Range for results
'If the change is not in the range then ignore
If Intersect(Union(rngO, rngP), Target) Is Nothing Then Exit Sub
'At the firs occasion the current status has to be saved
If VarType(vO) < vbArray Then
vO = rngO.Value
vP = rngP.Value
ReDim bolOChanged(1 To UBound(vO))
End If
Dim iIndex As Long 'Adjust the index of the array to the Row of Target cell
Dim item As Variant
For Each item In Target
iIndex = item.Row - rngO(1).Row + 1
If Not Intersect(rngO, item) Is Nothing Then
'Change was in O range, so next P change shall be ignored
bolOChanged(iIndex) = True
Else
'rngP changed
If bolOChanged(iIndex) Then
'There was a previous O range change, ignore
bolOChanged(iIndex) = False 'Delete the recent change flag
vP(iIndex, 1) = item.Value 'Store the value
Else
rngQ(iIndex).Value = item.Value - vP(iIndex, 1)
MsgBox "Value changed in cell " & item.Address & " from: " & vP(iIndex, 1) & ", to: " & item.Value & ". Difference is: " & item.Value - vP(iIndex, 1)
vP(iIndex, 1) = item.Value 'Store the value
End If
End If
Next item
End Sub
This solution uses more columns of your worksheet to store previous values to be compared to actual values. In my example, the values in cells O2 and O3 will always be the same.
Sub Populate_OandP()
'Store previous values
Call PreviousValues
'This code just simulates the data population in columns O and P
Dim intRndNumber As Integer
Range("O2").Value = 10.2
Range("O3").Value = 10
intRndNumber = Int((10 - 1 + 1) * Rnd + 1)
For i = 4 To 20
intRndNumber = Int((10 - 1 + 1) * Rnd + 1)
Cells(i, 15).Value = intRndNumber * 10
Next i
For i = 2 To 20
intRndNumber = Int((10 - 1 + 1) * Rnd + 1)
Cells(i, 16).Value = intRndNumber * 10
Next i
'Check differences
Call CheckDifferenceIfOChanges
End Sub
Sub PreviousValues()
For i = 2 To 20
Cells(i, 18).Value = Cells(i, 15).Value
Cells(i, 19).Value = Cells(i, 16).Value
Next i
End Sub
Sub CheckDifferenceIfOChanges()
For i = 2 To 20
If Cells(i, 18).Value = Cells(i, 15).Value Then
Cells(i, 20).Value = Cells(i, 19).Value - Cells(i, 16).Value
Else: Cells(i, 20).Value = "O columns value changed"
End If
Next i
End Sub
I am trying to create a listbox with the column headers of my data to use that as an input from the user on which columns the user wants displayed in the final result. So far I've been able to divide the listbox into two columns and populate the relevant data in it, but on selecting the items, it is selecting the complete row and not individual items.
Can you please let me know how can I select individual items from a multicolumn listbox.
Here's the code :
Private Sub ListBox1_Enter()
Dim firstrow As Range
Dim c_no As Integer
Dim Arr() As String
Dim i As Integer
Dim j As Integer
Set firstrow = ThisWorkbook.Sheets("Tag Dump").Range("A1:AR1")
With firstrow
c = .Cells.Count
End With
Application.ScreenUpdating = False
ReDim Preserve Arr(c, 2)
'If WorksheetFunction.IsEven(c) = True Then
For i = 1 To c / 2
Arr(i, 1) = Sheets("Tag Dump").Cells(1, i).Value
Arr(i, 2) = Sheets("Tag Dump").Cells(1, i + (c / 2)).Value
'ElseIf WorksheetFunction.IsEven(c) = False Then
'End If
With ListBox1
.ColumnCount = 2
.Additem
.List(i - 1, 0) = Arr(i, 1)
.List(i - 1, 1) = Arr(i, 2)
End With
Next
With ListBox1
.ListStyle = fmListStyleOption
.Font = "Arial"
.MultiSelect = fmMultiSelectExtended
End With
Application.ScreenUpdating = True
End Sub
Here's an image of the result I'm getting:
Listbox selection
Thanks in advance.
I have a ComboBox that has a Value of "ConcretePad". I also have a Range named "ConcretePad".
i am trying to Select Range based off of ComboBox Value.
***Private Sub CatagoryCB_Change()
Dim rg As String
rg = (CatagoryCB.Value)
Worksheets("Data").Select
If (CatagoryCB.Value = "") Then
GoTo Line2
ElseIf (CatagoryCB.Value <> "") Then
Range(rg).Select
Line2:
End If
End Sub***
Trying to make rg represent the Value of CatagoryCB.Value, which i did but when i put it in the cell reference for range i get an error
You're probably looking for something like this (provided you're using a ListFillRange):
Private Sub CatagoryCB_Change()
If (CatagoryCB.ListIndex <> -1) Then
Worksheets("Data").Select
Range(CatagoryCB.ListFillRange).Cells(CatagoryCB.ListIndex + 1, 1).Select
End If
End Sub
This just grabs the ListFillRange, navigates to the ListIndex which is in sync with it and selects it.
CatagoryCB.ListIndex will return the index of the selected item in the list.
If a value that isn't in the list is selected, it will return -1.
So, for example, if I set my ListFillRange to A1:A3 and select the first option, I will do a Range("A1:A3").Cells(1, 1).Select because the ListIndex of the selected item is 0 (first item) and .Cells(0 + 1, 1) = .Cells(1, 1).
If you're populating the ComboBox manually, you'd need to give it the range you want to link to or perform a find operation.
It's hard to tell from your code.
I figured it out. My (CatagoryCB.Value) was not equal to my Range Name. This is the code i was able to produce to add a part to my datasheet on my current worksheet. This also adds the new row to my range
Dim i As String
Dim c As Integer
Dim g As Integer
i = CatagoryCB.Value
Worksheets("Data").Select
If i = "" Then
GoTo Line2
ElseIf i <> "" Then
Range(i).Select
c = Range(i).Count
Range(i).Activate
ActiveCell.Offset(c, 0).Select
g = ActiveCell.Row
Worksheets("Data").Rows(g).Insert
Range(i).Resize(c + 1).Name = i
Cells(g, 1).FormulaR1C1 = Cells(g - 1, 1).FormulaR1C1
Cells(g, 3) = (Part_NumberTB.Value)
Cells(g, 4) = (VendorCB.Value)
Cells(g, 5) = (DescriptionTB.Value)
Cells(g, 7) = (CostTB.Value)
Cells(g, 8) = (CostTB.Value * 1.35)
Cells(g, 9) = (CostTB.Value * 1.35)
Cells(g, 10).FormulaR1C1 = Cells(g - 1, 10).FormulaR1C1
Cells(g, 11).FormulaR1C1 = Cells(g - 1, 11).FormulaR1C1
Line2:
End If
I'm trying to transpose data based on the cell information from another column.
I can fairly quickly with the macro below when I only have two data that are the same. My problem is when I hit more than one data that are the same.
For example:
Clients What they want
20 B
20 C
33 B
33 C
202 A
202 B
202 C
55 A
55 C
The macro I have is this
Sub TransposeDuplciateData()
Sheets("Duplicate").Select
While Range("A2") <> ""
Range("B2").Select
ActiveCell.Resize(2, 1).Select
Selection.Copy
Sheets("Clients").Select
Range("B1").Select
Selection.End(xlDown).Offset(1, 0).Select
Selection.PasteSpecial Paste:=xlPasteValues, Operation:=xlNone, SkipBlanks _
:=False, Transpose:=True
Sheets("Duplicate").Select
Selection.EntireRow.Delete Shift:=xlUp
Wend
End Sub
The problem is when I hit client number 202, he wants three different things not only two.
I'm therefore looking for a macro that it would first recognize how many times the clients appear and from there copy the relevant information from column B and transpose it into my Clients sheet, then delete the entire rows from my Duplicate sheet (since I dealt with it) and move to the next clients information and do the same thing until there is no more clients information.
Here is the end results I would like it too look like
Clients Option 1 Option 2 Option 3 Option 4
20 B C
33 B C
202 A B C
55 B C
a possible way to achieve your desired outcome is to use a pivot table.
If you set Column A as Row, Column B as Column and values as count of Column B, you get the following output.
A B C
20 1 1
33 1 1
55 1 1
202 1 1 1
Would that help?
For a macro based solution, try the following code. It may need to be adapted to your exact need. Make also sure, column A is sorted in some kind of a way (this can also be done within the macro)
Sub remove_dub()
With Sheets("Dublicate")
Dim row_dubl As Integer
Dim row_clie As Integer
Dim col_clie As Integer
row_dubl = 1
row_clie = 1
col_clie = 2
While .Cells(row_dubl, "A") <> ""
Sheets("Clients").Cells(row_clie, "A") = .Cells(row_dubl, "A")
Sheets("Clients").Cells(row_clie, col_clie) = .Cells(row_dubl, "B")
If .Cells(row_dubl, "A") = .Cells(row_dubl + 1, "A") Then
row_clie = row_clie
col_clie = col_clie + 1
Else
row_clie = row_clie + 1
col_clie = 2
End If
row_dubl = row_dubl + 1
Wend
End With
End Sub
Best regards
A bit "simplified" version:
Dim c As Range
Set c = [a2]
While c > ""
While c = c(2) ' while c equals the cell below it
c.End(xlToRight)(, 2) = c(2, 2) ' get the second value below c
c(2).Resize(, 2).Delete xlShiftUp ' delete the 2 cells below c
Wend
Set c = c(2)
Wend
Here is a macro which creates a user defined object as a class, which has the properties of Client and a dictionary of Opts (for Option). You can easily add other properties, if you want to extend this.
Set reference to Microsoft Scripting Runtime
EDIT: Rename the class module cClient
Class Module
Option Explicit
Private pClient As String
Private pOpt As String
Private pOpts As Dictionary
Public Property Get Client() As String
Client = pClient
End Property
Public Property Let Client(Value As String)
pClient = Value
End Property
Public Property Get Opt() As String
Opt = pOpt
End Property
Public Property Let Opt(Value As String)
pOpt = Value
End Property
Public Property Get Opts() As Dictionary
Set Opts = pOpts
End Property
Public Function ADDOpt(Value As String)
If Not pOpts.Exists(Value) Then
pOpts.Add Key:=Value, Item:=Value
End If
End Function
Private Sub Class_Initialize()
Set pOpts = New Dictionary
pOpts.CompareMode = TextCompare
End Sub
Regular Module
Option Explicit
'Set reference to Microsoft Scripting Runtime
Sub OrganizeClientOptions()
Dim wsSrc As Worksheet, wsRes As Worksheet, rRes As Range
Dim vSrc As Variant, vRes As Variant
Dim cC As cClient, dC As Dictionary
Dim I As Long, J As Long
Dim V As Variant, W As Variant
'Set worksheets
Set wsSrc = Worksheets("sheet1")
On Error Resume Next
Set wsRes = Worksheets("Results")
If Err.Number = 9 Then
Worksheets.Add.Name = "Results"
End If
On Error GoTo 0
Set wsRes = Worksheets("Results")
Set rRes = wsRes.Cells(1, 1)
With wsSrc
vSrc = .Range(.Cells(1, 1), .Cells(.Rows.Count, 1).End(xlUp)).Resize(columnsize:=2)
End With
'collect the data
Set dC = New Dictionary
For I = 2 To UBound(vSrc, 1)
Set cC = New cClient
With cC
.Client = vSrc(I, 1)
.Opt = vSrc(I, 2)
.ADDOpt .Opt
If Not dC.Exists(.Client) Then
dC.Add Key:=.Client, Item:=cC
Else
dC(.Client).ADDOpt .Opt
End If
End With
Next I
'Size vRes
J = 0
For Each V In dC.Keys
I = dC(V).Opts.Count
J = IIf(J > I, J, I)
Next V
ReDim vRes(0 To dC.Count + 1, 1 To J + 1)
'headers
vRes(0, 1) = "Client"
For J = 2 To UBound(vRes, 2)
vRes(0, J) = "Option " & J - 1
Next J
'Data
I = 0
For Each V In dC.Keys
I = I + 1
vRes(I, 1) = V
J = 1
For Each W In dC(V).Opts
J = J + 1
vRes(I, J) = W
Next W
Next V
'Write the results
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
End With
.EntireColumn.AutoFit
End With
End Sub
Results