Evaluate/MATCH function in VBA not working when comparing multiple criteria - excel

I've scoured the internet for a solution to this issue.
I have lists that should match and need to be compared reciprocally. I need to compare 5 or so different variables within each row, then using the MATCH function, would identify the first matching row which is then deleted. I will then loop through the list until there are remaining entries that aren't deleted. The reason I need to delete is because there may be multiple matches within each list, but if there are 3 in one list and 4 in another, I would need the 4th (extra) entry to be identified.
Please critique the code I have below, I have yet to create the loop as I think that will be the easy part once I have gotten the MATCH function to work accurately. The standard CSE formula works within the sheet, but I need VBA for the looping ability. Thanks.
I try to verify the value of RowDelete with the msgbox, and return a run-time error 13: "type mismatch". I also tried use the WATCH window to see what result get passed but the actual formula itself doesn't seem to work.
EDIT: This code returns a run-time error '13': type mismatch. I cannot resolve it. I would just like to know what I can do to pass Formula a result I can use (in this instance the first result is row 62). After that I will be able to do everything on my own.
Sub DeleteMatches2()
Dim Ws As Worksheet
Dim Direction As String
Dim OrderType As String
Dim Amount As String
Dim CCY As String
Dim Rate As String
Dim RowCt As Long
Dim Formula As Integer
Dim iRow As Long
Dim colNum As Integer
Dim RowDelete As Long
Set Ws = Sheets("KOOLTRA RAW")
With Ws
RowCt = .Cells(.Rows.Count, 11).End(xlUp).Row - 1
For iRow = 2 To RowCt
Direction = .Cells(iRow, "K").Value
OrderType = .Cells(iRow, "L").Value
Amount = .Cells(iRow, "M").Value
CCY = .Cells(iRow, "N").Value
Rate = .Cells(iRow, "P").Value
Formula = Evaluate("MATCH(1,(""" & OrderType & """ = B:B)*(""" & Direction & """ = C:C)*(""" & Amount & """ = D:D)*(""" & CCY & """ = E:E)*(""" & Rate & """ = H:H),0)")
MsgBox Formula
Exit For
Next iRow
End With
End Sub

I have commented your code in the hope that my comments will help you improve it.
Sub DeleteMatches()
Dim Ws As Worksheet
Dim Direction As Variant
Dim OrderType As Variant
Dim Amount As Variant
Dim CCY As Variant
Dim Rate As Variant
Dim RowCt As Long ' rows and columns should be of Long type
Dim Formula As Variant
Dim iRow As Long
Dim colNum As Long
Dim RowDelete As Long
Set Ws = Sheets("Example") ' don't "select" anything
With Ws
' creating variable for toral rows to cycle through:-
' you aren't "creating" a variable.
' RowCt is the variable and you are assigning a value to it.
RowCt = .Cells(.Rows.Count, 11).End(xlUp).Row - 1
For iRow = 2 To RowCt ' loop through all rows
' assigning a Range to a Variant (Direction etc) assigns the
' Range object to the variant. I have modified the code
' to assign the specified cell's value to the variant.
' A Variant can be anything. It would be better if you
' would declare your variables as String or Double or Integer or Long.
Direction = .Cells(iRow, "K").Value ' Range("K" & iRow)
OrderType = .Cells(iRow, "L").Value ' Range("L" & iRow)
Amount = .Cells(iRow, "M").Value ' Range("M" & iRow)
CCY = .Cells(iRow, "N").Value ' Range("N" & iRow)
Rate = .Cells(iRow, "P").Value ' Range("P" & iRow)
' Formula is a property of the Range object.
' use like .Cells(iRow, "X").Formula = "MATCH(1,((B:B="" & OrderType & "") ......
' Formula = "MATCH(1,((B:B="" & OrderType & "")*(C:C="" & Direction & "")*(D:D="" & Amount & "")*(E:E="" & CCY& "")*(H:H="" & Rate & "")),0)"
' To set a formula, you need to enter the = sign, like
' .Cells(iRow, "X").Formula = " = MATCH(1 ...
' it is best that you test the formula on the worksheet
' before attempting to let VBA write it to a cell.
' Your above formula looks like nothing Excel would be able to execute.
' Please read up on how to use the Evaluate function.
' It can't do what you appear to expect it to do.
' RowDelete = Evaluate(Formula)
MsgBox RowDelete
'colNum = Application.Match(1,((B1:B2=OrderType)*(C1:C2=Direction)*(D:D=Amount)*(E:E=CCY)*(H:H=Rate)),0)
' I think it is the better idea to let VBA execute the MATCH function
' rather than writing the formula to the worksheet.
' However, your "code" has no similarity with what MATCH can do.
' Read up on how to use the the MATCH function correctly.
' When executed by VBA it needs the same syntax as when called from the worksheet.
'Formula = "MATCH(1,((B:B=OrderType)*(C:C=Direction)*(D:D=Amount)*(E:E=CCY)*(H:H=Rate)),0)"
'Formula = "MATCH(1,((B:B=L2)*(C:C=K2)*(D:D=M2)*(E:E=N2)*(H:H=P2)),0)"
'colNum = Worksheets("Example").Evaluate("MATCH(1,((B:B=OrderType)*(C:C=Direction)*(D:D=Amount)*(E:E=CCY)*(H:H=Rate)),0)")
Exit For ' stop the loop here during testing
' remove this stop after your code works
Next iRow
End With
End Sub

Related

VBA code for index/match function not working properly

I wrote the following macro to create a dynamic index/match formula which retrieves data from one sheet (US acq_CUSIP data) and outputs it to another (ws_output). My code is below, ws_input contains a bunch of data incl. a list of companies (in the range R43:R3223) and calendar years (for example, 2011, 2012, etc.). When trying to run the macro, I get the following error: "Application-defined or object-defined error", which seems to be due to the errors in the resulting index formula. How can I fix this? Thank you!
Sub fetching_acq_data_compustat()
Dim ws_input As Worksheet
Dim strTkr As String
Dim rngTkr As Range
Dim c As Range
Dim ws_output As Worksheet
Dim strTaxPaid As String
Dim StrPretaxIncome As String
Dim strSpecItem As String
Dim strTkrCell As String
Dim Dated As String
Dim strDatedCell As String
Set ws_input = ThisWorkbook.Sheets("MA_ExportFiltered_RawData")
Set ws_output = ThisWorkbook.Sheets("Acquirer_ETR")
ws_output.Activate
With ws_output
Set rngTkr = .Range("R43:R3223")
i = 1
For Each c In rngTkr
strTkrCell = c.Address
k = 14
If k < 16 Then
Dated = c.Offset(0, k).Address
strDatedCell = Dated
ws_output.Range("A1").Offset(2, 0).Value = c.Offset(0, 14).Value
ActiveCell.Offset(0, i).Value = "=INDEX( 'US acq_CUSIP data'!$A$3:$AJ$77388" & ";" & "MATCH(1; ('US acq_CUSIP data'!$I$3:$I$77388=" & "MA_ExportFiltered_RawData!" & strTkrCell & ")*('US acq_CUSIP data'!$C$3:$C$77388=" & "MA_ExportFiltered_RawData!" & strDatedCell & ");0); 26)"
ActiveCell.Offset(-1, 0).Value = 26
ActiveCell.Offset(-1, 0).Value = c.Value
k = k + 1
End If
Next
End With
End Sub
This isn't an answer to the question, but it's too long to post in comments (plus the comment discussion was getting too long anyway).
If as you say the above is your entire code without anything trimmed out for relevance, then I've refactored it to the below (which does exactly the same actions just with less code):
Sub fetching_acq_data_compustat()
'Variables:
'Worksheets:
Dim ws_input As Worksheet, ws_output As Worksheet
Set ws_input = ThisWorkbook.Sheets("MA_ExportFiltered_RawData")
Set ws_output = ThisWorkbook.Sheets("Acquirer_ETR")
'Ranges:
Dim rngTkr As Range, c As Range
'Strings:
Dim strTkrCell As String, strDatedCell As String
With ws_output
.Activate
Set rngTkr = .Range("R43:R3223")
For Each c In rngTkr ' This loop achieves nothing because the same cells are getting edited every time, the end result will be just the results of the last iteration of the loop
strTkrCell = c.Address
strDatedCell = c.Offset(0, 14).Address
' there were variables i and k in here which weren't doing anything;
' i got set but then never incremented
' k got set and incremented, but then reset to its original value for every loop of c
.Range("A1").Offset(2, 0).Value = c.Offset(0, 14).Value
' substituted 'Range("A1")' for 'Activecell'; that may be wrong.
.Range("A1").Offset(0, 1).Value = _
"=INDEX('US acq_CUSIP data'!$A$3:$AJ$77388;MATCH(1;('US acq_CUSIP data'!$I$3:$I$77388=MA_ExportFiltered_RawData!" & strTkrCell & ")*('US acq_CUSIP data'!$C$3:$C$77388=MA_ExportFiltered_RawData!" & strDatedCell & ");0); 26)"
.Range("A1").Offset(-1, 0).Value = c.Value
Next
End With
End Sub
As mentioned in the comments (and further noticeable from the code) there were variables that weren't getting used, and the loop through c isn't achieving anything either.
I recommend you use F8 to step through your code line-by-line and work out if it's doing what you expected it would.

VBA macro: If Range Contains Words from Another Range Then Type x in Third Range

I would like to solve the following problem:
In Worksheet1 I have a range in text form from O3 to O4500. If the cells in this range contain certain words, I want an "x" to be put in the range U3:U4500 (in the same row). The words to be tested are in range B4:B15 in another Worksheet (Worksheet2).
I made it work with the following code (solution1), but now I don't want to type the code manually for word1, word2, words3... instead it should be taken from the other range in Worksheet 2 (see my draft below in solution2). I believe the problem are the "* *" which are missing when I use the referral to the other range.
Any help is very much appreciated!
Sub solution1()
Dim i As Long
For i = 3 To 4500
If LCase$(Worksheet1.Range("O" & i).Value) Like "*word1*" Or _
LCase$(Worksheet1.Range("O" & i).Value) Like "*word2*" Or _
LCase$(Worksheet1.Range("O" & i).Value) Like "*word3*" Then
Worksheet1.Range("U" & i).Value = "x"
End If
Next
End Sub
Sub solution2()
Dim i As Long, c As Long
For i = 3 To 4500
For c = 4 To 15
If LCase$(Worksheet1.Range("O" & i).Value) Like LCase$(Worksheet2.Range("B" & c).Value) Then
Worksheet1.Range("U" & i).Value = "x"
End If
Next
Next
End Sub
try something like:
Sub solution2()
Dim i As Long, c As Long
searchstring = LCase$(Worksheets("Worksheet2").Range("B1").Value & "|" & Worksheets("Worksheet2").Range("B2").Value & "|" & Worksheets("Worksheet2").Range("B3").Value)
For i = 2 To 9
If Len(LCase$(Worksheets("Worksheet1").Range("O" & i).Value)) < 1 Then GoTo neexxtt
'line above prevents empty lines to be marked
If InStr(searchstring, LCase$(Worksheets("Worksheet1").Range("O" & i).Value)) <> 0 Then Worksheets("Worksheet1").Range("U" & i).Value = "x"
neexxtt:
Next
End Sub
A VBA Lookup: Using an (Array)Formula For Partial Matches
In Excel, in cell U3, you could use the following array formula:
=IF(COUNT(MATCH("*"&Sheet2!$B$4:$B$15&"*",O3,0))>0,"X","")
and copy it down (adjust the lookup worksheet name (Sheet2)).
The following solution is based on this formula avoiding any loops.
Sub VBALookup()
Const Flag As String = "x"
' Reference the ranges.
Dim srg As Range ' Source
Dim drg As Range ' Destination
Dim lrg As Range ' Lookup
With Worksheet1
Set srg = .Range("O3", .Cells(.Rows.Count, "O").End(xlUp))
Set drg = srg.EntireRow.Columns("U")
End With
With Worksheet2
Set lrg = .Range("B4", .Cells(.Rows.Count, "B").End(xlUp))
End With
' Build the array formula.
Dim ArrayFormula As String
ArrayFormula = "=IF(COUNT(MATCH(""*""&'" & Worksheet2.Name & "'!" _
& lrg.Address & "&""*""," & srg.Cells(1).Address(0, 0) & ",0))>0,""" _
& Flag & ""","""")"
' Write the formulae (values).
With drg
' Write the array formula to the first cell.
.Cells(1).FormulaArray = ArrayFormula
' Autofill to the bottom.
.Cells(1).AutoFill .Cells, xlFillDefault
' Not sure, but instead of the previous 2 lines, in Office 365,
' the following single line should work:
'.Cells.Formula = ArrayFormula
' Convert to values (out-comment if you want to keep the formulae).
.Value = .Value
End With
End Sub

excel vba auto-generate email problems

I would like to know what happened to my codes below.
I am trying to auto-generate emails based on a list of emails and the list can be changed. Therefore, I have to use do until loop (till range ("A" & i) is empty)
auto generate email pic
I am new to VBA and just trying to learn.
Sub own()
Dim shName As String
Dim cell As Range
Dim i As Integer
i = 10
Do Until Range("A" & i).Value = ""
shName = Range("A" & i).Value
ThisWorkbook.Worksheets(shName).Copy
Application.Dialogs(xlDialogSendMail).Show cell.Offset(0, 1).Value, cell.Offset(0, 2).Value
i = i + 1
Loop
End Sub
The error message is invalid procedure call or argument..
why?
The expected result should be able to send out email according to the list (which can be changed and therefore do until loop is used) .
You have at least one object cell in your code that needs to be Set.
Do you have a sheet that actually has the name of the email you are trying to send? Because that is what your code attempts to do.
I've modified your code from a Do... Loop to For... Next. It's too easy to get stuck in a endless loop withou knowing what is wrong.
Whether this code will work depends on your input variables.
Sub own()
Dim shName As String
Dim cell As Range
Dim ws As Worksheet
Dim i As Integer
Set cell = ActiveCell 'or whatever refrence needed
For i = 1 To 10
If Not (Range("A" & i).Value = "") Then
shName = Range("A" & i).Value
'Is there a worksheet that corresponds to the email name?
ThisWorkbook.Worksheets(shName).Copy
Application.Dialogs(xlDialogSendMail).Show _
cell.Offset(0, 1).Value, cell.Offset(0, 2).Value
End If
Next i
End Sub

Cell Color Total Count

I'm Trying to create a program to total the amount of cells containing green and red font in column A among all the sheets in the workbook.
In the provided code below the code it counts ALL the cells containing green and red font in all the cells of the worksheets.
Please be sure to leave a comment if you can guide me in the right direction!
I also made an example google sheet of what im trying to accomplish:
https://docs.google.com/spreadsheets/d/1yLfCxaT-cIl_W77Y67xdg_ZTSQlg9X2a5vxAH4JtDpk/edit?usp=sharing
Sub Test_It()
Dim mySheet As Worksheet ' Define as worksheet if you're going to loop through sheets and none is a Graph/Chart sheet
Dim printRow As Integer ' Beware that integer it's limited to 32k rows (if you need more, use Long)
printRow = 2
For Each mySheet In ThisWorkbook.Sheets ' use the mySheet object previously defined
Range("N" & printRow).Value = "Sheet Name:"
Range("O" & printRow).Value = mySheet.Name
Range("P" & printRow).Value = "Approval:"
Range("Q" & printRow).Value = SumGreen(mySheet) ' you can pass the sheet as an object
Range("R" & printRow).Value = "Refused:"
Range("S" & printRow).Value = SumRed(mySheet)
printRow = printRow + 1
Next mySheet
End Sub
Function SumGreen(mySheet As Worksheet) As Long ' define the type the function is going to return
Dim myCell As Range
Dim counter As Long
For Each myCell In mySheet.UsedRange ' UsedRange is the range that has information
If myCell.Font.Color = RGB(112, 173, 71) Then ' 255 is red, not green, change to whatever you need
counter = counter + 1 ' change to counter + mycell.value if you have values and you want to sum them
End If
Next myCell
' Set the function to return the counter
SumGreen = counter
End Function
Function SumRed(mySheet As Worksheet) As Long ' define the type the function is going to return
Dim myCell As Range
Dim counter As Long
For Each myCell In mySheet.UsedRange ' UsedRange is the range that has information
If myCell.Font.Color = 255 Then ' 255 is red, not green, change to whatever you need
counter = counter + 1 ' change to counter + mycell.value if you have values and you want to sum them
End If
Next myCell
' Set the function to return the counter
SumRed = counter
End Function```
Cris:
Remember for next time to actually copy/paste your code in text, instead of pasting a picture.
Try this code and read the comments:
' If it's not going to return something, you can define this as a procedure (sub) and not a function
Sub Test_It()
Dim mySheet As Worksheet ' Define as worksheet if you're going to loop through sheets and none is a Graph/Chart sheet
Dim printRow As Integer ' Beware that integer it's limited to 32k rows (if you need more, use Long)
printRow = 2
For Each mySheet In ThisWorkbook.Sheets ' use the mySheet object previously defined
Range("N" & printRow).Value = "Sheet Name:"
Range("O" & printRow).Value = mySheet.Name
Range("P" & printRow).Value = "Approval:"
Range("Q" & printRow).Value = SumGreen(mySheet) ' you can pass the sheet as an object
Next mySheet
End Sub
Function SumGreen(mySheet As Worksheet) As Long ' define the type the function is going to return
Dim myCell As Range
Dim counter As Long
For Each myCell In mySheet.UsedRange ' UsedRange is the range that has information
If myCell.Font.Color = 255 Then ' 255 is red, not green, change to whatever you need
counter = counter + 1 ' change to counter + mycell.value if you have values and you want to sum them
End If
Next myCell
' Set the function to return the counter
SumGreen = counter
End Function

My match function is taking too long (3 hours!!), need another recommendation

As the title says, match function taking too long. One spreadsheet is 100,000 rows long and it has a bunch of securities that i need to make sure are on another spreadsheet which has 800,000 rows. Below is the code:
FYI i am average in code building so i am pretty rudimentary in terms of laying out my arguments.
Option Explicit
'a lot of dims
StartTime = Timer
Set ShVar = ThisWorkbook.Worksheets("in1")
With wnewwqr
Set OutShVar = wnewwqr.Worksheets("First Sheet")
Set RngConcat = OutShVar.Range("B:B")
Set RngConcatISIN = OutShVar.Range("A:A")
Set OutShVar1 = wnewwqr.Worksheets("Second Sheet")
Set RngConcat1 = OutShVar1.Range("B:B")
Set RngConcatISIN1 = OutShVar1.Range("A:A")
End With
With ShVar
lastrow = .Cells(.Rows.Count, "H").End(xlUp).Row
End With
For i = 2 To lastrow
With ShVar
If .Range("O" & i).Value = "" Then
.Range("P" & i & ":Q" & i).Value = "No Security" 'Checking for no securities
Else
If Not IsError(Application.Match(.Range("O" & i).Value, RngConcat, 0)) Then
.Range("P" & i).Value = "US" ' writing US when it finds a US security in the confidential workbook
Else
.Range("P" & i).Value = "Not a US Security"
End If
End If
If .Range("P" & i).Value = "Not a US Security" Then
If Not IsError(Application.Match(.Range("O" & i).Value, RngConcat1, 0)) Then 'Only searching for securities if the first vlookup resulted in nothing and then it would go into the second sheet
.Range("Q" & i).Value = "US"
Else
.Range("Q" & i).Value = .Range("P" & i).Value
End If
End If
End With
Next i
SecondsElapsed = Round(Timer - StartTime, 2)
'Notify user in seconds
MsgBox "This code ran successfully in " & SecondsElapsed & " seconds", vbInformation
End Sub
Update:
I have turned everything to variant and now using find function but still not that fast as i would have hoped. Took 14 mins approx. to do a trial run of 2000 rows. And i have to do this on 90,000 rows
Option Explicit
Sub something
Dim lastrow As Long
Dim OutShVar As Worksheet
Dim ShVar As Worksheet
Dim WhatCell As Range
Dim i As Long
Dim TaskID As Variant
Dim confidentialfp As String
Dim confidential As String
Dim wconfidential As Workbook
Dim x As Variant
Set ShVar = ThisWorkbook.Worksheets("in1")
With ShVar
lastrow = .Cells(.Rows.Count, "H").End(xlUp).Row
End With
confidential = "confidential_2018-03-01 (Consolidated).xlsx"
Set wconfidential = Workbooks(confidential)
With wconfidential
Set OutShVar = .Worksheets("First Sheet")
End With
With ShVar
For i = 1 To lastrow
TaskID = ShVar.Range("O" & i).Value
Set x = .Range("A" & i)
Set WhatCell = OutShVar.Range("B:B").Find(TaskID, lookat:=xlWhole)
On Error Resume Next
x.Offset(0, 7).Value = WhatCell.Offset(0, 1)
Next i
End With
End Sub
I'm not sure you're quite getting ScottCraner's point. What he's saying is you should read all of your reference values (ie the big list of securities) into a couple of arrays, and you should write your output values to another array. You'd then write the entire output array to the sheet in one command.
It might also be worth you converting your list of securities to a Collection as that has a very fast 'look-up' capability. There'd be ways of making this much faster, for example by sorting the securities, but you'd need to get into some mathematics for that.
In the example below, this skeleton code shows how it might be done. You should be aware that I didn't bother splitting the two securities lists into two collections, so you'd want to do that yourself if you needed it. I've also put all my test sheets on the same workbook, so adjust the worksheet qualifiers as needed:
Option Explicit
Sub RunMe()
Dim securities As Collection
Dim testSheet As Worksheet
Dim testItems As Variant
Dim i As Long
Dim exists As Boolean
Dim output() As Variant
'Read the first list of securities into the collection.
PopulateColumnCollection _
ThisWorkbook.Worksheets("First Sheet"), _
"B", _
securities
'Read the second list of securities into the collection.
'I've used the same collection in this example, you'll need
'to create two if you want separate columns in your output.
PopulateColumnCollection _
ThisWorkbook.Worksheets("Second Sheet"), _
"B", _
securities
'Read the test items into an array.
Set testSheet = ThisWorkbook.Worksheets("in1")
With testSheet
testItems = RangeTo2DArray(.Range( _
.Cells(2, "O"), _
.Cells(.Rows.Count, "O").End(xlUp)))
End With
'Prepare your output array.
'I've just used one column for output. If you want two then
'you'll need to resize the second dimension.
ReDim output(1 To UBound(testItems, 1), 1 To 1)
'Populate the output array based on the presence of
'a matching security.
For i = 1 To UBound(testItems, 1)
If IsEmpty(testItems(i, 1)) Then
output(i, 1) = "No Security"
Else
exists = False: On Error Resume Next
exists = securities(CStr(testItems(i, 1))): On Error GoTo 0
output(i, 1) = IIf(exists, "US", "Not a US Security")
End If
Next
'Write the output array to your sheet.
testSheet.Cells(2, "P").Resize(UBound(output, 1), UBound(output, 2)).Value = output
End Sub
Private Function RangeTo2DArray(rng As Range) As Variant
'Helper function to read range values into an array.
Dim v As Variant
Dim arr(1 To 1, 1 To 1) As Variant
v = rng.Value2
If Not IsArray(v) Then
arr(1, 1) = v
RangeTo2DArray = arr
Else
RangeTo2DArray = v
End If
End Function
Private Sub PopulateColumnCollection(ws As Worksheet, columnIndex As String, col As Collection)
'Helper sub to read a column of values into a collection.
Dim rng As Range
Dim v As Variant
Dim i As Long
With ws
Set rng = .Range( _
.Cells(1, columnIndex), _
.Cells(.Rows.Count, columnIndex).End(xlUp))
End With
v = RangeTo2DArray(rng)
If col Is Nothing Then Set col = New Collection
On Error Resume Next 'this avoids duplicates.
For i = 1 To UBound(v, 1)
col.Add True, CStr(v(i, 1))
Next
End Sub

Resources