Excel VBA splititng data into colums without duplicating - vba

I'm trying to create a macro that goes through a huge list of data and that would split into columns parts of the data according to some criteria. Please not that there is no pattern that can be hard coded since in the example below the number of dependants can change drastically.
As of now, my data looks like this.
I would want it like this
I've written some code that goes through everything, and write each dependent on a new column but that create duplicates. I'm not able to detect whether that value was already on the row or not. Here is the code that I tried to write. Take not that we're already in the context of looping through every row.
dim isUnique
For i = 1 To 100
If not WsT.Cells(Rt, i).Value = .Cells(NbcDepfname).Value Then
isUnique = true
else
isUnique = false
End If
Next i
Ct = 10
If isUnique Then
WsT.Cells(Rt, Ct).Value = .Cells(NbcDepLname).Value
WsT.Cells(Rt, Ct + 1).Value = .Cells(NbcDepfname).Value
WsT.Cells(Rt, Ct + 2).Value = .Cells(NbcDepBDate).Value
End If
End With
I have very little experience with VBA and macros so my approach might not be the best or good at all. I've noticed also huge performance drops with that approach but it's alright for that project.
**EDIT ******
I understand my mistake perfectly. I'm looping through "EVERY" row of the first sheet with the unformatted data. I don't check correctly if the data is unique for that row before writing it, therefore, It will obviously keep writing a dependant per row. I tried with dictionaries but since it is possible that a dependant has both the same name and birthdate as another one on another row with different parents i couldn't keep that solution. The detection has to be done at the Row level.

Minor changes to the routine I posted in your previous, very similar thread, is all that is needed. That routine already uses dictionaries to check on duplicates, so it was merely a matter of adapting to your slightly different layout in this thread.
Of note, this requires, obviously, that the "Main" person be identified by a combination of FirstNameLastName + EmploymentDate. However, it does NOT require that the list be sorted.
It should adapt to any number of unique Dependent Name / BirthDate pairs.
It will Clear the entire Results worksheet before writing the results.
Be sure to read the notes in the different modules. They are critical to having it run. You will use two Class Modules and one Regular Module
Class 1
Option Explicit
'Rename this module: cDependents
'set reference to Microsoft Scripting Runtime
Private pBirthDt As Date
Private pDepName As String
Public Property Get BirthDt() As Date
BirthDt = pBirthDt
End Property
Public Property Let BirthDt(Value As Date)
pBirthDt = Value
End Property
Public Property Get DepName() As String
DepName = pDepName
End Property
Public Property Let DepName(Value As String)
pDepName = Value
End Property
Class 2
Option Explicit
'rename this module: cFamily
'set reference to Microsoft Scripting Runtime
Private pFirstName As String
Private pLastName As String
Private pBirthDate As Date
Private pEmploymentDate As Date
Private pDependents As Dictionary
Public Property Get FirstName() As String
FirstName = pFirstName
End Property
Public Property Let FirstName(Value As String)
pFirstName = Value
End Property
Public Property Get LastName() As String
LastName = pLastName
End Property
Public Property Let LastName(Value As String)
pLastName = Value
End Property
Public Property Get BirthDate() As Date
BirthDate = pBirthDate
End Property
Public Property Let BirthDate(Value As Date)
pBirthDate = Value
End Property
Public Property Get EmploymentDate() As Date
EmploymentDate = pEmploymentDate
End Property
Public Property Let EmploymentDate(Value As Date)
pEmploymentDate = Value
End Property
Public Function ADDDependents(BrthDt, Name)
Dim cD As New cDependents
Dim sKey As String
With cD
.DepName = Name
.BirthDt = BrthDt
sKey = .BirthDt & Chr(1) & .DepName
End With
If Not pDependents.Exists(sKey) Then
pDependents.Add Key:=sKey, Item:=cD
End If
End Function
Public Property Get Dependents() As Dictionary
Set Dependents = pDependents
End Property
Private Sub Class_Initialize()
Set pDependents = New Dictionary
End Sub
Regular Module
Option Explicit
'set reference to Microsoft Scripting Runtime
Sub Family()
Dim wsSrc As Worksheet, wsRes As Worksheet, rRes As Range
Dim vSrc As Variant, vRes As Variant
Dim dF As Dictionary, cF As cFamily
Dim I As Long, J As Long
Dim sKey As String
Dim V As Variant, W As Variant
'Set source and results worksheets and results range
Set wsSrc = Worksheets("sheet1")
Set wsRes = Worksheets("sheet2")
Set rRes = wsRes.Cells(1, 1)
'read source data into array
With wsSrc
vSrc = .Range(.Cells(1, 1), .Cells(.Rows.Count, 1).End(xlUp)).Resize(columnsize:=5)
End With
'Collect and organize the family and dependent objects
Set dF = New Dictionary
For I = 2 To UBound(vSrc, 1)
Set cF = New cFamily
With cF
.LastName = vSrc(I, 1)
.FirstName = vSrc(I, 2)
.EmploymentDate = vSrc(I, 3)
.ADDDependents vSrc(I, 5), vSrc(I, 4)
sKey = .LastName & .FirstName & .EmploymentDate
If Not dF.Exists(sKey) Then
dF.Add Key:=sKey, Item:=cF
Else
dF(sKey).ADDDependents vSrc(I, 5), vSrc(I, 4)
End If
End With
Next I
'Results will have two columns for each relation
' + three columns at the beginning
'get number of extra columns
Dim ColCount As Long
For Each V In dF
I = dF(V).Dependents.Count
ColCount = IIf(I > ColCount, I, ColCount)
Next V
ColCount = ColCount * 2 + 3
ReDim vRes(0 To dF.Count, 1 To ColCount)
vRes(0, 1) = "Last Name"
vRes(0, 2) = "First Name"
vRes(0, 3) = "Employment Date"
For J = 4 To UBound(vRes, 2) Step 2
vRes(0, J) = "Dependent Name "
vRes(0, J + 1) = "Dependent BirthDate"
Next J
I = 0
For Each V In dF
I = I + 1
With dF(V)
vRes(I, 1) = .LastName
vRes(I, 2) = .FirstName
vRes(I, 3) = .EmploymentDate
J = 2
For Each W In .Dependents
J = J + 2
With .Dependents(W)
vRes(I, J) = .DepName
vRes(I, J + 1) = .BirthDt
End With
Next W
End With
Next V
Set rRes = rRes.Resize(rowsize:=UBound(vRes, 1) + 1, columnsize:=UBound(vRes, 2))
With rRes
.Worksheet.Cells.Clear
.Value = vRes
With .Rows(1)
.Font.Bold = True
.Font.Color = vbWhite
.Interior.Color = vbBlue
.HorizontalAlignment = xlCenter
End With
For I = 3 To .Columns.Count - 1 Step 2
.Columns(I).NumberFormat = "yyyy-mm-dd"
Next I
.EntireColumn.AutoFit
End With
End Sub
Results when run on your data above

It occurred to me that you might have two dependents of the same name, spouse and child. Therefore I added another test to my previous solution to check the DoB. Here is the extended version.
Private Function IsUnique(ByVal DepName As String, _
DepDob As String, _
Ws As Worksheet, _
R As Long) As Boolean
' 11 Apr 2017
Dim Rng As Range
Dim Dob As Variant
Dim C As Long
With Ws.Rows(R)
Set Rng = Range(.Cells(NedDependt), .Cells(Ws.UsedRange.Columns.Count))
End With
DepName = Trim(DepName)
With Rng
For C = (.Cells.Count - 1) To 1 Step ((NedDepDob + 1) * -1)
If StrComp(Trim(.Cells(C).Value), DepName, vbTextCompare) = 0 Then
Dob = .Cells(C + NedDepDob).Value
If IsDate(Dob) Then
If IsDate(DepDob) Then
If CDate(Dob) = CDate(DepDob) Then Exit For
Else
If Trim(Dob) = Trim(DepDob) Then Exit For
End If
Else
If Trim(Dob) = Trim(DepDob) Then Exit For
End If
End If
Next C
End With
IsUnique = (C < 1)
End Function
For the purpose of this procedure I created a new enumeration which replaces the previous Enum Nfd. The new enum is adjusted to the actual columns as you published them above. You might need it not Private, if you refer to it in modules other than where it is located.
Private Enum Ned ' worksheet Employee Data
NedFirstDataRow = 2 ' Adjust as required
NedName = 1 ' columns:
NedFname
NedEmployed
NedDependt
NedDepName = 0 ' Offset from NedDependt
NedDepDob
End Enum
Below is the calling procedure I used for testing. Once you integrate the function into your project, the worksheet should be the one containing the formatted data while name, Dob and the row are variables.
Private Sub TestIsUnique()
Debug.Print IsUnique("vanessa", "1/12/1976", ActiveSheet, 9)
Debug.Print IsUnique("vanessa", "01/02/1976", ActiveSheet, 9)
End Sub
The date is a bit of a problem. I notice that you use yyyy/mm/ddd. The code will have no problem with that, but it may have a problem if the dates in your data are strings. I programmed it so that the code will first attempt to compare actual dates but will compare strings if one of the values can't be converted to a date.

Related

Randomly Assign Employees to Tasks

This is a follow-up to a previous question that I had. I was provided an answer, but due to my own inexperience and inability, I can't seem to implement it properly.
My situation is as follows:
I need to assign a list of employees to tasks.
There will always be MORE tasks than employees.
Each employee must be assigned to at least ONE
No employee should be assigned to more than two
I need the employee list to randomize during the sorting process so the same employees don't get the same tasks over and over
Where I am coming up short is finding a way that starts "assigning" employees, keeps track of how many times the array(i) employee has been assigned, and if it's greater than two, move onto the next.
An awesome user tried helping me here: Excel VBA to assign employees to tasks using loops
Here is the "test" table I am working with:
Here is the macro I have written to sort my list of employees, which works:
Sub ShuffleEmp()
' This macro's intention is to shuffle the current liste of process assessors
Application.ScreenUpdating = False
Dim tempString As String, tempInteger As Integer, i As Integer, j As Integer, lastRow As Integer
' this grabs the last row with data, so that it can be dynamic
With Sheets("Test")
lastRow = .Range("M" & .Rows.Count).End(xlUp).Row
End With
' this assumes ALWAYS 45 tasks
' starting row 6, going until row 35
For i = 6 To lastRow
' row 6, column 14 (next to Emp column) to start....
Cells(i, 14).Value = WorksheetFunction.RandBetween(0, 1000)
Next i
'now it has assigned random values...
For i = 6 To lastRow
For j = i + 1 To lastRow
'14 is the number column...
If Cells(j, 14).Value < Cells(i, 14).Value Then
'change the string, which is the Emp column...
tempString = Cells(i, 13).Value
Cells(i, 13).Value = Cells(j, 13).Value
Cells(j, 13).Value = tempString
tempInteger = Cells(i, 14).Value
Cells(i, 14).Value = Cells(j, 14).Value
Cells(j, 14).Value = tempInteger
End If
Next j
Next i
Worksheets("Test").Range("N:N").EntireColumn.Delete
Application.ScreenUpdating = True
End Sub
Here is the macro for turning that list into an array:
Sub EmpArray()
' This stores the column of Emps as an array
Dim Storage() As String ' initial storage array to take values
Dim i As Long
Dim j As Long
Dim lrow As Long
lrow = Cells(Rows.Count, "M").End(xlUp).Row ' The amount of stuff in the column
ReDim Storage(1 To lrow - 5)
For i = lrow To 6 Step -1
If (Not IsEmpty(Cells(i, 13).Value)) Then ' checks to make sure the value isn't empty
j = j + 1
Storage(j) = Cells(i, 13).Value
End If
Next i
ReDim Preserve Storage(1 To j)
For j = LBound(Storage) To UBound(Storage) ' loop through the previous array
MsgBox (Storage(j))
Next j
End Sub
This is your entire program here. It's tested and works. The only problem is that your screenshot didn't show the row and column headers, so I had to assume that Task was column B, row 1.
Here is your main Subroutine. This is the program that you will assign your button to. This will automatically check to see if your employeeList is uninitialized (basically empty) and rebuild it using the function buildOneDimArr.
Sub assignEmployeeTasks()
Dim ws As Worksheet, i As Long
Set ws = ThisWorkbook.Worksheets(1)
Dim employeeList() As Variant
With ws
For i = 2 To lastRow(ws, 2)
If (Not employeeList) = -1 Then
'rebuild employeelist / array uninitialized
employeeList = buildOneDimArr(ws, "F", 2, lastRow(ws, "F"))
End If
.Cells(i, 4) = randomEmployee(employeeList)
Next
End With
End Sub
These are the "support" functions that allow your program to do it's job:
Function randomEmployee(ByRef employeeList As Variant) As String
'Random # that will determine the employee chosen
Dim Lotto As Long
Lotto = randomNumber(LBound(employeeList), UBound(employeeList))
randomEmployee = employeeList(Lotto)
'Remove the employee from the original array before returning it to the sub
Dim retArr() As Variant, i&, x&, numRem&
numRem = UBound(employeeList) - 1
If numRem = -1 Then 'array is empty
Erase employeeList
Exit Function
End If
ReDim retArr(numRem)
For i = 0 To UBound(employeeList)
If i <> Lotto Then
retArr(x) = employeeList(i)
x = x + 1
End If
Next i
Erase employeeList
employeeList = retArr
End Function
' This will take your column of employees and place them in a 1-D array
Function buildOneDimArr(ByVal ws As Worksheet, ByVal Col As Variant, _
ByVal rowStart As Long, ByVal rowEnd As Long) As Variant()
Dim numElements As Long, i As Long, x As Long, retArr()
numElements = rowEnd - rowStart
ReDim retArr(numElements)
For i = rowStart To rowEnd
retArr(x) = ws.Cells(i, Col)
x = x + 1
Next i
buildOneDimArr = retArr
End Function
' This outputs a random number so you can randomly assign your employee
Function randomNumber(ByVal lngMin&, ByVal lngMax&) As Long
'Courtesy of https://stackoverflow.com/a/22628599/5781745
Randomize
randomNumber = Int((lngMax - lngMin + 1) * Rnd + lngMin)
End Function
' This gets the last row of any column you specify in the arguments
Function lastRow(ws As Worksheet, Col As Variant) As Long
lastRow = ws.Cells(ws.Rows.Count, Col).End(xlUp).Row
End Function
You are going to want to place all of these into a standard module.
I created a solution for you which might help you to develop further also in general understanding of programming.
With my solution you dont need to shuffle your employees beforehand and you will use some stuff you might have not used before.
First of all I created a new Class Module called Employee which looks like this:
Private p_name As String
Private p_task As String
Public Property Get Name() As String
Name = p_name
End Property
Public Property Let Name(ByVal value As String)
p_name = value
End Property
Public Property Get Task() As String
Task = p_task
End Property
Public Property Let Task(ByVal value As String)
p_task = value
End Property
This is only a small class to hold an employeename and a task.
In a normal Module I added a method called ShuffleTasks with 2 collections as parameters. A Collection is a slightly more comfortable and therefor slightly heavier and slower version of an array.
Private Sub ShuffleTasks(t As Collection, emp As Collection)
Dim i As Integer
Dim count As Integer
Dim employ As employee
count = emp.count
Dim remIndex As Integer
For i = 1 To count
'randomize
Randomize
'get a random index from tasks by its count
remIndex = Int((t.count) * Rnd + 1)
'add the task to the employee list
emp.Item(i).Task = t.Item(remIndex)
'remove the task so it wont be assigned again
t.Remove (remIndex)
Next
End Sub
The first parameter is a collection of the tasks(which is just a string with the name), the second the collection of the employees. The second one will also the one being used as the result.
Then I iterate through all employees and generate a random integer between 1 and the count of the tasks. I'll add the task to the current employee in the collection and REMOVE it from the tasklist. In the next iteration the numbers of tasks will be -1 and again randomized chosen from the amount of items in the collection.
Then I modified your EmpArray Method to fill some data from a sheet and call the ShuffleTasks method
Sub EmpArray()
' This stores the column of Emps as an Collection
Dim sEmployees As New Collection, sTasks As New Collection ' initial storage array to take values
Dim i As Long
Dim j As Long
Dim s As Variant
Dim lrow As Long
Dim emp As employee
lrow = Cells(Rows.count, "M").End(xlUp).Row ' The amount of stuff in the column
For i = lrow To 6 Step -1
If (Not IsEmpty(Cells(i, 13).value)) Then ' checks to make sure the value isn't empty
j = j + 1
'Storage(j) = Cells(i, 13).Value
Set emp = New employee
emp.Name = Cells(i, 13).value
sEmployees.Add emp
End If
Next i
' This stores the column of Tasks as an Collection
' I assume it is column 9
lrow = Cells(Rows.count, "I").End(xlUp).Row ' The amount of stuff in the column
For i = lrow To 6 Step -1
If (Not IsEmpty(Cells(i, 9).value)) Then ' checks to make sure the value isn't empty
j = j + 1
sTasks.Add Cells(i, 9).value
End If
Next i
ShuffleTasks sTasks, sEmployees
For Each emp In sEmployees
Debug.Print (emp.Name & ": " & emp.Task)
Next
End Sub
As you can see the modifications on the Collection will show you each time a new employee name and task. Keep in mind that it is ofc not true random. Also the collection of tasks will have less items after the method ShuffleTasks. I just wanted to show you an approach which is basically working a bit with data in vba. You only load data from the sheet, then manipulate that in pure vba objects. The results can also be written back to the sheet, I just print them to Debug Window in your vba editor.
Hope this helps. It is for sure a quick and dirty solution and I also didnt cover all aspects of Collections and also Parameters and ByVal vs ByRef etc. But maybe this will inspire you a bit ;)
I hope I understood it correctly:
Sub AssignEmpl()
Dim TaskTable As Range, EmpTable As Range
Dim lRowT As Long, lRowE As Long, iCell As Range
lRowT = Worksheets("Test").Range("I" & Worksheets("Test").Rows.Count).End(xlUp).Row
lRowE = Worksheets("Test").Range("M" & Worksheets("Test").Rows.Count).End(xlUp).Row
' Don't know what are actual ranges, modify
Set TaskTable = Worksheets("Test").Range("I6:K" & lRowT)
Set EmpTable = Worksheets("Test").Range("M6:M" & lRowE)
' Starting loop
Do
' Populate column with random nubmers between 1 and number of employees
' 5 is a number of employees (essentialy lRowE - 5 or something like that)
TaskTable.Columns(3).Formula = "=RANDBETWEEN(1," & lRowE - 5 & ")"
' Remove formula (so it doesn't recalculate)
TaskTable.Columns(3).Value = TaskTable.Columns(3).Value
' Check if any number appears more than 2 times
Loop While Evaluate("AND(MAX(COUNTIF(" & TaskTable.Columns(3).Address & "," & TaskTable.Columns(3).Address & "))>2)")
' Put these employee in there
For Each iCell In TaskTable.Columns(3).Cells
iCell.Value = EmpTable.Cells(iCell.Value, 1)
Next
End Sub

Excel Macro append duplicates to first line

I'm an Excel VBA newbie and i'm trying to get the duplicates rows to appends to the first occurence of that row.
Per exemple we have the table here
I would like to format data as here
The logic goes like this. Whenever we detect that the last name and the birth date are the same for the current and following line that mean we have a dependant and we need to append the dependant's data to the "Main"
I have started writing code but i'm not able to detect the dependants properly.
Below is what i have. please consider that i'm a real noob and i'm trying hard.
Sub formatData()
Dim sh As Worksheet
Dim rw As Range
Dim RowCount As Integer
'This variable is checked to see if we have a first occurence of a line
Dim firstOccurence
'Initialise the variables for that will be used to match the data
Dim LocationName
Dim PlanCode
Dim LastName
Dim FirstName
Dim dependantFirstName
Dim dependantLastName
Dim dependantBirthdate
RowCount = 0
firstOccurence = True
'Check if the spreadsheet already exist if not create it.
For i = 1 To Worksheets.Count
If Worksheets(i).Name = "Benefits Census Formatted" Then
exists = True
End If
Next i
If Not exists Then
'Create a new spreadsheet to add the data to
Set ws = Sheets.Add
Sheets.Add.Name = "Benefits Census Formatted"
End If
'Set the ActiveSheet to the one containing the original data
Set sh = Sheets("BENEFIT Census")
With ActiveSheet
LastRow = .Cells(.Rows.Count, "A").End(xlUp).Row
For Each rw In sh.Rows
'If the data of one cell is empty EXIT THE LOOP
If sh.Cells(rw.Row, 1).Value = "" Then
Exit For
End If
If rw.Row > 1 Then
'Afffecting the variables to the next loop so we can compare the values
nextLocationName = sh.Cells(rw.Row + 1, 1).Value
nextPlanCode = sh.Cells(rw.Row + 1, 2).Value
nextLastName = sh.Cells(rw.Row + 1, 3).Value
nextFirstName = sh.Cells(rw.Row + 1, 4).Value
nextEmploymentDate = sh.Cells(rw.Row + 1, 5).Value
nextBirthDate = sh.Cells(rw.Row + 1, 6).Value
nextDependantFirstName = sh.Cells(rw.Row + 1, 25).Value
nextDependantLastName = sh.Cells(rw.Row + 1, 26).Value
nextDependantBirthdate = sh.Cells(rw.Row + 1, 27).Value
Debug.Print LastName & " - " & FirstName & " ::: " & nextLastName & " - " & nextFirstName & " : " & rw.Row & " : " & firstOccurence
'First time you pass through the loop write the whole lane
If firstOccurence = True Then
'Affecting the variables to the current loops values
LocationName = sh.Cells(rw.Row, 1).Value
PlanCode = sh.Cells(rw.Row, 2).Value
LastName = sh.Cells(rw.Row, 3).Value
FirstName = sh.Cells(rw.Row, 4).Value
dependantFirstName = sh.Cells(rw.Row, 25).Value
dependantLastName = sh.Cells(rw.Row, 26).Value
dependantBirthdate = sh.Cells(rw.Row, 27).Value
'Write the current line
sh.Rows(rw.Row).Copy
'We copy the value into another sheet
Set ns = Sheets("Benefits Census Formatted")
LastRow = ns.Cells(ns.Rows.Count, "A").End(xlUp).Row + 1
ns.Rows(LastRow).PasteSpecial xlPasteValues
firstOccurence = False
Else
'We match the location with the plan code and the last name and first name of the user to find duplicates
If dependantFirstName <> nextDependantFirstName And PlanCode <> nextPlanCode And LastName <> nextLastName And FirstName <> nextFirstName Then
'We find a different dependant if the first name or the last name or the birthdate differs
'If Not (dependantFirstName <> nextDependantFirstName) Or Not (dependantLastName <> nextDependantLastName) Or Not (dependantBirthdate <> nextDependantBirthdate) Then
'We have a dependant Append it to the line
'append the user to the currentLine
'End If
Else
'If the dependantFirstName and the nextDependant First name doesn't match then on the next loop we print the full line
firstOccurence = True
End If
End If
RowCount = RowCount + 1
'End of if row > 2
End If
Next rw
End With
End Sub
This is the code I wrote for you. (Glad to see that so many others did, too. So you got a choice :-))
Sub TransscribeData()
' 25 Mar 2017
Dim WsS As Worksheet ' Source
Dim WsT As Worksheet ' Target
Dim TargetName As String
Dim LastRow As Long ' in WsS
Dim Rs As Long ' Source: row
Dim Rt As Long, Ct As Long ' Target: row / column
Dim Tmp As String
Dim Comp As String ' compare string
' Set Source sheet to the one containing the original data
Set WsS = Worksheets("BENEFIT Census")
LastRow = WsS.Cells(WsS.Rows.Count, NbcName).End(xlUp).Row
Application.ScreenUpdating = False
TargetName = "Benefits Census Formatted"
On Error Resume Next
Set WsT = Worksheets(TargetName) ' Set the Target sheet
If Err Then
' Create it if it doesn't exist
Set WsT = Worksheets.Add(After:=Worksheets(Worksheets.Count))
WsT.Name = TargetName
' insert the column captions here
End If
On Error GoTo 0
Rt = WsT.Cells(WsS.Rows.Count, NfdName).End(xlUp).Row
AddMain WsS, WsT, NbcFirstDataRow, Rt ' Rt is counting in the sub
For Rs = NbcFirstDataRow To LastRow - 1
With WsS.Rows(Rs)
Tmp = .Cells(NbcFname).Value & .Cells(NbcName).Value & .Cells(NbcDob).Value
End With
With WsS.Rows(Rs + 1)
Comp = .Cells(NbcFname).Value & .Cells(NbcName).Value & .Cells(NbcDob).Value
End With
If StrComp(Tmp, Comp, vbTextCompare) Then
AddMain WsS, WsT, Rs + 1, Rt
Else
Ct = WsT.Cells(Rt, WsT.Columns.Count).End(xlToLeft).Column
If Ct > NfdMain Then Ct = Ct + 1
With WsS.Rows(Rs + 1)
WsT.Cells(Rt, Ct + NfdRelate).Value = .Cells(NbcRelate).Value
WsT.Cells(Rt, Ct + NfdDepName).Value = .Cells(NbcDepName).Value
End With
End If
Next Rs
Application.ScreenUpdating = True
End Sub
The above code calls one Sub routine which you must add in the same code module which, by the way, should be a normal code module (by default "Module1" but you can rename it to whatever).
Private Sub AddMain(WsS As Worksheet, WsT As Worksheet, _
Rs As Long, Rt As Long)
' 25 Mar 2017
Rt = Rt + 1
With WsS.Rows(Rs)
WsT.Cells(Rt, NfdFname).Value = .Cells(NbcFname).Value
WsT.Cells(Rt, NfdName).Value = .Cells(NbcName).Value
WsT.Cells(Rt, NfdDob).Value = .Cells(NbcDob).Value
WsT.Cells(Rt, NfdMain).Value = "Main"
End With
End Sub
Observe that I inserted the word "Main" as hard text. You could also copy the content of the appropriate call in the Source sheet. This procedure only writes the first entry. Dependents are written by another code.
The entire code is controlled by two "enums", enumerations, one for each of the worksheets. Enums are the quickest way to assign names to numbers. Please paste these two enums at the top of your code sheet, before either of the procedures.
Private Enum Nbc ' worksheet Benefit Census
NbcFirstDataRow = 2 ' Adjust as required
NbcFname = 1 ' columns:
NbcName
NbcDob
NbcRelate
NbcDepName
End Enum
Private Enum Nfd ' worksheet Formatted Data
NfdFirstDataRow = 2 ' Adjust as required
NfdName = 1 ' columns:
NfdFname
NfdDob
NfdMain
NfdRelate = 0 ' Offset from NfdMain
NfdDepName
End Enum
Note that the rule of enums is that you can assign any integer to them. If you don't assign any number the value will be one higher than the previous. So, NfdMain = 4, followed by NfdRelate which has an assigned value of 0, followed by NfdDepName which has a value of 0 + 1 = 1.
The numbers in these enumerations are columns (and rows). You can control the entire output by adjusting these numbers. For example, "Main" is written into column NfdMain (=4 =D). Change the value to 5 and "Main" will appear in column 5 = E. No need to go rummaging in the code. Consider this a control panel.
In the formatted output I introduced a logic which is slightly different from yours. If you don't like it you can change it easily by modifying the enums. My logic has the family name as the main criterion in the first column (switched from the raw data). In column D I write "Main". But when there is a dependent I write the relationship in column D. Therefore only entries without any dependents will have "Main" in that column. For your first example, the formatted row will show Rasmond / Shawn / 01-01-1990 / Spouse / Jessica, Child 1 / Vanessa.
If you wish to keep the "Main and place "Spouse" in the next column, just set the enum NfdRelate = 1. With the "control panel" it's that simple.
I would use an approach using Dictionaries to collect and organize the data, and then output it. Judging both by your comments, and the code, there is a lot of stuff you haven't included. But the following code will take your original data, and output a table close to what you show -- some of the results ordering is different, but it is standardized (i.e. there is a relation listed with every dependent name.
In the dictionary, we use Last Name and Birthdate as the "key" so as to combine what you stated were the duplicates.
We define two Class objects
Dependent object which includes the Name and the Relation
Family object which includes the First and Last Names, and Birthdate as well as a collection (dictionary) of the dependent objects.
Once we have it organized, it is relatively simple to output it as we want.
For a discussion of Classes, you can do an Internet search. I would recommend Chip Pearson's Introduction to Classes
Be sure to read the notes in the code about renaming the class modules, and also setting a reference to Microsoft Scripting Runtime
Class1
Option Explicit
'Rename this module: cDependents
'set reference to Microsoft Scripting Runtime
Private pRelation As String
Private pDepName As String
Public Property Get Relation() As String
Relation = pRelation
End Property
Public Property Let Relation(Value As String)
pRelation = Value
End Property
Public Property Get DepName() As String
DepName = pDepName
End Property
Public Property Let DepName(Value As String)
pDepName = Value
End Property
Class2
Option Explicit
'rename this module: cFamily
'set reference to Microsoft Scripting Runtime
Private pFirstName As String
Private pLastName As String
Private pBirthdate As Date
Private pDependents As Dictionary
Public Property Get FirstName() As String
FirstName = pFirstName
End Property
Public Property Let FirstName(Value As String)
pFirstName = Value
End Property
Public Property Get LastName() As String
LastName = pLastName
End Property
Public Property Let LastName(Value As String)
pLastName = Value
End Property
Public Property Get Birthdate() As Date
Birthdate = pBirthdate
End Property
Public Property Let Birthdate(Value As Date)
pBirthdate = Value
End Property
Public Function ADDDependents(Typ, Nme)
Dim cD As New cDependents
Dim sKey As String
With cD
.DepName = Nme
.Relation = Typ
sKey = .Relation & Chr(1) & .DepName
End With
If Not pDependents.Exists(sKey) Then
pDependents.Add Key:=sKey, Item:=cD
End If
End Function
Public Property Get Dependents() As Dictionary
Set Dependents = pDependents
End Property
Private Sub Class_Initialize()
Set pDependents = New Dictionary
End Sub
Regular Module
Option Explicit
'set reference to Microsoft Scripting Runtime
Sub Family()
Dim wsSrc As Worksheet, wsRes As Worksheet, rRes As Range
Dim vSrc As Variant, vRes As Variant
Dim dF As Dictionary, cF As cFamily
Dim I As Long, J As Long
Dim sKey As String
Dim V As Variant, W As Variant
'Set source and results worksheets and results range
Set wsSrc = Worksheets("sheet1")
Set wsRes = Worksheets("sheet2")
Set rRes = wsRes.Cells(1, 1)
'read source data into array
With wsSrc
vSrc = .Range(.Cells(1, 1), .Cells(.Rows.Count, 1).End(xlUp)).Resize(columnsize:=5)
End With
'Collect and organize the family and dependent objects
Set dF = New Dictionary
For I = 2 To UBound(vSrc, 1)
Set cF = New cFamily
With cF
.FirstName = vSrc(I, 1)
.LastName = vSrc(I, 2)
.Birthdate = vSrc(I, 3)
.ADDDependents vSrc(I, 4), vSrc(I, 5)
sKey = .LastName & Chr(1) & .Birthdate
If Not dF.Exists(sKey) Then
dF.Add Key:=sKey, Item:=cF
Else
dF(sKey).ADDDependents vSrc(I, 4), vSrc(I, 5)
End If
End With
Next I
'Results will have two columns for each relation, including Main
' + three columns at the beginning
'get number of extra columns
Dim ColCount As Long
For Each V In dF
I = dF(V).Dependents.Count
ColCount = IIf(I > ColCount, I, ColCount)
Next V
ColCount = ColCount * 2 + 3
ReDim vRes(0 To dF.Count, 1 To ColCount)
vRes(0, 1) = "First Name"
vRes(0, 2) = "Last Name"
vRes(0, 3) = "Birthdate"
vRes(0, 4) = "Dependant"
vRes(0, 5) = "Dependant Name"
For J = 6 To UBound(vRes, 2) Step 2
vRes(0, J) = "Relation " & J - 5
vRes(0, J + 1) = "Dependant Name"
Next J
I = 0
For Each V In dF
I = I + 1
With dF(V)
vRes(I, 1) = .FirstName
vRes(I, 2) = .LastName
vRes(I, 3) = .Birthdate
J = 2
For Each W In .Dependents
J = J + 2
With .Dependents(W)
vRes(I, J) = .Relation
vRes(I, J + 1) = .DepName
End With
Next W
End With
Next V
Set rRes = rRes.Resize(rowsize:=UBound(vRes, 1) + 1, columnsize:=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
Source Data
Results

Return a User-defined Array from a function VBA-Excel

Hi I've been trying the different solution that this website gave me, but i can't make my code run. I have a user-defined Type() and a Sub() and a function. Like this:
Type Base
UT As String
N_Inic As Integer
N_Fin As Integer
Largo As Integer
End Type
Sub Test()
Dim A() As Base
A = Base()
End Sub
Function Base() As Base
Sheets("ARISTAS").Select
ActiveSheet.Cells(2, 1).Select
j = 2
b = 0
Set UT_Range = Range(ActiveCell, Cells(Rows.Count,_ Selection.Column).End(xlUp))
Total_1 = UT_Range.Count
Dim Base_UT() As Base
ReDim Base_UT(Total_1)
While Sheets("ARISTAS").Cells(j, 1).Value
Base_UT(b).UT = Sheets("ARISTAS").Cells(j, 1).Value
Base_UT(b).N_Inic = Sheets("ARISTAS").Cells(j, 2).Value
Base_UT(b).N_Fin = Sheets("ARISTAS").Cells(j, 3).Value
Base_UT(b).Largo = Sheets("ARISTAS").Cells(j, 9).Value '**
b = b + 1
j = j + 1
Wend
Base = Base_UT
End Function
When I run my sub it said that can't assign to a matrix and highlight "A"
Does Anyone knows why?
Thanks you So Much
You're declaring A() as an array of type Base and you're also including a function called Base. From a maintenance and readibility perspective, this is a bad idea, I would strongly recommend giving a different name to your function.
That said, using simple example I can get this to work by tweaking the function signature to return an array:
Sub Test()
Dim a() As Base
a = GetBase()
End Sub
Function GetBase() As Base()
Dim Total_1 As Long
Dim b As Long
b = 0
Total_1 = 0
Dim Base_UT() As Base
ReDim Base_UT(Total_1)
Base_UT(b).UT = "string"
Base_UT(b).N_Inic = 1
Base_UT(b).N_Fin = 2
Base_UT(b).Largo = 3
GetBase = Base_UT
End Function
instead of:
Function Base() As Base
use:
Function Base() As Base()
but there other possible faults in your code, that the following code should help to avoid
Function Base1() As Base()
Dim b As Long, Total_1 As Long
Dim UT_Range As Range, cell As Range
With sheets("ARISTAS")
Set UT_Range = .Range(.Cells(2, 1), .Cells(Rows.Count, 1).End(xlUp))
Total_1 = UT_Range.Count - 1
ReDim Base_UT(Total_1) As Base
For Each cell In UT_Range.SpecialCells(XlCellType.xlCellTypeConstants)
Base_UT(b).UT = cell.Value
Base_UT(b).N_Inic = cell.Offset(, 1).Value
Base_UT(b).N_Fin = cell.Offset(, 2).Value
Base_UT(b).Largo = cell.Offset(, 8).Value '**
b = b + 1
Next cell
ReDim Preserve Base_UT(b - 1)
End With
Base1 = Base_UT
End Function
as you see I also changed the function name not to match the UDT name
Here is a very simple example based on your question:
Type Base
UT As String
N_Inic As Integer
N_Fin As Integer
Largo As Integer
End Type
Sub MAIN()
Dim alpha As Base
alpha = whatever()
MsgBox alpha.UT & alpha.N_Inic & alpha.N_Fin & alpha.Largo
End Sub
Public Function whatever() As Base
Dim poiuyt As Base
poiuyt.UT = "hello"
poiuyt.N_Inic = 11
poiuyt.N_Fin = 17
poiuyt.Largo = 19
whatever = poiuyt
End Function
It uses an internal variable of Type Base within the function and just passes it back:

How to split cells containing multiple values (comma delimited) into separate rows?

I am working with a sample of data that I'd like to split into several rows based on a comma delimiter. My data table in Excel prior to the split looks like this:
I would like develop VBA code to split values in Column C ('Company Point of Contact') and create separate lines for each 'Company Point of Contact'.
So far I have managed to split the values in Column C into separate lines. However I have not managed to split values in Columns D (Length of Relationship) and E (Strength of Relationship) as well, so that each value separated by a comma corresponds to its respective contact in Column C.
You will find below a sample of the code I borrowed to split my cells. The limitation with this code was that it didn't split the other columns in my table, just the one.
How can I make this code work to split the values in the other columns?
Sub Splt()
Dim LR As Long, i As Long
Dim X As Variant
Application.ScreenUpdating = False
LR = Range("A" & Rows.Count).End(xlUp).Row
Columns("A").Insert
For i = LR To 1 Step -1
With Range("B" & i)
If InStr(.Value, ",") = 0 Then
.Offset(, -1).Value = .Value
Else
X = Split(.Value, ",")
.Offset(1).Resize(UBound(X)).EntireRow.Insert
.Offset(, -1).Resize(UBound(X) - LBound(X) + 1).Value = Application.Transpose(X)
End If
End With
Next i
Columns("B").Delete
LR = Range("A" & Rows.Count).End(xlUp).Row
With Range("B1:C" & LR)
On Error Resume Next
.SpecialCells(xlCellTypeBlanks).FormulaR1C1 = "=R[-1]C"
On Error GoTo 0
.Value = .Value
End With
Application.ScreenUpdating = True
End Sub
You should not only iterate the rows, but also the columns, and check in each cell whether there is such a comma. When at least one of the cells in a row has a comma, it should be split.
You could then insert the row, and copy the parts before the comma in the newly created row, while removing that part from the original row which is then moved up one index.
You should also take care to increase the number of rows to traverse whenever you insert a row, or else you will do an incomplete job.
Here is code you could use:
Sub Splt()
Dim LR As Long, LC As Long, r As Long, c As Long, pos As Long
Dim v As Variant
Application.ScreenUpdating = False
LR = Cells(Rows.Count, 1).End(xlUp).Row
LC = Cells(1, Columns.Count).End(xlToLeft).Column
r = 2
Do While r <= LR
For c = 1 To LC
v = Cells(r, c).Value
If InStr(v, ",") Then Exit For ' we need to split
Next
If c <= LC Then ' We need to split
Rows(r).EntireRow.Insert
LR = LR + 1
For c = 1 To LC
v = Cells(r + 1, c).Value
pos = InStr(v, ",")
If pos Then
Cells(r, c).Value = Left(v, pos - 1)
Cells(r + 1, c).Value = Trim(Mid(v, pos + 1))
Else
Cells(r, c).Value = v
End If
Next
End If
r = r + 1
Loop
Application.ScreenUpdating = True
End Sub
I would adapt an approach using User Defined Objects (Class) and Dictionaries to collect and reorganize the data. Using understandable names so as to make future maintenance and debugging easy.
Also, by using VBA arrays, the macro should execute much more quickly than with multiple reads and writes to/from the worksheet
Then recompile the data into the desired format.
The two classes I have defined as
Site (and I have assumed that each site has only a single site contact, although that is easily changed, if needed) with information for:
Site Name
Site Key Contact
and a dictionary of Company Contact information
Company contact, which has the information for
name
length of relationship
Strength of relationship
I do check to make sure there are the same number of entries in the last three columns.
As you can see, it would be fairly simple to add additional information to either Class, if needed.
Enter two Class Modules and one Regular Module
Rename the Class Modules as indicated in the comments
Be sure to set a reference to Microsoft Scripting Runtime so as to be able to use the Dictionary object.
Also, you will probably want to redefine wsSrc, wsRes and rRes for your source/results worksheets/ranges. I put them on the same worksheet for convenience, but there is no need to.
Class Module 1
Option Explicit
'Rename this to: cSite
'Assuming only a single Site Key Contact per site
Private pSite As String
Private pSiteKeyContact As String
Private pCompanyContactInfo As Dictionary
Private pCC As cCompanyContact
Public Property Get Site() As String
Site = pSite
End Property
Public Property Let Site(Value As String)
pSite = Value
End Property
Public Property Get SiteKeyContact() As String
SiteKeyContact = pSiteKeyContact
End Property
Public Property Let SiteKeyContact(Value As String)
pSiteKeyContact = Value
End Property
Public Property Get CompanyContactInfo() As Dictionary
Set CompanyContactInfo = pCompanyContactInfo
End Property
Public Function AddCompanyContactInfo(ByVal CompanyContact As String, _
ByVal RelationshipLength As String, ByVal RelationshipStrength As String)
Set pCC = New cCompanyContact
With pCC
.CompanyContact = CompanyContact
.LengthOfRelationship = RelationshipLength
.StrengthOfRelationship = RelationshipStrength
pCompanyContactInfo.Add Key:=.CompanyContact, Item:=pCC
End With
End Function
Private Sub Class_Initialize()
Set pCompanyContactInfo = New Dictionary
End Sub
Class Module 2
Option Explicit
'Rename to: cCompanyContact
Private pCompanyContact As String
Private pLengthOfRelationship As String
Private pStrengthOfRelationship As String
Public Property Get CompanyContact() As String
CompanyContact = pCompanyContact
End Property
Public Property Let CompanyContact(Value As String)
pCompanyContact = Value
End Property
Public Property Get LengthOfRelationship() As String
LengthOfRelationship = pLengthOfRelationship
End Property
Public Property Let LengthOfRelationship(Value As String)
pLengthOfRelationship = Value
End Property
Public Property Get StrengthOfRelationship() As String
StrengthOfRelationship = pStrengthOfRelationship
End Property
Public Property Let StrengthOfRelationship(Value As String)
pStrengthOfRelationship = Value
End Property
Regular Module
Option Explicit
'Set Reference to Microsoft Scripting Runtime
Sub SiteInfo()
Dim wsSrc As Worksheet, wsRes As Worksheet, rRes As Range
Dim vSrc As Variant, vRes As Variant
Dim cS As cSite, dS As Dictionary
Dim I As Long, J As Long
Dim V As Variant, W As Variant, X As Variant
'Set source and results worksheets and results range
Set wsSrc = Worksheets("Sheet4")
Set wsRes = Worksheets("Sheet4")
Set rRes = wsRes.Cells(1, 10)
'Get source data
With wsSrc
vSrc = .Range(.Cells(1, 1), .Cells(.Rows.Count, 5).End(xlUp))
End With
'Split and collect the data into objects
Set dS = New Dictionary
For I = 2 To UBound(vSrc, 1) 'skip first row
Set cS = New cSite
V = Split(vSrc(I, 3), ",")
W = Split(vSrc(I, 4), ",")
X = Split(vSrc(I, 5), ",")
If Not UBound(V) = UBound(W) And UBound(V) = UBound(X) Then
MsgBox "Mismatch in Company Contact / Length / Strength"
Exit Sub
End If
With cS
.Site = vSrc(I, 1)
.SiteKeyContact = vSrc(I, 2)
For J = 0 To UBound(V)
If Not dS.Exists(.Site) Then
.AddCompanyContactInfo Trim(V(J)), Trim(W(J)), Trim(X(J))
dS.Add .Site, cS
Else
dS(.Site).AddCompanyContactInfo Trim(V(J)), Trim(W(J)), Trim(X(J))
End If
Next J
End With
Next I
'Set up Results array
I = 0
For Each V In dS
I = I + dS(V).CompanyContactInfo.Count
Next V
ReDim vRes(0 To I, 1 To 5)
'Headers
For J = 1 To UBound(vRes, 2)
vRes(0, J) = vSrc(1, J)
Next J
'Populate the data
I = 0
For Each V In dS
For Each W In dS(V).CompanyContactInfo
I = I + 1
vRes(I, 1) = dS(V).Site
vRes(I, 2) = dS(V).SiteKeyContact
vRes(I, 3) = dS(V).CompanyContactInfo(W).CompanyContact
vRes(I, 4) = dS(V).CompanyContactInfo(W).LengthOfRelationship
vRes(I, 5) = dS(V).CompanyContactInfo(W).StrengthOfRelationship
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

VBA - Split string into individual cells

I have a string compressed into one cell. I need to separate each part of the string into their own cell, while copying the data from the same row.
Here is my example data:
A | B
Row1 ABC ABD ABE ABF | CODE1
Row2 BCA DBA EBA FBA | CODE2
Row3 TEA BEF | CODE3
The result would be:
A B
ABC CODE1
ABD CODE1
ABE CODE1
ABF CODE1
BCA CODE2
DBA CODE2
EBA CODE2
FBA CODE2
TEA CODE3
BEF CODE3
I have about 2000 rows and would literally take 30 years to use the text to column function for this. So I am trying to write a vba macro. I think I am making this harder than it needs to be. Any thoughts or pushes in the right direction would be appreciated. Thanks in advance for any help.
This will work, (but it's mighty inefficient unless you do it in an array... nevertheless for only 2000 rows, you won't even notice the lag)
Function SplitThis(Str as String, Delimiter as String, SerialNumber as Long) As String
SplitThis = Split(Str, Delimiter)(SerialNumber - 1)
End Function
Use it as
= SPLITTHIS("ABC EFG HIJ", " ", 2)
' The result will be ...
"EFG"
You will still need to put in a whole lot of extra error checking, etc. if you need to use it for a distributed application, as the users might put in values greater than the number of 'split elements' or get delimiters wrong, etc.
I like iterating over cells for problems like this post.
' code resides on input sheet
Sub ParseData()
Dim wksOut As Worksheet
Dim iRowOut As Integer
Dim iRow As Integer
Dim asData() As String
Dim i As Integer
Dim s As String
Set wksOut = Worksheets("Sheet2")
iRowOut = 1
For iRow = 1 To UsedRange.Rows.Count
asData = Split(Trim(Cells(iRow, 1)), " ")
For i = 0 To UBound(asData)
s = Trim(asData(i))
If Len(s) > 0 Then
wksOut.Cells(iRowOut, 1) = Cells(iRow, 2)
wksOut.Cells(iRowOut, 2) = s
iRowOut = iRowOut + 1
End If
Next i
Next iRow
MsgBox "done"
End Sub
Assuming your data is on the first sheet, this populates the second sheet with the formatted data. I also assume that the data is uniform, meaning there is the same type of data on every row until the data ends. I did not attempt the header line.
Public Sub FixIt()
Dim fromSheet, toSheet As Excel.Worksheet
Dim fromRow, toRow, k As Integer
Dim code As String
Set fromSheet = Me.Worksheets(1)
Set toSheet = Me.Worksheets(2)
' Ignore first row
fromRow = 2
toRow = 1
Dim outsideArr() As String
Dim insideArr() As String
Do While Trim(fromSheet.Cells(fromRow, 1)) <> ""
' Split on the pipe
outsideArr = Split(fromSheet.Cells(fromRow, 1), "|")
' Split left of pipe, trimmed, on space
insideArr = Split(Trim(outsideArr(0)), " ")
' Save the code
code = Trim(outsideArr(UBound(outsideArr)))
' Skip first element of inside array
For k = 1 To UBound(insideArr)
toSheet.Cells(toRow, 1).Value = insideArr(k)
toSheet.Cells(toRow, 2).Value = code
toRow = toRow + 1
Next k
fromRow = fromRow + 1
Loop
End Sub
Let me try as well using Dictionary :)
Sub Test()
Dim r As Range, c As Range
Dim ws As Worksheet
Dim k, lrow As Long, i As Long
Set ws = Sheet1 '~~> change to suit, everything else as is
Set r = ws.Range("B1", ws.Range("B" & ws.Rows.Count).End(xlUp))
With CreateObject("Scripting.Dictionary")
For Each c In r
If Not .Exists(c.Value) Then
.Add c.Value, Split(Trim(c.Offset(0, -1).Value))
End If
Next
ws.Range("A:B").ClearContents
For Each k In .Keys
lrow = ws.Range("A" & ws.Rows.Count).End(xlUp).Row
If lrow = 1 Then i = 0 Else i = 1
ws.Range("A" & lrow).Offset(i, 0) _
.Resize(UBound(.Item(k)) + 1).Value = Application.Transpose(.Item(k))
ws.Range("A" & lrow).Offset(i, 1).Resize(UBound(.Item(k)) + 1).Value = k
Next
End With
End Sub
Above code loads all items in Dictionary and then return it in the same Range. HTH.
Here is an approach using a User Defined Type, Collection and arrays. I've been using this lately and thought it might apply. It does make writing the code easier, once you get used to it.
The user defined type is set in a class module. I called the type "CodeData" and gave it two properties -- Code and Data
I assumed your data was in columns A & B starting with row 1; and I put the results on the same worksheet but in columns D & E. This can be easily changed, and put on a different worksheet if that's preferable.
First, enter the following code into a Class Module which you have renamed "CodeData"
Option Explicit
Private pData As String
Private pCode As String
Property Get Data() As String
Data = pData
End Property
Property Let Data(Value As String)
pData = Value
End Property
Property Get Code() As String
Code = pCode
End Property
Property Let Code(Value As String)
pCode = Value
End Property
Then put the following code into a Regular module:
Option Explicit
Sub ParseCodesAndData()
Dim cCodeData As CodeData
Dim colCodeData As Collection
Dim vSrc As Variant, vRes() As Variant
Dim V As Variant
Dim rRes As Range
Dim I As Long, J As Long
'Results start here. But could be on another sheet
Set rRes = Range("D1:E1")
'Get Source Data
vSrc = Range("A1", Cells(Rows.Count, "B").End(xlUp))
'Collect the data
Set colCodeData = New Collection
For I = 1 To UBound(vSrc, 1)
V = Split(vSrc(I, 1), " ")
For J = 0 To UBound(V)
Set cCodeData = New CodeData
cCodeData.Code = Trim(vSrc(I, 2))
cCodeData.Data = Trim(V(J))
colCodeData.Add cCodeData
Next J
Next I
'Write results to array
ReDim vRes(1 To colCodeData.Count, 1 To 2)
For I = 1 To UBound(vRes)
Set cCodeData = colCodeData(I)
vRes(I, 1) = cCodeData.Data
vRes(I, 2) = cCodeData.Code
Next I
'Write array to worksheet
Application.ScreenUpdating = False
rRes.EntireColumn.Clear
rRes.Resize(rowsize:=UBound(vRes, 1)) = vRes
Application.ScreenUpdating = True
End Sub
Here is the solution I devised with help from above. Thanks for the responses!
Sub Splt()
Dim LR As Long, i As Long
Dim X As Variant
Application.ScreenUpdating = False
LR = Range("A" & Rows.Count).End(xlUp).Row
Columns("A").Insert
For i = LR To 1 Step -1
With Range("B" & i)
If InStr(.Value, " ") = 0 Then
.Offset(, -1).Value = .Value
Else
X = Split(.Value, " ")
.Offset(1).Resize(UBound(X)).EntireRow.Insert
.Offset(, -1).Resize(UBound(X) - LBound(X) + 1).Value = Application.Transpose(X)
End If
End With
Next i
Columns("B").Delete
LR = Range("A" & Rows.Count).End(xlUp).Row
With Range("B1:C" & LR)
On Error Resume Next
.SpecialCells(xlCellTypeBlanks).FormulaR1C1 = "=R[-1]C"
On Error GoTo 0
.Value = .Value
End With
Application.ScreenUpdating = True
End Sub