VBA, Parse by "|" and Transpose, Next row - vba

I have the following data in a cells A1
|stack|over|flow|
and cells A2..
|today|is|friday
How can I delimit this and transpose it into a vertical/column based view view?
Delimiting will give me data row based, which is good but that I have to transpose this manually each time. I plan to do this for many rows. I realized this could be tricky as the next row will need to be pushed back down for each time.
Result A1:A6:
Stack
Over
flow
today
is
friday
Edit

For unlimited rows and unlimited columns:
Sub splt()
Dim str As String
Dim col As Long, rw As Long, colcnt As Long, rwcnt As Long
With Sheets("Sheet1")
colcnt = .Cells(1, .Columns.Count).End(xlToLeft).Column 'total no of columns
For col = 1 To colcnt
rwcnt = .Cells(.Rows.Count, col).End(xlUp).Row 'total no of rows for specific column
For rw = 1 To rwcnt
str = str & .Cells(rw, col)
Next rw
rw = 1
For Each Item In Split(str, "|") 'split string and display output
If Item <> "" Then
.Cells(rw, col) = Item
rw = rw + 1
End If
Next
str = ""
Next
End With
End Sub
Edit:
You can use an array for this, but the following method is less complicated to easy to write and read:
Sub splt()
Dim rw As Long, i As Long, rwcnt As Long
i = 1
With Sheets("Sheet1")
rwcnt = .Cells(.Rows.Count, 2).End(xlUp).Row 'last non-empty row number
For rw = 1 To rwcnt 'from row 1 till last non-empty row
For Each Item In Split(.Cells(rw, 2), "|") 'split the string in column 2 from "|"
If Item <> "" Then ' 'if the splitted part of the string is not empty
.Cells(i, 4) = .Cells(rw, 1) 'populate column 4 with column 1
.Cells(i, 5) = Item 'populate column 5 with splitted part of the string
.Cells(i, 6) = .Cells(rw, 3) 'populate column 6 with column 3
i = i + 1 ' increase i variable by one to be able to write the next empty row for the next loop
End If
Next 'loop to next splitted string
Next rw 'loop to next row
.Columns("A:C").EntireColumn.Delete 'when all data is extracted to Columns D-E-F, delete Columns A-B-C and your results will be in Column A-B-C now
End With
End Sub

This one manages an unlimited number of rows on column A
Sub go()
Dim strFoo As String
Dim LastRow As Long
Dim LastPosition As Long
Dim MySheet As Worksheet
Dim arr() As String
Dim i As Long
Dim j As Long
Set MySheet = ActiveWorkbook.ActiveSheet
MySheet.Range("A1").EntireColumn.Insert
LastRow = MySheet.Cells(MySheet.Rows.Count, "B").End(xlUp).Row
LastPosition = 1
For i = 1 To LastRow
strFoo = MySheet.Range("B" & i)
If strFoo <> "" Then
arr = Split(strFoo, "|")
For j = 0 To UBound(arr)
If arr(j) <> "" Then
MySheet.Range("A" & LastPosition) = arr(j)
LastPosition = LastPosition + 1
End If
Next j
End If
Next i
End Sub

You can do this with Power Query or Get & Transform
Data --> Get & Transform Data --> From Table/Range
Then in the Query Editor
Split Column by Delimiter
Use a Custom Delimiter: the Pipe |
Split at left most (to get rid of that first pipe
Remove Column 1 (the blank column)
Split Column by delimiter
Use the Advanced Option and select to split into rows
Save and you are done.

Related

Splitting a value that is delimitted

transactions sheet
ID1 Name Amount ID2
123 A 1 0;124;0
456 B 2 124;0;0
789 C 3 456;0;0
transactions sheet (Expected Result)
ID1 Name Amount ID2 Summary
123 A 1 0;124;0 124
456 B 2 124;0;0 456
789 C 3 456;0;0
I have tried text to columns but I am unsure on how to ignore all the 0's and only display the value if its >0 in column D. I am new to vba so would appreciate some advice on this so I can learn.
Code:
Sub SplitRange()
Dim cell As Range
Dim str As Variant 'string array
Dim r As Integer
For Each cel In ActiveSheet.UsedRange
If InStr(cell.Value, ";") > 0 Then 'split
str = Split(cell.Value, ";")
For r = LBound(str) To UBound(str)
cel.Offset(r).Value = Trim(str(r))
If r < UBound(str) Then cell.Offset(r + 1).EntireRow.Insert
Next r
End If
Next cell
End Sub
At first we should not loop through all used cells but only the row where these ID2 are that we need, which is a lot faster.
The easiest way would be just to remove all ;0 and 0; then only the value remains. The following will work if there is always only one real value that is not 0 e.g 0;124;0.
Public Sub FindValueRangeInColumn()
Const Col As Long = 4 'the column where the ID2 is in
Dim ws As Worksheet
Set ws = ThisWorkbook.ActiveSheet
Dim lRow As Long
lRow = ws.Cells(ws.Rows.Count, Col).End(xlUp).Row 'find last used row in column
Dim iRow As Long
For iRow = 2 To lRow 'loop throug rows from 2 to last used row
Dim strSource As String
strSource = ws.Cells(iRow, Col) 'read value
strSource = Replace(ws.Cells(iRow, Col), ";0", "") 'remove all ;0
If Left$(strSource, 2) = "0;" Then strSource = Right$(strSource, Len(strSource) - 2) 'remove 0; from the beginnning
ws.Cells(iRow, Col + 1).Value = strSource 'write value
Next iRow
End Sub
If there can be more than 1 non-zero value like 0;124;0;222;0;0;144 then replace
ws.Cells(iRow, Col + 1).Value = strSource 'write value
with a splitting alternative …
If InStr(1, strSource, ";") > 1 Then
Dim SplitValues As Variant
SplitValues = Split(strSource, ";")
Dim iValue As Long
For iValue = LBound(SplitValues) To UBound(SplitValues)
ws.Cells(iRow, Col + 1 + iValue).Value = SplitValues(iValue) 'write value
Next iValue
Else
ws.Cells(iRow, Col + 1).Value = strSource 'write value
End If
Morning,
What you need here is to split the entry into an array and then check the values of the array as you loop the array:
Sub SplitString()
Dim TempArray() As String
Dim i as Integer
Dim j As Integer
For i = 1 To 10
TempArray = Split(Worksheets("Sheet1").Cells(i,4).Value,";")
For j = 0 to UBound(TempArray)
If CDbl(TempArray(j)) <> 0 Then
[Output value]
End if
Next j
Next i
End Sub
Create a more useful loop than 1 = 1 to 10 but you get the idea...
Note in the above:
- The CDbl is to ensure that the check reads it as a number and not a text string.
So, you want to concatenate non-0 values into a string, then put that in the next cell?
Sub SplitRange()
Dim workcell As Range
Dim str() As String 'string array
Dim r As Long 'VBA automatically stores Integers as Longs, so there is no Memory saved by not using Long
Dim output As String
output = ";" 'Start with a single delimiter
For Each workcell In Intersect(ActiveSheet.UsedRange,ActiveSheet.Columns(4)) 'Go down the cells in Column D
If InStr(workcell.Value, ";") > 0 Then 'split
str = Split(workcell.Value,";")
For r = LBound(str) To UBound(str)
If inStr(output, ";" & Trim(str(r)) & ";") < 1 Then 'If number is not already in output
output = output & Trim(str(r)) & ";" 'Add the number and ";" to the end of the string
End If
Next r
Erase str 'Tidy up array, ready to recycle
End If
Next workcell
'We now have a unique list of all items, starting/ending/delimited with ";"
output = Replace(output,";0;",";") 'Remove the item ";0;" if it exists
If Len(output) > 2 Then 'output contains at least 1 non-zero number
output= Mid(output,2,len(output)-2) 'Remove ";" from the start and end
str = Split(output,";") 'Split the list of unique values into an array
For r = lbound(str) To ubound(str)
ActiveSheet.Cells(r+2-lbound(str),5).Value = str(r) 'List the values in column 5, starting from row 2
Next r
Erase str 'Tidy up array
End If
End Sub
To remove "0"s from a single row as an Excel formula, try this:
=SUBSTITUTE(SUBSTITUTE(SUBSTITUTE("|;" & A1 & ";|", ";0;",";"),";|",""),"|;","")
From the inside-out:
SUBSTITUTE("|;" & A1 & ";|", ";0;",";") Sandwich our values in wrappers ("|;0;240;0;|") and replace any ";0;" with ";" ("|;240;|")
.
SUBSTITUTE(PREV,";|","") Remove ";|" ("|;240")
.
SUBSTITUTE(PREV,"|;","") Remove "|;" ("240")

Excel VBA: Transpose different parts of a string

I have values that are horizontally in cells next to each other. In each cell, I'm extracting a certain substring of the cell and want to transpose each part vertically in certain columns.
Example:
ColA ColB ColC
First.Second<Third> Fourth.Fifth<Sixth> Seventh.Eighth<Ninth>
Should look like on a new worksheet (ws2):
ColA ColB ColC
First Second Third
Fourth Fifth Sixth
Seventh Eighth Ninth
I tried looping over rows and columns, but that skipped randomly
For i = 2 to lastRow
lastCol = ws.Cells(i, ws.Columns.Count).End(xlToLeft).Column
For j = 2 to lastCol
cellVal = ws.Cells(i, j).Value
firstVal = Split(cellVal, ".")
secondVal = 'extract second val
thirdVal = 'extract third val
ws2.Cells(i,1).Value = firstVal
ws2.Cells(i,2).Value = secondVal
ws3.Cells(i,4).Value = thirdVal
EDIT: Updated almost working code below:
Sub transPose()
Dim used As Range
Set used = Sheet1.UsedRange 'make better constraint if necessary
Dim cell As Range
Dim arr(0 To 3) As String
Dim str As String
Dim pointStr As Variant, arrowSplit As Variant
Dim rowCount As Long
rowCount = 0
For Each cell In used 'This goes across rows and then down columns
str = Trim(cell.Value2)
If str <> "" Then 'Use better qualification if necessary
spaceStr = Split(str, " ")
arr(0) = spaceStr(0)
arr(1) = spaceStr(1)
arrowSplit = Split(spaceStr(1), "<")
arr(2) = LCase(Mid(str, Application.Find("<", str) + 1, 1)) & LCase(arrowSplit(0))
openEmail = InStr(str, "<")
closeEmail = InStr(str, ">")
arr(3) = Mid(str, openEmail + 1, closeEmail - openEmail - 1)
rowCount = rowCount + 1
Sheet2.Cells(1 + rowCount, 1).Resize(1, 4).Value = arr
End If
Next cell
End Sub
EDIT2: Data actually looks like
ColA ColB etc...
John Smith<John.Smith#google.com> Jane Doe<Jane.Doe#google.com>
And Should look like:
ColA ColB ColC ColD
John Smith jsmith john.smith#google.com
Jane Doe jdoe jane.doe#google.com
Try this:
Sub transPose()
Dim used As Range
Set used = Sheet1.UsedRange 'make better constraint if necessary
Dim cell As Range
Dim arr(0 To 2) As String
Dim str As String
Dim pointStr As Variant, arrowSplit As Variant
Dim rowCount As Long
rowCount = 0
For Each cell In used 'This goes across rows and then down columns
str = cell.Value2
If str <> "" Then 'Use better qualification if necessary
pointStr = Split(str, ".")
arr(0) = pointStr(0)
arrowSplit = Split(pointStr(1), "<")
arr(1) = arrowSplit(0)
arr(2) = Split(arrowSplit(1), ">")(0)
rowCount = rowCount + 1
Sheet2.Cells(1 + rowCount, 1).Resize(1, 3).Value = arr
End If
Next cell
End Sub
For each input row, you will have 3 output rows, meaning you increment the output row by 3 for each input row. Additionally, the Cells function takes (row, col) parameters.
The math becomes goofy if you're iterating i and j from the start row/col to the last row/col, so I suggest instead iterating over the count of rows/cols and using a starting point for reference, either a cell stored as a Range object or the start row/col.
For i = 0 to ws.Rows.Count
For j = 0 to ws.Columns.Count
cellVal = ws.Cells(i + startRow, j + startCol).Value
firstVal = Split(cellVal, ".")
secondVal = 'extract second val
thirdVal = 'extract third val
ws2.Cells((i*3) + startRow, j + startCol).Value = firstVal
ws2.Cells((i*3) + 1 + startRow, j + startCol).Value = secondVal
ws3.Cells((i*3) + 2 + startRow, j + startCol).Value = thirdVal
Etc...
In fact, if I were doing this, I would probably just make inputRange and outputRange parameters of the function and then just iterate through those. It would simplify both the iteration (no need for the messy startRow or startCol) and the indexing. If you are looking for that sort of solution, drop a comment and I can add it.
edited after OP's edited question
you could try this:
Sub main2()
Dim cell As Range, row As Range
Dim arr As Variant
Dim finalValues(1 To 4) As String
Dim iRow As Long
Dim ws As Worksheet, ws2 As Worksheet
Set ws = Worksheets("originalData") '<--| change "originalData" to your actual sheet name with starting data
Set ws2 = Worksheets("results") '<--| change "results" to your actual sheet name with starting data
For Each row In ws.UsedRange.Rows
For Each cell In row.SpecialCells(xlCellTypeConstants)
arr = Split(Replace(Replace(cell.Value, "<", " "), ">", ""), " ")
finalValues(1) = arr(0): finalValues(2) = arr(1): finalValues(3) = Left(arr(0), 1) & arr(1): finalValues(4) = arr(2)
iRow = iRow + 1
ws2.Cells(iRow, 1).Resize(, UBound(finalValues)).Value = finalValues
Next
Next
End Sub

remove blanks from combobox with two lists

I'm trying to remove the blank records from a combobox with two lists.
This is my code:
Private Sub UserForm_Initialize()
Dim N As Range
Dim LastRow As Integer
Dim ws As Worksheet
PREST.ColumnCount = 2
Set ws = Worksheets("L_Location")
LastRow = ws.Range("A" & ws.Rows.Count).End(xlUp).Row
Dim i, j As Integer
Dim location(2 To 100, 1 To 2) As String
For j = 1 To 2
For i = 2 To LastRow
If ws.Cells(i, j).Value <> vbNullString Then
location(i, j) = ws.Cells(i, j).Value
End If
Next i
Next j
PREST.List = location
End Sub
I don't know what I'm doing wrong.
You are having blanks because your 2D array is already sized with 100 rows. A simple workaround would be to first count the non-empty rows, then dimension the Array accordingly.
Dim location() As String
Dim count As Long
count = Range("A2:A" & LastRow).SpecialCells(xlCellTypeConstants).Cells.count
ReDim location(1 To count, 1 To 2)
'then continue from here to fill the array
This code will fill the combobox with your range value then will delete any empty item:
Private Sub UserForm_Initialize()
Dim LastRow As Long
Dim ws As Worksheet
PREST.ColumnCount = 2
Set ws = Worksheets("L_Location")
LastRow = ws.Range("A" & ws.Rows.Count).End(xlUp).Row
Dim i As Long ', j As Integer
PREST.List = ws.Range("a1:b" & LastRow).Value
For i = PREST.ListCount - 1 To 0 Step -1
If PREST.List(i) = "" Then PREST.RemoveItem i
Next
End Sub
I tried this :
Dim location() As String
ReDim location(LastRow - 2, 1)
For j = 0 To 1
For i = 0 To LastRow - 2
If ws.Cells(i + 2, j + 1).Value <> vbNullString And ws.Cells(i + 2, j + 1).Value <> "" Then
location(i, j) = ws.Cells(i + 2, j + 1).Value
End If
Next i
Next j
PREST.List = location
which seems to work but i guess its gonna give me an error if the list is empty (lastrow = 1)
Since you say that any two cells on the same row are both either blank or with values, then you could go like follows:
Dim cell As Range
Dim i As Long, j As Long
PREST.ColumnCount = 2
With Worksheets("L_Location") '<--| reference your worksheet
With .Range("A2", .Cells(.Rows.Count,1).End(xlUp)).SpecialCells(xlCellTypeConstants) '<--| reference its column A not empty cells from row 1 down to last not empty one
Dim location(1 To .Count, 1 To 2) As String '<--| size your array rows number to that of referenced cells
For Each cell In .Cells '<--| loop through referenced cells
i = i + 1 '<--| update array row index
For j = 1 To 2 '<--| loop through array columns
location(i, j) = cell.Offset(j -1).Value '<--| fill array
Next j
Next cell
End With
End With
PREST.List = location

Find the first empty row after the given row number in Excel VBA

I need to find the first empty row number after the given row number.
please check the image below
for e.g: assume, my current row number is 6 then my output should be 10.
Something like this?
function FindFirstEmpty(i as long)
while cells(i,1).value <> ""
i = i + 1
wend
FindFirstEmpty = i
End function
Depends how you are obtaining the row from which to begin.
Dim startRow As Long
Dim i As Long
Dim lastRow As Long
Dim sh As Worksheet
startRow = 2 'Set first row to check
Set sh = ThisWorkbook.Worksheets("Sheet1")
lastRow = sh.Cells(sh.Rows.Count, "A").End(xlUp).Row
For i = startRow To lastRow
If sh.Cells(i, 1) = "" Then
MsgBox "First empty row: " & i
Exit For
End If
Next i
Have you tried something like this?
Note: This won't show you when the last row is empty.
I use CountA to see if the entire row is empty.
Function FirstEmtpyRow(startRow As Long) As Long
Do
startRow = startRow + 1
If startRow = rpws.Count Then Exit Function
Loop Until WorksheetFunction.CountA(Rows(startRow)) = 0
FirstEmtpyRow = startRow
End Function
You can use .End(xlDown) but you have to be careful that the immediately next cell is not blank or you could skip over it.
dim rw as long, nrw as long
rw = 6
with worksheets("sheet1")
with .cells(rw, "A")
if IsEmpty(.Offset(1, 0)) then
nrw = rw + 1
else
nrw = .end(xldown).offset(1, 0).row
end if
end with
end with
debug.print nrw

how to insert a row before pasting an array

I currently have an array which I populate and paste in a sheet named "T1" using a macro. My current macro uses the rowcount function to determine the used rows and pastes the array from the next available row.
The problem I am having is that when I paste this array multiple times, the arrays need to be spaced by a row so that i can differentiate different submissions. This is what I have so far, and I was hoping someone could help me with this:
Sub CopyData()
Dim Truearray() As String
Dim cell As Excel.Range
Dim RowCount1 As Integer
Dim i As Integer
Dim ii As Integer
Dim col As Range
Dim col2 As Range
i = 0
ii = 2
RowCount1 = DHRSheet.UsedRange.Rows.Count
Set col = DHRSheet.Range("I1:I" & RowCount1)
For Each cell In col
If cell.Value = "True" Then
Dim ValueCell As Range
Set ValueCell = Cells(cell.Row, 3)
ReDim Preserve Truearray(i)
Truearray(i) = ValueCell.Value
Dim siblingCell As Range
Set siblingCell = Cells(cell.Row, 2)
Dim Siblingarray() As String
ReDim Preserve Siblingarray(i)
Siblingarray(i) = DHRSheet.Name & "$" & siblingCell.Value
i = i + 1
End If
Next
Dim RowCount2 As Integer
RowCount2 = DataSheet.UsedRange.Rows.Count + 1
For ii = 2 To UBound(Truearray)
DataSheet.Cells(RowCount2 + ii, 2).Value = Truearray(ii)
Next
For ii = 2 To UBound(Siblingarray)
DataSheet.Cells(RowCount2 + ii, 1).Value = Siblingarray(ii)
Next
DataSheet.Columns("A:B").AutoFit
MsgBox ("Data entered has been successfully validated & logged")
End Sub
If you Offset two rows from the bottom cell, you will leave a blank row of separation. You should also consider filling the whole array as base 1 and writing it to DataSheet in one shot.
Sub CopyData2()
Dim rCell As Range
Dim aTrues() As Variant
Dim rRng As Range
Dim lCnt As Long
'Define the range to search
With DHRSheet
Set rRng = .Range(.Cells(1, 9), .Cells(.Rows.Count, 9).End(xlUp))
End With
'resize array to hold all the 'trues'
ReDim aTrues(1 To Application.WorksheetFunction.CountIf(rRng, "True"), 1 To 2)
For Each rCell In rRng.Cells
If rCell.Value = "True" Then
lCnt = lCnt + 1
'store the string from column 2
aTrues(lCnt, 1) = DHRSheet.Name & "$" & rCell.Offset(0, -7).Value
'store the value from column 3
aTrues(lCnt, 2) = rCell.Offset(0, -6).Value
End If
Next rCell
'offset 2 from the bottom row to leave a row of separation
With DataSheet.Cells(DataSheet.Rows.Count, 1).End(xlUp).Offset(2, 0)
'write the stored information at one time
.Resize(UBound(aTrues, 1), UBound(aTrues, 2)).Value = aTrues
End With
End Sub