vba - sum unique with multiple conditions - excel

I would like to create a macro that will sum the unit column base by Product & DC. It will populate out the same product name, code with dc code and code without. Base on the code given I try to modify the code but fail when I added the column Sales Person in front, Date & Country, as output I want to show the data with the latest date and country and the new data in the column.
Sub Button1_Click()
' Define constants.
Const sName As String = "Sheet1"
Const sFirstCellAddress As String = "A2"
Const dName As String = "Sheet2"
Const dFirstCellAddress As String = "A1"
' Define workbook.
Dim wb As Workbook: Set wb = ThisWorkbook
' Write values from Source Range to Data Array.
Dim LastRow As Long
Dim Data As Variant
With wb.Worksheets(sName).Range(sFirstCellAddress)
LastRow = .Worksheet.Cells(.Worksheet.Rows.Count, .Column).End(xlUp).Row
Data = .Resize(LastRow - .Row + 1, 8) 'modify
End With
' Write unique values from Data Array to Unique Dictionary.
Dim dict As Object: Set dict = CreateObject("Scripting.Dictionary")
dict.CompareMode = vbTextCompare
Dim arr(1 To 2) As Variant
Dim Key As Variant
Dim cArr As Variant
Dim r As Long
For r = 1 To UBound(Data, 1)
Key = Data(r, 1)
If Not IsError(Key) Then
If Not dict.Exists(Key) Then
dict.Add Key, arr
End If
If Data(r, 2) = 0 Then
cArr = dict(Key)
cArr(2) = cArr(2) + Data(r, 3)
dict(Key) = cArr
Else
cArr = dict(Key)
cArr(1) = cArr(1) + Data(r, 3)
dict(Key) = cArr
End If
End If
Key2 = Data(r, 4)
Key3 = Data(r, 5)
Key4 = Data(r, 6)
Key5 = Data(r, 7)
Key6 = Data(r, 8)
Next r
' Write values from Unique Dictionary to Result Array.
Dim rCount As Long: rCount = dict.Count + 1
Dim Result As Variant: ReDim Result(1 To rCount, 1 To 8) 'change from 3 to 8
Result(1, 1) = "SalesPerson"
Result(1, 2) = "Company"
Result(1, 3) = "Product"
Result(1, 4) = "Unit With DC Code"
Result(1, 5) = "Unit Without DC Code"
Result(1, 6) = "Date"
Result(1, 7) = "Country"
Result(1, 8) = "Grade"
If rCount > 1 Then
r = 1
For Each Key In dict.Keys
r = r + 1
Result(r, 1) = Key1
Result(r, 2) = Key2
Result(r, 3) = Key3
Result(r, 4) = CLng(dict(Key)(1))
Result(r, 5) = CLng(dict(Key)(2))
Result(r, 6) = Key4
Result(r, 7) = Key5
Result(r, 8) = Key6
Next Key
End If
' Write values from Result Array to Destination Range.
With wb.Worksheets(dName).Range(dFirstCellAddress).Resize(, 5)
.Resize(rCount).Value = Result
' Delete below.
'.Resize(.Worksheet.Rows.Count - .Row - rCount + 1) _
.Offset(rCount).ClearContents
End With
MsgBox ("Done")
End Sub
SalesPerson
Company
Product
DC
Unit
Date
Country
Grade
JOHN
TPP
ABC
2
12-Feb
MY
AA
JOHN
TPP
ABC
1234
4
13-Feb
MY
AA
JOHN
TPP
ABC
1234
4
14-Feb
US
AA
JOHN
PEN
DEF
5678
2
12-Feb
US
AA
JOHN
PEN
DEF
5678
2
18-Feb
MY
AA
JOHN
PKG
GHI
9012
2
16-Feb
UK
AA
I want to the output in another sheets as below:-
SalesPerson
Company
Product
Unit with DC Code
Unit Without DC Code
Date
Country
Grade
JOHN
TPP
ABC
6
2
14-Feb
US
AA
JOHN
PEN
DEF
4
18-Feb
MY
AA
JOHN
PKG
GHI
2
16-Feb
UK
AA

You need to build the dictionary key from the first 3 fields SalesPerson,Company,Product. I have concatenated them with tab character so they can be separated later for the results sheet.
Option Explicit
Sub Button1_Click()
' Define constants.
Const sName As String = "Sheet1"
Const sFirstCellAddress As String = "A2"
Const dName As String = "Sheet2"
Const dFirstCellAddress As String = "A1"
' Define workbook.
Dim wb As Workbook: Set wb = ThisWorkbook
' Write values from Source Range to Data Array.
Dim LastRow As Long
Dim Data As Variant
With wb.Worksheets(sName).Range(sFirstCellAddress)
LastRow = .Worksheet.Cells(.Worksheet.Rows.Count, .Column).End(xlUp).Row
Data = .Resize(LastRow - .Row + 1, 8) 'modify
End With
' Write unique values from Data Array to Unique Dictionary.
Dim dict As Object: Set dict = CreateObject("Scripting.Dictionary")
dict.CompareMode = vbTextCompare
Dim arr(3) As Variant, Key As String, tmp As Variant
Dim r As Long, i As Long
For r = 1 To UBound(Data, 1)
' composite key SalesPerson,Company,Product
Key = Data(r, 1) & vbTab & Data(r, 2) & vbTab & Data(r, 3)
If Not IsError(Key) Then
If Not dict.Exists(Key) Then
dict.Add Key, Array(0, 0, r)
End If
i = 0
If Len(Data(r, 4)) = 0 Then i = 1
' sum counts
tmp = dict(Key)
tmp(i) = tmp(i) + Data(r, 5)
tmp(2) = r ' store last row
dict(Key) = tmp
End If
Next r
' result
Dim k As Variant, wsOut As Worksheet, rngOut As Range
Set wsOut = wb.Worksheets(dName)
Set rngOut = wsOut.Range(dFirstCellAddress)
rngOut.Resize(, 8) = Array("SalesPerson", "Company", "Product", "Unit With DC Code", _
"Unit Without DC Code", "Date", "Country", "Grade")
For Each k In dict
Set rngOut = rngOut.Offset(1, 0)
tmp = Split(k, vbTab)
rngOut.Offset(0, 0) = tmp(0)
rngOut.Offset(0, 1) = tmp(1)
rngOut.Offset(0, 2) = tmp(2)
rngOut.Offset(0, 3) = dict(k)(0)
rngOut.Offset(0, 4) = dict(k)(1)
' Date Country Grade
r = dict(k)(2)
rngOut.Offset(0, 5) = Data(r, 6)
rngOut.Offset(0, 6) = Data(r, 7)
rngOut.Offset(0, 7) = Data(r, 8)
Next
MsgBox "Done", vbInformation
End Sub

In addition to a VBA solution (and I would probably use a dictionary with the SalesPerson as the key, and a class object containing the relevant information), I would suggest also looking at Power Query (available in Excel 2010+).
It may prove easier to maintain and modify once you get used to the language.
For the task at hand, you could start, for example, by
select a cell in the data table
Data => Get&Transform => from Table/Range
When the PQ UI opens, goto Home => Advanced Editor
Note the name of the table in line 2 of the code you see.
Then replace that code with the M-Code below, changing the table name to what your actual table name is.
You should be able to figure out what is happening by going through the applied steps window in the UI, and also reading the annotations.
To update, you merely Refresh the query. And you could program a button to do that.
M Code edited and shortened
let
//change table name in next line to the real table name in your workbook
Source = Excel.CurrentWorkbook(){[Name="Table1"]}[Content],
#"Changed Type" = Table.TransformColumnTypes(Source,
{{"SalesPerson", type text}, {"Company", type text}, {"Product", type text}, {"DC", Int64.Type},
{"Unit", Int64.Type}, {"Date", type date}, {"Country", type text}, {"Grade", type text}}),
//Group by SalesPerson, Company and Product
//Extract the needed information
#"Grouped Rows" = Table.Group(#"Changed Type", {"SalesPerson", "Company", "Product"},
{
{"Unit with DC Code",(t) => List.Sum(Table.Column(Table.SelectRows(t, each [DC] <> null),"Unit")), Int64.Type},
{"Unit without DC Code", (t) => List.Sum(Table.Column(Table.SelectRows(t, each [DC] = null),"Unit")),Int64.Type},
{"Date", each List.Max([Date]), type nullable date},
{"Country", (t)=> Table.Column(Table.SelectRows(t, each [Date] = List.Max(Table.Column(t,"Date"))),"Country"){0},type text},
{"Grade", (t)=> Table.Column(Table.SelectRows(t, each [Date] = List.Max(Table.Column(t,"Date"))),"Grade"){0}, type text}
})
in
#"Grouped Rows"

Related

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

How to Transpose header row value group by particular column value

I have some data in excel in following format:
Cust_Name Prod1 Prod2 Prod3 Prod4 Prod5
A 0 100 120 0 0
B 145 120 168 0 200
C 350 300 0 340 0
I need to convert the following format in below-mentioned report format.
I want to transpose those product in a column group by Cust_Name which has value >0 else it shouldn't be part of final output report.
I have tried many pivot option but it didn't work.
Required Output:
Cust_Name Product Price
A Prod2 100
Prod3 120
Total A - 220
B Prod1 145
Prod2 120
Prod3 168
Prod5 200
Total B - 633
C Prod1 350
Prod2 300
Prod4 340
Total C - 890
You can do this with PowerQuery.
Select any cell in your source data. Use Data>Get & Transform Data>From Table/Range.
The PowerQuery editor will open, like this:
Select the Cust_Name column by clicking the column header. Use Transform>Unpivot Columns>Unpivot Other Columns:
At this point, optionally filter the Value column to exclude 0.
Now use Home>Close & Load to put the data back into your workbook.
You can now create a pivot table to get your sub totals:
Here is the query from the Advanced Editor dialog in the PowerQuery editor:
let
Source = Excel.CurrentWorkbook(){[Name="Table1"]}[Content],
#"Changed Type" = Table.TransformColumnTypes(Source,{{"Cust_Name", type text}, {"Prod1", Int64.Type}, {"Prod2", Int64.Type}, {"Prod3", Int64.Type}, {"Prod4", Int64.Type}, {"Prod5", Int64.Type}}),
#"Unpivoted Other Columns" = Table.UnpivotOtherColumns(#"Changed Type", {"Cust_Name"}, "Attribute", "Value")
in
#"Unpivoted Other Columns"
If you need a VBA solution, please test the next code. It will return in another sheet the processing result. It is designed to process as many products are on the sheet (in the headers row). The code should be very fast, all processing being done in memory:
Sub TransposeSummarize()
Dim sh As Worksheet, shRet As Worksheet, lastR As Long, lastCol As Long, dict As Object
Dim arr, arrFin, arrDict, i As Long, J As Long, k As Long, count As Long
Set sh = ActiveSheet
Set shRet = sh.Next 'use here the sheet you need returning
lastR = sh.Range("A" & sh.rows.count).End(xlUp).row 'last row
lastCol = sh.cells(1, sh.Columns.count).End(xlToLeft).Column 'last column
arr = sh.Range("A1", sh.cells(lastR, lastCol)).Value 'place the range in an array for faster iteration
ReDim arrDict(UBound(arr, 2) - 2) 'Redim the arrDict for first time
Set dict = CreateObject("Scripting.Dictionary") 'set the Dictionary (Scripting) object
For i = 2 To UBound(arr) 'iterate between the array elements (rows and columns)
For J = 2 To UBound(arr, 2)
arrDict(J - 2) = arr(i, J) 'fill arrDict with values on the i row
Next J
dict(arr(i, 1)) = arrDict 'place the array as dictionary item
Erase arrDict: ReDim arrDict(UBound(arr, 2) - 2) 'reinitialize the necessary array
Next i
ReDim arrFin(1 To dict.count * lastCol + 1, 1 To 3): k = 1
'Put the Headers in the final array:
arrFin(1, 1) = "Cust_Name": arrFin(1, 2) = "Product": arrFin(1, 3) = "Price": k = 2
For i = 0 To dict.count - 1 'process the dictionary keys/items and create the final array
arrFin(k, 1) = dict.Keys()(i)
For J = 0 To UBound(dict.Items()(i))
If dict.Items()(i)(J) <> 0 Then
arrFin(k, 2) = arr(1, J + 2): arrFin(k, 3) = dict.Items()(i)(J)
count = count + dict.Items()(i)(J): k = k + 1
End If
Next J
arrFin(k, 1) = "Total " & dict.Keys()(i): arrFin(k, 2) = "-": arrFin(k, 3) = count
count = 0: k = k + 1
Next i
'drop the array content at once, at the end of the code:
shRet.Range("A1").resize(k - 1, UBound(arrFin, 2)).Value = arrFin
End Sub
Edited:
I overcomplicated the above code only because I started working at it before receiving the answer regarding the unique "cust_names". I intended to use the dictionary to also process multiple such names. The next version, does not use a dictionary, anymore and extracts the final array content only processing the first one. Just playing with VBA:
Sub TransposeSummarizeUnique()
Dim sh As Worksheet, shRet As Worksheet, lastR As Long, lastCol As Long
Dim arr, arrFin, i As Long, J As Long, k As Long, count As Long
Set sh = ActiveSheet
Set shRet = sh.Next 'use here the sheet you need returning
lastR = sh.Range("A" & sh.rows.count).End(xlUp).row 'last row
lastCol = sh.cells(1, sh.Columns.count).End(xlToLeft).Column 'last column
arr = sh.Range("A1", sh.cells(lastR, lastCol)).Value 'place the range in an array for faster iteration
ReDim arrFin(1 To UBound(arr) * lastCol + 1, 1 To 3)
'Put the Headers in the final array:
arrFin(1, 1) = "Cust_Name": arrFin(1, 2) = "Product": arrFin(1, 3) = "Price": k = 2
For i = 2 To UBound(arr) 'iterate between the array rows:
arrFin(k, 1) = arr(i, 1) 'fill the customer name
For J = 2 To UBound(arr, 2) 'iterate beteen the array columns"
If arr(i, J) <> 0 Then
arrFin(k, 2) = arr(1, J): arrFin(k, 3) = arr(i, J)'fill the product and its value
count = count + arr(i, J): k = k + 1 'calculate the values sum
End If
Next J
'write the balance row (Total, Sum):
arrFin(k, 1) = "Total " & arr(i, 1): arrFin(k, 2) = "-": arrFin(k, 3) = count
count = 0: k = k + 1 'reinitialize the count and increment the array row
Next i
'drop the array content at once, at the end of the code:
shRet.Range("A1").resize(k - 1, UBound(arrFin, 2)).Value = arrFin
End Sub

Identify Column Number by defined Header String (ignore upper case, lower case, indentation)

I have a defined header string that I am trying to locate the respective column number for in a table. The table headers titles formats are indented with Alt+Enter.
The code works fine if the header string is written normally (without indentation)
Sample: Origin_Country(in 2-letter codes)
In the excel table however the header format is different as mentioned.
sample:
Origin_
Country
(in 2 letter format)
Excel seems to think there is a space in the header due to the indentation and it is not recognized.
Did anyone encounter this and know a work around?
Option Compare Text
Option Explicit
Sub ListChanges()
Dim arrOrig, arrCurrent, delta, i As Long, ii As Long, r As Long, m
Dim rngOrig As Range, rngCurrent As Range, id, col As Long, vO, vC
Dim ls As String
Dim HeaderName As String, HeaderRow As Long
Dim j As Long
Dim LastRow As Long, LastCol As Long, RowProcess As Long
Dim TopLeftAddress As String
'Origin Country
Dim OCountryCol As Long
Dim strOCountryCol As String
'DGF Lane ID
Dim DGFLaneIDCol As Long
Dim strDGFLaneIDCol As String
'Origin Region
Dim ORegionCol As Long
Dim strORegionCol As String
'------
Dim ActSht As Worksheet
Set ActSht = Worksheets(2)
'ActiveWorkbook.Sheets(General).Range ("1:1")
TopLeftAddress = ActSht.Range("1:1").Address 'identify top left cell of the table, to see where the table starts
LastCol = ActSht.Range(TopLeftAddress).End(xlToRight).Column ' total number of columns in each pivot table
i = ActSht.Range(TopLeftAddress).Column 'column number where the table starts
HeaderRow = ActSht.Range("1:1").Row 'row number where the table starts
For j = 25 To LastCol 'i set j at 25 so that I don't have to loop through all columns
HeaderName = ActSht.Cells(HeaderRow, j)
If InStr(HeaderName, "Origin_Country(in 2-letter codes)") > 0 Then
OCountryCol = j
strOCountryCol = Split(ActSht.Cells(, OCountryCol).Address, "$")(1)
ElseIf InStr(HeaderName, "Origin_Region(enter AP, AM, EURO, MEA)") > 0 Then
ORegionCol = j
strORegionCol = Split(ActSht.Cells(, ORegionCol).Address, "$")(1)
End If
Next
Set rngOrig = Original.Cells(1).CurrentRegion
Set rngCurrent = Current.Cells(1).CurrentRegion
arrOrig = rngOrig.Value
arrCurrent = rngCurrent.Value
ReDim delta(1 To UBound(arrCurrent, 1) * (UBound(arrCurrent, 2)), 1 To 8) 'max possible size
delta(1, 1) = "Location"
delta(1, 2) = "Original Value"
delta(1, 3) = "Changed Value"
delta(1, 4) = "Deviation"
delta(1, 5) = "Header"
delta(1, 6) = "Row ID"
delta(1, 7) = "Origin Region"
delta(1, 8) = "Origin Country"
r = 1 'row in delta array
For i = 2 To UBound(arrCurrent, 1)
id = arrCurrent(i, 1)
'find the corresponding row
m = Application.Match(id, rngOrig.Columns(1), 0)
If Not IsError(m) Then
For col = 2 To UBound(arrCurrent, 2)
vO = arrOrig(m, col)
vC = arrCurrent(i, col)
If (Len(vC) > 0 Or Len(vO) > 0) And vC <> vO Then
r = r + 1
delta(r, 1) = rngCurrent.Cells(i, col).Address(False, False)
delta(r, 2) = vO
delta(r, 3) = vC
If Len(vO) > 0 And Len(vC) > 0 Then
If IsNumeric(vO) And IsNumeric(vC) Then
delta(r, 4) = vC / vO - 1 'eg
End If
End If
delta(r, 5) = arrCurrent(1, col) 'header
delta(r, 6) = arrCurrent(i, 1) 'id
delta(r, 7) = arrCurrent(i, 26) 'id region
delta(r, 8) = arrCurrent(i, 27) 'id country
End If
Next col
Else
'no id match, just record the cell address and the current id
r = r + 1
delta(r, 1) = rngCurrent.Cells(i, 1).Address(False, False)
delta(r, 6) = id
End If
Next
With Changes
.Activate
.Cells(1).CurrentRegion.Clear
.[a1].Resize(r, UBound(delta, 2)) = delta '<< edited here
With .Cells(1).CurrentRegion
.HorizontalAlignment = xlCenter
With Rows(1).Font
.Size = 12
.Bold = 1
End With
.Columns.AutoFit
End With
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

Splitting and replacing delimited strings into new rows using VBA in Excel

I have data in 2 columns. The data in column B is comma delimited. I need each instance to appear on a new row while retaining it's original ID from column A. I also need the data in 3 columns so Name is in B and Number in C. It appears as so:
A--------B
1--------Sam Jones, 1 hours, Chris Bacon, 2 hours
2--------John Jacob, 3 hours
3--------John Hancock, 4 hours, Brian Smith, .5 hours
I am able to get it as such using my code below:
A--------B
1--------Sam Jones, 1
1--------Chris Bacon, 2 hours
2--------John Jacob, 3 hours
3--------John Hancock, 4
3--------Brian Smith, .5 hours
I need it to be: (notice last value in string also has hours removed when added to new line)
A---------B------------------------C
1---------Sam Jones-----------1
1---------Chris Bacon----------2
2---------John Jacob-----------3
3---------John Hancock-------4
3---------Brian Smith----------.5
I have the following code started: (I can't manage to remove the "hours" from the last person in each delimited string and I can't get it into 3 columns)
Sub splitByColB()
Dim r As Range, i As Long, ar
Set r = Worksheets("Sheet1").Range("B2").End(xlDown)
Do While r.Row > 1
ar = Split(r.Value, " hours, ")
If UBound(ar) >= 0 Then r.Value = ar(0)
For i = UBound(ar) To 1 Step -1
r.EntireRow.Copy
r.Offset(1).EntireRow.Insert
r.Offset(1).Value = ar(i)
Next
Set r = r.Offset(-1)
Loop
End Sub
Something like this is what you're looking for:
Sub tgr()
Dim ws As Worksheet
Dim aData As Variant
Dim aTemp As Variant
Dim aResults(1 To 65000, 1 To 3) As Variant
Dim ResultIndex As Long
Dim i As Long, j As Long
Set ws = ActiveWorkbook.Sheets("Sheet1")
With ws.Range("B2", ws.Cells(ws.Rows.Count, "B").End(xlUp))
If .Row < 2 Then Exit Sub 'No data
aData = .Offset(, -1).Resize(, 2).Value
End With
For i = LBound(aData, 1) To UBound(aData, 1)
If Len(Trim(aData(i, 2))) = 0 Then
ResultIndex = ResultIndex + 1
aResults(ResultIndex, 1) = aData(i, 1)
Else
aTemp = Split(aData(i, 2), ",")
For j = LBound(aTemp) To UBound(aTemp) Step 2
ResultIndex = ResultIndex + 1
aResults(ResultIndex, 1) = aData(i, 1)
aResults(ResultIndex, 2) = Trim(aTemp(j))
aResults(ResultIndex, 3) = Trim(Replace(aTemp(j + 1), "hours", vbNullString, , , vbTextCompare))
Next j
End If
Next i
ws.Range("A2").Resize(ResultIndex, UBound(aResults, 2)).Value = aResults
End Sub
You can use Power Query. It is a free MS add-in in 2010, 2013 and included in 2016 where it is called Get & Transform
Split column 2 by delimiter custom --> hours,
Select the ID column and unpivot other columns
Select column 2 and split by delimiter = comma
Remove unnecessary column
Replace value "hours"
And if you add to the table, you can re-run the query
let
Source = Excel.CurrentWorkbook(){[Name="Table1"]}[Content],
#"Changed Type" = Table.TransformColumnTypes(Source,{{"ID", Int64.Type}, {"Data", type text}}),
#"Split Column by Delimiter" = Table.SplitColumn(#"Changed Type", "Data", Splitter.SplitTextByDelimiter("hours,", QuoteStyle.Csv), {"Data.1", "Data.2"}),
#"Changed Type1" = Table.TransformColumnTypes(#"Split Column by Delimiter",{{"Data.1", type text}, {"Data.2", type text}}),
#"Unpivoted Other Columns" = Table.UnpivotOtherColumns(#"Changed Type1", {"ID"}, "Attribute", "Value"),
#"Split Column by Delimiter1" = Table.SplitColumn(#"Unpivoted Other Columns", "Value", Splitter.SplitTextByDelimiter(",", QuoteStyle.Csv), {"Value.1", "Value.2"}),
#"Changed Type2" = Table.TransformColumnTypes(#"Split Column by Delimiter1",{{"Value.1", type text}, {"Value.2", type text}}),
#"Removed Columns" = Table.RemoveColumns(#"Changed Type2",{"Attribute"}),
#"Replaced Value" = Table.ReplaceValue(#"Removed Columns","hours","",Replacer.ReplaceText,{"Value.2"})
in
#"Replaced Value"
I would use a class with the name data
Option Explicit
Public Id As String
Public FullName As String
Public hours As String
and the following code
Option Explicit
Sub SplitIt()
Dim rg As Range
Dim col As New Collection
Dim dataLine As data
Set rg = Worksheets("Sheet1").Range("A1").CurrentRegion
Dim vDat As Variant
vDat = rg.Columns
Dim lDat As Variant
Dim i As Long, j As Long
For i = LBound(vDat) To UBound(vDat)
lDat = Split(vDat(i, 2), ",")
For j = LBound(lDat) To UBound(lDat) Step 2
Dim hDat As Variant
hDat = Split(Trim(lDat(j + 1)), " ")
Set dataLine = New data
dataLine.Id = vDat(i, 1)
dataLine.FullName = Trim(lDat(j))
dataLine.hours = hDat(0)
col.Add dataLine
Next j
Next i
' Print Out
For i = 1 To col.Count
Set dataLine = col(i)
rg.Cells(i, 1) = dataLine.Id
rg.Cells(i, 2) = dataLine.FullName
rg.Cells(i, 3) = dataLine.hours
Next i
End Sub
Why not split on hours to a) add a record delimiter and b) get rid of hours?
Option Explicit
Sub splitByColB()
Dim r As Long, i As Long, hrs As Variant, cms As Variant
With Worksheets("sheet1")
For r = .Cells(.Rows.Count, "B").End(xlUp).Row To 2 Step -1
hrs = Split(.Cells(r, "B").Value2 & ", ", " hours, ")
ReDim Preserve hrs(UBound(hrs) - 1)
If CBool(UBound(hrs)) Then _
.Cells(r, "A").Offset(1, 0).Resize(UBound(hrs), 1).EntireRow.Insert
For i = UBound(hrs) To LBound(hrs) Step -1
cms = Split(hrs(i), ", ")
.Cells(r, "A").Offset(i, 0) = .Cells(r, "A").Value
.Cells(r, "A").Offset(i, 1) = cms(0)
.Cells(r, "A").Offset(i, 2) = cms(1)
Next i
Next r
End With
End Sub

Resources