VBA script to concatenate a cell value based on two columns - excel

I'm very new to VBA and just now started to automate things in excel. I have a requirement to concatenate a cell value based on two columns. For example
In the above excel, in the A column if Doc2 exists thrice, but it has levels of 3,4 & 3 (in row number 3,4, &6 respectively). I want to concatenate values of the id into a single column like below
Based on level and Document Name, if both are same then concatenate id else do not.
Sub ConcatenateCellsIfSameValueExists()
DestRowRef = 2
CheckedCell = Cells(2, "A").Value
For i = 2 To Range("A" & Rows.Count).End(xlUp).Row + 1
If Cells(i, "A").Value <> CheckedCell Then
tempConValues = tempConValues
Cells(DestRowRef, "C").Value = tempConValues
tempConValues = ""
DestRowRef = DestRowRef + 1
End If
tempConValues = tempConValues & " " & Cells(i, "B").Value
CheckedCell = Cells(i, "A").Value
Next
End Sub
I tried the above code, it only concatenates based on single-cell and also, repeated Document name once concatenated is not deleted. Can anyone please help here?

In VBA I would use a Dictionary to organize the data.
For a key, concatenate what you want to group (DocumentName and Level) and for the contents the concatenated ID's.
'Set Reference to Microsoft Scripting Runtime
Option Explicit
Sub jugate()
'Declare variables
Dim wsSrc As Worksheet, wsRes As Worksheet, rRes As Range
Dim vSrc As Variant, vRes As Variant
Dim D As Dictionary
Dim I As Long, V As Variant
Dim sKey As String
'set Source and Result worksheets and ranges
Set wsSrc = ThisWorkbook.Worksheets("sheet4") 'edit to real worksheet
Set wsRes = ThisWorkbook.Worksheets("sheet4") 'could put this on different sheet
Set rRes = wsRes.Cells(1, 6)
'read table into array for fastest processing
With wsSrc
vSrc = .Range(.Cells(1, 1), .Cells(.Rows.Count, 1).End(xlUp)).Resize(columnsize:=3)
End With
'read into dictionary, combining like doc/level
Set D = New Dictionary
D.CompareMode = TextCompare
For I = 2 To UBound(vSrc) 'skip header row
sKey = vSrc(I, 1) & "|" & vSrc(I, 3)
If Not D.Exists(sKey) Then
D.Add Key:=sKey, Item:=vSrc(I, 2)
Else
D(sKey) = D(sKey) & vbLf & vSrc(I, 2)
End If
Next I
'create results array
ReDim vRes(0 To D.Count, 1 To 3)
'header row
For I = 1 To 3
vRes(0, I) = vSrc(1, I)
Next I
'populate data
I = 0
For Each V In D.Keys
I = I + 1
vRes(I, 1) = Split(V, "|")(0) 'doc name
vRes(I, 2) = D(V) 'concatenated ID
vRes(I, 3) = Split(V, "|")(1) 'level
Next V
'write results to worksheet
Set rRes = rRes.Resize(rowsize:=UBound(vRes, 1) + 1, columnsize:=UBound(vRes, 2))
With rRes
.EntireColumn.Clear
.Value = vRes
'Next lines are just for formatting
'not really necessary and not internationally aware
.Style = "output"
.EntireColumn.AutoFit
.VerticalAlignment = xlCenter
End With
End Sub
You can also obtain your desired output using Power Query, available in Windows Excel 2010+ and Office 365 Excel
Select some cell in your original table
Data => Get&Transform => From Table/Range
When the PQ UI opens, navigate to Home => Advanced Editor
Make note of the Table Name in Line 2 of the code.
Replace the existing code with the M-Code below
Change the table name in line 2 of the pasted code to your "real" table name
Examine any comments, and also the Applied Steps window, to better understand the algorithm and steps
After Close/Load, format the resultant table for Word Wrap and centering.
This formatting should persist through subsequent refreshes.
M Code
let
//Change table name in next line to your actual table name
Source = Excel.CurrentWorkbook(){[Name="Table11"]}[Content],
//set data types
#"Changed Type" = Table.TransformColumnTypes(Source,{
{"DocumentName", type text}, {"ID", type text}, {"Level", Int64.Type}}),
//group by doc name and Level
//then aggregate the text strings
#"Grouped Rows" = Table.Group(#"Changed Type", {"DocumentName", "Level"}, {
{"ID", each List.Accumulate([ID],"",
(state,current)=> if state = "" then current else state & "#(lf)" & current), Text.Type}
}),
//Place columns in desired order
#"Reordered Columns" = Table.ReorderColumns(#"Grouped Rows",{"DocumentName", "ID", "Level"})
in
#"Reordered Columns"

Related

How to parse unique records and their row index

I have a set of files, some have no suffix and some with different suffixes. I would like to segregate the file names irrespective of their suffixes and list them along with the range of their row indices in the same spreadsheet. Below is the example and my failed code. Also attached the spreadsheet snapshot. Can you please help? Any new code/logic is welcome.
Input:
Row index
Filename
1
File1
2
File2_a
3
File2_b
4
File2_c
5
File3_a
6
File3_b
Output:
Filename
Row indices range
File1
1
1
File2
2
4
File3
5
6
VBA code
Sub GetUniqueFiles()
Dim sh As Worksheet
Set sh = ThisWorkbook.Sheets("Sheet1")
lastrow = sh1.Range("A" & Rows.Count).End(xlUp).Row
SameFile = False ' Flag to compare 2 consecutive file names
i = 3: j = 3
While i <= (lastrow - 1)
name_curt = sh.Range("B" & i).Value
name_next = sh.Range("B" & i + 1).Value
file_curt = Split(name_curt, "_")(0)
file_next = Split(name_next, "_")(0)
If file_curt <> file_next Then
sh.Range("D" & j).Value = file_curt
k1 = i
sh.Range("E" & j).Value = k1
sh.Range("F" & j).Value = k2
i = i + 1: j = j + 1
ElseIf file_curt = file_next Then
SameFile = True
sh.Range("B" & j).Value = file_curt
k1 = i
While SameFile
i = i + 1
name_curt = sh.Range("B" & i).Value
name_next = sh.Range("B" & i + 1).Value
file_curt = Split(name_curt, "_")(0)
file_next = Split(name_next, "_")(0)
Wend
End If
Wend
End Sub
Try this:
Sub GetUniqueFiles()
Dim sh As Worksheet, m, indx, rw As Range, f As String
Dim r As Long
Set sh = ThisWorkbook.Sheets("Sheet1")
Set rw = sh.Range("A3:B3") 'first input row
r = 2 'start row for output
Do While Application.CountA(rw) = 2 'loop while have data
indx = rw.Cells(1).Value
f = Split(rw.Cells(2).Value, "_")(0) ' "base" file name
m = Application.Match(f, sh.Columns("D"), 0) 'see if already listed
If IsError(m) Then 'not already listed ?
sh.Cells(r, "D").Value = f 'write file name
sh.Cells(r, "E").Value = indx 'write "first seen" index
m = r
r = r + 1
End If
sh.Cells(m, "F").Value = indx 'write "last seen" index
Set rw = rw.Offset(1, 0) 'next input row
Loop
End Sub
You can solve this without VBA. Add something like this in column C:
=LEFT(B2,IFERROR(FIND("_",B2)-1,LEN(B2)+1))
It will cut the underscore and everything after it. Next step is counting the distinct values. I would go for a pivot table, but there are lots of other ways.
You can obtain your desired output using Power Query, available in Windows Excel 2010+ and Office 365 Excel
Select some cell in your original table
Data => Get&Transform => From Table/Range or From within sheet
When the PQ UI opens, navigate to Home => Advanced Editor
Make note of the Table Name in Line 2 of the code.
Replace the existing code with the M-Code below
Change the table name in line 2 of the pasted code to your "real" table name
Examine any comments, and also the Applied Steps window, to better understand the algorithm and steps
M Code
let
//Change table name in next line to actual table name in your workbook
Source = Excel.CurrentWorkbook(){[Name="Table22"]}[Content],
//split on the underscore and remove the splitted suffix
#"Split Column by Delimiter" = Table.SplitColumn(Source, "Filename",
Splitter.SplitTextByDelimiter("_", QuoteStyle.Csv), {"Filename", "Filename.2"}),
//set data types -- frequently a good idea in PQ
#"Changed Type1" = Table.TransformColumnTypes(#"Split Column by Delimiter",{
{"Filename", type text}, {"Filename.2", type text}}),
#"Removed Columns" = Table.RemoveColumns(#"Changed Type1",{"Filename.2"}),
//Group by file name and extract the lowest and highest rows
#"Grouped Rows" = Table.Group(#"Removed Columns", {"Filename"}, {
{"Start Row", each List.Min([Row index]), type number},
{"End Row", each List.Max([Row index]), type number}})
in
#"Grouped Rows"
For execution speed and resource conservation it's best to minimize interaction with the worksheet from VBA. For example the following references the worksheet precisely twice no matter how long the list of files. Don't underestimate the value of restricting worksheet interaction from VBA.
Sub GetUniqueFiles()
Dim c&, i&, a$, b$, vIn, vOut
Const FILES_IN$ = "b3"
Const FILES_OUT$ = "d3"
With Range(FILES_IN)
vIn = .Resize(.End(xlDown).Row - .Row + 1)
End With
ReDim vOut(1 To UBound(vIn), 1 To 3)
For i = 1 To UBound(vIn)
b = Split(vIn(i, 1), "_")(0)
If a <> b Then
a = b
c = c + 1
vOut(c, 1) = b
vOut(c, 2) = i
If c > 1 Then vOut(c - 1, 3) = i - 1
End If
Next
If c > 1 Then vOut(c, 3) = i - 1
Range(FILES_OUT).Resize(UBound(vIn), 3) = vOut
End Sub

VBA transposing an array with parameters

I've managed to record all changes my team has done and in order to use it further I'll need to transpose the table into format supported by our systems. I was able to transpose one row but there are hundreds in my file so my code was useless.
My input table look like this
My goal for output looks like this
I've managed to transpose the whole table from tutorial (Essential Excel), however it doesn't fit my needs.
Sub TransposeTest()
Dim wks As Worksheet, wks2 As Worksheet
Dim MyArray As Variant
Dim LastRow As Integer, LastColumn As Integer
Dim StartCells As Range
Set wks = ThisWorkbook.Sheets("Sheet1")
Set wks2 = ThisWorkbook.Sheets("Transpose")
Set StartCell = wks.Range("A2")
LastRow = wks.Cells(wks.Rows.Count, StartCell.Column).End(xlUp).row
LastColumn = wks.Cells(StartCell.row, wks.Columns.Count).End(xlToLeft).Column
MyArray = wks.Range(StartCell, wks.Cells(LastRow, LastColumn)).Value2
MyArray = WorksheetFunction.Transpose(MyArray)
wks2.Range("a2", wks2.Cells(LastColumn, LastRow)).Value = MyArray
End Sub
I've been advised to use array for months and loop through each row however I unable to achieve it.
Please, try the next code. It should be very fast, even for large ranges. It uses arrays and works only in memory, the result being dropped at once:
Sub TransposeData()
Dim sh As Worksheet, shTr As Worksheet, lastR As Long, arr, arrfin, ArrH, i As Long, k As Long, j As Long
Set sh = ActiveSheet
Set shTr = sh.Next 'use here the sheet you need to return.
'if the next sheet is empty you can let the code as it is
lastR = sh.Range("A" & sh.rows.count).End(xlUp).row 'last row
arr = sh.Range("A1:Q" & lastR).value 'place the range in an array for faster iteration
ReDim arrfin(1 To UBound(arr) * 12 + 1, 1 To 7): k = 1 'redim the final array dimensions
ArrH = Split("Index,Person,Dept,Month,Sales,STMP,User", ",") 'create an array from the header strings
'place the headers in the first row of the final array:
For i = 0 To UBound(ArrH): arrfin(k, i + 1) = ArrH(i): Next: k = k + 1
'build the final array:
For i = 2 To UBound(arr)
For j = 1 To 12
arrfin(k + j - 1, 1) = arr(i, 1): arrfin(k + j - 1, 2) = arr(i, 2): arrfin(k + j - 1, 3) = arr(i, 3)
arrfin(k + j - 1, 4) = j & "." & Year(Date): arrfin(k + j - 1, 5) = arr(i, j + 3)
arrfin(k + j - 1, 6) = arr(i, 16): arrfin(k + j - 1, 7) = arr(i, 17)
Next j
k = k + j - 1 'reinitialize k variable for the next data row
Next i
'drop the final array content at once, and do some formatting:
With shTr.Range("A1").Resize(UBound(arrfin), UBound(arrfin, 2))
.value = arrfin
.rows(1).Font.Bold = True
.EntireColumn.AutoFit
For i = 7 To 9
.Borders(i).Weight = xlThin
.Borders.LineStyle = xlContinuous
Next
End With
MsgBox "Ready..."
End Sub
You can obtain your desired output using Power Query, available in Windows Excel 2010+ and Office 365 Excel
Select some cell in your original table
Data => Get&Transform => From Table/Range or From within sheet
When the PQ UI opens, navigate to Home => Advanced Editor
Make note of the Table Name in Line 2 of the code.
Replace the existing code with the M-Code below
Change the table name in line 2 of the pasted code to your "real" table name
Examine any comments, and also the Applied Steps window, to better understand the algorithm and steps
M Code
let
//Change table name in next row to the actual table name in your workbook
Source = Excel.CurrentWorkbook(){[Name="Table1"]}[Content],
//set the data types
#"Changed Type" = Table.TransformColumnTypes(Source,{
{"Index", Int64.Type}, {"Person", Int64.Type}, {"Dept", Int64.Type},
{"Jan", Int64.Type}, {"Feb", Int64.Type}, {"Mar", Int64.Type},
{"Apr", Int64.Type}, {"May", Int64.Type}, {"Jun", Int64.Type},
{"Jul", Int64.Type}, {"Aug", Int64.Type}, {"Sep", Int64.Type},
{"Oct", Int64.Type}, {"Nov", Int64.Type}, {"Dec", Int64.Type},
{"Time", type datetime}, {"User", type text}},"en-150"),
//Unpivot the Month columns
#"Unpivoted Other Columns" = Table.UnpivotOtherColumns(#"Changed Type",
{"Index", "Person", "Dept", "Time", "User"}, "Month", "Sales"),
//Transform "Month" column to "MonthNum.YearNum
//Not sure where the year should come from.
// for now will just hard code at as 2022
// but could use a different method.
mnthCol = Table.TransformColumns(#"Unpivoted Other Columns", {"Month", each
Date.ToText(Date.FromText("1-" & _ & "-2022"),"M.yyyy"),type text}),
//Reorder the columns
#"Reordered Columns" = Table.ReorderColumns(mnthCol,{"Index", "Person", "Dept", "Month", "Sales", "Time", "User"}),
//Rename the columns as per your example
rename = Table.RenameColumns(#"Reordered Columns",{
{"Time","STMP"},
{"Dept","Depr"}
})
in
rename
Original Data
Partial Results

Transform table structure

Hello is any sensible algorithm for transforming data table (Table A to Table B) ?
I trying to moving cells , but have no idea how to calculate a place where I should place additional row after my key field Name.
Table A origin
Name
Salary
Bonus
Amount
John S.
5000
Bonus A
50
John S.
Bonus B
100
Alex G.
7000
Bonus C
150
Alex G.
Bonus D
300
Table B (Expected outcome)
Name
Salary
Bonus
Amount
John S.
5000
John S.
Bonus A
50
John S.
Bonus B
100
Alex G.
7000
Alex G.
Bonus C
150
Alex G.
Bonus D
300
Sub TransformTable()
' Setting variables
Dim Name As String
Dim BaseSalary As String
Dim BonusName As String
Dim BonusAmount As Double
'Setting worksheet object
Dim SheetData As Worksheet
Set SheetData = Sheets("SheetData")
'counter for main loop
Dim x As Long
'Setting main object array
Dim MyArray As Variant
Dim Item As Integer
Item = 1
'reading values from table
MyArray = Worksheets("SheetData").ListObjects("Table1").DataBodyRange.Value
'counting last row value
'main loop
For x = LBound(MyArray) To UBound(MyArray)
'condition check how many costcenter ids with fixed value
lstRowSrs = SheetData.Cells(Rows.Count, 1).End(xlUp).Row
Worksheets("SheetData").Cells(Item + 1, 13).Value = MyArray(x, 1)
Worksheets("SheetData").Cells(Item + 1, 14).Value = MyArray(x, 2)
If MyArray(x, 3) <> "" Then
' Cells(x, lstRowSrs).EntireRow.Insert
Worksheets("SheetData").Cells(Item + 2, 15).Value = MyArray(x, 3)
Worksheets("SheetData").Cells(Item + 2, 16).Value = MyArray(x, 4)
Item = Item + 1
Else
Worksheets("SheetData").Cells(Item + 1, 15).Value = MyArray(x, 3)
Worksheets("SheetData").Cells(Item + 1, 16).Value = MyArray(x, 4)
Item = Item + 1
End If
Next x
End Sub
You can obtain your desired output using Power Query, available in Windows Excel 2010+ and Office 365 Excel
Select some cell in your original table
Data => Get&Transform => From Table/Range
When the PQ UI opens, navigate to Home => Advanced Editor
Make note of the Table Name in Line 2 of the code.
Replace the existing code with the M-Code below
Change the table name in line 2 of the pasted code to your "real" table name
Examine any comments, and also the Applied Steps window, to better understand the algorithm and steps
The basic algorithm:
Unpivot the Salary and Amount columns which puts them all into a single column
the Bonus column will have some duplicates -- remove them if the Attribute column contains "Salary"
Remove the contents of the Salary column; rename and reorder the columns
M Code
let
//change table name in next line to actual name in your workbook
Source = Excel.CurrentWorkbook(){[Name="Table1"]}[Content],
//set the data types
#"Changed Type" = Table.TransformColumnTypes(Source,{
{"Name", type text}, {"Salary", Int64.Type}, {"Bonus", type text}, {"Amount", Int64.Type}}),
//Unpivot the columns other than Name and Bonus
#"Unpivoted Other Columns" = Table.UnpivotOtherColumns(#"Changed Type", {"Name", "Bonus"}, "Attribute", "Value"),
//blank the "bonus" if attribute=salary
#"Replace Bonus with null" = Table.ReplaceValue(#"Unpivoted Other Columns",
each [Bonus],
each if [Attribute]="Salary" then null else [Bonus],
Replacer.ReplaceValue,{"Bonus"}),
//set columns in correct order
#"Reordered Columns" = Table.ReorderColumns(#"Replace Bonus with null",{"Name", "Attribute", "Bonus", "Value"}),
//rename "Attribute"=>"Salary" and blank the contents
Rename = Table.RenameColumns(#"Reordered Columns",{{"Attribute","Salary"},{"Value","Amount"}}),
blankIt = Table.ReplaceValue(Rename, each [Salary],null, Replacer.ReplaceValue,{"Salary"})
in
blankIt
Here's another way. It has the same results as #Sgdva but uses some slightly different techniques. Not better, just something to consider.
Sub TransformTable()
Dim vaValues As Variant
Dim i As Long
Dim aOutput() As Variant
Dim lCnt As Long
'put all the values in a 2-d array
vaValues = Sheet1.ListObjects(1).DataBodyRange
'make your output array - double the rows of the input
'it will be too many rows, but you won't run out of room
ReDim aOutput(1 To UBound(vaValues, 1) * 2, 1 To 4)
'Loop through the 2-d array
For i = LBound(vaValues, 1) To UBound(vaValues, 1)
If Len(vaValues(i, 2)) > 0 Then 'a salary exists
'add a row to the output array
lCnt = lCnt + 1
aOutput(lCnt, 1) = vaValues(i, 1)
aOutput(lCnt, 4) = vaValues(i, 2)
End If
If Len(vaValues(i, 4)) > 0 Then 'a bonus exists
'add a row to the output array
lCnt = lCnt + 1
aOutput(lCnt, 1) = vaValues(i, 1)
aOutput(lCnt, 3) = vaValues(i, 3)
aOutput(lCnt, 4) = vaValues(i, 4)
End If
Next i
'write out the output array in one shot
Sheet1.Range("G1").Resize(UBound(aOutput, 1), UBound(aOutput, 2)).Value = aOutput
End Sub
Solution
I changed the logic that you posted as follows
Identify the rows to be added
Insert them at once (to save memory instead of inserting one by one)
Append the data needed by looping again in the rows
For demonstration purposes, I limited the logic to the active sheet and with the data sample shown.
Demo
Code
Sub Exec_DivideSalary()
Dim CounterRow As Long
Dim RangeRowsToAdd As Range
For CounterRow = 2 To Cells.SpecialCells(xlCellTypeLastCell).Row
If Cells(CounterRow, 2).Value <> "" Then ' 1. If Cells(CounterRow, 2).Value <> ""
If RangeRowsToAdd Is Nothing Then ' 2. If RangeRowsToAdd Is Nothing
Set RangeRowsToAdd = Rows(CounterRow + 1)
Else ' 2. If RangeRowsToAdd Is Nothing
Set RangeRowsToAdd = Union(RangeRowsToAdd, Rows(CounterRow + 1))
End If ' 2. If RangeRowsToAdd Is Nothing
End If ' 1. If Cells(CounterRow, 2).Value <> ""
Next CounterRow
RangeRowsToAdd.Insert Shift:=xlDown
For CounterRow = 2 To Cells.SpecialCells(xlCellTypeLastCell).Row
If Cells(CounterRow, 2).Value <> "" Then ' 3. If Cells(CounterRow, 2).Value <> ""
Cells(CounterRow + 1, 1).Value = Cells(CounterRow, 1).Value: Cells(CounterRow + 1, 3).Value = Cells(CounterRow, 3).Value: Cells(CounterRow + 1, 4).Value = Cells(CounterRow, 4).Value
Cells(CounterRow, 4).Value = Cells(CounterRow, 2).Value
Cells(CounterRow, 2).Value = "": Cells(CounterRow, 3).Value = ""
End If ' 3. If Cells(CounterRow, 2).Value <> ""
Next CounterRow
End Sub

Find and extract data from Excel sheet and paste it into related columns using VBA

I have the following objective:
Loop through a huge excel sheet (200,000+ rows)
Find some data based on matching parameters (the original file is an xml file, so structured data... but I am using a Mac, where the XML parser is not supported)
Copy the data between double quote related to each variable
Paste the value in the relative column
Additional constraints I have to face:
Every value to copy is between double quotes (this is "good news", helping me to identify the right data to copy and paste)
Imagine the txt. data as a list of data objects (=> it's sequence of purchases, with related info, made by customers). The macro should be able to loop through the list and copy paste the data, starting a new row every time a new ID purchase comes up. Good news is that every purchase is marked by a unique ID.
I’m providing below an example of input and output. I would really be grateful if someone could help me on this.
//INPUT
<SequenceNumber="1">
<PurchaseSegment DayDateTime="2020-02-29T06:45:00" ArrivalDateTime="2020-02-29T09:40:00" StopQuantity="0" PurchaseNumber="229" ElapsedTime="115">"
<DayPoS LocationCode="AAA" DockID="4" />"
<ArrivalPoS LocationCode="CCC" />"
</SequenceNumber>
<SequenceNumber="2">
<PurchaseSegment DayDateTime="2019-09-28T06:41:00" ArrivalDateTime="2020-02-29T09:40:00" StopQuantity="1" PurchaseNumber="123" ElapsedTime="115">"
<DayPoS LocationCode="AAA" DockID="3" />"
<ArrivalPoS LocationCode="QQC" />"
</SequenceNumber>
//EXPECTED OUTPUT (by running the VBA macro)
Here you can find also my VBA attempt, I leveraged some VBA code I already found, but didn't succeed.
Public Sub TextDataToColumn()
Dim val As Variant val = "PurchaseSegment DayDateTime" // it would be great to have a list of paramaters here...
Set c = Cells.Find(val, LookIn:=xlValues, MatchCase:=False)
If Not c Is Nothing Then
Do
MsgBox "Value of val is found at " & c.Address & vbCrLf & c.Offset(0, 1).Value & vbCrLf & c.Offset(0, 2).Value
Set c = Cells.FindNext(c)
Loop While Not c Is Nothing And c.Address <> firstaddress
End If
End Sub
You can parse your text using VBA text functions.
As I mentioned in my comments, what you posted is NOT a valid XML document.
I adapted what I would have done using a Dictionary and Classes, to using a Collection and Array. (Although there is a Mac add-in to enable use of dictionary object).
After pre-processing the text lines to make it easier to parse, we loop through all the text lines and store the appropriate items in defined locations in the array.
We collect each row of item into the collection object, and then output them onto a worksheet.
It works for the sample data you posted, but if your data is, in addition to being invalid xml, also has irregularities in the naming and formatting of the different nodes, you'll need a more sophisticated parsing method.
Option Explicit
Option Compare Text
Sub splitSeq()
Dim cS As Collection
Dim WB As Workbook, wsSrc As Worksheet, wsRes As Worksheet
Dim rRes As Range
Dim vSrc As Variant, vRes As Variant
Dim I As Long, v, w, x
'Set workbook, worksheet and range parameters
Set WB = ThisWorkbook
With WB
Set wsSrc = .Worksheets("Sheet4") 'or wherever the data exists
On Error Resume Next 'add a sheet if not present
Set wsRes = Worksheets("Results")
Select Case Err.Number
Case 9 'need to add a sheet
Set wsRes = WB.Worksheets.Add
wsRes.Name = "Results"
Case Is <> 0 'Something else went wrong
MsgBox "Error number " & Err.Number & vbLf & Err.Description
Err.Clear
End Select
End With
'set results range
Set rRes = wsRes.Cells(1, 1)
'read data into array for processing speed
'assuming all data is in column A
With wsSrc
vSrc = .Range(.Cells(1, 1), .Cells(.Rows.Count, 1).End(xlUp))
End With
'loop through data and save the Sequence objects
'Each starts with <sequence and ends with </sequence
Set cS = New Collection
For I = 1 To UBound(vSrc, 1)
If vSrc(I, 1) Like "<Sequence*" Then
ReDim vRes(1 To 8) 're-initialize array
Else
'Remove confusing spaces in node names and <> in attributes
vSrc(I, 1) = Replace(vSrc(I, 1), "Segment DayDate", "SegmentDayDate")
vSrc(I, 1) = Replace(vSrc(I, 1), "Pos Location", "PosLocation")
vSrc(I, 1) = Replace(vSrc(I, 1), "<", "")
vSrc(I, 1) = Replace(vSrc(I, 1), ">""", "")
vSrc(I, 1) = Replace(vSrc(I, 1), ">", "")
v = Split(vSrc(I, 1))
For Each w In v
x = Split(w, "=")
'Remove leading and trailing double quotes
If Left(x(1), 1) = """" And Right(x(1), 1) = """" Then
x(1) = Mid(x(1), 2)
x(1) = Left(x(1), Len(x(1)) - 1)
End If
Select Case x(0)
Case "PurchaseSegmentDayDateTime"
vRes(1) = x(1)
Case "ArrivalDateTime"
vRes(2) = x(1)
Case "StopQuantity"
vRes(3) = x(1)
Case "PurchaseNumber"
vRes(4) = x(1)
Case "ElapsedTime"
vRes(5) = x(1)
Case "DayPosLocationCode"
vRes(6) = x(1)
Case "ArrivalPosLocationCode"
vRes(8) = x(1)
Case "DockID"
vRes(7) = x(1)
Case "/SequenceNumber"
cS.Add vRes
End Select
Next w
End If
Next I
'set up results array
ReDim vRes(0 To cS.Count, 1 To 8)
'Headers
vRes(0, 1) = "PurchaseSegment DayDateTime"
vRes(0, 2) = "ArrivalDateTime"
vRes(0, 3) = "StopQuantity"
vRes(0, 4) = "PurchaseNumber"
vRes(0, 5) = "ElapsedTime"
vRes(0, 6) = "DayPoS LocationCode"
vRes(0, 7) = "DockID"
vRes(0, 8) = "ArrivalPoS LocationCode"
'fill in the data
I = 0
For Each v In cS
I = I + 1
With v
vRes(I, 1) = v(1)
vRes(I, 2) = v(2)
vRes(I, 3) = v(3)
vRes(I, 4) = v(4)
vRes(I, 5) = v(5)
vRes(I, 6) = v(6)
vRes(I, 7) = v(7)
vRes(I, 8) = v(8)
End With
Next v
'Set Results range
Set rRes = rRes.Resize(UBound(vRes, 1) + 1, UBound(vRes, 2))
'Write and format results
With rRes
.EntireColumn.Clear
.Value2 = vRes
.Style = "Output"
.EntireColumn.AutoFit
End With
End Sub

How to parse a row?

I need to write VBA code that:
Reads in rows in a sheet
Checks if column E has the character ";#" and parses the string on that character
Creates a new row and copies and pastes the row contents from the parsed row to the new row (both rows will have the same contents)
Renames the original column to the word that comes before ;#" and renames the copied column to the word that follows ";#"
Example with three columns:
Original row: String A;#String B;#StringC (cell 1) Complete (cell 2) 5/20/2019 (cell 3)
What I need:
Updated_Original row: String A Complete 5/20/2019
New row 1: String B Complete 5/20/2019
New row 2: String C Complete 5/20/2019
Private Sub CommandButton1_Click()
Dim SplitText
Dim WrdArray() As String, size As Integer
'iterate through all the rows in the sheet
For i = 1 To i = 2000
'take one cell at a time
cell_value = ThisWorkbook.ActiveSheet.Cells(i, 1).Value
size = WorksheetFunction.CountA(Worksheets(1).Columns(1))
'Split cell contents
WrdArray() = Split(cell_value, vbLf)
For j = LBound(WrdArray) To UBound(WrdArray)
Var = WrdArray()(0)
Next j
' WrdArray().Resize(UBound(SplitText) + 1).Value = Application.Transpose(SplitText)
ReDim WrdArray(size)
counter = counter + 1
Var = SplitText
Next i
End Sub
So long as you don't need the Title column of your screenshot in the particular order you show, this is a simple task for Power Query (aka Get & Transform in excel 2016+).
Merely
Get&Transform Data from Table/Range
Split by delimiter (and your delimiter appears to be ;# and not just #
Split into rows
And you're done:
This is the M-Code for the PQ:
let
Source = Excel.CurrentWorkbook(){[Name="Table1"]}[Content],
#"Changed Type" = Table.TransformColumnTypes(Source,{{"Status", type text}, {"Priority", type text}, {"Name", type text}, {"Date", type date}}),
#"Split Column by Delimiter" = Table.ExpandListColumn(Table.TransformColumns(#"Changed Type", {{"Name", Splitter.SplitTextByDelimiter(";#", QuoteStyle.Csv), let itemType = (type nullable text) meta [Serialized.Text = true] in type {itemType}}}), "Name"),
#"Changed Type1" = Table.TransformColumnTypes(#"Split Column by Delimiter",{{"Name", type text}})
in
#"Changed Type1"
I chose to use a combo of Len() and InStr() to figure where "complete" was in your string to figure the content to append to each part of the split. I made a few assumptions related to your columns/rows (see image below):
Option Explicit
Sub fdsa()
Dim arr As Variant, i As Long, s As Long, lr As Long, c As Long, z As String
lr = Cells(Rows.Count, 1).End(xlUp).Row
For i = 1 To lr
arr = Split(Cells(i, 1).Value, ",")
If InStr(Cells(i, 1).Value, "Complete") Then z = Right(Cells(i, 1).Value, Len(Cells(i, 1).Value) - InStr(Cells(i, 1).Value, "Complete") + 1)
c = 2
For s = LBound(arr) To UBound(arr)
If s = UBound(arr) Then z = ""
Cells(i, c).Value = arr(s) & " " & z
c = c + 1
Next s
Next i
End Sub
Here's the data I used:
Making a lot of assumptions based on what appears to be incomplete information here, but according to the information and examples provided, something like this should work for you:
Sub tgr()
Dim ws As Worksheet
Dim rData As Range
Dim aResults() As Variant
Dim aData As Variant
Dim vTemp As Variant
Dim sTemp As String
Dim ixResult As Long
Dim i As Long, j As Long
Set ws = ActiveWorkbook.ActiveSheet
Set rData = ws.Range("A1").CurrentRegion
If rData.Cells.Count = 1 Then
ReDim aData(1 To 1, 1 To 1)
aData(1, 1) = rData.Value
Else
aData = rData.Value
End If
ReDim aResults(1 To 65000, 1 To UBound(aData, 2))
ixResult = 0
For i = 1 To UBound(aData, 1)
For Each vTemp In Split(Replace(aData(i, 1), ";#", ","), ",")
If Len(Trim(vTemp)) > 0 Then
ixResult = ixResult + 1
aResults(ixResult, 1) = Trim(vTemp)
For j = 2 To UBound(aData, 2)
aResults(ixResult, j) = aData(i, j)
Next j
End If
Next vTemp
Next i
rData.Resize(ixResult).Value = aResults
End Sub

Resources