I'm looking for a simple Excel VBA or formula that can convert an entire row in Excel from 'number stored as Text' to an actual Number for vlookup reasons.
Can anyone point me in the right direction?
Better Approach
You should use INDEX(MATCH) instead of VLOOKUP because VLOOKUP behaves in an unpredictable manner which causes errors, such as the one you're presumably experiencing.
INDEX ( <return array> , MATCH ( <lookup value> , <lookup array> , 0) )
Using 0 as the last argument to MATCH means the match must be exact
Here is some more in-depth information on INDEX(MATCH)-ing
Further
Add zero +0 to convert a value to a number.
This can be (dangerously) extended with IFERROR() to turn non-numeric text into a zero:
=A2+0
=IFERROR(A2+0,0)
For the inverse, you can catenate an empty string &"" to force the value to be a string.
Notes
If 0 is not used as the last argument to MATCH, it will find all sorts of unexpected "matches" .. and worse, it may find a different value even when an exact match is present.
It often makes sense to do some extra work to determine if there are duplicates in the MATCH lookup column, otherwise the first value found will be returned (see example).
Help with MATCH comes from here, notably the matching logic the 3rd argument controls.
This should work if you add it before your vlookup or index/match lines:
Sheets("Sheet1").UsedRange.Value = Sheets("Sheet1").UsedRange.Value
I did find this, but does anyone have a formula as well?
Sub macro()
Range("F:F").Select 'specify the range which suits your purpose
With Selection
Selection.NumberFormat = "General"
.Value = .Value
End With
End Sub
http://www.ozgrid.com/forum/showthread.php?t=64027
Try this:
Sub ConvertToNumber()
Application.ScreenUpdating = False
Dim cl As Range
For Each cl In Selection.Cells
cl.Value = CInt(cl.Value)
Next cl
Application.ScreenUpdating = True
End Sub
To use it, simply select the relevant block of cells with the mouse, and then run the macro (Alt+F8 to bring up the dialogue box). It will go through each cell in the selected range and convert whatever value it holds into a number.
I wrote a custom vlookup function that doesn't care about data formats. Put this into a module in VBA and use = VLOOK instead of = VLOOKUP
Public Function VLook(sValue As String, rDest As Range, iColNo As Integer)
' custom vlookup that's insensitive to data formats
Dim iLastRow As Long
Dim wsDest As Worksheet
Set wsDest = Sheets(rDest.Parent.Name)
iLastRow = wsDest.Range(wsDest.Cells(100000, rDest.Column).Address).End(xlUp).Row
If iLastRow < rDest.Row + rDest.Rows.Count Then
For X = rDest.Column To rDest.Column + rDest.Columns.Count
If wsDest.Cells(100000, X).End(xlUp).Row > iLastRow Then iLastRow = wsDest.Cells(100000, X).End(xlUp).Row
Next X
End If
sValue = UCase(Application.Clean(Trim(sValue)))
For X = rDest.Row To iLastRow
If UCase(Application.Clean(Trim(wsDest.Cells(X, rDest.Column)))) = sValue Then
VLookDM = wsDest.Cells(X, rDest.Column + iColNo - 1)
Exit For
End If
Next X
End Function
The easiest way I can think of is using the built-in function =VALUE(TEXT_TO_CONVERT_TO_STRING).
Related
I'm trying to build a function to return the end string/cell value of an xlFillSeries. Is there any way to do it without actually writing to the worksheet and then selecting last cell? I want to avoid manipulating the worksheet/workbook
.
Here is the code to generate the series:
Dim SeqStart As String, SeqInt As Integer
SeqStart = "XYZ100"
SeqInt = 42 ' Function should return XYZ141
With Range("A1")
.Value = UCase(SeqStart)
If SeqInt > 1 Then
.AutoFill Destination:=Range("A1").Resize(SeqInt), Type:=xlFillSeries ' Will cause error if only 1 sample sequence
End If
End With
I want to utilize XlFillSeries as it handles odd data well, Eg: If my SeqStart = A1B100 then I can't utilize Regex to strip this down to just numbers, perform math, and then put it back together as there is a B in the middle of the string. I do know the series will/should always end in numbers, but I've struggled a bit to strip just the numeric portion from the right hand of the string without knowing string length and or mix of alphanumeric oddities.
So I guess my question could be answered by figuring out how to strip numbers from right side of string and then doing math and putting string back together. The numbers are what increments.
Or, I would just utilize XlFillSeries but without actually writing to the workbook. Currently I did just set it up so that it writes to a "temp sheet" and then captures the last cell and deletes the temp sheet, but I wondered if there was a better way.
Specifically talking about XlFillSeries there is no way to utilize it without writing to a Range which must be "real cells". Therefor the best method is to utilize a "temp sheet" and return the last cell. It may be possible to utilize functions to strip numbers from right hand side of string and then re-build string, but I trust XlFillSeries more and have resigned to just using a "temp sheet".
Here is my function:
Note: There are some custom functions that I wont include, but you can guess what they do based on the name!
Public Function EndOfXlFillSeries(SeqStart As String, SeqInt As Integer) As String
Dim DestSheet As Worksheet
WorksheetCreateDelIfExists ("XLFillSeriesTmp")
Set DestSheet = Worksheets("XLFillSeriesTmp")
With DestSheet.Range("A1")
.Value = UCase(SeqStart)
If SeqInt > 1 Then
.AutoFill Destination:=DestSheet.Range("A1").Resize(SeqInt), Type:=xlFillSeries ' Will cause error if only 1 SeqInt
Else
EndOfXlFillSeries = SeqStart
End If
End With
Dim lRow As Integer
lRow = lRowOfCol(1)
EndOfXlFillSeries = DestSheet.Range("A" & lRow).Value
Call WorksheetDelete(DestSheet)
End Function
I am new to coding and have written some code to do some calculations within a table and fill in columns. I have it working for the first row within the table but I am having some trouble figuring out how to loop it so that it completes the calculations for every row within the table. Any help and advice would be greatly appreciated!
UPDATE:
Thanks for the Help! The code works perfectly for the first part provided here, I have tried to apply this to the other 2 parts, but am coming up with an error. I think due to the fact that I am trying to use a string as the input? I have tried without the quotation marks but all it returns is "#NAME?".
Sub CommandButton1_Click()
Dim tbl As ListObject
Set tbl = ThisWorkbook.Sheets("Data").ListObjects("Table1")
Dim formulaText As String
formulaText =
"=IF([#Reach]>=100000,5,IF([#Reach]>=50000,3,IF([#Reach]>=10000,2,1)))"
tbl.ListColumns("Media Significance").DataBodyRange.Formula = formulaText
Dim formulaText1 As String
formulaText1 = "=IF([#Headline Mentions]>="Yes",5,IF([#Exclusive
Mentions]>="Yes",3,1))"
tbl.ListColumns("Prominence Score").DataBodyRange.Formula = formulaText1
Dim formulaText2 As String
formulaText2 = "=IF([#Sentiment]>="Very Positive",2,IF([#Sentiment]>="Very
Negative",2,1))"
tbl.ListColumns("Very Positive/ Very Negative").DataBodyRange.Formula =
formulaText2
End Sub
Looping through each cell in a range is very slow, so you're either going to want to either load your data into an array first, or use a regular Excel formula + the FillDown function.
In this particular case, I'd recommend the second option, which will allow you to add your formula to a single cell and fill it down the rest of the column. Something like this should work:
Dim colNum As Long
With ThisWorkbook.Sheets("Example Sheet")
'Find last row in sheet
Dim lastRow As Long
lastRow = .Cells(.Rows.Count, 1).End(xlUp).Row
'Add first formula
colNum = .Range("V2").Column
.Cells(2, colNum).Formula = "=IF(T2>=100000,5,IF(T2>=50000,3,IF(T2>=10000,2,1)))"
.Range(.Cells(2, colNum), .Cells(lastRow, colNum)).FillDown
End With
One problem with your current code is that the column letters are hard-coded. IE, you're expecting to find something called "Reach" in column L, and assuming that this will always be the case. However, if you ever add another column to the left of "Reach", it will break your code.
That's one reason why I'd probably recommend turning your range into a table object with descriptive column names. That should make your code much easier to read and maintain, like this:
Dim tbl As ListObject
Set tbl = ThisWorkbook.Sheets("Example Sheet").ListObjects("YourTable")
Dim formulaText As String
formulaText = "=IF([#Reach]>=100000,5,IF([#Reach]>=50000,3,IF([#Reach]>=10000,2,1)))"
tbl.ListColumns("Reach Analysis").DataBodyRange.Formula = formulaText
For starters, you have redundant criteria in your first If/ElseIf/End If statement.
This,
If Reach >= 100000 Then
Result = 5
ElseIf Reach < 100000 And Reach >= 50000 Then
Result = 3
ElseIf Reach < 50000 And Reach >= 10000 Then
Result = 2
ElseIf Reach < 10000 Then
Result = 1
End If
... can be written more succinctly as,
If Reach >= 100000 Then
Result = 5
ElseIf Reach >= 50000 Then
Result = 3
ElseIf Reach >= 10000 Then
Result = 2
Else
Result = 1
End If
These If/ElseIf/Else/End If conditions are resolved sequentially. Since you won't get into the second criteria unless Reach is less than 100000, there is no need to put that specification into the second criteria. The same logic can be applied for the remainder of the conditions.
Your second If/ElseIf/End If has an error in syntax.
ElseIf Headline = "No" And Exclusive = Yes Then
The Yes here should be quoted or the condition will be looking for a variable named Yes. Putting Option Explicit at the top of the module code sheet in the Declarations area will catch these errors quickly. You can also access the VBE's Tools, Options command and put a checkmark beside Require Variable Declaration and Option Explicit will be automatically put into the Declaration area of each new code sheet you create.
In SQL Server, MIN and MAX can act on text that doesn't evaluate to numbers, returning the text item with the lowest or highest text sort order, or as it's known in SQL Server-speak, "collation order".
Is it possible to do that in Excel without going to a UDF that actually sorts?
For example, for MIN("bb","aa","cc") to return "aa", and MAX("bb","cc","aa") to return "cc".
Excel's MIN/MAX ignore text, and although MINA/MAXA can work on text, they break on text that doesn't resolve to a number. LARGE/SMALL don't do it either.
FWIW, a coworker asked me how to do this in a pivot. I don't see a way without going to a custom function. Am I wrong?
This array formula looks promising. since it is an array it needs to be entered with ctrl-shift-enter.
Max:
=INDEX(A2:A6,MATCH(0,COUNTIF(A2:A6,">"&A2:A6), 0))
Min:
=INDEX(A2:A6,MATCH(0,COUNTIF(A2:A6,"<"&A2:A6), 0))
Change the three ranges to what you want.
The max and min versions are the same except for the > versus <.
I believe you are correct, a custom function is best. The good thing to note is the normal comparator operators work similarly as you described.
Public Function MinStr(ByVal strVal As Range) As String
Dim i As Integer
Dim cell As Range
MinStr = ""
'Check to make sure the range is not empty
if strVal.Rows.Count > 0 then
'Initialize MinStr to a known value
MinStr = strVal.cells(1,1).Value
'Iterate through the entire range
For Each cell in strVal.Cells
if(MinStr > cell.Value) then
MinStr = cell.Value
end if
Next cell
end if
End Function
Public Function MaxStr(ByVal strVal As Range) As String
Dim i As Integer
Dim cell As Range
MaxStr = ""
'Check to make sure the range is not empty
if strVal.Rows.Count > 0 then
'Initialize MaxStr to a known value
MaxStr = strVal.cells(1,1).Value
'Iterate through the entire range
For Each cell in strVal.Cells
if(MaxStr < cell.Value) then
MaxStr = cell.Value
end if
Next cell
end if
End Function
I use check boxes on individual worksheets to set ranges for performing VLookup functions. One of the check boxes needs to set two distinct ranges in which to search. I'm out of ideas on how to make this work. All the other possible variants are searching a continuous string of cells (i.e. [S9:T20] or [S55:T66] but not both. If I end up having to u multiple variables and perform the function twice the rest of my code will probably not work. Any ideas would be appreciated including if some sort of Find function might do similar work.
Below are snippets of the code that I use:
Dim rngO As Variant
ElseIf ActiveSheet.Shapes("Check Box 43").ControlFormat.Value = 1 Then
rngO = [S9:T20;S55:T66]
The rngO variant is used as shown below (one example):
Case 2
With ActiveSheet
.Range("U2").Value = "1Y"
.Range("V2").Value = WorksheetFunction.VLookup("1Y", rngO, 2, False)
.Range("U3").Value = "1P"
.Range("V3").Value = WorksheetFunction.VLookup("1P", rngO, 2, False)
.Range("U4").Value = "."
.Range("V4").Value = "."
short answer: Yes - it is!
longer answer:
You wrap the WorksheetFunction.VLookup() by some code looking at each area of your source range individually.
Function MyVLookup(Arg As Variant, Source As Range, ColNum As Integer, Optional CmpSwitch As Boolean = True) As Variant
Dim Idx As Integer
MyVLookup = CVErr(xlErrNA) ' default return value if nothing found
On Error Resume Next ' trap 1004 error if Arg is not found
For Idx = 1 To Source.Areas.Count
MyVLookup = WorksheetFunction.VLookup(Arg, Source.Areas(Idx), ColNum, CmpSwitch)
If Not IsError(MyVLookup) Then Exit For ' stop after 1st match
Next Idx
End Function
and in your original code replace all calls to WorksheetFunction.VLookup() by calls to MyVLookup() with the same parameters.
Alternatively you can use this function directly in a cell formula (that's what I usually do with it ...)
I have a problem. I spent hours designing a form which works just great with all your feedback. Today, everything went wrong. The reason for this is simple. A few new columns got added and, obviously, the data my form is reading in is now wrong.
Thus I was thinking of trying the following...
Rather than using the column number as below
TK = Cells(ActiveCell.Row, "S").Value 'everything in the form refers to the active row
I could possibly use the column headings in Row 1.
Is that possible ? This way the spreadsheet can have columns added up to as many as a user would like and the form would dynamically scan for the right heading and get the column number that way.
My thought is, on opening the form, read in all the headings, pick out the ones I need and assign them to a variable. Then I use my normal code and substitute the variable into the column section.
It sounds easy, but I have no idea how to do this.
Use the versatile Find to give you a quick method of detecting where your header is - or if it is missing
Find details here
In the code below I have specified that the search must return
an exact match (xlWhole)
a case sensitive match (False)
The match can be a partial match (xlPart) if you were looking to match say Game out of Game X
code
Const strFind = "Game"
Sub GetEm()
Dim rng1 As Range
Set rng1 = ActiveSheet.Rows(1).Find(strFind, , xlValues, xlWhole, , , False)
If Not rng1 Is Nothing Then
MsgBox "Your column is " & rng1.Column
Else
MsgBox strFind & " not found", vbCritical
End If
End Sub
Why use a loop? There's no need to.
Dim col as variant
Col = application.match("my header", rows(1), 0)
If iserror(col) then
'not found
Else
TK = cells(activecell.row, col)
End if
For this purpose I usually use a function which runs through the headers (in the first row of a sheet) and returns the number of the column which contains the value I have searched for.
Public Function FindColumn(HeaderName As String, Sht As String) As Long
Dim ColFound As Boolean
Dim StartingPoint As Range
ColFound = False
Set StartingPoint = Sheets(Sht).Range("A1")
Do While StartingPoint.Value <> ""
If UCase(Trim(StartingPoint.Value)) = UCase(Trim(HeaderName)) Then
FindColumn = StartingPoint.Column
ColFound = True
Exit Do
Else
Set StartingPoint = StartingPoint.Offset(0, 1)
End If
Loop
If Not ColFound Then FindColumn = 0
End Function
Example:
If the first row of your sheet named "Timeline" contains headers like e.g. "Date" (A1), "Time" (B1), "Value" (C1) then calling FindColumn("Time", "Timeline") returns 2, since "Time" is the second column in sheet "Timeline"
Hope this may help you a little.
Your thought is a good one. Reading in column headers to calculate addresses is one way to avoid hard coding - e.g.
Sub Test()
Dim R As Range
Set R = ActiveSheet.[A1]
Debug.Print ColNo(R, "Col1Hdr")
End Sub
Function ColNo(HdrRange As Range, ColName As String) As Integer
' 1st column with empty header is returned if string not found
ColNo = 1
Do While HdrRange(1, ColNo) <> ""
If HdrRange(1, ColNo) = ColName Then Exit Do
ColNo = ColNo + 1
Loop
End Function
Another way I frequently use - and I must admit I prefer it over the above, is to define Enum's for all my tables in a seperate "definition" module, e.g.
Public Enum T_VPN ' sheet VPN
NofHRows = 3 ' number of header rows
NofCols = 35 ' number of columns
MaxData = 203 ' last row validated
GroupNo = 1
CtyCode = 2
Country = 3
MRegion = 4
PRegion = 5
City = 6
SiteType = 7
' ....
End Enum
and use it like
Sub Test1()
Debug.Print ActiveSheet(T_VPN.NofHRows, T_VPN.Country)
End Sub
As you can see, the usage is simpler. Allthough this is again "some kind" of hardcoding, having all definition in one place reduces maintenance significantly.