Why is my subscript out of range in vba? - vba

I wanted excel to go through every single cell of a column, perform an operation on it and then copy the results on another column.
This was my initial code:
For i = 2 To dataRows
' Cells(i, aStampCol) = Cells(i, stampCol) - stim1TimeStamp
'Next i
This code actually worked, but ran extremely slowly, I looked at another post and they were saying it was better to just copy the column into an array, manipulate it and then copy it back to a column.
So I wrote the following code:
cellsAStamp = Range(Cells(2, stampCol), Cells(datarows, stampCol))
For i = 0 To datarows - 2
cellsAStamp(i) = cellsAStamp(i) - stim1TimeStamp
Next i
Range(Cells(2, aStampCol), Cells(endRow, aStampCol)) = cellsAStamp
The problem is, as soon as the for loop is initiated, I get a "Subscript out of Range" error. I get the impression that the cellsAsStamp is not storing the data properly, but I don't exactly know how to solve this problem, or for that matter, what the problem is!
I've pasted my full code below so you can look at how I initialized the variables:
Sub WM()
Dim col As Integer
Dim spanCol As Integer
Dim msgCol As Integer
Dim stampCol As Integer 'The column containing the timestamp
Dim aStampCol As Integer 'The column containing the adjusted timestamp
Dim row As Long
Dim startRow As Long
Dim stimRow As Long 'the row on the Sample_Message column that says "stim1"
Dim endRow As Long 'the row on the Sample_Message column that says "participant_trial_end"
Dim triNum() As String 'a string array that will hold "Trial: x" after it has been split
Dim stim1TimeStamp As Long
Dim cellsAStamp() As Variant 'will contain the names of all the NoBlink sheets to allow for
'Identifies Timestamp column, adds ADJUSTED_TIMESTAMP column
For stampCol = 1 To 10
If Cells(1, stampCol) = "TIMESTAMP" Then
aStampCol = stampCol
colLetter = ConvertToLetter(stampCol)
Columns(colLetter & ":" & colLetter).Select
Selection.Insert Shift:=xlToRight, CopyOrigin:=xlFormatFromLeftOrAbove
stampCol = stampCol + 1
Cells(1, aStampCol) = "ADJUSTED_TIMESTAMP"
GoTo out
End If
Next stampCol
out:
'Identifies Trial Label column
For col = 1 To 10
If Cells(1, col) = "TRIAL_LABEL" Then
GoTo out1
End If
Next col
out1:
'Identifies Span column
For spanCol = 1 To 10
If Cells(1, spanCol) = "span" Then
GoTo out2
End If
Next spanCol
out2:
'Identifies Message column
For msgCol = 1 To 10
If Cells(1, msgCol) = "SAMPLE_MESSAGE" Then
GoTo out3
End If
Next msgCol
out3:
'Goes through Trial_Label column and deletes trials 1 and 2
row = 2
While Cells(row, col) Like "Trial: [12]"
row = row + 1
Wend
row = row - 1
If row = 1 Then 'in case the trials weren't there, it wont start at the header
row = 2
GoTo skipDelete
End If
Rows("2:" & CStr(row)).Delete
skipDelete:
'Goes through Trial_Label column and stops once the trial changes
row = 2
GoTo stillMoreLeft
stillMoreLeft:
startRow = row
currTrial = Cells(row, col) 'did not initialize currSpan and currTrial as strings
currSpan = Cells(row, spanCol)
While currTrial = Cells(row, col)
'highlights any row that has a message
If Cells(row, msgCol) <> "." Then
Rows(CStr(row) & ":" & CStr(row)).Interior.Color = vbYellow
End If
'Identifies the row that contains "stim1" in Sample_Message
If Cells(row, msgCol) = "stim1" Then
stimRow = row
End If
'Identifies the row that contains "participant_trial_end" in Sample_Message
If Cells(row, msgCol) = "participant_trial_end" Then
endRow = row
End If
row = row + 1
Wend
row = row - 1
'Copies all of the rows containted in a trial
Rows(CStr(stimRow) & ":" & CStr(endRow)).Select
Selection.Copy
'Creates new sheet that will be named appropriately
Worksheets.Add
triNum = Split(currTrial)
currSheetName = "Trial" & triNum(1) & "Span" & currSpan
ActiveSheet.Name = currSheetName
'Pastes all the rows contained in at trial
Rows("2:2").Select
ActiveSheet.Paste
'Gets timestamp for stim1
stim1TimeStamp = Cells(2, stampCol)
'Puts the whole timestamp column in an array/ Does the appropriate calculations to each value
datarows = endRow - stimRow + 2
cellsAStamp = Range(Cells(2, stampCol), Cells(datarows, stampCol)) 'looks like a legit way to use range
For i = 0 To datarows - 2
cellsAStamp(i) = cellsAStamp(i) - stim1TimeStamp
Next i
Range(Cells(2, aStampCol), Cells(endRow, aStampCol)) = cellsAStamp
'Fills the Adjusted_TimeStamp column
'dataRows = endRow - stimRow + 2
'For i = 2 To dataRows
' Cells(i, aStampCol) = Cells(i, stampCol) - stim1TimeStamp 'This equation says: the Adjusted_Time_Stamp=TimeStamp-TimeStamp of Stim1
'Next i
'Copies header row and pastes it to first row of most recent trial sheet
Sheets(ActiveWorkbook.Name).Select
Rows("1:1").Select
Selection.Copy
Sheets(currSheetName).Select
Rows("1:1").Select
ActiveSheet.Paste
row = row + 1 'we increment the row so that on the next line, when they check for whether the cell is empty or not, we aren't looking at the last cell of our current trial, but the first cell of our following trial
Sheets(ActiveWorkbook.Name).Select
'Looks to see if there is still a trial left, if so, it goes through all of it
If Cells(row, col) <> "" Then
GoTo stillMoreLeft
Else
bob = 1 + 1
End If
End Sub
Function ConvertToLetter(iCol As Integer) As String
Dim iAlpha As Integer
Dim iRemainder As Integer
iAlpha = Int(iCol / 27)
iRemainder = iCol - (iAlpha * 26)
If iAlpha > 0 Then
ConvertToLetter = Chr(iAlpha + 64)
End If
If iRemainder > 0 Then
ConvertToLetter = ConvertToLetter & Chr(iRemainder + 64)
End If
End Function

When you read a range into an array, it will be a 2D array (1-based) -- dimension one is the rows, dimension two is the columns -- even if there is just one column. So try:
cellsAStamp(i,1) = cellsAStamp(i,1) - stim1TimeStamp

Related

Auto scheduling

I am trying to make an auto scheduling program with an excel.
For example, each number is certain job assigned to the person given day.
1/2 1/3 1/4 1/5
Tom 1 2 2 ?
Justin 2 3 1 ?
Mary 3 3 ?
Sam 1 ?
Check O O X ? ## check is like =if(b2=c2,"O","X")
The things I want to make sure is every person is given a different job from yesterday.
My idea
while
randomly distribute jobs for 1/5
wend CheckCell = "O"
But I found that checking cell in the vba script doesn't work - the cell is not updated in each while loop.
Could you give me a little pointer for these kinds of program? Because I am new to vbaScript, any kinds of help would be appreciated.
Using VBA, I'm sure there are better ways to do this, but this will check the values from the penultimate column against values from last column and if they match it will write "O" to under the last column, else it will write "X":
Sub foo()
Dim ws As Worksheet: Set ws = Sheets("Sheet1")
'declare and set your worksheet, amend as required
LastRow = ws.Cells(ws.Rows.Count, "A").End(xlUp).Row
'get the last row with data on Column A
LastCol = ws.Cells(1, ws.Columns.Count).End(xlToLeft).Column
counter = 0 'set counter
For i = 2 To LastRow 'loop through penultimate column and add values to array
If ws.Cells(i, LastCol - 1).Value <> "" Then
Values = Values & ws.Cells(i, LastCol - 1) & ","
End If
Next i
Values = Left(Values, Len(Values) - 1)
Values = Split(Values, ",") 'split values into array
For i = 2 To LastRow 'loop through last column and add values to array
If ws.Cells(i, LastCol).Value <> "" Then
ValuesCheck = ValuesCheck & ws.Cells(i, LastCol) & ","
End If
Next i
ValuesCheck = Left(ValuesCheck, Len(ValuesCheck) - 1)
ValuesCheck = Split(ValuesCheck, ",")
For y = LBound(Values) To UBound(Values) 'loop through both arrays to find all values match
For x = LBound(ValuesCheck) To UBound(ValuesCheck)
If Values(y) = ValuesCheck(x) Then counter = counter + 1
Next x
Next y
If counter = UBound(Values) + 1 Then 'if values match
ws.Cells(LastRow + 1, LastCol).Value = "O"
Else 'else write X
ws.Cells(LastRow + 1, LastCol).Value = "X"
End If
End Sub
just to clarify are you looking to implement the random number in the vba or the check.
To do the check the best way would be to set the area as a range and then check each using the cells(r,c) code, like below
Sub checker()
Dim rng As Range
Dim r As Integer, c As Integer
Set rng = Selection
For r = 1 To rng.Rows.Count
For c = 1 To rng.Columns.Count
If rng.Cells(r, c) = rng.Cells(r, c + 1) Then
rng.Cells(r, c).Interior.Color = RGB(255, 0, 0)
End If
Next c
Next r
End Sub
this macro with check the text you have selected for the issue and change the cell red if it matches the value to the right.
To make it work for you change set rng = selection to your range and change the rng.Cells(r, c).Interior.Color = RGB(255, 0, 0) to the action you want
A sligthly different approach than the other answers.
Add this function:
Function PickJob(AvailableJobs As String, AvoidJob As String)
Dim MaxTries As Integer
Dim RandomJob As String
Dim Jobs() As String
Jobs = Split(AvailableJobs, ",")
MaxTries = 100
Do
MaxTries = MaxTries - 1
If MaxTries = 0 Then
MsgBox "Could find fitting job"
End
End If
RandomJob = Jobs(Int((1 + UBound(Jobs)) * Rnd()))
Loop Until RandomJob <> AvoidJob
PickJob = RandomJob
End Function
And put this formula in your sheet
=PickJob("1,2,3",D2)
where D2 points to is the previous job

Sum into blank cell while summing all above until next blank cell, in Excel VBA

I've got this spreadsheet in which I need to Sum up worked hours.
In Column 'I' I've got all worked hours which I sorted through weeknumbers in row 'E' with the following loop I found somewhere on Google (can't remember who wrote it but it works).
Dim i, itotalrows As Integer
Dim strRange As String
itotalrows = ActiveSheet.Range("E20000").End(xlUp).Offset(1, 0).Row
Do While i <= itotalrows
i = i + 1
strRange = "E" & i
strRange2 = "E" & i + 1
If Range(strRange).Text <> Range(strRange2).Text Then
Rows(i + 1).Insert
itotalrows = ActiveSheet.Range("E20000").End(xlUp).Offset(1, 0).Row
i = i + 1
End If
Loop
In the picture you can see one of the cells marked with "Total value of cells up
"
there's a blank every few rows with a cell on 'I' where the total value should go.
Sheet Picture:
Perhaps to sum the groups in column I, based on where the blanks are in column G
Sub x()
Dim r As Range
For Each r In Range("G:G").SpecialCells(xlCellTypeConstants).Areas
r.Cells(r.Count + 1).Offset(, 2).Value = WorksheetFunction.Sum(r.Offset(, 2))
Next r
End Sub
If you were to replace your code with the following, I believe it should do what you expect:
Sub foo()
Dim i, itotalrows As Integer
Dim strRange As String
itotalrows = ActiveSheet.Range("E20000").End(xlUp).Offset(1, 0).Row
Do While i <= itotalrows
i = i + 1
strRange = "E" & i
strRange2 = "E" & i + 1
If Range(strRange).Text <> Range(strRange2).Text Then
Rows(i + 1).Insert
Cells(i + 1, "I").FormulaR1C1 = "=SUMIF(C[-4],R[-1]C[-4],C)"
'when inserting a new row, simply add this formula to add up the values on column I
itotalrows = ActiveSheet.Range("E20000").End(xlUp).Offset(1, 0).Row
i = i + 1
End If
Loop
End Sub
Seeing as your code already does what you wanted (ie. add a new row when values on Column E differ) then adding the formula into that row will add up anything on Column I where the value of Column E is the same.
This is a general approach how to sum the cells in the blank cell.
If this is the input then the right ppicture should be the output:
.
Using this code:
Sub TestMe()
Dim myCell As Range
Dim currentSum As Double
For Each myCell In Worksheets(1).Range("A1:A14")
If myCell = vbNullString Then
myCell = currentSum
myCell.Interior.Color = vbRed
currentSum = 0
Else
currentSum = currentSum + myCell
End If
Next myCell
End Sub
The idea is simply to use a variable for the currentSum and to write it every time when the cell is empty. If it is not empty, increment it with the cell value

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

VBA- Delete row if value is not a Duplicate and keep all rows with duplicate values

I've been working on a VBA script for a while now that goes through the values of a column and deletes all rows with values appearing only once (pretty much the inverse of deleting duplicates).
Column Headers to make explanation easier
There are numbers in the 'VTR' column that appear more than once. most appear just once.
I'd like the macro to delete all rows where the number in the 'VTR' column appears only once.(in the case of one of these numbers appearing more than once, the difference lies at the 'AFTARTKRZ' column where the value can either be (GAPNK or GAPN2) or RSLNV or RSVNK. (GAPNK or GAPN2 are the same thing)
i.e a row can appear either once with AFTARTKRZ,
(GAPNK or GAPN2)
-OR twice
either (GAPNKorGAPN2), RSLNV
or (GAPNKorGAPN2), RSVNK
OR thrice
(GAPNK or GAPN2), RSLNV, RSVNK.
I'd like to delete all those that appear only once (GAPNKorGAPN2)
Furthermore, I'd like to then add the values of the 'AFTARTKRZ' values of the duplicates to 2 extra columns at the end.
i.e, when a (GAPNK or GAPN2) appears two or threee other times, I'd like to input the 'AFTARTKRZ' column value in the 2 last columns at the end.
Something like this should be the final result
VTR|AFTARTKRZ | Add1 | Add2
11 |GAPNK |RSLNV | RSVNK| - VTR appeared thrice
12 |GAPN2 |RSLNV | | - Appeared twice as (GAPNKorGAPN2), RSLNV
13 |GAPNK |RSVNK | | - Appeared twice as (GAPNKorGAPN2), RSVNK
14 |GAPN2 | |
15 |GAPNK | |
16 |GAPN2 | |
The relevant part begins at '~~~~ Work on A
Sub Test()
Dim wb As Workbook
Dim ws1 As Worksheet, ws2 As Worksheet, ws3 As Worksheet
Dim RowsToTestC As Range, Delrange As Range
Dim i As Long, Lrow As Long, Lop As Long
Set ws1 = ThisWorkbook.Worksheets(1)
ThisWorkbook.ActiveSheet.Name = "A"
ws1.Copy ThisWorkbook.Sheets(Sheets.Count)
ThisWorkbook.ActiveSheet.Name = "B"
Set ws2 = ThisWorkbook.Worksheets(2)
ws1.Copy ThisWorkbook.Sheets(Sheets.Count)
ThisWorkbook.ActiveSheet.Name = "C"
Set ws2 = ThisWorkbook.Worksheets(3)
'~~~~ Work on C
Worksheets("C").Activate
With ActiveSheet
ActiveSheet.Range("A:AQ").RemoveDuplicates Columns:=6, Header:=xlNo
End With
Worksheets("C").Activate
Application.ScreenUpdating = False
'~~> Delete all but RSV
For Lrow = Range("D" & Rows.Count).End(xlUp).Row To 2 Step -1
If Range("D" & Lrow).Value = "GAPNK" Or Range("D" & Lrow) = "GAPN2" Then
Rows(Lrow).EntireRow.Delete
End If
Next Lrow
'~~~~ Work on B
Worksheets("B").Activate
With ActiveSheet
ActiveSheet.Range("A:AQ").RemoveDuplicates Columns:=6, Header:=xlNo
End With
Worksheets("B").Activate
Application.ScreenUpdating = False
'~~> Delete all but GAP
For Lrow = Range("D" & Rows.Count).End(xlUp).Row To 2 Step -1
If Range("D" & Lrow).Value = "RSVNK" Or Range("D" & Lrow) = "RSLNV" Then
Rows(Lrow).EntireRow.Delete
End If
Next Lrow
'~~~~ Work on A
Worksheets("A").Activate
Range("AR1").Select
ActiveCell.FormulaR1C1 = "RSVNK"
Range("AS1").Select
ActiveCell.FormulaR1C1 = "RSLNV"
With ws1
'~~> Get the last row which has data in Col A
Lop = .Range("A" & .Rows.Count).End(xlUp).Row
'~~> Loop through the rows
For i = 2 To Lop
'~~> For for multiple occurances
If .Cells(i, 6).Value <> "" And .Cells(i, 4).Value <> "" Then
If Application.WorksheetFunction.CountIf(.Columns(6), .Cells(i, 6)) = 1 And _
Application.WorksheetFunction.CountIf(.Columns(4), .Cells(i, 4)) = 1 Then
'~~> Store thee row in a temp range
If Delrange Is Nothing Then
Set Delrange = .Rows(i)
Else
Set Delrange = Union(Delrange, .Rows(i))
End If
End If
End If
Next
End With
End Sub
The logic of your code isn't valid.
The condition If Application.WorksheetFunction.CountIf(.Columns(4), .Cells(i, 4)) = 1 will always be False because this is the column containing your AFTARTKRZ keys. I don't know how many rows of data you have, but even in the 10 row sample you gave us the result is always greater than 1.
I do think you're making this unnecessarily complicated. Aren't you just trying to populate two lists: one of GAPs and the other of RSVs? You then want to create a third list where the GAP entries have corresponding RSV entries?
That could be done in a couple of short routines. You could do away with all of your sheet copying and row deleting and simply write your three lists directly to your sheets.
The code below shows you how this could be done. I've created 4 sheets, so you may need to add another to your Workbook: Sheet1 is your summary list (A), Sheet2 is your GAP list (B), Sheet3 is your RSV list (C), and Sheet4 holds the raw data.
Hopefully this code can get you started:
Option Explicit
Public Sub RunMe()
Const AFTARTKRZ_COL As Long = 4
Const VTR_COL As Long = 6
Dim data As Variant
Dim GAPs As Collection
Dim RSVs As Collection
Dim multis As Collection
Dim vtrKey As String
Dim multi(0 To 1) As Long
Dim i As Long, r As Long, c As Long
Dim v As Variant
Dim countRSV As Long
Dim output() As Variant
'Name your sheets.
'If you have fewer than 3 sheets or
'sheets already names A, B, C then this
'will throw an error.
Sheet1.Name = "A"
Sheet2.Name = "B"
Sheet3.Name = "C"
'Initialise the 3 collections
Set GAPs = New Collection
Set RSVs = New Collection
Set multis = New Collection
'Read the data - I've put my dummy data on Sheet4
data = Sheet4.UsedRange.Value2
'Iterate rows and place row in relevant collection
For r = 1 To UBound(data, 1)
vtrKey = CStr(data(r, VTR_COL))
On Error Resume Next 'removes duplicate entries
Select Case data(r, AFTARTKRZ_COL)
Case Is = "GAPNK", "GAPN2": GAPs.Add r, vtrKey
Case Is = "RSLNV": RSVs.Add r, vtrKey & "|RSLNV"
Case Is = "RSVNK": RSVs.Add r, vtrKey & "|RSVNK"
End Select
On Error GoTo 0
Next
'Check if each GAP also has RSVs
For Each v In GAPs
vtrKey = CStr(data(v, VTR_COL))
countRSV = 0
If Exists(RSVs, vtrKey & "|RSLNV") Then countRSV = countRSV + 1
If Exists(RSVs, vtrKey & "|RSVNK") Then countRSV = countRSV + 2
If countRSV > 0 Then
multi(0) = CLng(v)
multi(1) = countRSV
multis.Add multi, vtrKey
End If
Next
'Write your outputs
'Sheet C
ReDim output(1 To RSVs.Count + 1, 1 To UBound(data, 2))
For c = 1 To UBound(data, 2)
output(1, c) = data(1, c)
Next
i = 2
For Each v In RSVs
For c = 1 To UBound(data, 2)
output(i, c) = data(v, c)
Next
i = i + 1
Next
With Sheet3
.Cells.Clear
.Range("A1").Resize(UBound(output, 1), UBound(output, 2)).Value = output
.Columns.AutoFit
End With
'Sheet B
ReDim output(1 To GAPs.Count + 1, 1 To UBound(data, 2))
For c = 1 To UBound(data, 2)
output(1, c) = data(1, c)
Next
i = 2
For Each v In GAPs
For c = 1 To UBound(data, 2)
output(i, c) = data(v, c)
Next
i = i + 1
Next
With Sheet2
.Cells.Clear
.Range("A1").Resize(UBound(output, 1), UBound(output, 2)).Value = output
.Columns.AutoFit
End With
'Sheet A
ReDim output(1 To multis.Count + 1, 1 To 5)
output(1, 1) = "VTR"
output(1, 2) = "AFTARTKRZ"
output(1, 3) = "Add1"
output(1, 4) = "Add2"
i = 2
For Each v In multis
r = v(0)
output(i, 1) = data(r, VTR_COL)
output(i, 2) = data(r, AFTARTKRZ_COL)
output(i, 2) = data(r, AFTARTKRZ_COL)
Select Case v(1)
Case 1
output(i, 3) = "RSLNV"
output(i, 5) = "Appeared twice as (GAPNK or GAPN2), RSLNV"
Case 2
output(i, 3) = "RSVNK"
output(i, 5) = "Appeared twice as (GAPNK or GAPN2), RSVNK"
Case 3
output(i, 3) = "RSLNV"
output(i, 4) = "RSVNK"
output(i, 5) = "VTR appeared thrice"
End Select
i = i + 1
Next
With Sheet1
.Cells.Clear
.Range("A1").Resize(UBound(output, 1), UBound(output, 2)).Value = output
.Columns.AutoFit
End With
End Sub
Private Function Exists(col As Collection, key As String) As Boolean
Dim v As Variant
On Error Resume Next
v = col(key)
On Error GoTo 0
Exists = Not IsEmpty(v)
End Function

Split rows that have multiline text and single line text

I'm trying to figure out how to split rows of data where columns B,C,D in the row contain multiple lines and others do not. I've figured out how to split the multi-line cells if I copy just those columns into a new sheet, manually insert rows, and then run the macro below (that's just for column A), but I'm lost at coding the rest.
Here's what the data looks like:
So for row 2, I need it split into 6 rows (one for each line in cell B2) with the text in cell A2 in A2:A8. I also need columns C and D split the same as B, and then columns E:CP the same as column A.
Here is the code I have for splitting the cells in columns B,C,D:
Dim iPtr As Integer
Dim iBreak As Integer
Dim myVar As Integer
Dim strTemp As String
Dim iRow As Integer
iRow = 0
For iPtr = 1 To Cells(Rows.Count, col).End(xlUp).Row
strTemp = Cells(iPtr1, 1)
iBreak = InStr(strTemp, vbLf)
Range("C1").Value = iBreak
Do Until iBreak = 0
If Len(Trim(Left(strTemp, iBreak - 1))) > 0 Then
iRow = iRow + 1
Cells(iRow, 2) = Left(strTemp, iBreak - 1)
End If
strTemp = Mid(strTemp, iBreak + 1)
iBreak = InStr(strTemp, vbLf)
Loop
If Len(Trim(strTemp)) > 0 Then
iRow = iRow + 1
Cells(iRow, 2) = strTemp
End If
Next iPtr
End Sub
Here is a link to an example file (note this file has 4 rows, the actual sheet has over 600): https://www.dropbox.com/s/46j9ks9q43gwzo4/Example%20Data.xlsx?dl=0
This is a fairly interesting question and something I have seen variations of before. I went ahead and wrote up a general solution for it since it seems like a useful bit of code to keep for myself.
There are pretty much only two assumptions I make about the data:
Returns are represented by Chr(10) or which is the vbLf constant.
Data that belongs with a lower row has enough returns in it to make it line up. This appears to be your case since there are return characters which appear to make things line up like you want.
Pictures of the output, zoomed out to show all the data for A:D. Note that the code below processes all of the columns by default and outputs to a new sheet. You can limit the columns if you want, but it was too tempting to make it general.
Code
Sub SplitByRowsAndFillBlanks()
'process the whole sheet, could be
'Intersect(Range("B:D"), ActiveSheet.UsedRange)
'if you just want those columns
Dim rng_all_data As Range
Set rng_all_data = Range("A1").CurrentRegion
Dim int_row As Integer
int_row = 0
'create new sheet for output
Dim sht_out As Worksheet
Set sht_out = Worksheets.Add
Dim rng_row As Range
For Each rng_row In rng_all_data.Rows
Dim int_col As Integer
int_col = 0
Dim int_max_splits As Integer
int_max_splits = 0
Dim rng_col As Range
For Each rng_col In rng_row.Columns
'splits for current column
Dim col_parts As Variant
col_parts = Split(rng_col, vbLf)
'check if new max row count
If UBound(col_parts) > int_max_splits Then
int_max_splits = UBound(col_parts)
End If
'fill the data into the new sheet, tranpose row array to columns
sht_out.Range("A1").Offset(int_row, int_col).Resize(UBound(col_parts) + 1) = Application.Transpose(col_parts)
int_col = int_col + 1
Next
'max sure new rows added for total length
int_row = int_row + int_max_splits + 1
Next
'go through all blank cells and fill with value from above
Dim rng_blank As Range
For Each rng_blank In sht_out.Cells.SpecialCells(xlCellTypeBlanks)
rng_blank = rng_blank.End(xlUp)
Next
End Sub
How it works
There are comments within the code to highlight what is going on. Here is a high level overview:
Overall, we iterate through each row of the data, processing all of the columns individually.
The text of the current cell is Split using the vbLf. This gives an array of all the individual lines.
A counter is tracking the maximum number of rows that were added (really this is rows-1 since these arrays are 0-indexed.
Now the data can be output to the new sheet. This is easy because we can just dump the array that Split created for us. The only tricky part is getting it to the right spot on the sheet. To that end, there is a counter for the current column offset and a global counter to determine how many total rows need to be offset. The Offset moves us to the right cell; the Resize ensures that all of the rows are output. Finally, Application.Transpose is needed because Split returns a row array and we're dumping a column.
Update the counters. Column offset is incremented every time. The row offset is updated to add enough rows to cover the last maximum (+1 since this is 0-indexed)
Finally, I get to use my waterfall fill (your previous question) on all of the blanks cells that were created to ensure no blanks. I forgo error checking because I assume blanks exist.
Thank you for providing a sample. This task was so interesting that I thought of writing the code for that. You are more than welcome to tweak it to your satisfaction, and I hope your team gets to use an RDBMS to manage this kind of data in the future.
Sub OrganizeSheet()
Dim LastRow As Integer
LastRow = GetLastRow()
Dim Barray() As String
Dim Carray() As String
Dim Darray() As String
Dim LongestArray As Integer
Dim TempInt As Integer
Dim i As Integer
i = 1
Do While i <= LastRow
Barray = Split(Range("B" & i), Chr(10))
Carray = Split(Range("C" & i), Chr(10))
Darray = Split(Range("D" & i), Chr(10))
LongestArray = GetLongestArray(Barray, Carray, Darray)
If LongestArray > 0 Then
' reset the values of B, C and D columns
On Error Resume Next
Range("B" & i).Value = Barray(0)
Range("C" & i).Value = Carray(0)
Range("D" & i).Value = Darray(0)
Err.Clear
On Error GoTo 0
' duplicate the row multiple times
For TempInt = 1 To LongestArray
Rows(i & ":" & i).Select
Selection.Copy
Range(i + TempInt & ":" & i + TempInt).Select
Selection.Insert Shift:=xlDown
' as each row is copied, change the values of B, C and D columns
On Error Resume Next
Range("B" & i + TempInt).Value = Barray(TempInt)
If Err.Number > 0 Then Range("B" & i + TempInt).Value = ""
Err.Clear
Range("C" & i + TempInt).Value = Carray(TempInt)
If Err.Number > 0 Then Range("C" & i + TempInt).Value = ""
Err.Clear
Range("D" & i + TempInt).Value = Darray(TempInt)
If Err.Number > 0 Then Range("D" & i + TempInt).Value = ""
Err.Clear
On Error GoTo 0
Application.CutCopyMode = False
Next TempInt
' increment the outer FOR loop's counters
LastRow = LastRow + LongestArray
i = i + LongestArray
End If
i = i + 1
Loop
End Sub
' ----------------------------------
Function GetLongestArray(ByRef Barray() As String, ByRef Carray() As String, ByRef Darray() As String)
GetLongestArray = UBound(Barray)
If UBound(Carray) > GetLongestArray Then GetLongestArray = UBound(Carray)
If UBound(Darray) > GetLongestArray Then GetLongestArray = UBound(Darray)
End Function
' ----------------------------------
Function GetLastRow() As Integer
Worksheets(1).Select
Range("A1").Select
Selection.End(xlDown).Select
GetLastRow = Selection.Row
Range("A1").Select
End Function
Give it a shot!