Extract only numbers from Word table cell into Excel cell - vba

I have a table in a word document that I need to extract only the numbers from. There are 2 cells in the document and the first one has the following string in it:
"24.00 (Hour(s))"
I just need the number "24" from that. It won't always be 2 digits since it's a duration of hours. It may be over 100. It's normally in that format "xxx.xxx" though.
The second cell I need to extract from is a bit more difficult. It looks like this:
"$125.00 to $140.00 per hour"
I would need to extract "125" and place it in a cell in excel and then extract "140" and place it in another cell. These number will always be between "$" and ".00" separated by the word "to".
The duration needs to go into column J and the rates need to be separated into column K & L.
Here is my current code:
Sub ImportWordTable()
Dim wdDoc As Object
Dim wdFileName As Variant
Dim TableNo As Integer 'table number in Word
Dim iTable As Integer 'table number index
Dim iRow As Long 'row index in Excel
Dim iCol As Integer 'column index in Excel
wdFileName = Application.GetOpenFilename("Word files (*.docx),*.docx", , _
"Browse for file containing table to be imported")
If wdFileName = False Then Exit Sub '(user cancelled import file browser)
Set wdDoc = GetObject(wdFileName) 'open Word file
Worksheets("Request Detail").Activate 'activates sheet of specific name
With wdDoc
TableNo = wdDoc.tables.Count
If TableNo = 0 Then
MsgBox "This document contains no tables", _
vbExclamation, "Import Word Table"
ElseIf TableNo > 1 Then
TableNo = InputBox("This Word document contains " & TableNo & " tables." & vbCrLf & _
"Enter table number of table to import", "Import Word Table", "1")
End If
For iTable = 1 To TableNo
Dim lRow As Long
lRow = Range("A" & Rows.Count).End(xlUp).Offset(1).Row + 1
With .tables(TableNo)
Cells(lRow - 1, "A") = WorksheetFunction.Clean(.cell(14, 2).Range.Text) 'polaris id
Cells(lRow - 1, "B").Value = Date 'post current date
Cells(lRow - 1, "C") = WorksheetFunction.Clean(.cell(16, 2).Range.Text) 'resource manager name
Cells(lRow - 1, "D") = WorksheetFunction.Clean(.cell(15, 2).Range.Text) 'requestor name
Cells(lRow - 1, "E") = WorksheetFunction.Clean(.cell(1, 2).Range.Text) 'customer name
Cells(lRow - 1, "H") = WorksheetFunction.Clean(.cell(7, 2).Range.Text) 'start date
Cells(lRow - 1, "I") = WorksheetFunction.Clean(.cell(8, 2).Range.Text) 'end date
Cells(lRow - 1, "J") = WorksheetFunction.Clean(.cell(9, 2).Range.Text) 'duration
Cells(lRow - 1, "K") = WorksheetFunction.Clean(.cell(12, 2).Range.Text) 'request low rate
Cells(lRow - 1, "L") = WorksheetFunction.Clean(.cell(12, 2).Range.Text) 'request high rate
'Cells(lRow - 1, "S") = WorksheetFunction.Clean(.cell(3, 2).Range.Text) need to post name of negotiatoe
End With
Next iTable
End With
Set wdDoc = Nothing
End Sub
Here is an example of the table parts I'm referring to:

Try this UDF and modify to suit your need. It returns a negative one (-1) if there isn't a match for the N'th number in a line of text.
Assuming the text in Word cell has been put into an Excel range (say C3), Hours stored in column D, Rate min in column E, Rate max in column F, then Formulas in:
D3: =GetNthNumber(C3)
E3: =GetNthNumber(C3,1)
F3: =GetNthNumber(C3,2)
You can do more if line of text contains "days" for the Time.
Option Explicit
Function GetNthNumber(oItem As Variant, Optional Nth As Long) As Double
Dim sText As String, n As Long, i As Long, oTmp As Variant
n = Nth
' Set to First if argument "Nth" is not passed in
If n <= 0 Then n = 1
' Retrieve the text from the input item
Select Case TypeName(oItem)
Case "Range": sText = oItem.Text
Case "String": sText = oItem
Case Else: sText = CStr(oItem)
End Select
i = 0 ' Initialize counter
' Loop through all the words in the text
For Each oTmp In Split(sText, " ")
' Process only if the word is a number
If IsNumeric(oTmp) Then
i = i + 1
' Check if it's the Nth number
If i = n Then
sText = oTmp
Exit For
End If
End If
Next
' Return -1 if there isn't an answer
If Not IsNumeric(sText) Then sText = "-1"
GetNthNumber = CDbl(sText)
End Function
UPDATE
For what you are interested in, first paste in my code above, on a new Module or bottom of your existing code, then change a few lines within the With .tables(TableNo) block to below:
Cells(lRow - 1, "J").Value = GetNthNumber(WorksheetFunction.Clean(.cell(9, 2).Range.Text)) 'duration (Time to Book?)
Cells(lRow - 1, "K").Value = GetNthNumber(WorksheetFunction.Clean(.cell(12, 2).Range.Text), 1) 'request low rate
Cells(lRow - 1, "L").Value = GetNthNumber(WorksheetFunction.Clean(.cell(12, 2).Range.Text), 2) 'request high rate

Related

Stop VBA from changing text to date when copy/paste

I want to copy some texts from a sheet to another. For example: 01/02/2021 .
However VBA automatically convert it to 2020/01/02. How can I stop it?
The following codes didn't work.
Example1:
sheet_1.Range("A1:A" & sheet1.Cells(1, 1).CurrentRegion.End(xlDown).row).Copy
ws.Range("start").PasteSpecial xlPasteValues
ws.Range("start").PasteSpecial xlPasteFormats
Example2:
sheet_1.Range("A1:A" & sheet1.Cells(1, 1).CurrentRegion.End(xlDown).row).Copy
ws.Range("start").PasteSpecial xlPasteFormulasAndNumberFormats
Example3:
sheet_1.Range("A1:A" & sheet1.Cells(1, 1).CurrentRegion.End(xlDown).row).Copy
ws.Range("start").Paste xlPaste Format:="Text" 'This causes an error
Please, try the next code. It will extract the date from the (pseudo) xls file and place it in the first column of the active sheet. Correctly formatted as date:
Sub openXLSAsTextExtractDate()
Dim sh As Worksheet, arrTXT, arrLine, arrD, arrDate, fileToOpen As String, i As Long, k As Long
Set sh = ActiveSheet 'use here the sheet you need
fileToOpen = "xls file full name" 'use here the full name of the saved xls file
'put the file content in an array splitting the read text by end of line (vbCrLf):
arrTXT = Split(CreateObject("Scripting.FileSystemObject").OpenTextFile(fileToOpen, 1).ReadAll, vbCrLf)
ReDim arrDate(UBound(arrTXT)) 'redim the array where the date will be kept, to have enough space for all the date values
For i = 39 To UBound(arrTXT) - 1 'iterate between the array elements, starting from the row where date data starts
arrLine = Split(arrTXT(i), vbTab) 'split the line by vbTab
arrD = Split(arrLine(0), "/") 'split the first line element (the date) by "/"
arrDate(k) = DateSerial(arrD(2), arrD(1), arrD(0)): k = k + 1 'properely format as date and fill the arrDate elements
Next i
ReDim Preserve arrDate(k - 1) 'keep only the array elements keeping data
With sh.Range("A1").Resize(UBound(arrDate) + 1, 1)
.value = Application.Transpose(arrDate) 'drop the array content
.NumberFormat = "dd/mm/yyyy" 'format the column where the date have been dropped
End With
End Sub
Edited:
You did not say anything...
So, I made a code returning the whole table (in the active sheet). Please, test it. It will take only some seconds:
Sub openXLSAsText()
Dim sh As Worksheet, arrTXT, arrLine, arrD, arrData, fileToOpen As String, i As Long, j As Long, k As Long
Set sh = ActiveSheet 'use here the sheet you need
fileToOpen = "xls file full name" 'use here the full name of the saved xls file
'put the file content in an array splitting the read text by end of line (vbCrLf):
arrTXT = Split(CreateObject("Scripting.FileSystemObject").OpenTextFile(fileToOpen, 1).ReadAll, vbCrLf)
ReDim arrData(1 To 10, 1 To UBound(arrTXT)) 'redim the array where the date will be kept, to have enough space for all the date values
For i = 38 To UBound(arrTXT) - 1 'iterate between the array elements, starting from the row where table header starts
arrLine = Split(arrTXT(i), vbTab) 'split the line by vbTab
k = k + 1 'increment the k variable (which will become the table row)
For j = 0 To 9
If j = 0 And k > 1 Then
arrD = Split(arrLine(j), "/") 'split the first line element (the date) by "/"
arrData(j + 1, k) = DateSerial(arrD(2), arrD(1), arrD(0)) 'propperely format as date and fill the arrDate elements
ElseIf j = 2 Or j = 3 Then
arrData(j + 1, k) = Replace(arrLine(j), ",", ".") 'correct the format for columns 3 and four (replace comma with dot)
Else
arrData(j + 1, k) = arrLine(j) 'put the rest of the column, not processed...
End If
Next j
Next i
ReDim Preserve arrData(1 To 10, 1 To k) 'keep only the array elements with data
With sh.Range("A1").Resize(UBound(arrData, 2), UBound(arrData))
.value = Application.Transpose(arrData) 'drop the array content
.EntireColumn.AutoFit 'autofit columns
.Columns(1).NumberFormat = "dd/mm/yyyy" 'format the first column
End With
MsgBox "Ready..."
End Sub

Calculating new cells containing True/False outputs from cells also containing #N/A values using VBA

In an Excel worksheet there is a title in the first row and there are titles of each column in the second row. The columns titled 'A' and 'B' contain the initial data, and the column titled 'TF' will contain the resulting data (Excel columns A, B and C respectively).
In the following code, the numbers from 1 to 5 on the left are just row headers and are not data in the worksheet.
1 Table
2 A B TF
3 ABC ABC TRUE
4 ABC BAC FALSE
5 #N/A ABC #N/A
What I have tried.
Sub Compare2Col()
Dim colAnum As Integer, colBnum As Integer, loopNum As Integer, i As Integer
Dim holder As Variant
colAnum = Worksheets("Sheet1").Range("A1048576").End(xlUp).Row
colBnum = Worksheets("Sheet1").Range("B1048576").End(xlUp).Row
If colAnum > colBnum Then
loopNum = colAnum
Else
loopNum = colBnum
End If
For i = 3 To loopNum
If Range("A" & i).Value = "" Or Range("B" & i).Value = "" Or Range("A" & i).Value = "#N/A" Or Range("B" & i).Value = "#N/A" Then
Range("C" & i).Value = "#N/A"
Else
If Range("A" & i).Value = Range("B" & i).Value Then
Range("C" & i).Value = True
Else
Range("C" & i).Value = False
End If
End If
Next i
End Sub
This is the code I am trying to work with currently. In some cells I will be having these "#N/A" values. How do I have an if statement so that when it is true, it just places the same "#N/A" value into the third column.
I read that these #N/A values are errors. So in VBA I placed a #N/A value into a variable in the following way:
holder = Range("A" & 5).Value
The result of the 'holder' variable was 'Error 2042'.
Thanks in advance. Really appreciate any help!
Handling the infamous VBA Errors (2042) successfully!?
Before using this code be sure you have studied at least the customize section carefully or you might lose data.
Most importantly the second column must always be adjacent to the right of the first column, otherwise this code couldn't have been done with the 'array copy-paste version'.
#Melbee: I am assuming you have your initial data in columns A
ciFirstCol
and B iSecondCol = ciFirstCol + 1 and the result should be in column C cCOff 'if 1 then first column next to the second column. If not make changes in the customize section.
Option Explicit
'-------------------------------------------------------------------------------
Sub XthColumnResult()
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
'Description
'In an Excel worksheet uses two adjacent columns of initial data as arguments
'for a function whose result is pasted into a third column anywhere to the
'right of the two initial columns.
'(In short: 2 cols of data, perform calculation, result in third column)
'Arguments as constants
'cWbName
'The path of the workbook, if "" then ActiveWorkbook
'cWsName
'Name of the worksheet, if "" then ActiveSheet
'cloFirstRow
'First row of data
'ciFirstCol
'First column of data
'cCOff
'Column offset, where to paste the results into.
'Returns
'The resulting data in a new column to the right of the two initial adjacent
'columns of data.
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
'-- CUSTOMIZE BEGIN --------------------
Const cWbName As String = "" 'Workbook Path (e.g. "C:\MyExcelVBA\Data.xls")
Const cWsName As String = "" 'Worksheet Name (e.g. "Sheet1", "Data",... etc.
Const cloFirstRow As Long = 3 'First Row of Data
'Const cloLastRow as Long = Unknown >therefore> Dim loRow as Long
Const ciFirstCol As Integer = 1 'First Column of Data (1 for A, 2 for B etc.
'Second column of data must be adjacent to the right of first column.
'See iSecondCol. Therefore Dim iSecondCol As Integer
'Column offset where to paste the results into. Default is 1 i.e. the first
'column next to the second column.
Const cCOff As Integer = 1
'-- CUSTOMIZE END ----------------------
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
'Variables
Const cStrVBAError As String = "Error 20" 'Debug VBA Error Variable
Const cStrVBAErrorMessage As String = "Not Possible." 'Debug VBA Error Message
Dim oWb As Workbook
Dim oWs As Worksheet
Dim oRng As Range
Dim TheArray() As Variant
Dim SmallArray() As Variant
Dim loRow As Long 'Last Row of Data
Dim iSecondCol As Integer 'Second Column of Data
Dim iF1 As Integer 'Column Counter
Dim loArr As Long 'Array Row Counter
Dim iArr As Integer 'Array Column Counter
Dim str1 As String 'Debug String
Dim str2 As String 'Debug Helper String
Dim varArr As Variant 'Helper Variable for the Array
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
'Determine workbook and worksheet
If cWbName = "" Then
Set oWb = ActiveWorkbook
Else
Set oWb = Workbooks(cWbName)
End If
If cWsName = "" Then
Set oWs = oWb.ActiveSheet
Else
Set oWs = oWb.Worksheets(cWsName)
End If
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
'Calculate second column of data
iSecondCol = ciFirstCol + 1
'Calculate last row of data (the greatest row of all columns)
loRow = 0
'Trying to translate the code to English:
'For each column go to the last cell and press crtl+up which is the last
'cell used in that column and use the row property...
For iF1 = ciFirstCol To iSecondCol
'...and check if it is greater than loRow.
If loRow < oWs.Cells(Rows.Count, ciFirstCol + iF1 - 1).End(xlUp).Row Then
'Assign the row to loRow (if it is greater than loRow).
loRow = oWs.Cells(Rows.Count, ciFirstCol + iF1 - 1).End(xlUp).Row
End If
Next
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
'Status
'The last row of data has been calculated. Additionally the first row, the
'first column and the second column will be the arguments of the following
'range (to be assigned to an array).
'Remarks
'When performing calculation, objects like workbooks, worksheets, ranges are
'usually very slow. To speed up, an array is introduced to hold the data
'and to calculate from there which is dozens of times faster.
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
'Assign the range of data to an array.
TheArray = oWs.Range(Cells(cloFirstRow, ciFirstCol), Cells(loRow, iSecondCol))
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
'Status
'All data is now in TheArray ready for calculation.
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
' str1 = "Initial Contents in TheArray"
' For loArr = LBound(TheArray, 1) To UBound(TheArray, 1)
' For iArr = LBound(TheArray, 2) To UBound(TheArray, 2)
' If iArr > 1 Then
' str1 = str1 & Chr(9) 'Next Column
' Else 'First run-though.
' str1 = str1 & vbCrLf 'Next Row
' End If
' If Not IsError(TheArray(loArr, iArr)) Then
' str1 = str1 & TheArray(loArr, iArr)
' Else
' str1 = str1 & VbaErrorString(TheArray(loArr, iArr))
' End If
' Next
' Next
' Debug.Print str1
' str1 = ""
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
'Remarks
'A one-based array is needed to be pasted into the worksheet via range.
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
'Create a new array for the resulting column.
ReDim SmallArray(LBound(TheArray) To UBound(TheArray), 1 To 1)
'Calculate values of the resulting column.
For loArr = LBound(TheArray, 1) To UBound(TheArray, 1)
'Read values from TheArray and calculate.
If IsError(TheArray(loArr, 1)) Then 'First column error
'VBA Error Handling, the result if both columns contain an error.
varArr = VbaErrorString(TheArray(loArr, 1))
Else
If IsError(TheArray(loArr, 2)) Then 'Second column error
'VBA Error Handling
varArr = VbaErrorString(TheArray(loArr, 2))
Else
If TheArray(loArr, 1) = "" Or TheArray(loArr, 2) = "" Then '""
varArr = "#N/A"
Else
Select Case TheArray(loArr, 1) 'Equal
Case TheArray(loArr, 2)
varArr = True
Case Is <> TheArray(loArr, 2) 'Not equal
varArr = False
Case Else
varArr = "UNKNOWN ERROR" 'Should never happen.
End Select
End If
End If
End If
'Write the results to SmallArray.
SmallArray(loArr, 1) = varArr
Next
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
'Status
'The resulting column containing the results has been written to SmallArray.
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
' str1 = "Resulting Contents in SmallArray"
' For loArr = LBound(SmallArray, 1) To UBound(SmallArray, 1)
' If Not IsError(SmallArray(loArr, 1)) Then
' str1 = str1 & vbCrLf & SmallArray(loArr, 1)
' Else
' 'VBA Error Handling
' str1 = str1 & vbCrLf & VbaErrorString(SmallArray(loArr, 1))
' End If
' Next
' Debug.Print str1
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
'Calculate the range where to paste the data,
Set oRng = oWs.Range(Cells(cloFirstRow, iSecondCol + 1), _
Cells(loRow, iSecondCol + 1))
'Paste the resulting column to worksheet.
oRng = SmallArray
' str1 = "Results of the Range"
' For loArr = 1 To oRng.Rows.Count
' If Not IsError(oRng.Cells(loArr, 1)) Then
' str2 = oRng.Cells(loArr, 1)
' Else
' 'VBA Error Handling
' str2 = VbaErrorCell(oRng.Cells(loArr, 1))
' End If
' str1 = str1 & vbCrLf & str2
' Next
' Debug.Print str1
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
'Status
'The resulting data has been pasted from SmallArray to the resulting
'column in the worksheet.
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
End Sub
'-------------------------------------------------------------------------------
Function VbaErrorCell(rCell As Range) As String
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
'Description
'Converts a VBA error (variant) IN A RANGE to an Excel error value (string).
'Arguments
'rCell
'A cell range with a possible VBA error.
'If cell range contains more than one cell, the first cell is used.
'Returns
'An Excel error value (string) if the cell contains an error value, "" if not.
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
Const cVErrLeft As String = "Error 20" 'Debug VBA Error Variable
Const cStrNewError As String = "New Error. Update this Function!"
Const cStrNoError As String = ""
''''''''''''''''''''''''''''''''''''''''
Dim strCStr As String 'The rCell Value Converted to a String
Dim strRes As String 'One of the Excel Cell Error Values
''''''''''''''''''''''''''''''''''''''''
strCStr = Left(CStr(rCell(1, 1)), Len(cVErrLeft))
If strCStr = cVErrLeft Then
Select Case Right(CStr(rCell), 2)
Case "00": strRes = "#NULL!"
Case "07": strRes = "#DIV/0!"
Case "15": strRes = "#VALUE!"
Case "23": strRes = "#REF!"
Case "29": strRes = "#NAME?"
Case "36": strRes = "#NUM!"
Case "42": strRes = "#N/A"
Case Else: strRes = cStrNewError 'New Error.
End Select
Else
strRes = cStrNoError 'Not a VBA Error
End If
VbaErrorCell = strRes
''''''''''''''''''''''''''''''''''''''''
End Function
'-------------------------------------------------------------------------------
Function VbaErrorString(strString As Variant) As String
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
'Description
'Converts a VBA error (variant) IN A STRING to an Excel error value (string).
'Arguments
'strString
'A string with a possible VBA Error.
'Returns
'An Excel error value (string) if the cell contains an error value, "" if not.
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
Const cVErrLeft As String = "Error 20" 'Debug VBA Error Variable
Const cStrNewError As String = "New Error. Update this Function!"
Const cStrNoError As String = ""
''''''''''''''''''''''''''''''''''''''''
Dim strCStr As String 'The strString Value Converted to a String
Dim strRes As String 'One of the Excel Cell Error Values
''''''''''''''''''''''''''''''''''''''''
strCStr = Left(CStr(strString), Len(cVErrLeft))
If strCStr = cVErrLeft Then
Select Case Right(CStr(strString), 2)
Case "00": strRes = "#NULL!"
Case "07": strRes = "#DIV/0!"
Case "15": strRes = "#VALUE!"
Case "23": strRes = "#REF!"
Case "29": strRes = "#NAME?"
Case "36": strRes = "#NUM!"
Case "42": strRes = "#N/A"
Case Else: strRes = cStrNewError 'New Error.
End Select
Else
strRes = cStrNoError 'Not a VBA Error
End If
VbaErrorString = strRes
''''''''''''''''''''''''''''''''''''''''
End Function
'-------------------------------------------------------------------------------
Additionally in view of automation to update the cells automatically, you might want to put the following code into the sheets code window:
Option Explicit
Private Sub Worksheet_SelectionChange(ByVal Target As Range)
XthColumnResult
End Sub
The ideal solution should be with the Change event, but it throws the 'Run-time error 28: Out of stack space', so I used the SelectionChange event instead.
The only drawback I could find was that when you delete a cell with 'del' the value in the third column isn't updated before you move out of the cell.
As always sorry for the 'overcommenting'.
Try using IsEmpty and IsError
For i = 1 To loopNum
If IsEmpty(Range("A" & i)) Or IsEmpty(Range("B" & i)) Or IsError(Range("A" & i)) Or IsError(Range("B" & i)) Then
Range("C" & i).Value = "#N/A"
Else
If Range("A" & i).Value = Range("B" & i).Value Then
Range("C" & i).Value = True
Else
Range("C" & i).Value = False
End If
End If
Next i
Assuming there isn't a reason you actually need to do this in VBA (since you haven't included any code with your question) all you need is a simple worksheet formula.
If Columns A and B contain the data you need to compare, starting on row 3 (like your example implies), enter this formula in Cell C3:
=IF(A3&B3="","",A3=B3)
...then copy/paste (of "fill down") the formula as far as necessary.
If the concatenated values of columns A & B are blank it returns an empty string ("") otherwise it returns the comparison of columns A & B (TRUE or FALSE).
Incidentally if not for the requirement to "return nothing if blank" then the formula would have been about as simple as they get:
=A3=B3

Excel VBA - Randomly select 3 rows per username

I have a large list of tickets with a total of 6 different user names. What I need the code to do is randomly select 3 rows of data per user (18 total) and hide the rest of the rows, as I only need to see the selected rows.
The code will be something like the below, but I am not sure how to write the "random" part.
LastRow = Cells(Rows.Count, "A").End(xlUp).Row
With Range("A2:F" & LastRow)
*Select 3 random rows for user A*
*Select 3 random rows for user B*
*The same for C-F*
*Hide all other rows*
End With
Found this to be an interesting challenge. Something like this should work for you. Commented code for clarity.
Sub tgr()
'Adjust these parameters as necessary
Const sDataSheet As String = "Sheet1"
Const sUserCol As String = "A"
Const lHeaderRow As Long = 1
Const lShowRowsPerUser As Long = 3
Const bSortDataByUser As Boolean = False
'Declare variables
Dim ws As Worksheet
Dim rData As Range
Dim rShow As Range
Dim aData() As Variant
Dim aUserRows() As Variant
Dim lTotalUnqUsers As Long
Dim lMaxUserRows As Long
Dim i As Long, j As Long, k As Long
Dim lRandIndex As Long
'Test if sDataSheet name provided exists in ActiveWorkbook
On Error Resume Next
Set ws = ActiveWorkbook.Sheets(sDataSheet)
On Error GoTo 0
If ws Is Nothing Then
MsgBox "No sheet named [" & sDataSheet & "] found in " & ActiveWorkbook.Name & Chr(10) & _
"Correct sDataSheet in code and try again."
Exit Sub
End If
ws.Cells.EntireRow.Hidden = False 'Reset rows to show all data
'Work with the data range set by parameters
With ws.Range(sUserCol & lHeaderRow + 1, ws.Cells(ws.Rows.Count, sUserCol).End(xlUp))
'Verify data exists in specified location
If .Row < lHeaderRow + 1 Then
MsgBox "No data found in [" & sDataSheet & "]" & Chr(10) & _
"Verify column containing users is Column [" & sUserCol & "] or correct sUserCol in code." & Chr(10) & _
"Verify header row is Row [" & lHeaderRow & "] or correct lHeaderRow in code." & Chr(10) & _
"Once corrections have been made and data is available, try again."
Exit Sub
End If
lTotalUnqUsers = Evaluate("SUMPRODUCT((" & .Address(external:=True) & "<>"""")/COUNTIF(" & .Address(external:=True) & "," & .Address(external:=True) & "&""""))") 'Get total unique users
lMaxUserRows = Evaluate("max(countif(" & .Address(external:=True) & "," & .Address(external:=True) & "))") 'Get max rows per user
If bSortDataByUser Then .Sort .Cells, xlAscending, Header:=xlNo 'If bSortByUser is set to True, then sort the data
Set rData = .Cells 'Store the data in a range object for later use
aData = .Value 'Load the data into an array to speed operations
ReDim aUserRows(1 To lTotalUnqUsers, 1 To 3, 1 To lMaxUserRows) 'Ready the results array that random rows will be selected from
End With
'Load all available rows into the results array, grouped by the user
For i = LBound(aData, 1) To UBound(aData, 1)
For j = LBound(aUserRows, 1) To UBound(aUserRows, 1)
If IsEmpty(aUserRows(j, 1, 1)) Or aUserRows(j, 1, 1) = aData(i, 1) Then 'Find correct user
If IsEmpty(aUserRows(j, 1, 1)) Then aUserRows(j, 1, 1) = aData(i, 1) 'If user isn't in results array yet, add it
k = aUserRows(j, 2, 1) + 1 'Increment row counter for this user
aUserRows(j, 2, 1) = k
aUserRows(j, 3, k) = i + lHeaderRow 'Load this row into this user's group of rows
Exit For
End If
Next j
Next i
'Select random rows up to lShowRowsPerUser for each user from the grouped results array
For j = LBound(aUserRows, 1) To UBound(aUserRows, 1)
Do
Randomize
lRandIndex = Int(Rnd() * aUserRows(j, 2, 1)) + 1
If Not rShow Is Nothing Then
Set rShow = Union(rShow, ws.Cells(aUserRows(j, 3, lRandIndex), sUserCol))
Else
Set rShow = ws.Cells(aUserRows(j, 3, lRandIndex), sUserCol)
End If
Loop While rShow.Cells.Count < j * Application.Min(lShowRowsPerUser, aUserRows(j, 2, 1))
Next j
rData.EntireRow.Hidden = True 'Hide all relevant rows
rShow.EntireRow.Hidden = False 'Only show the rows that have been randomly selected
End Sub

Adding Text after every line on the same cell

I have the following Excel cells:
D001
D002
D003
345
(In the same cell)
I need to add a string of text after every line on the same cell, like this:
D001 First Text
D0002 Second Text
D003 Third Text
345 Fouth Text
I found a code which allows me to count how many lines there are on the same cell, but I dont find any way of using it to write after the text on each of those lines:
Public Sub CountLines()
Dim H1 As Double
Dim H2 As Double
Dim row As Long
row = 1
While Cells(row, 1).Value <> ""
With Cells(row, 1)
.WrapText = False
H1 = .height
.WrapText = True
H2 = .height
.Offset(0, 1).Value = H2 / H1
End With
row = row + 1
Wend
End Sub
I guess the right way of doing it is by using a For to write text before any change of line he finds (Ch(10)) on VBA, but i havent been able to make it work
Thanks for the help.
Adding Text To Count Line Breaks
This code will loop through all cells with any value in Column A.
I have recreated your data set in my Excel:
The code will break up each line, add which line it is, and move on to the next:
Below is the code:
Sub AddText()
Dim ws As Worksheet: Set ws = ThisWorkbook.Sheets(1)
Dim myCell As Variant, myRange As Range, tempArr() As String
Dim i As Integer
Set myRange = ws.Range("A1", ws.Cells(ws.Rows.Count, "A").End(xlUp))
For Each myCell In myRange
tempArr = Split(myCell, Chr(10))
myCell.Value = ""
For i = 0 To UBound(tempArr)
tempArr(i) = tempArr(i) & " text " & i
If i = UBound(tempArr) Then
myCell.Value = myCell.Value & tempArr(i)
Else: myCell.Value = myCell.Value & tempArr(i) & Chr(10)
End If
Next i
Next myCell
End Sub
If you want it to count from base 1 instead of base 0, change the lines myCell.Value = myCell.Value & tempArr(i) (and the following one in the If statement) to myCell.Value = myCell.Value & tempArr(i) + 1
I should mention again that this is already set up for a dynamic range in Column A. Meaning if you add more data formatted the same way in A2, the code will apply itself to that as well, all the way to the last set of data in column A.
Dim arr() As String
Dim arr2() As String
arr = Split(yourCell, char(10))
arr2 = Split("first, second, third", "," )
For i = 1 To UBound(arr)
debug. print arr(i) + arr2(i)
next i
after rebuilding the new string the new string assign it back to the cell
This will only place (random) text after each line in the cell. But it gives you a place to start.
Option Explicit
Public Sub RePrint()
Dim MyRange As Range
Dim MyArray As Variant
Dim i As Long
Set MyRange = Range("A1")
MyArray = Split(MyRange, Chr(10))
For i = LBound(MyArray) To UBound(MyArray)
MyArray(i) = MyArray(i) & " Text" & i
Next i
MyRange = Join(MyArray, Chr(10))
End Sub
you could use this function:
Function AddText(rng As Range, textsArr As Variant) As String
Dim nTexts As Long, nLines As Long, iLine As Long
Dim linesArr As Variant
nTexts = UBound(textsArr) - LBound(textsArr) + 1
With rng
linesArr = Split(.Value, vbLf)
nLines = UBound(linesArr) - LBound(linesArr) + 1
If nTexts < nLines Then nLines = nTexts
For iLine = 1 To nLines
linesArr(LBound(linesArr) - 1 + iLine) = linesArr(LBound(linesArr) - 1 + iLine) & " " & textsArr(LBound(textsArr) - 1 + iLine)
Next iLine
AddText = Join(linesArr, vbLf)
End With
End Function
to be exploited as follows
Option Explicit
Sub main()
Dim cell As Range
Dim additionalTexts As Variant
additionalTexts = Array("First Text", "Second Text", "Third Text", "Fourth Text") '<--| set your array of additional text, each element index corresponding to to be processed cell content line
With Worksheets("ADDTEXT") '<--| reference your relevant worksheet (change "ADDTEXT" to your actual relevant worksheet name)
For Each cell In .Range("A1", .Cells(.Rows.Count, 1).End(xlUp)) '<--| reference its column "A" cells form row 1 down to last not empty row
cell.Value = AddText(cell, additionalTexts) '<--| process
Next cell
End With
End Sub
This will but in the text "First Line", "Second Line"... after each line. The way it is set up now uses the value in A1 and replaces the value in A1. It is ideal for cells with 4 lines or less, but it will work with more.
Sub appendCharacters()
Dim lines() As String
Dim text As String
lines = Split(Range("A1"), Chr(10))
Range("A1").Value = ""
For i = LBound(lines) To UBound(lines)
Select Case i
Case 0
text = " First Line"
Case 1
text = " Second Line"
Case 2
text = " Third Line"
Case 3
text = " Fourth Line"
Case Else
text = " Another Line"
End Select
lines(i) = lines(i) + text
Range("A1").Value = Range("A1").Value + lines(i)
If i <> UBound(lines) Then
Range("A1").Value = Range("A1").Value + vbCrLf
End If
Next i
End Sub

Why is my subscript out of range in 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