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
Related
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
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"
If there is a duplicate value in column A, I'd like column C-E summed, and column B and F to display the first value that appears.
For example:
A B C D E F
h 4 2 3 1 5
h 3 3 5 3 7
h 4 4 7 5 4
h 1 1 4 1 4
k 9 3 6 2 4
k 5 3 6 2 7
k 4 3 9 2 7
k 9 4 1 1 4
Would become:
A B C D E F
h 4 10 19 10 5
k 9 13 22 7 4
This is the code I used when I was given 4 columns and it worked fine. Now the documents I'm editing have 6 columns and I can't get it to work now.
Sub mergeCategoryValues()
Dim lngRow As Long
With ActiveSheet
lngRow = .Cells(65536, 1).End(xlUp).Row
.Cells(1).CurrentRegion.Sort key1:=.Cells(1), Header:=xlYes
Do
If .Cells(lngRow, 1) = .Cells(lngRow - 1, 1) Then
.Cells(lngRow - 1, 3) = .Cells(lngRow - 1, 3) + .Cells(lngRow, 3)
.Cells(lngRow - 1, 4) = .Cells(lngRow - 1, 4) + .Cells(lngRow, 4)
.Rows(lngRow).Delete
End If
lngRow = lngRow - 1
Loop Until lngRow = 1
End With
End Sub
Would anyone be able to help with this? Thanks in advance.
Note where I added two new statements to sum your two add'l columns in the code below, i.e.:
.Cells(lngRow - 1, 5) = .Cells(lngRow - 1, 5) + .Cells(lngRow, 5)
.Cells(lngRow - 1, 6) = .Cells(lngRow - 1, 6) + .Cells(lngRow, 6)
Essentially the code you provided is starting at the last row of your worksheet and working its way up, row-by-row, adding the values for the current row to the row right above it if the values in column 1 match. The 5 and 6 lines I added refer to the column numbers that will be aggregated.
Sub mergeCategoryValues()
Dim lngRow As Long
With ActiveSheet
lngRow = .Cells(65536, 1).End(xlUp).Row
.Cells(1).CurrentRegion.Sort key1:=.Cells(1), Header:=xlYes
Do
If .Cells(lngRow, 1) = .Cells(lngRow - 1, 1) Then
.Cells(lngRow - 1, 3) = .Cells(lngRow - 1, 3) + .Cells(lngRow, 3)
.Cells(lngRow - 1, 4) = .Cells(lngRow - 1, 4) + .Cells(lngRow, 4)
' added the next two statements for your new columns
.Cells(lngRow - 1, 5) = .Cells(lngRow - 1, 5) + .Cells(lngRow, 5)
.Cells(lngRow - 1, 6) = .Cells(lngRow - 1, 6) + .Cells(lngRow, 6)
.Rows(lngRow).Delete
End If
lngRow = lngRow - 1
Loop Until lngRow = 1
End With
End Sub
You can also do this with Power Query, available in Windows Excel 2010+ and O365
It's really just a single step of grouping by column A, and doing the correct aggregation on columns B..F. The third line of the MCode below is where all the magic occurs.
note with either of the below methods, sorting of the data is not required.
Also, either is easily modified to add/remove columns; and/or to decide for which rows to return either SUM or FIRST entry.
Although you can paste the M Code into the Advanced Editor, I suggest going through the steps of generating it yourself, especially if your columns have different names, or if the values are not integers
select some cell in the table
Data / Get&Transform / from Table/Range
When the PQ Editor opens
Select column A and group by
Select Advanced
Enter a Sum aggregation for each column B..F
After you have done that
Home / Advanced Editor
You'll see the Table.Group line has multiple List.Sum operations for each of those aggregated columns.
Change the List.Sum to List.First for the 2nd and last columns.
You can also change the column headers in that same line of code.
M Code
Source = Excel.CurrentWorkbook(){[Name="Table1"]}[Content],
#"Changed Type" = Table.TransformColumnTypes(Source,{{"A", type text}, {"B", Int64.Type}, {"C", Int64.Type}, {"D", Int64.Type}, {"E", Int64.Type}, {"F", Int64.Type}}),
#"Grouped Rows" = Table.Group(#"Changed Type", {"A"}, {{"First B", each List.First([B]), type nullable number},
{"Sum C", each List.Sum([C]), type nullable number}, {"Sum D", each List.Sum([D]), type nullable number},
{"Sum E", each List.Sum([E]), type nullable number},{"First F", each List.First([F]), type nullable number}})
in
#"Grouped Rows"
If you must use VBA, I suggest collecting the data for each item in column A into a dictionary, where the dictionary entry is another dictionary that sums the values for each column (except for the first and last column, where only the first value is retained).
Note that we work in a VBA array as it is usually an order of magnitude faster than working to/from the worksheet.
'Set reference to Microsoft Scripting Runtime (preferable)
'or convert to late binding
Option Explicit
Sub mergeCategoryValues()
Dim wsSrc As Worksheet, wsRes As Worksheet, rRes As Range
Dim vSrc As Variant, vRes As Variant
Dim Da As Dictionary, Dv As Dictionary
Dim I As Long, J As Long, sKeyA As String
Dim v, w
'set the Source and results worksheets, ranges
Set wsSrc = Worksheets("sheet6")
Set wsRes = Worksheets("sheet6")
Set rRes = wsRes.Cells(12, 16)
'read source data into vba array for faster processing
With wsSrc
vSrc = .Range(.Cells(1, 1), .Cells(.Rows.Count, 1).End(xlUp)).Resize(columnsize:=6)
End With
'read the values into dictionaries
'each dictionary of col a will contain dictionary with the other column values, either summed or just first
Set Da = New Dictionary
Da.CompareMode = TextCompare
For I = 2 To UBound(vSrc, 1) 'skip the header row
sKeyA = vSrc(I, 1)
'initial set up for column A ID
If Not Da.Exists(sKeyA) Then
Set Dv = New Dictionary
Dv.CompareMode = TextCompare
For J = 2 To UBound(vSrc, 2)
Dv.Add Key:=vSrc(1, J), Item:=vSrc(I, J)
Next J
Da.Add Key:=sKeyA, Item:=Dv
Else 'Column A entry already exists
'we just add the value from column C..next to last column
' leaving the first entry in columns A and the last column
For J = 3 To UBound(vSrc, 2) - 1
Da(sKeyA)(vSrc(1, J)) = Da(sKeyA)(vSrc(1, J)) + vSrc(I, J)
Next J
End If
Next I
'can sort the Da keys if necessary
'create results array
ReDim vRes(0 To Da.Count, 1 To UBound(vSrc, 2))
'Headers
For J = 1 To UBound(vSrc, 2)
vRes(0, J) = vSrc(1, J)
Next J
'Data
I = 0
For Each v In Da.Keys
I = I + 1
vRes(I, 1) = v
J = 1
For Each w In Da(v)
J = J + 1
vRes(I, J) = Da(v)(w)
Next w
Next v
'write to the worksheet
Set rRes = rRes.Resize(rowsize:=UBound(vRes, 1) + 1, columnsize:=UBound(vRes, 2))
Application.ScreenUpdating = False
With rRes
.EntireColumn.Clear
.Value = vRes
.HorizontalAlignment = xlCenter
.Style = "Output" 'can change or ignore this, especially if non-english version
.EntireColumn.AutoFit
End With
End Sub
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
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