I need to create a formula in VBA where it need to have the option of iferror.
Basically my formula in Excel would be:
Iferror(vlookup(A1&A2;A:B;2;FALSE);vlookup(A1;P:Q;2;FALSE))
Then I tried to replicate that into only one formula in VBA
Function DestAcc ( Account as string, FA as string)
Dim rng1,rng2 as range
With Workbooks(“ACCOUNTS”).worksheets(“Accounts”)
Set rng1=.Range(.cells(1,1),cells(50000,2)
Set rng2=.Range(.cells(1,16),cells(50000,17)
DestAcc=WorksheetFunction.IfError(WorksheetFunction.VLookup(Account & FA, rng1, 2, False), WorksheetFunction.VLookup(Account, rng2, 2, False))
End function
Then in the Workbook I put on an empty cell
=DestAcc(C1;D1)
And I get the result #VALUE
If I try the 2 members individually I get a result, if I try the function with the "IfError" I always get #VALUE.
Can someone help me?
Thank you
Give this a try.
instead of using worksheetfunction use application
UPDATE: Set the reference to the workbook with the extension
Function DestAcc(Account As String, FA As String)
Dim accountsWorkbook As Workbook
Dim accountsWorksheet As Worksheet
Dim accountsWithFARange As Range
Dim AccountsOnlyRange As Range
Dim resultAccountFA As Variant
Dim resultAccount As Variant
Set accountsWorkbook = Workbooks("Accounts.xlsm")
Set accountsWorksheet = accountsWorkbook.Worksheets("Accounts")
With accountsWorksheet
Set accountsWithFARange = .Range("$A$1:$B$50000") ' $A$1:$B$50000
Set AccountsOnlyRange = .Range("$P$1:$Q$50000") ' $P$1:$Q$50000
End With
resultAccountFA = Application.VLookup(Account & FA, accountsWithFARange, 2, False)
resultAccount = Application.VLookup(Account, AccountsOnlyRange, 2, False)
DestAcc = IIf(Not IsError(resultAccountFA), resultAccountFA, resultAccount)
End Function
Note:
Your code has a couple of flaws:
You are defining rng1 as variant (this Dim rng1,rng2 as range is not the same as Dim rng1 as range, rng2 as range)
You are not closing the With block (missing End With)
Some suggestions:
- Always define the variables types (even if you're expecting a variant result)
Try to name your variables to something anybody can understand (rng1 doesn't mean much)
Try to write short lines (the iferror mixed with the worksheetfunction.vlookup could be splitted in two)
The way you are setting the ranges is difficult to read. you can use Set rng1 = .Range($A$1:$B$50000)
let me know if it works.
A Conditional Consecutive LookUp
Put all three procedures in a standard module (e.g. Module1) of the
workbook containing this code.
Adjust luAccount, luFA and ws.
Note: You have to use the extension of the open workbook (.xlsm, .xlsx, .xls)
This solution ignores case i.e. A=a.
You already know how to use it in Excel.
The Code
Option Explicit
' A Conditional Consecutive Lookup
Function DestAcc(Account As String, FA As String)
Application.Volatile
Dim luAccount As Variant, luFA As Variant
' Specify: First Rows, Match Columns, Value Columns
luAccount = Array(1, 1, 2)
luFA = Array(1, 16, 17)
Dim ws As Worksheet
' Either on the ActiveSheet:
'Set ws = Cells.Worksheet ' or Application.ThisCell.Worksheet
' or on a specified worksheet:
On Error GoTo exitProcedure
Set ws = Workbooks("Accounts.xlsm").Worksheets("Accounts")
On Error GoTo 0
Dim rng As Range
Dim vMatch As Variant, vValue As Variant
Dim MatchIndex As Long
Dim Criteria As String
' 1st LookUp
Set rng = getPartialColumn(ws, luAccount(0), luAccount(1))
If rng Is Nothing Then GoTo SecondLookUp
vMatch = rng: vValue = rng.Offset(, luAccount(2) - luAccount(1))
Criteria = Account & FA: GoSub findMatch
' 2nd LookUp
SecondLookUp:
Set rng = getPartialColumn(ws, luFA(0), luFA(1))
If rng Is Nothing Then GoTo exitProcedure
vMatch = rng: vValue = rng.Offset(, luFA(2) - luFA(1))
Criteria = Account: GoSub findMatch
GoTo exitProcedure
findMatch:
MatchIndex = getMatchIndex(Criteria, vMatch)
If MatchIndex > 0 Then GoTo returnLookup
Return
returnLookup:
DestAcc = vValue(MatchIndex, 1)
GoTo exitProcedure
exitProcedure:
End Function
' Returns the column range from a specified row to the last non-empty row.
Function getPartialColumn(WorksheetObject As Worksheet, _
Optional ByVal FirstRowNumber As Long = 1, _
Optional ByVal columnNumber As Long = 1) As Range
Dim rng As Range
With WorksheetObject
Set rng = .Columns(columnNumber).Find(What:="*", _
LookIn:=xlFormulas, SearchDirection:=xlPrevious)
If rng Is Nothing Then Exit Function
Set getPartialColumn = .Range(.Cells(FirstRowNumber, columnNumber), rng)
End With
End Function
' Returns the index of a found value in an array, or 0 if not found.
Function getMatchIndex(MatchValue As Variant, MatchArray As Variant) As Long
If Not IsError(Application.Match(MatchValue, MatchArray, 0)) Then
getMatchIndex = Application.Match(MatchValue, MatchArray, 0)
End If
End Function
Related
How do I include a vlookup into my current set of code to do a countif of all vlookup results across all similar sheets. The codes I have will attempt to perform countif across sheets for one specified cell or a whole range of data in a column or row. Instead, I would like the below function to have the capability to count the number of vlookup result in a column across sheets of similar name.
Function myCountIfSheet1(rng As Range, criteria) As Long
Dim ws As Worksheet
For Each ws In ThisWorkbook.Worksheets
If ws.Name Like "Sheet1*" Then
myCountIfSheet1 = myCountIfSheet1 + WorksheetFunction.CountIf(ws.Range(rng.Address), criteria)
End If
Next ws
End Function
Public Function shifted_lookup(lookup_value As Variant, table_array As Range, column_index As Integer, range_lookup As Integer) As Variant
Dim curr_wsname As String, oth_wsname As String
Dim curr_ws As Worksheet, oth_ws As Worksheet
Set curr_ws = ActiveSheet
curr_wsname = curr_ws.Name
oth_wsname = Right(curr_wsname, 3)
Set oth_ws = Worksheets(oth_wsname)
Dim src_rng_base As String, src_rng As Range
src_rng_base = table_array.Address
Set src_rng = oth_ws.Range(src_rng_base)
Dim aux As Variant
shifted_lookup = Application.WorksheetFunction.VLookup(lookup_value, src_rng, column_index, range_lookup)
End Function
This ought to do the job. Please try it.
Function myCountIfSheet1(Rng As Range, _
Clm1 As Long, _
Crit1 As Variant, _
Clm2 As Long, _
Crit2 As Variant) As Long
' 011
Dim Fun As Long ' function return value
Dim Ws As Worksheet
For Each Ws In ThisWorkbook.Worksheets
With Ws
If .Name Like "Sheet1*" Then
Fun = Fun + WorksheetFunction.CountIfs( _
.Range(Rng.Columns(Clm1).Address), Crit1, _
.Range(Rng.Columns(Clm2).Address), Crit2)
End If
End With
Next Ws
myCountIfSheet1 = Fun
End Function
For ease of calling, I have structured the function call to provide one range address only. In my tests I used A1:D30. Column(A) contained one criterium, Column(D) the other. Of course, column(A) is the first column - Columns(1) - of the range and column D is Columns(4) of the range. So, the following function call would look for "3" in column A and "red" in column D.
Debug.Print myCountIfSheet1(Range("A1:D30"), 4, "red", 1, 3)
The sequence of the criteria is immaterial. You can also add more criteria using the same structure.
I'm trying sort a predetermined range of cells from a UserForm (let's say A1:A5) using an array size as an integer, and the array itself. I've checked out 20+ links without finding a solution.
The code below successfully gets the values of the array (for my testing there are five doubles), pastes them into the worksheet sheetOperations (I always use code-targeted sheets to minimize issues). So the sheet targeting works, and the looping through the array and getting the values works.
Sorting the range (A1:A5) hasn't been successful. I've tried a variety of code. I'm trying to get A1 to A5 (on that specific worksheet) to list the previous values in the range in descending order - when I run this code (I tried ascending, descending) it has given me various errors such 1004, etc.
If A1:A5 is {1,3,2, 4, 6}, I want it to make A1:A5 {6,4,2,3,1}.
Sub timeStampStorePart2(ByRef doubleArray() As Double, ByVal size As Integer)
Dim ws As Worksheet
Dim wsFound2 As Worksheet
For Each ws In ThisWorkbook.Worksheets
If StrComp(ws.CodeName, "sheetOperations", vbTextCompare) = 0 Then
Set wsFound2 = ws
'MsgBox ("Found")
End If
Next ws
Dim loopInt As Integer
Dim arrayInt As Integer
Dim rangeAddress As String
arrayInt = 0
loopInt = 1
For loopInt = 1 To size
rangeAddress = "A" & loopInt
wsFound2.Range(rangeAddress).Value = doubleArray(arrayInt)
arrayInt = arrayInt + 1
Next loopInt
'rangeAddress = "A1:" & rangeAddress
'MsgBox (rangeAddress)
'Dim dataRange As Range
'Set dataRange = wsFound2.Range(rangeAddress)
wsFound2.Range("A1:A5").Sort key1:=Range("A2"), order1:=xlAscending, Header:=xlNo
End Sub
In answer to your question, here is a pretty simple code that sorts the range you describe using an array...
slightly updated to meet the integer requirement.
Sub sortStuff()
Const addr As String = "A1:A6"
Dim WS As Worksheet: Set WS = ActiveSheet 'or whatever the sheet ID name is
Dim sRNG As Range: Set sRNG = WS.Range(addr)
'changed this from first answer to integer requirement and optimize code
ReDim aRay(1 To sRNG.Rows.Count, 1 To sRNG.Columns.Count) As Integer
Dim x As Long, y As Long
For x = LBound(aRay, 1) To UBound(aRay, 1)
For y = LBound(aRay, 2) To UBound(aRay, 2)
aRay(x, y) = Application.WorksheetFunction.Large(sRNG.Columns(y), x)
Next y
Next x
'Puts into excel
sRNG = aRay
End Sub
Pretty basic question here but my VBA skills are pretty rusty. I have two worksheets where a machine just dumps data into them. Each sheet is just one column and SheetA has ~250 rows and SheetB has ~1300 rows. So what I need to do is compare the first value in sheetA to every value in sheetB, if a match is found I need to copy it to another sheet (SheetC) and then move to the next value in SheetA and repeat this till every value in SheetA has been compared to every value in SheetB. I think the best way to do this is with arrays but I cannot for the life of me remember how to do the actual comparison. Below is the code calling up the sheets and arrays I think....any help is appreciated!
Dim SheetA As Variant
Dim SheetB As Variant
Dim RangeToCheckA As String
Dim RangeToCheckB As String
'Get the worksheets from the workbooks
Set wbkA = Workbooks.Open(Filename:="H:\Chelsea QE\CD6\Evan West\OSM37 with locations 9-30-19.xls")
Set SheetA = wbkA.Worksheets("OSM37")
Set wbkB = Workbooks.Open(Filename:="H:\Chelsea QE\CD6\Evan West\New folder\Flat Rock and Roush VIN Tracker U625 - U611 Lower control arm welds.xlsx")
Set SheetB = wbkB.Worksheets("Master VIN")
'This is the range in SheetA
RangeToCheckA = "B2:B239"
'This is the range in SheetB
RangeToCheckB = "B4:B1339"
SheetA = SheetA.Range(RangeToCheckA)
SheetB = SheetB.Range(RangeToCheckB)
Without changing much of your code and adding a call to a custom function, you could do the following:
Private Sub CompareWorkBooks()
Dim wbkA As Workbook, wbkB As Workbook
Dim SheetA As Worksheet, SheetB As Worksheet, SheetC As Worksheet
Dim RangeToCheckA As String
Dim RangeToCheckB As String
Dim arrySheetA() As Variant, arrySheetB() As Variant, _
arryOut() As Variant
'Get the worksheets from the workbooks
Set wbkA = Workbooks.Open(Filename:="H:\Chelsea QE\CD6\Evan West\OSM37 with locations 9-30-19.xls")
Set SheetA = wbkA.Worksheets("OSM37")
Set wbkB = Workbooks.Open(Filename:="H:\Chelsea QE\CD6\Evan West\New folder\Flat Rock and Roush VIN Tracker U625 - U611 Lower control arm welds.xlsx")
Set SheetB = wbkB.Worksheets("Master VIN")
'This is the range in SheetA
RangeToCheckA = "B2:B239"
'This is the range in SheetB
RangeToCheckB = "B4:B1339"
'Value 2 is faster as it doesn't copy formatting
arrySheetA() = SheetA.Range(RangeToCheckA).Value2
arrySheetB() = SheetB.Range(RangeToCheckB).Value2
Set SheetC = wbkB.Worksheets("Sheet C")
arryOut() = FastLookUp(arrySheetA, arrySheetB, 1, 1, 1)
SheetC.Range("A1").Resize(UBound(arryOut, 1), _
UBound(arryOut, 2)).Value = arryOut
End Sub
FastLookUp Function:
Private Function FastLookUp(ByRef arryLookUpVals As Variant, ByRef arryLookUpTable As Variant, _
ByVal lngLookUpValCol As Long, ByVal lngSearchCol As Long, _
ByVal lngReturnCol As Long, _
Optional ByVal boolBinaryCompare As Boolean = True) As Variant
Dim i As Long
Dim dictLooUpTblData As Object
Dim varKey As Variant
Dim arryOut() As Variant
Set dictLooUpTblData = CreateObject("Scripting.Dictionary")
If boolBinaryCompare Then
dictLooUpTblData.CompareMode = vbBinaryCompare
Else
dictLooUpTblData.CompareMode = vbTextCompare
End If
'add lookup table's lookup column to
'dictionary
For i = LBound(arryLookUpTable, 1) To UBound(arryLookUpTable, 1)
varKey = Trim(arryLookUpTable(i, lngSearchCol))
If Not dictLooUpTblData.Exists(varKey) Then
'this is called a silent add with is faster
'than the standard dictionary.Add Key,Item
'method
dictLooUpTblData(varKey) = arryLookUpTable(i, lngReturnCol)
End If
varKey = Empty
Next i
i = 0: varKey = Empty
ReDim arryOut(1 To UBound(arryLookUpVals, 1), 1 To 1)
For i = LBound(arryLookUpVals, 1) To UBound(arryLookUpVals, 1)
varKey = Trim(arryLookUpVals(i, lngLookUpValCol))
'if the lookup value exists in the dictionary
'at this index of the array, then return
'its correspoding item
If dictLooUpTblData.Exists(varKey) Then
arryOut(i, 1) = dictLooUpTblData.Item(varKey)
End If
varKey = Empty
Next i
FastLookUp = arryOut
End Function
FastLookup functions exactly like a VLOOKUP, but is a bit more flexible, because the the lookup column does not have to be the first one in the range you are looking up, as you are allowed to specify which column by providing a value for lngLookUpValCol parameter.
Concerning that you have 3 worksheets in 1 workbook - Worksheets(1) and Worksheets(2) are the one, in which the values in Range("A1:A7") and Range("A1:A3") are compared:
Sub TestMe()
Dim arrA As Variant
Dim arrB As Variant
With Application
arrA = .Transpose(Worksheets(1).Range("A1:A7"))
arrB = .Transpose(Worksheets(2).Range("A1:A3"))
End With
Dim a As Variant
Dim b As Variant
For Each a In arrA
For Each b In arrB
If a = b Then
Worksheets(3).Cells(1 + LastRow(Worksheets(3).Name), 1) = b
End If
Next
Next
End Sub
Function LastRow(wsName As String, Optional columnToCheck As Long = 1) As Long
Dim ws As Worksheet
Set ws = Worksheets(wsName)
LastRow = ws.Cells(ws.Rows.Count, columnToCheck).End(xlUp).Row
End Function
If you are planning to use the code above, it is a good idea to make sure that the values in Worksheets(1) are all unique, otherwise the code would repeat them N times. Or add a dictionary, to exclude repeated values.
I'm currently working on the statement that implies, that if any of the cell value in the range of "G3:ED3" in the worksheet named "Matrix", matches the cell value in the range of "H3:H204" in the worksheet named "Staff" and any cell value in the range "G5:ED57" in the "Matrix" worksheet is numeric, then the value of the cell in a column B, that intersects the numeric value, is retrieving to the required cell address in the target template.
Here's what I have tried so far:
Dim rng1 As Range
Set rng1 = Worksheets("Matrix").Range("G3:ED3")
Dim rng2 As Range
Set rng2 = Worksheets("Staff").Range("H3:H204")
Dim rng3 As Range
Set rng3 = Worksheets("Matrix").Range("G5:ED57")
For Each cell In Range(rng1, rng2, rng3)
While IsNumeric(rng3) And rng1.Value = rng2.Value
Worksheets("Matrix").Columns("B").Find(0).Row =
Worksheets("TEMPLATE_TARGET").Value(12, 4)
Wend
I'm unsure how to define the statement, so the code would automatically retrieve the value of the cell in a column B, that intersects any cell that contains numeric value in the rng3. Any recommendations would be highly appreciated.
it's probably best you take a proper look into documentation / whatever learning resource you are using as you seem to have missunderstood how While works (alongside few other things)
While is a loop within itself, it does not act as an Exit Condition for the For loop.
With all that said, it's also unclear from your question what you're trying to achieve.
My presumption is, that you want to check for all the conditions and
then if they do match, you're looking to paste the result into the
"TEMPLATE" sheet
First we create a function th ceck for values in the two data ranges:
Private Function IsInColumn(ByVal value As Variant, ByVal inSheet As String) As Boolean
Dim searchrange As Range
On Error Resume Next ' disables error checking (Subscript out of range if sheet not found)
' the range we search in
If Trim(LCase(inSheet)) = "matrix" Then
Set searchrange = Sheets("Matrix").Range("G5:ED7")
ElseIf Trim(LCase(inSheet)) = "staff" Then
Set searchrange = Sheets("Staff").Range("H3:H204")
Else
MsgBox ("Sheet: " & inSheet & " was not found")
Exit Function
End If
On Error GoTo 0 ' re-enable error checking
Dim result As Range
Set result = searchrange.Find(What:=value, LookIn:=xlValues, LookAt:=xlWhole)
' Find returns the find to a Range called result
If result Is Nothing Then
IsInColumn = False ' if not found is search range, return false
Else
If IsNumeric(result) Then ' check for number
IsInColumn = True ' ding ding ding, match was found
Else
IsInColumn = False ' if it's not a number
End If
End If
End Function
And then we run the procedure for our search.
Private Sub check_in_column()
Dim looprange As Range: Set looprange = Sheets("Matrix").Range("G3:ED3")
Dim last_row As Long
For Each cell In looprange ' loops through all the cells in looprange
'utlizes our created IsInColumn function
If IsInColumn(cell.Value2, "Matrix") = True And _
IsInColumn(cell.Value2, "Staff") = True Then
' finds last actively used row in TEMPLATE_TARGET
last_row = Sheets("TEMPLATE_TARGET").Cells(Rows.Count, "A").End(xlUp).Row
' pastes the found value
Sheets("TEMPLATE_TARGET").Cells(last_row, "A") = cell.Value2
End If
' otherwise go to next cell
Next cell
End Sub
I redefined your ranges a little in my example for utility reasons but it works as expected
In my Matrix sheet: (staff sheet only contains copy of this table)
In my TEMPLATE_TARGET sheet after running the procedure.
Result as expected
If I understand well, I would have done something like this:
Option Explicit
Public Sub Main()
Dim wsMatrix As Worksheet: Set wsMatrix = ThisWorkbook.Worksheets("Matrix")
Dim rgMatrix As Range: Set rgMatrix = wsMatrix.Range("G3:ED3")
Dim cell As Range
Dim cellStaff As Range
Dim cellMatrix As Range
For Each cell In rgMatrix
If CheckRangeStaff(cell.Range) And CheckRangeMatrix() Then
'Process in a column B? Which sheet? Which cell? Which Process?
End If
Next cell
Debug.Print ("End program.")
End Sub
Public Function CheckRangeStaff(ByVal value As String) As Boolean
Dim wsStaff As Worksheet: Set wsStaff = ThisWorkbook.Worksheets("Staff")
Dim rgStaff As Range: Set rgStaff = wsStaff.Range("H3:H204")
Dim res As Boolean
Dim cell As Range
res = False
For Each cell In rgStaff
If cell.value = value Then
res = True
Exit For
End If
Next cell
CheckRangeStaff = res
End Function
Public Function CheckRangeMatrix() As Boolean
Dim wsMatrix As Worksheet: Set wsMatrix = ThisWorkbook.Worksheets("Matrix")
Dim rgMatrix As Range: Set rgMatrix = wsMatrix.Range("G5:ED57")
Dim res As Boolean
Dim cell As Range
res = False
For Each cell In rgMatrix
If IsNumeric(cell.value) Then
res = True
Exit For
End If
Next cell
CheckRangeMatrix = res
End Function
I am getting the error
Autofilter method of range class failed
on the line
RngToSearch.AutoFilter 1, Criteria1:=ProgramNoVal
What I am trying to do is filter the data in column C of the Program Main sheet based on ProgramNoVal and then copy the value for the corresponding column K in my Mkting tab at RngDest.
Sub test()
Dim ProgramNoVal
Dim RngToSearch As Range, Rng As Range, RngDest As Range
ProgramNoVal = Sheets("Instructions").Range("G12").Value
With Sheets("Mkting")
Set RngDest = .Range("D1")
End With
Sheets("ProgramMain").Select
Set RngToSearch = Range("C1:K30000")
Set Rng = RngToSearch.Offset(1, 8).Resize(RngToSearch.Rows.Count - 1, 1)
ActiveSheet.AutoFilterMode = False
RngToSearch.AutoFilter 1, Criteria1:=ProgramNoVal
Rng.SpecialCells(xlCellTypeVisible).Copy RngDest
RngToSearch.AutoFilter
End Sub
I have tried this a different way, using vlookup. I get a type mismatch for foundRow. The value it is finding is a string. I have tried string, long, range, etc and nothing works. Solutions to either of these pieces of code is appreciated. The code for vlookup is:
Sub test()
Dim ProgramNoVal
Dim RngToSearch As Range, Rng As Range, RngDest As Range
Dim foundRow As long
With Sheets("Mkting")
Set RngDest = .Range("D1")
End With
ProgramNoVal = Sheets("Instructions").Range("G12").Value
Set RngToSearch = Sheets("ProgramMain").Range("C1:K30000")
foundRow = Sheets("ProgramMain").Application.VLookup(ProgramNoVal, RngToSearch, 9, False)
If Not IsError(foundRow) Then
RngToSearch.Cells(foundRow, 1).Copy RngDest
End If
End Sub
Dim foundRow as Variant.
This is because, if the value can't be found/doesn't exist, then Application.Vlookup will return an error type, which will raise a mismatch when assigned to a Long type variable.
But since vlookup returns the value and not the position of the cell, we can't use that the way we were trying to (which is what I get for not testing my code).
Instead of VLOOKUP use MATCH:
foundRow = Sheets("ProgramMain").Application.Match(ProgramNoVal, RngToSearch.Columns(1), False)
The Match function returns the relative row position of the searched for value. Since we're searching a range that begins in row 1, we can use this value without any modifications:
If Not IsError(foundRow) Then
RngToSearch.Cells(foundRow, 1).Copy RngDest
End If