Categorizing bank statements in Excel - excel

I'm wanting to categorize a bank statement from a list of rules in excel. I've tried using a vlookup but I would like to be able to have non exact matches and as far as I know vlookup is not suited to this.
For instance if my statement looked like this and was located in worksheet "Statement"
Date | Transaction desciption | Amount
7/3/2013 | The Home Depot | $345.00
7/4/2013 | McDonald's #27 | $4.50
And I had a list of rules located in worksheet "Rules"
Rule | Category
The Home Depot | Home improvements
McDonald's * | Fast food
Is there a simple way to add another column using vba to the sheet "Statement" called Category that uses the rules to generate categories for each transaction?

Simple, no. I've done something similar in the past, this is how I did it.
1) Setup a rules page, I've called mine 'Patterns'. These patterns are setup with with the A column (from A2 on) being the 'name' and the B column being the regex pattern.
2) Load these into a public variable with the following code (I put a button on the patterns sheet to run this macro and load them into memory.):
Option Explicit
Public Patterns() As String
Sub LoadPatterns()
Dim cell As Range
Dim bRow As Range
Sheets("Patterns").Activate
'select column A, and load into first dimensino
Range("A2", Sheets("Patterns").Range("A" & Sheets("Patterns").Range("A:A").Rows.Count).End(xlUp).Address).Select
ReDim Patterns(Selection.Rows.Count - 1, 1)
For Each cell In Selection
Patterns(cell.Row - 2, 0) = cell.Value
Next
'select column B and load into the second dimension
Range("B2", Sheets("Patterns").Range("A" & Sheets("Patterns").Range("A:A").Rows.Count).End(xlUp).Address).Select
For Each cell In Selection
Patterns(cell.Row - 2, 1) = cell.Value
Next
End Sub
3) Create the following UDF, load the VB regex library as a reference in vba (Microsoft VBScript Regular Expressions 5.5, see http://www.macrostash.com/2011/10/08/simple-regular-expression-tutorial-for-excel-vba/) and call it on your transaction description as a formula after running step 2:
Public Function rxBanking(sName As String)
Dim x As Integer
'Get & load Patterns
Dim regex As New VBScript_RegExp_55.RegExp
Dim match
For x = 0 To UBound(Patterns)
regex.Pattern = Patterns(x, 1)
regex.ignorecase=True
match = regex.Test(sName)
If match Then
rxBanking = Patterns(x, 0)
Exit For
Else
rxBanking = "Unknown"
End If
Next
End Function
so for example, after you've loaded a pattern such as:
Category | RegEx pattern
--------------------------------
Home loan | INTEREST[\s]CHARGED
If your transaction data was in cell D1, then you could categorise it using the formula
=rxBanking(D1)
If you reload your pattern, you will need to re-copy your formulas down on the spreadsheet, as it doesn't automatically recalculate.
For help using regex (which even if you are familiar, you might need) I find a great testing ground is http://regexpal.com/

Related

Find the nth instance of a number in a range of cells

I have a spreadsheet that contains entries for a general ledger account that we need to reconcile.
Ideally every entry starts with a set of numbers but sometimes due to human error or other reasons, the entries might have the number within the string of characters. For example, this is how the entry should look:
33333333 Name 12345
But sometimes it can look like this:
Name 33333333 12345
Or
12345 Name 33333333 Fee
What I'm trying to do is have a search for the number and tell me all the different cells that contain but don't necessarily start or end with that 33333333 number. Right now I have figured out how to get the first instance of the number, return the cell reference and then pull the value from the cell reference. Here is the formula for the cell reference:
=CELL("address",INDEX(C9:C199,MATCH("*"&B1&"*",C9:C199,0)))
Where C9:C199 is the range of cells I'm looking up and B1 is the Lookup cell, which will change depending on what number the Searcher wants to lookup. For example, the result could be $C$19 and then I have the Indirect function giving me the value of that cell reference. But I want to be able to pull the 1st, 2nd, 3rd, etc instance of a cell reference because there could be many, up to 5 or 6 cells that contain the number in some way (not all of which are relevant to what we're trying to reconcile but we want to see all the instances). So how do I get the 2nd instance of this formula, such that if there was a second answer of $C$26, I could also do an indirect reference to that cell and get that value as well.
=CELL("address",INDEX(C9:C199,MATCH("*"&B1&"*",C9:C199,0)))
My ultimate goal is to be able to easily see the following such that visually I can easily see that they offset. I've thrown in a random "fee" word because some users like to throw in random stuff like that:
33333333 Name 12345 Debit $500
Name 33333333 12345 Fee Credit $500
33333333 Name 12345 Other Fee Debit $250
Visually you would see the two $500 fees offset and then the remaining would be the $250 entry. Ultimately I would delete the two $500 entries to have the remaining $250 as the leftover to be offset at a future date. I can do the deleting of entries once I figure out how to get the nth cell reference instance.
Thank you!
You can try a custom function like this if you don't mind messing around with VBA and have Excel 365 or 2021 (dynamic array support is required for this function to be used in a cell). Add the function to a new module in the project and call it with =GetMatches(search range, search term). Also note, I've made this case insensitive with the use of LCase on both the search range and search term.
Function GetMatches(rng As Range, searchTerm As String) As Variant
Dim c As Range
Dim matches As New Collection
Dim output() As Variant
Dim iMax As Integer
Dim i As Integer
Set rng = Intersect(rng.Worksheet.UsedRange, rng)
For Each c In rng
If InStr(1, LCase(c), LCase(searchTerm)) > 0 Then matches.Add c
Next c
iMax = matches.Count
ReDim output(1 To iMax, 1)
For i = 1 To iMax
output(i, 0) = matches(i)
output(i, 1) = matches(i).Address
Next i
GetMatches = output
End Function
One way to do this, with Power Query:
Name cell B1 as 'tblSearchTerm' (create a ListObject) then name the source table as 'Table1' (create a LisObject).
If not shown, open the pane "Queries & Connections' (Data>Queries & Connections).
In 'ResultingSearchTable' right click and choose 'Load to...' then choose a starting cell to load the resulting search table.
If any term is found, the data will be listed as follows:
|RowNumber|NumberInRow|
RowNumber is the DataBodyRange row number of the ListObject.
let
tblSearchTerm = Excel.CurrentWorkbook(){[Name="tblSearchTerm"]}[Content],
Source = Excel.CurrentWorkbook(){[Name="Table1"]}[Content],
#"AddedIndex" = Table.AddIndexColumn(Source, "Index", 1, 1, Int64.Type),
#"DividedColumnByDelimiter" = Table.ExpandListColumn(Table.TransformColumns(AddedIndex, {{"TextLines", Splitter.SplitTextByDelimiter(" ", QuoteStyle.Csv), let itemType = (type nullable text) meta [Serialized.Text = true] in type {itemType}}}), "TextLines"),
#"ChangedType" = Table.TransformColumnTypes(#"DividedColumnByDelimiter",{{"TextLines", type text}}),
#"ReplacedValue" = Table.ReplaceValue(#"ChangedType","$","",Replacer.ReplaceText,{"TextLines"}),
#"ChangedType1" = Table.TransformColumnTypes(#"ReplacedValue",{{"TextLines", type number}}),
RemovedErrors = Table.RemoveRowsWithErrors(ChangedType1, {"TextLines"}),
#"RenamedColumns" = Table.RenameColumns(RemovedErrors,{{"Index", "RowNumber"}, {"TextLines", "NumberInRow"}}),
#"FinalFilter" = Table.NestedJoin(#"RenamedColumns", {"NumberInRow"}, tblSearchTerm, {"SearchTerm"}, "tblSearchTerm", JoinKind.Inner),
#"ResultingSearchTable" = Table.SelectColumns(FinalFilter,{"RowNumber", "NumberInRow"})
in
#"ResultingSearchTable"
With data in C2:C199 (adjust as needed)
B2: (the address) =ADDRESS(AGGREGATE(15,6,1/ISNUMBER(FIND($B$1, $C$2:$C$199))*ROW($C$2:$C$199), ROW(INDEX($A:$A,1):INDEX($A:$A, COUNTIF(C2:C199,"*"&$B$1&"*")))),3)
A2: (the contents) =INDEX($C:$C,AGGREGATE(15,6,1/ISNUMBER(FIND($B$1, $C$2:$C$199))*ROW($C$2:$C$199), ROW(INDEX($A:$A,1):INDEX($A:$A, COUNTIF(C2:C199,"*"&$B$1&"*")))))

How do I fuzzy match just adjacent cells?

I have a row of 10,000 names in two corresponding columns, 10,000 in each. Each cell in Column A corresponds to the adjacent cell in Column B. I want to do a fuzzy match and get a compatibility score on all of them just with the adjacent cell. I do not want it to search entire column versus entire column, just adjacent cells, which I don't seem to be able to do with the Fuzzy Match Excel add in, ideas?
Example:
Column A: Column B: Value:
Apple Aplle 80%
Banana Banana 100%
Orange Ornge 85%
Well, I don't know about Fuzzy Match Addin but you can accomplish similar to your requirement using UDF.
Something like this based on your sample data
Function FuzzyComparision(String1 As String, String2 As String) As Double
Dim intStringLength As Integer
Dim dblScore As Double
Dim dblUnitScore As Double
Dim intCounter As Integer
intStringLength = WorksheetFunction.Max(Len(String1), Len(String2))
dblUnitScore = 1 / intStringLength
dblScore = 0#
For intCounter = 1 To intStringLength
If Mid(UCase(String1), intCounter, 1) = Mid(UCase(String2), intCounter, 1) Then
dblScore = dblScore + dblUnitScore
Else
If Len(String1) <> Len(String2) And intCounter < intStringLength Then
If Mid(UCase(String1), intCounter + 1, 1) = Mid(UCase(String2), intCounter, 1) Then
dblScore = dblScore + dblUnitScore
End If
End If
End If
Next
FuzzyComparision = dblScore
End Function
Of course the comparison can be further improvised as required.
Let me know if that helps
Edit: as Grade Bacon pointed (thanks for that observation), this does not work as expected for missing letters. I have tweaked the function a bit to do a case insensitive comparison and compensate for missing letters as in example.
Even though it works, utility depends on acceptable score deviation +/-. OP may shed some light.
Import this VBA module: https://github.com/kyledeer-32/vba_fuzzymatching
It contains several Fuzzy Matching UDFs - the one your specifically looking for is =String_Similarity. It will give you the match percentage between two strings. You can also use =Fuzzy_Match for matching one string (one cell) to an array of cells (one column).
Here are my results using applying this VBA module to your example data:
Here is a formula view:
Note: after importing this module, you will need to enable the "Microsoft Scripting Runtime" library in the Visual Basic Editor Window it to run. Steps to do this (takes less than a minute):
From Excel Workbook:
Select Developer tab on ribbon
Select Visual Basic
Select Tools on the Toolbar
Select References
Scroll down until you see Microsoft Scripting Runtime, then check the
box
Press OK

Returning multiple values using Vlookup in excel

I have an excel sheet set up to automatically calculate meetings per day by day of the week. I would like to write a formula to return all dates I have a meeting scheduled (comma separated preferably), but I am having some difficulty. Using Vlookup, I can only get it to return the first date.
For example, here is what my data looks like:
A B C
Initial Meetings Follow-up Meetings Date
1 1 7/29/2015
0 1 7/30/2015
1 1 7/31/2015
0 0 8/1/2015
0 0 8/2/2015
I would like to write a formula to return "7/29/2015, 7/31/2015" in one cell, and "7/29/2015, 7/30/2015, 7/31/2015" in another, but I seem to be stuck.
You can't do this with vLookup.
This can be done relatively easily in a VB script, but it would affect portability as many if not most users disable macros by default and in many cases users are prevented from using Macros because their company disables them and makes it policy that users should not use them.
If you are OK with Macros, you can put the following into a new module and then use =MultiVlookup(lookup_value,table_array, col_index_num) in the same way as you'd use vlookup and it should give you a comma separated list of multiple matches:
Public Function MultiVlookup(find_value, search_range, return_row)
Dim myval ' String to represent return value (comma-separated list)
Dim comma ' Bool to represent whether we need to prefix the next result with ", "
comma = False
'Debug.Print find_value.value, return_row
For Each rw In search_range.Rows ' Iterate through each row in the range
If rw.Cells(1, 1).value = find_value Then ' If we have found the lookup value...
If comma Then ' Add a comma if it's not the first value we're adding to the list
myval = myval + ", "
Else
comma = True
End If
myval = myval + Str(rw.Cells(1, return_row).value)
End If
Next
MultiVlookup = myval
End Function
This may not be the cleanest way of doing it, and it isn't a direct copy of vlookup (for instance it does not have a fourth "range lookup" argument as vlookup does), but it works for my test:
Finally my original suggestion (in case it helps others - it's not the exact solution to the question) was:
I've not tried it myself, but this link shows what I think you might be looking for.
Great code, but don't forget to add the following is you use Option Explicit:
Dim rw As Range
WHEELS

Turning an excel formula into a VBA function

I'm a bit new to trying to program and originally was just trying to improve a spreadsheet but it's gone beyond using a basic function in excel. I have a table that I am having a function look at to find a building number in the first column and then look at start and finish dates in two other respective columns to find out if it should populate specific blocks on a calendar worksheet. The problem occurs because the same building number may appear multiple times with different dates and I need to to find an entry that matches the correct dates.
I was able to create a working though complicated formula to find the first instance and learned I can add a nested if of that formula again in the false statement with a slight change. I can continue doing that but it becomes very large and cumbersome. I'm trying to find a way to make a function for the formula with a variable in it that would look at how many times the it has already been used so it keeps searching down the table for an answer that fits the parameters.
This is currently my formula:
=IFERROR(IF(AND(DATE('IF SHEET (2)'!$F$7,MATCH('IF SHEET (2)'!$C$2,'IF SHEET (2)'!$C$2:'IF SHEET (2)'!$N$2,0),'IF SHEET (2)'!C$4)>=VLOOKUP("2D11"&1,A2:F6,4,0),DATE('IF SHEET (2)'!$F$7,MATCH('IF SHEET (2)'!$C$2,'IF SHEET (2)'!$C$2:'IF SHEET (2)'!$N$2,0),'IF SHEET (2)'!C$4)<=VLOOKUP("2D11"&1,A2:F6,4,0)),IF(VLOOKUP("2D11"&1,A2:F6,3,0)="2D11",VLOOKUP("2D11"&1,A2:F6,6,FALSE)),"NO ANSWER"),"ERROR")
Where you see 2D11&1 is where I need the variable for 1 so it would be "number of times it's been used in the function +1" then I could just loop it so it would keep checking till it ran out of 2D11's or found one that matched. I haven't posted before and I'm doing this through a lot of trial and error so if you need more info please post and say so and I'll try to provide it.
So rather than have someone try to make sense of the rediculous formula I posted I though I would try to make it simpler by just stating what I need to accomplish and trying to see how to turn that into a VBA function. So I'm kinda looking at a few steps:
Matches first instance of building name in column A with
building name for the row of the output cell.
Is date connected with the output cell >= start date of first entry(which is user entered in column D).
Is date connected with the output cell <= end date of first entry(which is user entered in column E).
Enters Unit name(located in column F) for first instance of the building if Parts 1, 2, and 3 are all True.
If parts 1, 2, or 3 are False then loops to look at next instance of the building name down column 1.
Hopefully this makes things clearer than the formula so I'm able to get help as I'm still pretty stuck due to low knowledge of VBA.
Here is a simple solution...
Building_name = ???
Date = ???
Last_Row = Range("A65536").End(xlUp).Row
For i = 1 To Last_Row
if cells(i,1).value = Building_Name Then
if date >= cells(i,4).value Then
if date <= cells(i,5).value Then
first instance = cells(i,6).value
end if
end if
end if
next
you should add a test at the end to avoid the case where there is no first instance in the table
If I understand correctly, you have a Table T1 made of 3 columns: T1.building, T1.start date, T1.end date.
Then you have 3 parameters: P1=building, P2=start date, P3=end date.
You need to find the first entry in table T1 that "fits" within the input parameters dates, that is:
P1=T1.building
P2<=T1.start date
P3>=T1.end date
If so, you can define a custom function like this
Public Function MyLookup(Key As Variant, DateMin As Variant, DateMax As Variant, LookUpTable As Range, ResultColumn As Integer) As Range
Dim iIndx As Integer
Dim KeyValue As Variant
Dim Found As Boolean
On Error GoTo ErrHandler
Found = False
iIndx = 1
Do While (Not Found) And (iIndx <= LookUpTable.Rows.Count)
KeyValue = LookUpTable.Cells(iIndx, 1)
If (KeyValue = Key) And _
(DateMin <= LookUpTable.Cells(iIndx, 2)) And _
(DateMax >= LookUpTable.Cells(iIndx, 3)) Then
Set MyLookup = LookUpTable.Cells(iIndx, ResultColumn)
Found = True
End If
iIndx = iIndx + 1
Loop
Exit Function
ErrHandler:
MsgBox "Error in MyLookup: " & Err.Description
End Function
That may not be the most performant piece of code in the world, but I think it's explanatory.
You can download this working example

Get maximum value of columns in Excel with macro

First of all I have no idea of writing macros in excel, but now I have to write a code for a friend. So here we go.
In my excel sheet I have a table which holds some producers as columns and 12 months of the year as rows. In their intersecting cell it's written the amount of products produced by the producer during that month. Now I need to find maximum and minimum values of produced goods within each month and output the producers of that goods.
I found a code for a similar problem, but I don't understand it clearly and it has errors.
Here is the code:
Sub my()
Dim Rng As Range, Dn As Range, Mx As Double, Col As String
Set Rng = Range(Range("A1"), Range("A6").End(xlUp))
ReDim ray(1 To Rng.Count)
For Each Dn In Rng
Mx = Application.Max(Dn)
Select Case Mx
Case Is = Dn.Offset(, 0): Col = "A"
Case Is = Dn.Offset(, 1): Col = "B"
Case Is = Dn.Offset(, 2): Col = "C"
Case Is = Dn.Offset(, 3): Col = "D"
End Select
ray(Dn.Row - 1) = Col
Next Dn
Sheets("Sheet2").Range("A2").Resize(Rng.Count) = Application.Transpose(ray)
End Sub
I get the following error:
Run-time error'9': Subscript out of range.
So my question is, what does this error mean and what do I need to change in this code to work?
EDIT1:
OK, now the error is gone. But where do I get the results?
EDIT2
I know this line is responsible for inserting the results in specified place, but I cant see them after execution. What's wrong with that?
Error means the array you are trying to access has not been defined to contain the ordinal you're looking for: For example Array 10 has positions 0-9 so if I try and access array(10) it would throw that error or array(-1) it would throw that error.
I can't remember if excel is 0 or 1 based arrays.
Possibly change
ray(Dn.Row - 1) = Col
to
if dn.row-1<> 0 then ' or set it to <0 if zero based.
ray(Dn.Row - 1) = Col
end if
You don't need VBA (a macro) to do this. It can be done using a worksheet formula.
E.g.
If your producers are P1,P2,P3,P4 and your sheet looks like this:-
A B C D E F
+-------------------------------------------
1 | Month P1 P2 P3 P4 Top Producer
2 | Jan 5 4 3 2
3 | Feb 2 3 5 1
4 | Mar 6 4 4 3
...
...
The following formula placed in cells F2,F3,F4,... will pick out the top producer in each month.
=INDEX($B$1:$E$1,MATCH(MAX(B2:E2),B2:E2,0))
Generally it's better to try and use built in Excel functionality where possible. Resort to VBA only if you really need to. Even if you were to use the top producer/month data for some other operation which is only possible in VBA, at least the top producer/month data derivation is done for you by the worksheet, which will simplify the VBA required for the whole process.
Transposing a range can also be done using a worksheet formula by using the TRANSPOSE() function.
BTW - I'm not sure what you want to do if two producers have the same output value. In the VBA example in your question, the logic seems to be:- if two producers are joint top in a month, pick the first one encountered. The formula I've given above should replicate this logic.
I have used these functions quite extensively and they are very reliable and fast:
Public Function CountRows(ByRef r As Range) As Integer
CountRows = r.Worksheet.Range(r, r.End(xlDown)).Rows.Count
End Function
Public Function CountColumns(ByRef r As Range) As Integer
CountColumns = r.Worksheet.Range(r.End(xlToRight), r).Columns.Count
End Function
Give it a reference (ex. "A2") and it will return the filled cells down, or the the right until and empty cell is found.
To select multiple sells I usually do something like
Set r = Range("A2")
N = CountRows(r)
Set r = r.Resize(N,1)

Resources