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
Related
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")
So I have been trying to build a function to increment the cells using a For loop
=IFERROR(IF(AND(N$10>=PivotTabel!$L5;N$10<=PivotTabel!$M5);"1";"0.5");"X") &
IF(AND(N$10>=Sheet1!$L125;N$10<=Sheet1!$M125);"yes";"n") &
IF(AND(N$10>=Sheet1!$L126;N$10<=Sheet1!$M126);"yes";"n") &
IF(AND(N$10>=Sheet1!$L127;N$10<=Sheet1!$M127);"yes";"n")
This formula is to check if a date is between a date period ( for example to check if 01.01.2020 is between 02.08.2019 and 20.02.2020 if True in the first case i print "1" and other cases i print "yes").
As you can see above is the formula that i need to write in VBA to iterate through a specified number of cells which are $L125:$L139 for one column, and the other column $M125:$M139
I wrote the function below but it gives me #VALUE!
Function Date_Increment(MaxCount As Integer) As String
' This function does incrementation of date ranges
Dim Result As String
Static i As Integer
i = 125
Dim cell As Range
'MaxCount is the number times to iterate just to test
'My idea is to use the function COUNT("L125:L139") to return the number of cells that are not blank
For Each i In MaxCount
' this is the cell i want to run formula in
Range("GUI!D12").Formula = "=IFERROR(IF(AND(D$10>=PivotTabel!$L5;D$10<=PivotTabel!$M5);""1"";""0.5"");""X"") & IF(AND(D$10>=Sheet1!$L" & i & ";D$10<=Sheet1!$M" & i & ");""yes"";""n"")"
Result = Range("GUI!D12").Formula
i = i + 1
Next cell
'Next j
DateRange_Increment
End Function
My question is : How can I make this function iterate through both of ranges? Or If there is a simple solutions using only formulas i would also use it. In addition please check if i have errors in the function.
Any help i would really appreciate it.
I have a table in Excel as shown in the attached image (Until Column F) and would like to see the final column "Result":
ExcelData:
The Result column should retrieve Column Names where the row values is "IF".
I can use the IF function in Excel, but that would be a long statement that I would have to write to retrieve the final output if there are like 20 columns that I would have to check.
If you are okay with creating a UDF (User Defined Function), then you can essentially create your own worksheet function to do this.
In this function, you will do a quick loop in the input range, and check if any cell in that range is equal to "IF". If so, you will strip the column letter out of the .Address property.
Public Function getIFCols(ByVal rng As Range) As String
Dim retVal As String, cel As Range
For Each cel In rng.Cells
If cel.Value = "IF" Then
retVal = Trim(retVal & " " & colLtr(cel))
End If
Next
getIFCols = Replace(retVal, " ", ", ")
End Function
Private Function colLtr(colRng As Range) As String
colLtr = Split(colRng.Address, "$")(1)
End Function
You will call this function as such:
=getIFCols(B1:F1)
The great thing about this function is that it doesn't require a set amount of columns. So if you need 3 columns checked on one row and 10 on another, it's pretty dynamic.
For Excel Online you can try the texjoin
=TEXTJOIN(",",TRUE,IF(B2:G2="IF",$B$1:$G$1,""))
I am trying to produce a code (simple for some) but I am inexperienced and would appreciate help.
Code to look at cell ("J1") and doing an if / then for a result in ("K1"), but I want to have this duplicated to look at the range J2 to J10 cells , to give the result in the range K2 to K10 cells as well.
The code below works for single row formula:
Sub Check()
Dim DDDD As Date, result As String
DDDD = Range("j1").Value
If DDDD >= Date Then
result = "Future"
Else
result = "Now"
End If
Range("k1").Value = result
End Sub
You can use a For Each loop and the Offset method, like this:
Sub Check()
Dim DDDD As Date, result As String
Dim cell as Range
For Each cell in Range("J1:J10")
DDDD = cell.Value
If DDDD >= Date Then
result = "Future"
Else
result = "Now"
End If
' Change value at the right side of the cell
cell.Offset(0,1).Value = result
Next cell
End Sub
But using VBA for this is really overkill. You could just put this formula in K1:
=IF(J1 >= TODAY(), "Future", "Now")
... and then copy/drag that formula down to the other cells below it.
Here is a piece of code that loops through Column J, and the result will be placed at Column K.
Sub Check()
Dim DDDD As Date
Dim result As String
Dim row, NumofRows As Integer
' use NumfofRows as a Dynamic value to decide how many rows of data you want to look at column J
NumofRows = 10
For row = 1 To NumofRows
If Cells(row, 10) >= Date Then
result = "Future"
Else
result = "Now"
End If
Cells(row, 11).Value = result
Next
End Sub
In the future, take a look at the rules of etiquette that make your questions both fun and enticing to answer. For your question, it will be more efficient to deal with Ranges rather than individual cells with dates in them. The For Each...Next loop can cycle through each cell in the range. Then, an If...Then...Else...End If loop can make a decision given various conditions. The Offset(0, 1) method can be used inside the If...Then loop to place results in the column to the right. The following code does what you seem to be asking:
Sub Check()
' Declare your variables
Dim MyDates As Range
Dim result As String
' First, grab all the values in the J2:JX column, where
' X can be any number of dates you choose. Note that
' you must "Set" Object variables, but you must "Dim"
' other types of variables.
Set MyDates = Range(Range("J2"), Range("J2").End(xlDown))
' Loop through every cell in the dates column and give the
' offset column (column K) a string value if the condition
' is met.
Dim Cell As Range
For Each Cell In MyDates
If Cell >= Date Then
result = "Future"
Cell.Offset(0, 1).Value = result
Else
result = "Now (or past)"
Cell.Offset(0, 1).Value = result
End If
Next Cell
End Sub
The End(xlDown) method emulates selecting a cell and pressing Ctrl+Down arrow. Also, note that this code does not handle mistakes at all. For example if you accidentally enter a date as text, the procedure will produce an incorrect result. Also, depending on the time you enter data, you could get confusing results. I've attached an image of the results in Excel here:
I would like to do a vertical lookup for a list of lookup values and then have multiple values returned into columns for each lookup value. I actually managed to do this after a long Google search, this is the code:
=INDEX(Data!$H$3:$H$70000, SMALL(IF($B3=Data!$J$3:$J$70000, ROW(Data!$J$3:$J$70000)-MIN(ROW(Data!$J$3:$J$70000))+1, ""), COLUMN(A$2)))
Now, my problem is, as you can see in the formula, my lookup range contains 70,000 rows, which means a lot of return values. But most of these return values are double. This means I have to drag above formula over many columns until all lookup values (roughly 200) return #NUM!.
Is there any possible way, I guess VBA is necessary, to return the values after duplicates have been removed? I'm new at VBA and I am not sure how to go about this. Also it takes forever to calculate having so many cells.
[Edited]
You can do what you want with a revised formula, not sure how efficient it will be with 70,000 rows, though.
Use this formula for the first match
=IFERROR(INDEX(Data!$H3:$H70000,MATCH($B3,Data!$J3:$J70000,0)),"")
Now assuming that formula in in F5 use this formula in G5 confirmed with CTRL+SHIFT+ENTER and copied across
=IFERROR(INDEX(Data!$H3:$H70000,MATCH(1,($B3=Data!$J3:$J70000)*ISNA(MATCH(Data!$H3:$H70000,$F5:F5,0)),0)),"")
changed the bolded part depending on location of formula 1
This will give you a list without repeats.....and when you run out of values you get blanks rather than an error
Not sure if you're still after a VBA answer but this should do the job - takes about 25 seconds to run on my machine - it could probably be accelerated by the guys on this forum:
Sub ReturnValues()
Dim rnSearch As Range, rnLookup As Range, rnTemp As Range Dim varArray
As Variant Dim lnIndex As Long Dim strTemp As String
Set rnSearch = Sheet1.Range("A1:A200") 'Set this to your 200 row value range
Set rnLookup = Sheet2.Range("A1:B70000") 'Set this to your lookup range (assume 2
columns)
varArray = rnLookup
For Each rnTemp In rnSearch
For lnIndex = LBound(varArray, 1) To UBound(varArray, 1)
strTemp = rnTemp.Value
If varArray(lnIndex, 1) = strTemp Then
If WorksheetFunction.CountIf(rnTemp.EntireRow, varArray(lnIndex, 2)) = 0 Then 'Check if value exists already
Sheet1.Cells(rnTemp.Row, rnTemp.EntireRow.Columns.Count).End(xlToLeft).Offset(0, 1).Value =
varArray(lnIndex, 2)
End If
End If
Next Next
End Sub