I'm trying to write a VBA code that would be the equivalent of this excel function for a definite range of cells.
=if(A1="", "", vlookup(A1, K1:M2000, 3, false))
I want to apply this for range(A1:A15) and insert the function in range (B1:B15) using VBA.
Not sure why you'd want to do this, but you can easily make this function in VBA using the below code. The above function will actually be faster and more efficient in the workbook rather than making the below, but it is possible.
Using the below function should now work in your file for cell B1. Then just copy and paste through B2:B15.
=customVLOOKUP(A1,$K$1:$M$2000,3,FALSE)
Note that the last two arguments are set to 3 and false by default, so =customVLOOKUP(A1,$K$1:$M$2000) will give the same result as above
Function customVLOOKUP(lookup As Range, lookupRng As Range, Optional lookupCol As Integer = 3, Optional lookupType As Boolean = False) As Variant
' check to see if lookup value is empty string
' if so return empty string to function and exit
' otherwise, evaluate normal vlookup function
If lookup.Value = "" Then
customVLOOKUP = ""
Exit Function
Else
customVLOOKUP = Application.WorksheetFunction.VLookup(lookup.Value2, lookupRng, lookupCol, lookupType)
End If
End Function
If you are looking to put that formula into B1:B15 and have all of the column A references adjust as the formula is filled down, you do not need to loop through the cells. Put the formula into the B1:B15 block of cells and the references to column A will adjust automatically.
with activesheet '<-reference this worksheet properly!
with .range("B1:B15")
' use this one
.formula = "=if(len(A1), iferror(vlookup(A1, K:M, 3, false), """"), """")"
' or this one
.formulaR1C1 = "=if(len(RC[-1]), iferror(vlookup(A1, C11:C13, 3, false), """"), """")"
end with
end with
Note that each of the pairs of double quotes from the original formula needs to be doubled up as they are quotes within a quoted string. I added an IFERROR function to handle non-matches and changes the check on A1 from "" to the LEN function to ease the double of quotes in the formula.
Related
I notice that numeric values like 123456 can be considered as numbers or non-numbers in Excel. Mixing numbers and non-numbers may result in unexpected results of = or XLOOKUP.
For instance, in the following worksheet, the formula of D3 is =ISNUMBER(C3) and the formula of D4 is =ISNUMBER(C4). Their values are not the same. Then =C3=C4 in another cell will return FALSE; =XLOOKUP(C3,C4,C4) will return #N/A.
So one solution to avoid such surprises is that I would like to convert all these numeric values from numbers to non-numbers, before applying formulas on them.
Does anyone know if it is possible to undertake this conversion by manual operations (select the range, then...)?
Does anyone know how to achieve this conversion by a subroutine in VBA (select the range, then run the VBA subroutine, then the selected range will be converted)?
If you firstly write numbers in a range, let us say "C:C", formatted as General, any such a cell will return TRUE when you try =ISNUMBER(C4).
If you preliminary format the range as Text and after that write a number, this will be seen by Excel as a String (non-numbers, as you say...) and =ISNUMBER(C4) will return False.
Now, if you will try formatting the range as Text after writing the numbers these cells will not be changed in a way to make =ISNUMBER(C4) returning FALSE. In order to do that, you can use TextToColumns, as in the next example:
Private Sub testTextToCol()
Dim sh As Worksheet, rng As Range
Set sh = ActiveSheet
Set rng = sh.Range("C:C")
rng.TextToColumns Destination:=rng, FieldInfo:=Array(1, 2)
End Sub
It will make the existing =ISNUMBER(C4), initially returning TRUE, to return FALSE...
Of course you cannot compare apples to oranges, thus strings are not comparable to integers/longs/numbers. Make sure that all you compare are apples.
In a routine this would be s.th. like
Option Explicit
Sub changeFormat():
' Declare variables
Dim Number As Variant
Dim check As Boolean
'Converts the format of cells D3 and D4 to "Text"
Range("D3:D4").NumberFormat = "#"
'Assign cell to be evaluated
Number = Range("D3")
Debug.Print Number 'Prints '123'
check = WorksheetFunction.IsText(Trim(Sheets("Tabelle1").Cells(4, 3)))
Debug.Print check 'Prints True
'Converts the format of cells D3 and D4 to "Numbers"
Range("D3:D4").NumberFormat = "0.00"
'Compare Cells
If Range("D3").NumberFormat = Range("D4").NumberFormat Then Range("D5").Value = "Same Format"
End Sub
Also see the docs
I am looking for reverse vlookup with more than 255 characters in Excel VBA.
This is the formula based one which I took from this website.
=INDEX(F2:F10,MATCH(TRUE,INDEX(D2:D10=A2,0),0))
I have try to convert it in VBA. Here below sample code
Sub test()
'concat
Range("i1") = WorksheetFunction.TextJoin(" ", True, Range("g1:h1"))
'lookup
Sal1 = Application.WorksheetFunction.Index(Sheets("sheet1").Range("a1:a2"), Application.WorksheetFunction.Match(True, Application.WorksheetFunction.Index(Sheets("sheet1").Range("i1:i1") = Range("i1").Value, 0), 0))
'=INDEX($W$3:$W$162,MATCH(TRUE,INDEX($W$3:$W$162=U3,0),0))
End Sub
It works well but it didn't when i change the range("i1:i1") to range("i1:i2")
I'm not sure what that worksheet formula does that =INDEX(F2:F11,MATCH(A2,D2:D11,FALSE)) doesn't do.
This part Index(Sheets("sheet1").Range("i1:i2") = Range("i1").Value, 0) is comparing a 2-d array to a single value, which should result in a Type Mismatch error. Whenever you reference a multi-cell range's Value property (Value is the default property in this context), you get a 2-d array even if the range is a single column or row.
You could fix that problem with Application.WorksheetFunction.Transpose(Range("D1:D10")) to turn it into a 1-d array, but I still don't think you can compare a 1-d array to a single value and have it return something that's suitable for passing into INDEX.
You could use VBA to create the array's of Trues and Falses, but if you're going to go to that trouble, you should just use VBA to do the whole thing and ditch the WorksheetFunction approach.
I couldn't get it to work when comparing a single cell to a single cell like you said it did.
Here's one way to reproduce the formula
Public Sub test()
Dim rFound As Range
'find A2 in D
Set rFound = Sheet1.Range("D1:D10").Find(Sheet1.Range("A2").Value, , xlValues, xlWhole)
If Not rFound Is Nothing Then
MsgBox rFound.Offset(0, 2).Value 'read column f - same position as d
End If
End Sub
If that simpler formula works and you want to use WorksheetFunction, it would look like this
Public Sub test2()
Dim wf As WorksheetFunction
Set wf = Application.WorksheetFunction
MsgBox wf.Index(Sheet1.Range("F2:F11"), wf.Match(Sheet1.Range("A2").Value, Sheet1.Range("D2:D11"), False))
End Sub
Function betterSearch(searchCell, A As Range, B As Range)
For Each cell In A
If cell.Value = searchCell Then
betterSearch = B.Cells(cell.Row, 1)
Exit For
End If
betterSearch = "Not found"
Next
End Function
i found this code from above link and it is useful for my current search.Below examples i try to get value..
Kindly consider Row 1 to 5 as empty for A and B column because my table always start from Row 6
Row
A Column
B Column
6
54
a
7
55
b
8
56
c
VBA Code:
Sub look_up ()
Ref = "b"
look_up = betterSearch(Ref, Range("B6:B8"), Range("A6:A8"))
End Sub
it show Empty while use Range("B6:B8"), Range("A6:A8")
but when changing the range from B6 and A6 to B1 and A1 (Range("B1:B8"), Range("A1:A8") )it gives the value...
My question is "can get the values from desired range"
Expressing matches via VBA
I like to know if there (are) any possibilities to convert this formula.
=INDEX(F2:F10,MATCH(TRUE,INDEX(D2:D10=A2,0),0))
So "reverse VLookUp" in title simply meant to express the (single) formula result via VBA (btw I sticked to the cell references in OP, as you mention different range addresses in comments).
This can be done by simple evaluation to give you a starting idea:
'0) define formula string
Dim BaseFormula As String
BaseFormula = "=INDEX($F$2:$F$10,MATCH(TRUE,INDEX($D$2:$D$10=$A2,0),0))"
'1) display single result in VB Editor's immediate
Dim result
result = Evaluate(BaseFormula)
Debug.Print IIf(IsError(result), "Not found!", result)
On the other hand it seems that you have the intention to extend the search string range
from A2 to more inputs (e.g. till cell A4). The base formula wouldn't return a results array with this formula,
but you could procede as follows by copying the start formula over e.g. 3 rows (note the relative address ...=$A2 to allow a row incremention in the next rows):
'0) define formula string
Dim BaseFormula As String
BaseFormula = "=INDEX($F$2:$F$10,MATCH(TRUE,INDEX($D$2:$D$10=$A1,0),0))"
'2) write result(s) to any (starting) target cell
'a)Enter formulae extending search cells over e.g. 3 rows (i.e. from $A2 to $A4)
Sheet3.Range("H2").Resize(3).Formula2 = BaseFormula
'b) optional overwriting all formulae, if you prefer values instead
'Sheet3.Range("H2").Resize(3).Value = Tabelle3.Range("G14").Resize(3).Value
Of course you can modify the formula string by any dynamic replacements (e.g. via property .Address(True,True,External:=True) applied to some predefined ranges to obtain absolute fully qualified references in this example).
Some explanations to the used formulae
The formula in the cited link
=INDEX(F2:F10,MATCH(TRUE,INDEX(D2:D10=A2,0),0))
describes a way to avoid an inevitable #NA error when matching strings with more than 255 characters directly.
Basically it is "looking up A2 in D2:D10 and returning a result from F2:F10" similar to the (failing) direct approach in such cases:
=INDEX(F2:F11,MATCH(A2,D2:D11,FALSE))
The trick is to offer a set of True|False elements (INDEX(D2:D10=A2,0))
which can be matched eventually without problems for an occurence of True.
Full power by Excel/MS 365
If, however you dispose of Excel/MS 365 you might even use the following much simpler function instead
and profit from the dynamic display of results in a so called spill range.
That means that matches can be based not only on one search string, but on several ones (e.g. A1:A2),
what seems to solve your additional issue (c.f. last sentence in OP) to extend the the search range as well.
=XLOOKUP(A1:A2,D2:D10,F2:F10,"Not found")
I'm an Excel VBA newbie.
How to change the value of the specified cell via a user-defined function? What's wrong with this code:
Function Test(ByVal ACell As Range) As String
ACell.Value = "This text is set by a function"
Test := "Result"
End Function
My wish is ... when I type =Test(E6) in cell E1, Excel will display the specified text in E6.
YES, of course, it is possible.
Put this code in Module1 of VBA editor:
Function UDF_RectangleArea(A As Integer, B As Integer)
Evaluate "FireYourMacro(" & Application.Caller.Offset(0, 1).Address(False, False) & "," & A & "," & B & ")"
UDF_RectangleArea = "Hello world"
End Function
Private Sub FireYourMacro(ResultCell As Range, A As Integer, B As Integer)
ResultCell = A * B
End Sub
The result of this example UDF is returned in another, adjacent cell. The user defined function UDF_RectangleArea calculates the rectangle area based on its two parameters A and B and returns result in a cell to the right. You can easily modify this example function.
The limitation Microsoft imposed on function is bypassed by the use of VBA Evaluate function. Evaluate simply fires VBA macro from within UDF. The reference to the cell is passed by Application.Caller. Have fun!
UDF limitation documentation: https://support.microsoft.com/en-us/help/170787/description-of-limitations-of-custom-functions-in-excel
A VBA UDF can be used as an array function to return results to multiple adjacent cells.
Enter the formula into E1 and E2 and press Ctrl-Shift-Enter to create a multi-cell array formula. Your UDF would look something like this:
Public Function TestArray(rng As Range)
Dim Ansa(1 To 2, 1 To 1) As Variant
Ansa(1, 1) = "First answer"
Ansa(2, 1) = "Second answer"
TestArray = Ansa
End Function
Excel VBA will not allow a user-defined function to alter the value of another cell.
The only thing a UDF is allowed to do (with a few minor exceptions) is to return values to the cells it is called from.
Why not just typing you formula in E6 then ? That's the Excel logic: put your formula where you want the result to appear.
I Have built a string using a formula in excel. as an example
Cell C3 contains text "Languages"
Cell C4 = "English, Spanish,German, French"
My Forumla = C3 & ":" & CHAR(10) & C4
The Desired text would be:
Languages:
English, Spanish, German, French
(where the bold text would actually be some color like red)
Is there a way to do this in Excel (change partial text formatting) .
I Have tried a formula... (Not working)
Function formatText(InText As Range)
'Set font color
InText.Characters(1.5).Font.Color = Red
'InText.Characters((InStr(1, ":", InText) + 1), (Len(InText) - InStr(1, ":", InText))).Font.ColorIndex = 3
End Function
Your posted function with work if and only if
It is called from a Sub (ie, as other have mentioned, not as a UDF)
And
the value(s) contained in range InText are string constants. (This is the main point of my answer)
It will not work for any cells in range InText containing a formula. AFAIK you cannot format part of a string returned by a formula.
BTW I would love to be proved wrong on this!
Regarding Hightower's question, "how would you cast a formula output to a string so that you can apply the text formatting?"
To "cast" the output of a formula so that you can apply text formatting, you must write the value returned by the formula into the spreadsheet, then apply the formatting to the value you wrote. You could either write the value into the cell containing the formula (which will erase the formula), or you could write the value into a different place in the spreadsheet (which will preserve the formula but then you'll be seeing double).
Sub Cell_Format(InText as Range)
InText.formula = cstr(InText.value) ' converts result of formula into a string literal
'or: InText.offset(0,1).formula = cstr(InText.value) -- writes the value in the cell next to InText
InText.characters(1, 5).font.color = vbRed
End Sub
Then Cell_Format range("$A$1") will replace the formula in cell $A$1 with a string constant and change the color of the first five characters to red.
If you want to do this for a range larger than one cell, add this code to the above:
Sub Range_Format(InText as Range)
For each c in InText
Cell_Format(c)
Next
End Sub
You need to use this code:
InText.Characters(1,5).Font.Color = RGB(255, 0, 0)
If you want to make it flexible, so that only the (fully) second line is red, use this code:
InText.Characters(Instr(InText, vbCr)+1, Len(InText)-Instr(InText, vbCr)).Font.Color = RGB(255, 0, 0)
Note that your function needs to be called from VBA, i.e. you cannot use it as a User-Defined-Function! UDFs can only return a result but never modify a cell!
You cannot directly call the below UDF in Excel interface. For that you will have use an event as UDF cannot change the physical characteristic of a cell. For more details you may read this link. http://support.microsoft.com/kb/170787
Function formatText(InText As Range)
'Set font color
InText.Interior.Color = vbRed
'InText.Characters((InStr(1, ":", InText) + 1), (Len(InText) - InStr(1, ":", InText))).Font.ColorIndex = 3
End Function
I wish to categorize my transactions in a way where I can alter the categories on the fly. I think it's easier explained by showing what I have.
I have the following tables
Transactions
A: Date
C: Name
D: Amount
Fast Food List:
L: Name (partial name since going to be doing string search)
I wish to sum the transaction amount based on multiple criteria, such as date and category. Here's a formula that works:
=SUMIFS(D:D,A:A,"*03/2013*",C:C,"*"&L3&"*")
There's one fundamental problem: it only supports ONE item from the Fast Food List. Is there any way I can simply do a text stringth search across the entire Fast Food names?
""&L3&"" to ""&L:L&"" or something?
Here are some things I've tried.
1) Modify the SUMIFS criteria ""&L3&"" with a boolean UDF. The issue I run into here is that I can't figure out how to pass the current Row being looped by SUMIF into the function.
Public Function checkRange(Check As String, R As Range) As Boolean
For Each MyCell In R
If InStr(Check, MyCell.Value) > 0 Then
checkRange = True
End If
Next MyCell
End Function
If I could send Check to this function, well I would be set.
2) Replace the sum_range of the SUMIFS with a UDF that returns the range of rows
Public Function pruneRange(Prune_range As Range, Criteria_range As Range) As Range
Dim Out_R As Range
Dim Str As String
ActiveWorkbook.Sheets("Vancity Trans").Activate
' Loop through the prune_range to make sure it belongs
For Each Cell In Prune_range
' loop through criteria to see if it matches current Cell
For Each MyCell In Criteria_range
If InStr(Cell.Value, MyCell.Value) > 0 Then
' Now append cell to Out_r and exit this foreach
' Str = Str & Cell.Address() & ","
Str = Str & "D" & Cell.Row() & ","
Exit For
End If
Next MyCell
Next Cell
' remove last comma form str
Str = Left(Str, Len(Str) - 1)
' use str to set the range
Set Out_R = Range(Str)
' MsgBox (Str)
Set pruneRange = Out_R
End Function
This works for a regular SUM loop, but for some reason it returns #Value when I try using it in a SUMIF or SUMIFS. Another issue is that even in the SUM loop if use C:C instead of C1:CX where X is however many rows, it crashes excel or takes forever to loop through. I'm guessing it's because excel doesn't know when to stop in a UDF unless I somehow tell it to?
Try this formula
=SUMPRODUCT(SUMIFS(D:D,A:A,"*03/2013*",C:C,"*"&L3:L30&"*"))
By using a range (L3:L30) for the final criterion the SUMIFS formula will generate an "array" (of 28 values - one for each value in L3:L30) ...and SUMPRODUCT is used to sum that array and get the result you want