Preserve Leading Zeros in Excel Split Output - excel

I will be in receipt of a weekly file in which a column must be Split using a "," delimiter, but in which the position of the target column and the length of the values in that column are unknown, and will vary.
Some of the values in the target column for the Split have leading 0's , which are currently being removed as you would expect if the characters were input to a column formatted as General. The target column for the Split is always formatted as Text prior to executing the Split. All values from columns other than the targeted column must be retained.
The target column appears to be converted to General instead of the intended Text format after the below procedure is run. I'm uncertain as to how to ensure the column remains Text and the leading 0's are preserved.
Current Sub:
Sub Test_Split_Column()
Dim LR As Long, i As Long, LC As Integer
Dim x As Variant
Dim r As Range, iCol As Integer
On Error Resume Next
Set r = Application.InputBox("Click in the column to split by", Type:=8)
On Error GoTo 0
If r Is Nothing Then Exit Sub
On Error Resume Next
ActiveSheet.UsedRange.SpecialCells(xlCellTypeBlanks).Value = " "
On Error GoTo 0
iCol = r.Column
Application.ScreenUpdating = False
LC = Cells(1, Columns.Count).End(xlToLeft).Column
LR = Cells(Rows.Count, iCol).End(xlUp).Row
Columns(iCol).Insert
For i = LR To 1 Step -1
With Cells(i, iCol + 1)
If InStr(.Value, ",") = 0 Then
.Offset(, -1).Value = .Value
Else
x = Split(.Value, ",")
.Offset(1).Resize(UBound(x)).EntireRow.Insert
.Offset(, -1).Resize(UBound(x) - LBound(x) + 1).Value = Application.Transpose(x)
End If
End With
Next i
Columns(iCol + 1).Delete
LR = Cells(Rows.Count, iCol).End(xlUp).Row
With Range(Cells(1, 1), Cells(LR, LC))
On Error Resume Next
.SpecialCells(xlCellTypeBlanks).FormulaR1C1 = "=R[-1]C"
On Error GoTo 0
.Value = .Value
End With
With ActiveSheet.UsedRange
.Replace What:=" ", Replacement:=vbNullString, LookAt:=xlWhole
End With
Application.ScreenUpdating = True
End Sub
Examples of Raw Data, Current Output, and Desired Output linked below:
Examples
Any assistance you can provide is greatly appreciated! Thanks!

' can be used to force excel to treat any value as text.
x = Split(Replace("'" & .Value, ",", ",'"), ",")

If you assign a String array instead of a Variant array to a range, the values will be set as Strings even if the values are numeric.
Something like this should work.
Dim strValue As String
strValue = "000123,1234,0001"
Dim x As Variant
x = Split(strValue, ",")
ReDim result(0 To UBound(x), 0 To 0) As String
Dim i As Integer
For i = 0 To UBound(x)
result(i, 0) = x(i)
Next i
ActiveSheet.Range("A1:A3").Value = result

Related

"For loop not initialized"

I tried to run the the script but once it reached blank cell the macro just stopped.
I also tried input some text on each blank cell but "For loop not initialized" appear.
Please see the code I used down below:
Sub test()
Dim lastrow As Integer
Dim i As Integer
Dim descriptions() As String
With Worksheets("Sheet1")
lastrow = .Range("O3").End(xlDown).Row
For i = lastrow To 3 Step -1
If InStr(1, .Range("O" & i).Value, ",") \<\> 0 Then
descriptions = Split(.Range("O" & i).Value, ",")
End If
For Each Item In descriptions
.Range("O" & i).Value = Item
.Rows(i).Copy
.Rows(i).Insert
Next Item
.Rows(i).EntireRow.Delete
Next i
End With
End Sub
Thank you in advanced.
I expected for the script to run through and insert row if cell have commas.
Insert Split Cell Values
Instead of .Rows(r).Insert, you should consider using .Cells(r, "O").Insert for the rest of the columns not to be affected.
On the other hand, if you have data in the other columns that need to be copied, in the middle of the inner loop, slip in the line .Rows(r).Copy.
Option Explicit
Sub SplitDescriptions()
With ThisWorkbook.Sheets("Sheet1")
Dim Descriptions() As String, dUpper As Long, d As Long
Dim r As Long, rString As String
For r = .Cells(.Rows.Count, "O").End(xlUp).Row To 3 Step -1
rString = CStr(.Cells(r, "O").Value)
If InStr(rString, ",") > 0 Then
Descriptions = Split(rString, ",")
dUpper = UBound(Descriptions)
For d = 0 To dUpper
.Cells(r, "O").Value = Descriptions(d)
If d < dUpper Then .Rows(r).Insert
Next d
End If
Next r
End With
End Sub
To get the order left-to-right as top-to-bottom, replace the inner loop with the following.
For d = dUpper To 0 Step -1
.Cells(r, "O").Value = Descriptions(d)
If d > 0 Then .Rows(r).Insert
Next d

Excel VBA Passing Variables

I need to pass the variables max, min, and their respective locations to another sub where it will format each max and min in their respective column. I am trying to create an array that will store the locations and the values but its not working.
I was told to first identify the number of columns used and the number of rows, which is the beginning.
Rows = wsData.UsedRange.Rows.Count
Columns = wsData.UsedRange.Col.Count
j = 1
ReDim Min(j)
With wsData.Range("A3:A19")
For j = 1 To 19 'colum
Min(j) = WorksheetFunction.Min(Range(.Offset(1, j), .Offset(Row, j)))
Max = WorksheetFunction.Max(Range(.Offset(1, j), .Offset(Row, j)))
Min(j) = Min
j = j + 1
ReDim Preserve Min(j) 'saves variables
Next 'next column
End With
The code below uses the ActiveSheet which you need to change to reference the worksheet for your data. Additionally, it assumes that your data starts with Row 1. The code looks at each column in the range and stores the minimum/maximum (it does not account for multiple cells which may share the min or max value) value found in the column as well as the cell's address, in an array and then passes the array to two different subs, one which simply displays the information in a message and one which formats the the background color of the cells. This code does not perform any kind of error handling, but should get you where you want to go.
the line Option Explicit requires that all of the variables be defined using a Dim statement
the line Option Base 1 makes the default starting point for arrays 1 instead of 0
Option Explicit
Option Base 1
Sub GatherData()
Dim iRows As Long
Dim iCols As Long
Dim j As Long
Dim iMin() As Variant
Dim iMax() As Variant
Dim R As Range
iRows = ActiveSheet.UsedRange.Rows.Count
iCols = ActiveSheet.UsedRange.Columns.Count
ReDim iMin(iCols, 2)
ReDim iMax(iCols, 2)
For j = 1 To iCols
Set R = Range(Cells(1, j), Cells(iRows, j)).Find(WorksheetFunction.Min(Range(Cells(1, j), Cells(iRows, j))), LookIn:=xlValues)
iMin(j, 1) = R.Value
iMin(j, 2) = R.Address
Set R = Range(Cells(1, j), Cells(iRows, j)).Find(WorksheetFunction.Max(Range(Cells(1, j), Cells(iRows, j))), LookIn:=xlValues)
iMax(j, 1) = R.Value
iMax(j, 2) = R.Address
Next j
ListMinMax iMax(), True
ListMinMax iMin(), False
FormatMinMax iMax, "green"
FormatMinMax iMin, "yellow"
Set R = Nothing
End Sub
Sub ListMinMax(ByRef Arr() As Variant, ByVal MinMax As Boolean)
Dim strOutput As String
Dim i As Long
If MinMax = True Then
strOutput = "Maximums:" & vbCrLf & vbCrLf
Else
strOutput = "Minimums:" & vbCrLf & vbCrLf
End If
For i = 1 To UBound(Arr, 1)
strOutput = strOutput & "Cell: " & Arr(i, 2) & " = " & Arr(i, 1) & vbCrLf
Next i
MsgBox strOutput, vbOKOnly
End Sub
Sub FormatMinMax(ByRef Arr() As Variant, ByVal BGColor As String)
Dim i As Long
Select Case UCase(BGColor)
Case "GREEN"
For i = 1 To UBound(Arr, 1)
ActiveSheet.Range(Arr(i, 2)).Interior.Color = vbGreen
Next i
Case "YELLOW"
For i = 1 To UBound(Arr, 1)
ActiveSheet.Range(Arr(i, 2)).Interior.Color = vbYellow
Next i
Case Else
MsgBox "Invalid Option", vbCritical
End Select
End Sub
======================================================================
The code below does away with the need for the arrays and formats the color of the min/max values as it finds them
Sub GatherData2()
Dim iRows As Long
Dim iCols As Long
Dim j As Long
Dim R As Range
iRows = ActiveSheet.UsedRange.Rows.Count
iCols = ActiveSheet.UsedRange.Columns.Count
For j = 1 To iCols
Set R = Range(Cells(1, j), Cells(iRows, j)).Find(WorksheetFunction.Min(Range(Cells(1, j), Cells(iRows, j))), LookIn:=xlValues)
R.Interior.Color = vbYellow
Set R = Range(Cells(1, j), Cells(iRows, j)).Find(WorksheetFunction.Max(Range(Cells(1, j), Cells(iRows, j))), LookIn:=xlValues)
R.Interior.Color = vbGreen
Next j
Set R = Nothing
End Sub

VBA code to list all months between a range of dates

I am new to VBA in Excel. I have looked through through the forum, but have not found an answer for my specific date VBA I am looking for. I have three date ranges in excel cells per row of data elements representing testing dates. Each of the three ranges has a start date and an end date columns A-F.
For each row of test date ranges, I would one cell in column G to calculate the month and year "MMMYY" for any months covered in any of the three date ranges. If the date ranges over three months, the resulting cell would list all three months.
Any help would be greatly appreciated. Thank you in advance.
Marc
Calculated VBA column G
A B C D E F G
1 T1 Start T1 End T2 Start T2 End T3 Start T3 End Months
2 02Nov20 16Nov20 17Nov20 19Nov20 02Nov20 1Jan21 Nov20
Dec20
Jan21
3 28Oct19 15Nov19 28Oct19 01Nov19 28Oct19 1Nov19 Oct20
Nov20
4 20Jul20 21Aug20 Jul20
Aug20
5 11Sep20 29Sep20 20Sep20 22Sep20 20Sep20 Sep20
Here is a macro that outputs ALL of the included month/year.
In order to find the data table, I used the .CurrentRegion property of the cell that contains T1 Start. Because of this, if the output were adjacent to the table, the second run would include that column. Accordingly, I wrote the results one column over (and hid the intervening column. But you could make any number of changes in determining the source table size if that is undesireable.
I also was not certain, from your screenshot, if the Dates were "real Excel Dates" formatted to look like ddmmmyy (except for Column F in your text table) or if they are strings. So there is code to account for the different things I see. Code could be simplified if the data is in a known format.
The output is text strings and the column is formatted as text. If you want the output to be real dates formatted as mmmyy, then code will need to be added so Excel does not erroneously convert 2 digit years to day of the month.
Be sure to read the notes in the macro, as it will help you understand what's going on.
Option Explicit
Sub mthList()
Dim cM As Collection
Dim rg As Range, dStart As Date, dEnd As Date
Dim vSrc As Variant, vRes As Variant
Dim i As Long, J As Long, K As Long
Dim d1 As Double, d2 As Double 'start and end dates
Dim WS As Worksheet, rRes As Range
'Find the table and read it into VBA array
Set WS = ThisWorkbook.Worksheets("Sheet1")
With WS
Set rg = .Cells.Find(what:="T1 Start", after:=.Cells(.Rows.Count, .Columns.Count), _
LookIn:=xlFormulas, lookat:=xlWhole, searchorder:=xlByRows, searchdirection:=xlNext, MatchCase:=False)
If rg Is Nothing Then
MsgBox "No Data Table"
Exit Sub
End If
vSrc = rg.CurrentRegion
ReDim vRes(1 To UBound(vSrc, 1), 1 To 1)
End With
'Collect all the included dates
'Convert date strings to real dates if they are strings
For i = 2 To UBound(vSrc, 1)
Set cM = New Collection
For J = 1 To UBound(vSrc, 2) Step 2 'can have N pairs of dates
If vSrc(i, J) <> "" Then
d1 = theDate(vSrc(i, J)) ' need to make sure this is a date and not a text string
If vSrc(i, J + 1) = "" Then
d2 = d1
Else
d2 = theDate(vSrc(i, J + 1))
End If
On Error Resume Next 'remove duplicates since Collection cannot have two entries with same key
For K = d1 To d2
cM.Add Format(K, "mmmyy"), Format(K, "mmmyy")
Next K
On Error GoTo 0
End If
Next J
'Output the data to results array
For K = 1 To cM.Count
vRes(i, 1) = vRes(i, 1) & vbLf & cM(K)
Next K
vRes(i, 1) = Mid(vRes(i, 1), 2)
Next i
'write the results
'formatting is optional, and Styles may not work with non-English versions
Set rRes = rg.Offset(0, rg.CurrentRegion.Columns.Count + 1)
Set rRes = rRes.Resize(UBound(vRes, 1), UBound(vRes, 2))
With rRes
.EntireColumn.Clear
.EntireColumn.NumberFormat = "#"
.Value = vRes
.WrapText = True
.EntireRow.AutoFit
.EntireColumn.AutoFit
.Style = "output"
.Offset(0, -1).EntireColumn.Hidden = True
End With
With rg.CurrentRegion
.VerticalAlignment = xlCenter
.HorizontalAlignment = xlCenter
.Style = "Input"
End With
End Sub
Private Function theDate(d) As Double
If Not IsDate(d) Then
theDate = CDate(Left(d, Len(d) - 5) & " " & Mid(d, Len(d) - 4, 3) & " " & Right(d, 2))
Else
theDate = d
End If
End Function
EDIT:
To use my algorithm as a function, just need to remove all that stuff with regard to finding the table and writing results back to the worksheet:
Option Explicit
Function mthList(rg As Range) As String
Dim cM As Collection
Dim dStart As Date, dEnd As Date
Dim vSrc As Variant
Dim I As Long, J As Long, K As Long
Dim d1 As Double, d2 As Double 'start and end dates
Dim S As String
'Collect all the included dates
'Convert date strings to real dates if they are strings
vSrc = rg
Set cM = New Collection
For J = 1 To UBound(vSrc, 2) Step 2 'can have N pairs of dates
If vSrc(1, J) <> "" Then
d1 = theDate(vSrc(1, J)) ' need to make sure this is a date and not a text string
If vSrc(1, J + 1) = "" Then
d2 = d1
Else
d2 = theDate(vSrc(1, J + 1))
End If
On Error Resume Next 'remove duplicates since Collection cannot have two entries with same key
For K = d1 To d2
cM.Add Format(K, "mmmyy"), Format(K, "mmmyy")
Next K
On Error GoTo 0
End If
Next J
'Output the data to a string
For K = 1 To cM.Count
S = S & vbLf & cM(K)
Next K
mthList = Mid(S, 2)
End Function
Private Function theDate(d) As Double
If Not IsDate(d) Then
theDate = CDate(Left(d, Len(d) - 5) & " " & Mid(d, Len(d) - 4, 3) & " " & Right(d, 2))
Else
theDate = d
End If
End Function
As said in the comments one could use a dictionary
Function listMthYear(rg As Range) As String
' Goto Tools/Reference and check Microsoft Scripting Runtime
Dim dict As Dictionary
Set dict = New Dictionary
Dim sngCell As Range
For Each sngCell In rg
If IsDate(sngCell.Value) Then
Dim mth As Long
Dim yr As Long
Dim dte As Date
dte = sngCell.Value
mth = VBA.Month(dte)
yr = VBA.year(dte)
dte = VBA.DateSerial(yr, mth, 1)
' This will create an unique entry in the dictionary if not already created
dict(dte) = dte
End If
Next sngCell
Dim output As Variant, i As Long
output = dict.Keys
For i = LBound(output) To UBound(output)
output(i) = Format(output(i), "MMMYY")
Next i
listMthYear = Join(output, vbLf)
End Function
You could use the function as an UDF or like that
Sub TestIt()
Dim rg As Range
Set rg = Range("A3:E3")
MsgBox listMthYear(rg)
End Sub

How to check whether the first array entry is empty in VBA

The below VBA code sets a range of cells as commentArray, removes any blanks from the array and creates a new, blank free array, called commentResults. I then want to declare the array.
There is a possibility, depending on my source data, that the array could then still be empty so the below doesn't work to declare
thisws.Cells(i, 19).Resize(columnsize:=UBound(commentResults) - LBound(commentResults) + 1).Value = commentResults
So I thought I would add a check (the if statement after the debug.print), that only declared the array if array(0) wasn't empty but I continuously get an error 9 which I can't resolve.
Dim commentArray(4) As Variant
commentArray(0) = Cells(24, 4).Value
commentArray(1) = Cells(25, 3).Value
commentArray(2) = Cells(26, 3).Value
commentArray(3) = Cells(27, 3).Value
'a and b as array loops
Dim a As Long, b As Long
Dim commentResults() As Variant
'loops through the array to remove blanks - rewrites array without blanks into commentArray
For a = LBound(commentArray) To UBound(commentArray)
If commentArray(a) <> vbNullString Then
ReDim Preserve commentResults(b)
commentResults(b) = commentArray(a)
b = b + 1
End If
Next a
Debug.Print b
If IsError(Application.Match("*", (commentResults), 0)) Then
Else
thisws.Cells(i, 19).Resize(columnsize:=UBound(commentResults) - LBound(commentResults) + 1).Value = commentResults
b = 0
End If
Any thoughts on why this might not work?
I have also tried:
If commentResults(0) <> vbNullString Then
thisws.Cells(i, 27).Resize(columnsize:=UBound(commentResults) - LBound(commentResults) + 1).Value = commentResults
End If
Sub CommentArray()
Dim Comments As Range, c As Range
Set Comments = Union(Cells(24, 4), Range(Cells(25, 3), Cells(27, 3)))
Dim commentResults() As Variant
Dim i As Long
i = 0
For Each cell In Comments
If cell.Value <> "" Then
ReDim Preserve commentResults(i)
commentResults(i) = cell.Value
i = i + 1
End If
Next cell
Dim debugStr As String
For i = LBound(commentResults) To UBound(commentResults)
debugStr = debugStr & commentResults(i) & Chr(10)
Next i
MsgBox debugStr
End Sub

Divide a string in a single cell into several cells

I have data that I need to split into individual points. My macro charts the data, as a scatter plot, with: Column A as the title of the chart, Column B as the X axis, and Columns C and D as the Y axis. What I need is for when the Product ID has more than 1 number listed to split the numbers out into their own rows and keep the columns B, C, and D the same for each row created form the original. So for row 167, I would want 3 rows (001,002,003) each with packaging, 200, and 100, in B, C, and D respectively. I am not sure where to begin. I tried to build a macro but, I immediately got tripped up when I tried to record a "Find" Formula to run on the data. Any help would be greatly appreciated.
Column A: 001, 002, 003 // Column B:packaging // Column C:200 // Column D:100
Sorry I couldn't post a screenshot of my data, the forum won't let me. If you have any questions please let me know, I will be sure to check in frequently.
Thanks in advance.
I worte this VERY quickly and without much care for efficiency, but this should do the trick:
Sub SplitUpVals()
Dim i As Long
Dim ValsToCopy As Range
Dim MaxRows As Long
Dim ValToSplit() As String
Dim CurrentVal As Variant
MaxRows = Range("A1").End(xlDown).Row
For i = 1 To 10000000
ValToSplit = Split(Cells(i, 1).Value, ",")
Set ValsToCopy = Range("B" & i & ":D" & i)
For Each CurrentVal In ValToSplit
CurrentVal = Trim(CurrentVal)
Cells(i, 1).Value = CurrentVal
Range("B" & i & ":D" & i).Value = ValsToCopy.Value
Cells(i + 1, 1).EntireRow.Insert
i = i + 1
MaxRows = MaxRows + 1
Next
Cells(i, 1).EntireRow.Delete
If i > MaxRows Then Exit For
Next i
End Sub
As a note, make sure there's no data in cells beneath your data as it might get deleted.
You will need to parse the data in column A. I would do this by splitting the string in to an array, and then iterate over the array items to add/insert additional rows where necessary.
Without seeing your worksheet, I would probably start with something like this, which will split your cell value from column A in to an array, and then you can iterate over the items in the array to manipulate the worksheet as needed.
Sub TestSplit()
Dim myString as String
Dim myArray() as String
Dim cell as Range
Dim i as Long
For each cell in Range("A2",Range("A2").End(xlDown))
myString = cell.Value
myArray = Split(myString, ",") '<-- converts the comma-delimited string in to an array
For i = lBound(myArray) to uBound(myArray)
If i >= 1 Then
'Add code to manipulate your worksheet, here
End If
Next
Next
End Sub
This is a better solution (now that I had more time :) ) - Hope this does the trick!
Sub SplitUpVals()
Dim AllVals As Variant
Dim ArrayIndex As Integer
Dim RowLooper As Integer
AllVals = Range("A1").CurrentRegion
Range("A1").CurrentRegion.Clear
RowLooper = 1
For ArrayIndex = 1 To UBound(AllVals, 1)
ValToSplit = Split(AllVals(ArrayIndex, 1), ",")
For Each CurrentVal In ValToSplit
CurrentVal = Trim(CurrentVal)
Cells(RowLooper, 1).Value = CurrentVal
Cells(RowLooper, 2).Value = AllVals(ArrayIndex, 2)
Cells(RowLooper, 3).Value = AllVals(ArrayIndex, 3)
Cells(RowLooper, 4).Value = AllVals(ArrayIndex, 4)
RowLooper = RowLooper + 1
Next
Next ArrayIndex
End Sub
Sub DivideData()
'This splits any codes combined into the same line, into their own separate lines with their own separate data
Dim a, b, txt As String, e, s, x As Long, n As Long, i As Long, ii As Long
With Range("a1").CurrentRegion
a = .Value
txt = Join$(Application.Transpose(.Columns(1).Value))
x = Len(txt) - Len(Replace(txt, ",", "")) + .Rows.Count
ReDim b(1 To x * 2, 1 To UBound(a, 2))
For i = 1 To UBound(a, 1)
For Each e In Split(a(i, 1), ",")
If e <> "" Then
For Each s In Split(e, "-")
n = n + 1
For ii = 1 To UBound(a, 2)
b(n, ii) = a(i, ii)
Next
b(n, 1) = s
Next
End If
Next
Next
With .Resize(n)
.Columns(1).NumberFormat = "#"
.Value = b
End With
End With
End Sub

Resources