So I have an application where I'm looking up data from a bunch of different tables and entering the data into a bunch of variables to be used elsewhere. I wrote a function to look up and return a row (as a Range) from a specified table given an entry in the first column:
Public Function RowLookup(table As Range, entry As String) As Variant
Dim rowNum As Variant
rowNum = Application.Match(entry, table.Columns.item(1), 0)
If IsError(rowNum) Then
RowLookup = CVErr(xlErrValue)
Else
RowLookup = table.Rows(rowNum)(1)
End If
End Function
However, now I've ended up with a ton of code that looks like this:
tempRow = RowLookup(Range("Table1"), var1)
If IsError(tempRow) Then
var2 = ""
var3 = ""
var4 = ""
var5 = ""
Debug.Print "Error looking up data"
Else
var2 = tempRow(1, 2)
var3 = tempRow(1, 3)
var4 = tempRow(1, 4)
var5 = tempRow(1, 6)
End If
Any ideas on how to work this boilerplate code into the function? I want it to take an arbitrarily long list of (colIndex, variable) pairs and then set each variable based on columnIndex. Using a Collection doesn't seem to work as I can only change the entry in the Collection, not the original variable.
You could pass the Vars and Table Index's as a ParamArray
I'd rework RowLookup as a Sub. Param Arrayexpects pairs of Variables, and Table Column Numbers.
An Odd number of passed parameters cauase an error.
A Column Index > number of columns in Table is ignored
Vars don't have to be Variants (they will be type cast, so could throw errors)
Public Sub RowLookup(table As Range, entry As Variant, ParamArray Vars() As Variant)
Dim rowNum As Variant
Dim i As Long
Dim TableData As Variant
Dim TableColumns As Long
If WorksheetFunction.IsEven(UBound(Vars)) Then
For i = 0 To UBound(Vars) - 1 Step 2
Vars(i) = CVErr(xlErrNA)
Next
Else
rowNum = Application.Match(entry, table.Columns(1), 0)
If IsError(rowNum) Then
For i = 0 To UBound(Vars) - 1 Step 2
Vars(i) = CVErr(xlErrValue)
Next
Else
TableData = table.Value
TableColumns = UBound(TableData, 2)
For i = 0 To UBound(Vars) - 1 Step 2
If Vars(i + 1) >= 1 And Vars(i + 1) <= TableColumns Then
Vars(i) = TableData(rowNum, Vars(i + 1))
End If
Next
End If
End If
End Sub
and call it like this
Sub Demo()
Dim SomeLongVar As Long
Dim SomeStringVar As String
Dim var3(1 To 2) As Variant
Dim var4 As Variant
Dim var5 As Variant
var3(2) = 5 ' Column Index
RowLookup Range("Table1"), "x", SomeLongVar, 2, SomeStringVar, 3, var3(1), var3(2)
Debug.Print SomeLongVar, SomeStringVar, var3(1)
End Sub
Related
I would like to make a user-defined function union in VBA, such that:
it could take variable parameters
each parameter is a one-column range like A1, A2:A10; we don't need to consider passing constant values to parameters
we could consider, within one input range, there are no duplicates; but it is very possible to have duplicates among input ranges.
union combines the input ranges, and keeps the order of the elements. For instance, =union(A1:A5, C1:C2, E1:E3) has the following expected output in Column I:
I wrote the following code which works. However, the problem is that it is slow. A union over a list of 4000 rows and a list of 20 rows takes already several seconds. First, I don't know the way I coded arrays could be improved. Second, the algorithm just consists in comparing each new element against the accumulating result list; there is no sort, no other techniques. Third, I don't know if there are any existing functions we could use in other objects of VBA (eg, VBA FILTER function, Collection, ArrayLists, Scripting.Dictionary).
Could anyone propose a more efficient code?
Function getDimension(var As Variant) As Long
On Error GoTo Err
Dim i As Long
Dim tmp As Long
i = 0
Do While True
i = i + 1
tmp = UBound(var, i)
Loop
Err:
getDimension = i - 1
End Function
Function exists(v As Variant, arr As Variant, resCount As Long) As Boolean
If resCount = 0 Then
exists = False
Else
exists = False
i = LBound(arr, 1)
Do While (i <= resCount) And (Not exists)
If arr(i) = v Then
exists = True
End If
i = i + 1
Loop
End If
End Function
' assumption: every input is a range (eg, A1, A1:A2)
' assumption: each input range has only one column
Function union(ParamArray arr() As Variant) As Variant
Dim res As Variant
ReDim res(1 To 100000)
Dim resCount As Long
resCount = 0
For k = LBound(arr) To UBound(arr)
Dim arrk As Variant
Dim v
arrk = arr(k).Value2
If getDimension(arrk) = 0 Then 'case of A1, B1
v = arrk
If Not exists(v, res, resCount) Then
resCount = resCount + 1
res(resCount) = v
End If
ElseIf getDimension(arrk) = 2 Then 'case of A1:A10, B1:B10
For i = LBound(arrk, 1) To UBound(arrk, 1)
v = arrk(i, 1)
If Not exists(v, res, resCount) Then
resCount = resCount + 1
res(resCount) = v
End If
Next i
End If
Next k
ReDim Preserve res(1 To resCount)
union = Application.WorksheetFunction.Transpose(res)
End Function
Something like this should work, using a Dictionary to eliminate duplicates.
Function UniqueValues(ParamArray arr() As Variant)
Dim r, c As Range, v, dict
Set dict = CreateObject("scripting.dictionary")
For Each r In arr
For Each c In r.Cells
v = c.Value
If Len(v) > 0 Then dict(v) = 1
Next c
Next r
UniqueValues = ToColumn(dict.keys)
End Function
Function ToColumn(arr)
Dim arrOut, i As Long
ReDim arrOut(1 To UBound(arr) + 1, 1 To 1)
For i = 1 To UBound(arr) + 1
arrOut(i, 1) = arr(i - 1)
Next i
ToColumn= arrOut
End Function
I've got the following:
Dim dupArray As Variant
dupArray = Array("dup")
For j = LBound(numArray) To UBound(numArray)
If IsInArray(Range(numArray(j)).Text, dupArray) Then
Range(numArray(j)).Interior.ColorIndex = 3
MsgBox "Duplicate value"
ElseIf IsInArray(Range(numArray(j)).Text, keyArray) Then
Application.OnKey Range(numArray(j)).Text, comArray(j)
ReDim Preserve dupArray(j)
Dim cellEntry As String
cellEntry = Range(numArray(j)).Text
MsgBox cellEntry
dupArray(j) = cellEntry
Else
Range(numArray(j)).Interior.ColorIndex = 3
MsgBox "Unrecognized key code in " & numArray(j)
End If
Next j
In essence, I want VBA to read the contents of a cell and append them to an array for comparison. Code to read the contents of a cell is Range(numArray(j)).Text... For some reason, cellEntry and dupArray(j) are not equal. More specifically, for the cell A6, cellEntry is "b" (which is the correct contents), but dupArray(j) is "A6"... any thoughts? There's no error code, it's just not putting the correct value in the array.
Thank you!
(Edit) Code for Function IsInArray:
Function IsInArray(stringToBeFound As String, arr As Variant) As Boolean
IsInArray = Not IsError(Application.Match(stringToBeFound, arr, 0))
End Function
(Edit 2) Don't pay attention to much else... I'm just wondering why cellEntry doesn't match dupArray(j) for all values of j when they should clearly be the same thing.
Your code seems to work, but it depends on keyArray being populated.
I've run this demo code, including populating numArray and keyArray with test values, to illustrate what happens.
If how I've populated these arrays doesn't match your code, please add that info to your Q.
Sub Demo()
Dim dupArray As Variant
Dim numArray As Variant
Dim keyArray As Variant
Dim comArray As Variant
Dim j As Long
' for testing
numArray = Application.Transpose([A1:A6].Value)
ReDim keyArray(1 To 3)
keyArray(1) = "x"
keyArray(2) = "a"
keyArray(3) = "s"
dupArray = Array("dup")
For j = LBound(numArray) To UBound(numArray)
If IsInArray(Range(numArray(j)).Text, dupArray) Then
Range(numArray(j)).Interior.ColorIndex = 3
'MsgBox "Duplicate value"
ElseIf IsInArray(Range(numArray(j)).Text, keyArray) Then
'Application.OnKey Range(numArray(j)).Text, comArray(j)
ReDim Preserve dupArray(j)
Dim cellEntry As String
cellEntry = Range(numArray(j)).Text
'MsgBox cellEntry
dupArray(j) = cellEntry
Else
Range(numArray(j)).Interior.ColorIndex = 4 ' changed to be distinct for testing
'MsgBox "Unrecognized key code in " & numArray(j)
End If
Next j
End Sub
Sheet, before
Sheet, after
Variable values at end of execution
As you can see, dupArray has been populated sparsley, in line with numArray. This is fine for how it's used with IsInArray. If it's used for something else too, you can change how it's populated.
Function test()
Dim result As String
Dim x As Integer
Dim search_value As String
Dim column As Integer
search_value = "esg001"
column = 1
For x = 2 To 3
Sheets(x).Select
Range("B:B").Select
On Error Resume Next
Cells.Find(search_value).Select
ActiveCell.Offset(0, column).Select
result = ActiveCell.Value
If search_value <> "" Then
GoTo ola
Else
End If
Next
ola:
test = result
End Function
as stated in the comments the following formula will do what you want:
=IFERROR(VLOOKUP("esg001",Sheet2!B:C,2,FALSE),VLOOKUP("esg001",Sheet3!B:C,2,FALSE))
Where Sheet2 and Sheet3 are the names of the sheets.
Now a couple of notes on your attempted code.
Do not use .Select. More info on that HERE
when using UDF avoid hardcoding ranges, pass them as parameters. The reason is that the formula would not update when the data updates if it is not a parameter.
This accepts two parameters: Search Value and which column to return. It also accepts as many ranges as desired to search for the value:
Function test(schVal As String, clm As Long, ParamArray schRng() As Variant) As Variant
Dim i As Long
For i = LBound(schRng) To UBound(schRng)
If TypeOf schRng(i) Is Range Then
If schRng(i).Columns.Count > clm Then Exit Function
Dim rngArr() As Variant
rngArr = Intersect(schRng(i), schRng(i).Parent.UsedRange).Value
Dim j As Long
For j = 1 To UBound(rngArr, 1)
If rngArr(j, 1) = schVal Then
test = rngArr(j, clm)
Exit Function
End If
Next j
Else
test = "Parameters 3 and higher should be ranges"
Exit Function
End If
Next i
test = "Not Found"
End Function
Now you would call it (using the formula above as a template):
=TEST("esg001",2,Sheet2!B:C,Sheet3!B:C)
It will first
I am working on a program that needs to read an array of values from cells in another worksheet in the same workbook. I am able to read a single value just fine, but when I try to read multiple, I cannot return an array.
Here is what I am trying to do:
Dim list() As Variant
list = ActiveWorkbook.Worksheets("Sheet2").Range("A2:C2").value
Debug.Print TypeName(list)
Debug.Print UBound(list)
Debug.Print LBound(list)
Debug.Print TypeName(list(UBound(list)))
For which the output is:
Variant()
1
1
Subscript out of range
However, If I try it where I expect a single string, instead of an array of strings
Dim value As String
Let value = ActiveWorkbook.Worksheets("Site IDs and CJONs").Range("A2").value
Debug.Print TypeName(value)
Debug.Print value
for which I get the output
String
Expected Value
According to this question I should be able to simply return an array from the range function (example from the answer below), but it doesn't seem to be working for me. What am I doing wrong?
Dim DirArray As Variant
DirArray = Range("a1:a5").Value
Although it is not obvious, this:
Dim DirArray As Variant
DirArray = Range("a1:a5").Value
actually is like:
Dim DirArray(1 To 5, 1 To 1) As Variant
DirArray(1, 1) = Range("A1").Value
DirArray(2, 1) = Range("A2").Value
DirArray(3, 1) = Range("A3").Value
DirArray(4, 1) = Range("A4").Value
DirArray(5, 1) = Range("A5").Value
Pulling a set of cells into an array usually makes a 2-D array.
NOTE:
If you want to go from array to worksheet cells then, for example:
Sub ytrewq()
Dim DirArray(1 To 5, 1 To 1) As Variant
DirArray(1, 1) = "Larry"
DirArray(2, 1) = "Moe"
DirArray(3, 1) = "Curly"
DirArray(4, 1) = "Shepp"
DirArray(5, 1) = "James"
Range("B9").Resize(5, 1) = DirArray
End Sub
I might as well put my comment as an answer:
Option Explicit
Sub test()
Dim list As Variant
list = Application.Transpose(Application.Transpose(ActiveWorkbook.Worksheets("Sheet1").Range("A2:C2").Value))
Debug.Print TypeName(list)
Debug.Print UBound(list)
Debug.Print LBound(list)
'Debug.Print UBound(list, 2) Error
'Debug.Print LBound(list, 2) Error
Debug.Print TypeName(list(UBound(list)))
Debug.Print list(UBound(list))
End Sub
Gives output:
Variant()
3
1
String
x
where C2 contains letter x.
I have an array of numbers in an excel spreadsheet which I am trying to sort (all numbers >60) using a user defined vba function and i want to return the result as a range in the same excel sheet.
I am getting a value error when i run this function in excel.I am not too sure where this error is coming from as I a new to VBA.I would really appreciate some guidance in resolving this issue.
Array Excel
Column A
200
50
23
789
Function trial(number As Range)
Dim cell As Range
Dim savearray() As Variant
Dim d As Long
For Each cell In Range("a3:a6").Cells
If cell.Value > 60 Then
d = d + 1
ReDim Preserve savearray(1 To d)
savearray(1, d) = cell.Value
trial = savearray
End If
Next cell
End Function
There is a bit of work to do on your Sub. However, to help you, below is a way to dynamically build an array:
d = 0
For Each cell In Range("A3:A1000")
If cell.Value > 60 Then
If d = 0 Then
ReDim savearray(0 To 0)
Else
ReDim Preserve savearray(0 To UBound(savearray) + 1)
End If
savearray(d) = cell.Value
d = d + 1
End If
Next cell
I feel like you might want to rather return a sorted array and only then, cast results to a Range
First we create a Function to sort our array
Private Function BubbleSort(ByRef from() As Variant) As Variant()
Dim i As Integer, j As Integer
Dim temp As Variant
For i = LBound(from) To UBound(from) - 1
For j = i + 1 To UBound(from)
If from(i) < from(j) Then
temp = from(j)
from(j) = from(i)
from(i) = temp
End If
Next j
Next i
BubbleSort = from ' returns sorted array
End Function
Then we create a simple "Range replacer" procedure
Private Sub replace_with_sorted(ByVal which As Range)
Dim arr() As Variant
arr = Application.Transpose(which)
arr = BubbleSort(arr)
which = Application.Transpose(arr)
End Sub
So the invokation would look the following way:
Private Sub test()
replace_with_sorted Range("A1:A4")
End Sub
This of course produces the expected result:
EDIT: Just noticed you want to sort only values larger than 60.
In that case, simply fill an array with values larger than 60 and use the same application.
Private Sub test()
Dim arr() as Variant: arr = Application.Transpose(Range("A1:A4"))
Dim above60() as Variant
Dim i as Integer, j as Integer: j = 0
For i = LBound(arr) To UBound(arr)
If arr(i) > 60 Then
ReDim Preserve above60(j)
above60(j) = arr(i)
j = j + 1
End If
Next i
ReDim arr()
arr = BubbleSort(above60)
' returns sorted array, do whatever u want with it _
(place it in whatever range u want, not defined in ur question)
End Sub