I created a lookup function that finds the result from a separate tab within the same worksheet with 4 different fields to match.
When running, this takes entirely too long to complete (to the point where I have to kill the macro run). I need to build the same lookup function for 8 different fields, based on the exact same match criteria. Any advice on how to speed up this query or build it in a more dynamic way, so I can lookup all 8 columns at once rather than building functions and subs for each lookup field?
Function fcst_bal_find(ByVal Anode As String, ByVal LoB As String, ByVal Month As String, ByVal Year As String) As Variant
Dim Fcst_Essbase As Worksheet
Dim fcst_rowcnt
Dim act_rowcnt
fcst_rowcnt = Sheets("Date Dims").Range("B7")
act_rowcnt = Sheets("Date Dims").Range("B8")
Set Fcst_Essbase = Sheets("Fcst Essbase Pull")
For i = 2 To fcst_rowcnt + 4
If WorksheetFunction.Trim(Fcst_Essbase.Cells(i, 1).Value) = Anode Then
If WorksheetFunction.Trim(Fcst_Essbase.Cells(i, 2).Value) = LoB Then
If WorksheetFunction.Trim(Fcst_Essbase.Cells(i, 3).Value) = Month Then
If "Y" & Right(WorksheetFunction.Trim(Fcst_Essbase.Cells(i, 4).Value), 2) = Year Then
fcst_bal_find = Fcst_Essbase.Cells(i, 5).Value
Exit Function
End If
End If
End If
End If
Next i
fcst_bal_find = "N/A"
End Function
Sub balfcst_find()
Dim fcst_tab As Worksheet
Dim match As Variant
Dim Anode As String
Dim LoB As String
Dim Month As String
Dim Year As String
Dim fcst_rowcnt
Dim act_rowcnt
fcst_rowcnt = Sheets("Date Dims").Range("B7")
act_rowcnt = Sheets("Date Dims").Range("B8")
Set fcst_tab = Sheets("Cartesian Product - Fcst")
For i = 2 To fcst_rowcnt
Anode = fcst_tab.Range("A" & i).Value
LoB = fcst_tab.Range("B" & i).Value
Month = fcst_tab.Range("C" & i).Value
Year = fcst_tab.Range("D" & i).Value
match = fcst_bal_find(Anode, LoB, Month, Year)
fcst_tab.Cells(i, 5) = match ' test the output
Next i
End Sub
Here is an example of using variant array to match something from a current project of mine. You can modify to suit your needs.
Private Function verifyMod(modValue As Double, state As String) As Boolean
If Len(modValue) Then
Dim modTable As ListObject
Set modTable = lookupsAUState.ListObjects("stateWritingCoModMinMax")
Dim v As Variant
v = modTable.DataBodyRange.value
Dim company As String
company = StrConv(xmlCo.Range("insuranceCompanyName"), vbProperCase)
Dim x As Long
For x = LBound(v) To UBound(v)
If v(x, modTable.ListColumns("Company").index) = company Then
If v(x, modTable.ListColumns("State").index) = state Then
If modValue >= v(x, modTable.ListColumns("Min").index) And modValue <= v(x, modTable.ListColumns("Max").index) Then
verifyMod = True
Else
MsgBox state & " allows for modifications between " & v(x, modTable.ListColumns("Min").index) & " and " & v(x, modTable.ListColumns("Max").index) & ". Please enter a modification within that range."
End If
Exit For
End If
End If
Next
End If
End Function
Related
I have a column in "dd-mm-yy hh:mm" format that formed as a result of some action on UserForm:
Dim ws as Worksheet
Set ws = Worksheets("Logs")
With ws
For i = 1 to Me.ListBox1.ListCount - 1
.Cells(lRow + 1 + i, 10).Value = CDate(VBA.Format(Me.ListBox1.List(i), "dd-mm-yy hh:mm"))
Next i
End With
I save the column to Variant variable to use later (to be used multiple times):
Dim arrTimeD As Variant
arrTimeD = Application.Transpose(.Range(TCL & "2:" & TCL & lRow).Value)
The locale date settings are European: "dd-mmm-yyyy"
The spreadsheet are used by different users, some have "dd-mmm" setting, others "mm-dd" etc.
I need to compare the dates in several uses. For, e.g.
Dim bDate as Date
bDate = CDate(VBA.Format(Me.lblCheckin.Caption,"dd-mm-yyyy"))
Do While CDate(arrTimeD(bIndex)) < bDate
If bIndex = lRow - 1 Then Exit Do
bIndex = bIndex + 1
Loop
When the user with US locale ("mm-dd") uses the spreadsheet, CDate(arrTimeD(bIndex)) throws error. CDate(VBA.Format(arrTimeD(bIndex))) and CDate(DateValue(arrTimeD(bIndex)) didn't help. What is the best way to do it?
Is it possible to set workbook's own date setting regardless of OS's?
Or I need to convert variant to string then concatenate?
The date string should be converted into a numeric date value.
Function DDMMYYYFormatToDateTimeValue(DateString As String) As Date
Dim Parts() As String
Parts = Split(DateString, "-")
DDMMYYYFormatToDateTimeValue = CDate(Parts(1) & "/" & Parts(0) & "/" & Parts(2))
End Function
Usage
Private Sub UserForm_Initialize()
Dim n As Long
For n = 1 To 100
ListBox1.AddItem Format(Date + n / 24, "MM-DD-YY HH:MM")
Next
End Sub
Public Function ListBoxDateValues()
Dim Data As Variant
ReDim Data(1 To Me.ListBox1.ListCount, 1 To 1)
Dim DateString As String
Dim r As Long
For r = 1 To Me.ListBox1.ListCount
DateString = Me.ListBox1.List(r - 1)
Data(r, 1) = DDMMYYYFormatToDateTimeValue(DateString)
Next
ListBoxDateValues = Data
End Function
Public Function wsLogs() As Worksheet
Set wsLogs = ThisWorkbook.Sheets("Logs")
End Function
Function DDMMYYYFormatToDateTimeValue(DateString As String) As Date
Dim Parts() As String
Parts = Split(DateString, "-")
DDMMYYYFormatToDateTimeValue = CDate(Parts(1) & "/" & Parts(0) & "/" & Parts(2))
End Function
First, true date values carry no format, so convert your text dates from the listbox directly to true date values:
.Cells(lRow + 1 + i, 10).Value = CDate(Me.ListBox1.List(i))
These you can apply the format you prefer for display.
The comparison is now straight:
Dim bDate As Date
bDate = CDate(Me.lblCheckin.Caption)
Do While arrTimeD(bIndex) < bDate
If bIndex = lRow - 1 Then
Exit Do
Else
bIndex = bIndex + 1
End If
Loop
I am trying to come up with a simple solution to count the number of columns containing certain numbers. It can be a formula or a function.
The cells will contain a mix of numbers and letters, i.e. Group 1, Section 11, Task 21, Lot1, Item-1, etc...
What I want to do is to count all the 1s only, and then the 2s, etc...Not the 21s, or 11s obviously.
There may be spaces, there may not be, nothing is too set in stone.
I am trying to make a formula work but am thinking I will need to do a function and call it from the cell I want the result in. i.e. "=CountCols("1").
I can think of a function but it seems awful clunky, thought I would see if anyone had a more elegant way.
Any help or ideas are appreciated.
and thanks to everyone for viewing my question.
It was answered by a friend with who I shared this post. She created these two functions and they work a dream.
(I only needed to search Row 6 of each column in a particular worksheet.)
Public Function countColumnsWithWord(strToFind As String, sSheet As String) As Long
Dim wks As Worksheet
Dim c As Long
Dim r As Long
Dim countOfFound As Long
Dim iLastCol As Integer
Dim strToSearch As String
Set wks = Application.Worksheets(sSheet)
iLastCol = wks.Cells(6, wks.Columns.Count).End(xlToLeft).Column
'start in col 2
'only row 6
c = 2
r = 6
For c = 1 To iLastCol
If wks.Cells(r, c).Value <> "" And Not IsEmpty(wks.Cells(r, c).Value) Then
strToSearch = CStr(wks.Cells(r, c).Value)
If findInString(strToFind, strToSearch) = True Then
countOfFound = countOfFound + 1
End If
End If
Next c
countColumnsWithWord = countOfFound
End Function
Public Function findInString(strToFind As String, strToSearch As String)
Dim isFound As Boolean
Dim strCurrent As String
Dim i As Long
Dim strToSearchLength As Long
strToSearchLength = Len(strToSearch)
isFound = False
strCurrent = ""
i = 1
While i < strToSearchLength + 1 And isFound = False
If Mid(strToSearch, i, 1) = " " Then
If strToFind = strCurrent Then
isFound = True
Else
strCurrent = ""
End If
Else
strCurrent = strCurrent & Mid(strToSearch, i, 1)
End If
i = i + 1
Wend
If isFound = False And strToFind = strCurrent Then
isFound = True
End If
findInString = isFound
End Function
New to VBA, trying to create a function that essentially searches a column for certain values. If it finds a hit then it returns a corresponding column, else returns a space. The way the worksheet is formatted, one cell can have multiple values (separated by ALT+ENTER, so each new value is on a separate line).
The code I used currently works but has an issue:
Since I am using inStr the code is returning partial matches as well (which I do not want).
Example:
**Column to Search (one cell)**
ABC
AB
B
When I run the code to find AB, it will return hits for both AB and ABC since AB is part of it.
Ideal solution would be to first split the cells based on ALT+ENTER and loop through all values per cell and then return the desired value. But not how the syntax would look.
Current Code
Function newFunc(Search_string As String, Search_in_col As Range, Return_val_col As Range)
Dim i As Long
Dim result As String
Dim mRange As Range
Dim mValue As String
For i = 1 To Search_in_col.Count
If InStr(1, Search_in_col.Cells(i, 1).Text, Search_string) <> 0 Then
If (Return_val_col.Cells(i, 1).MergeCells) Then
Set mRange = Return_val_col.Cells(i, 1).MergeArea
mValue = mRange.Cells(1).Value
result = result & mValue & ", "
Else
result = result & Return_val_col.Cells(i, 1).Value & ", "
End If
End If
Next
Example:
Adding an example to better explain the situation
you can split the string and loop that.
Function newFunc(Search_string As String, Search_in_col As Range, Return_val_col As Range) As String
If Search_in_col.Cells.Count <> Return_val_col.Cells.Count Then Exit Function
Dim sptStr() As String
sptStr = Split(Search_string, Chr(10))
Dim srchArr() As Variant
srchArr = Search_in_col.Value
Dim RetArr() As Variant
RetArr = Return_val_col.Value
Dim i As Long
For i = LBound(sptStr) To UBound(sptStr)
Dim j As Long
For j = LBound(srchArr, 1) To UBound(srchArr, 1)
If srchArr(j, 1) = sptStr(i) Then
newFunc = newFunc & RetArr(j, 1) & ", "
End If
Next j
Next i
newFunc = Left(newFunc, Len(newFunc) - 2)
End Function
EDIT:
As per the new information:
Function newFunc(Search_string As String, Search_in_col As Range, Return_val_col As Range) As String
Search_string = "|" & Search_string & "|"
Dim srchArr() As Variant
srchArr = Search_in_col.Value
Dim RetArr() As Variant
RetArr = Return_val_col.Value
Dim i As Long
For i = LBound(srchArr, 1) To UBound(srchArr, 1)
Dim T As String
T = "|" & Replace(srchArr(i, 1), Chr(10), "|") & "|"
If InStr(T, Search_string) > 0 Then
newFunc = newFunc & RetArr(i, 1) & ", "
End If
Next i
newFunc = Left(newFunc, Len(newFunc) - 2)
End Function
You can use regular expressions which have a word boundary token.
The following seems to reproduce what you show in your example:
Option Explicit
'Set reference to Microsoft VBScript Regular Expressions 5.5
Function col_return(lookFor As String, lookIn As Range) As String
Dim RE As RegExp
Dim C As Range
Dim S As String
Set RE = New RegExp
With RE
.Global = True
.IgnoreCase = True 'unless you want case sensitive searches
For Each C In lookIn
.Pattern = "\b(" & lookFor & ")\b"
If .Test(C.Text) = True Then
S = S & "," & C.Offset(0, -1)
End If
Next C
End With
col_return = Mid(S, 2)
End Function
I used early binding, which means you set a reference in VBA as noted in the comments.
You can use late-binding and avoid the reference. To do that you would change to the DIM and Set lines for RE to:
DIM RE as Object
Set RE = createobject("vbscript.regexp")
You can read about early vs late-binding by doing an internet search.
The formula I used and the layout is in the screenshot below:
I'm building an master excel file that is designed to gather data from lots of other excel files that are stored in the business Dropbox files and place them in the 2nd sheet of the master file. I built a original version on my local computer and that worked perfectly (the path3 variable) but once I tried to convert it based on a changing file path (because each user will have a different path from their PC) I am getting the run time error. The formula defined by path2 is what I have been trying to use but even though the variable seems to be holding the right value (I tested it by having it write out the values) it doesn't seem to be able to move the data, throwing the above error and highlighting the "rngdest.Formula = Chr(61) & path2" line. I really don't have any idea what is causing this and I have spent several days trying different approaches but to no avail so any ideas, solutions or links to already solved (I have spent a long time searching but haven't found anything) would be very much appreciated.
I've included the whole of the code for completeness, I think I've removed most of the redundant code that I left in but there may be some still left. If you need any clarifications on the code please let me know. Thanks for any potential help
Private Sub CommandButton2_Click()
Dim counter As Integer
Dim i As Long
Dim j As Long
Dim k As Long
Dim l As Long
Dim a As Integer
Dim z As Integer
Dim y As Integer
Dim p As Integer
Dim Names() As String
Dim Fix1() As String
Dim path3 As String
Dim path2 As String
Dim SheetName As String
Dim c As Range
Dim found As Range
Dim BookName As String
Dim var1 As String
Dim rngdest As Range
Dim rngsource As Range
Dim cell As String
Dim adjust As Integer
Dim adjust2 As Integer
Dim rngname As Range
Dim colNo As Integer
Dim fin As String
Dim fin2 As String
Dim fin3 As String
Dim comp As String
Dim teststring As String
Dim currentWb2 As Workbook
Set currentWb2 = ThisWorkbook
MsgBox "Excel will now update the sheet, please be patient as this can take a few minutes. You will be notified once it is complete"
ReDim Fix1(1 To 4)
Fix1(1) = "A-F"
Fix1(2) = "G-L"
Fix1(3) = "M-R"
Fix1(4) = "S-Z"
counter = 0
With ActiveSheet
i = .Cells(.Rows.Count, "A").End(xlUp).Row
End With
ReDim Names(1 To i, 1 To 4)
With ActiveSheet
For k = 1 To 4
For a = 2 To i
Names(a, k) = Cells(a, k).Value
Next a
Next k
End With
SheetName = "Analysis"
BookName = "Outcomes Final.xlsm"
For p = 1 To 4
fin2 = Split(Cells(, p).Address, "$")(1)
With ActiveSheet
l = .Cells(.Rows.Count, fin2).End(xlUp).Row
End With
For z = 1 To l
counter = counter + 1
fin = Split(Cells(, counter).Address, "$")(1)
currentWb2.Sheets("Sheet2").Range("" & fin & "1") = Names(z, p)
For y = 1 To 34
adjust = y + 1
cell = "$B$" & y & ""
If z = 1 Then
Else
teststring = GetPath()
teststring = teststring & "\Clients\"
path3 = "'C:\Users\Lewis\Documents\Outcomes\Floating Support\Clients\" & Fix1(p) & "\" & Names(z, p) & "\[Outcomes Final.xlsm]Analysis'!" & cell & ""
path2 = teststring & Fix1(p) & "\" & Names(z, p) & "\Outcomes\[Outcomes Final.xlsm]Analysis'!" & cell & ""
End If
Set rngdest = currentWb2.Sheets("Sheet2").Range("" & fin & "" & adjust & "")
Set rngsource = Range("B" & y & "")
rngdest.Formula = Chr(61) & path2
Next y
Next z
Next p
currentWb2.Sheets("Sheet2").Columns(1).EntireColumn.Delete
currentWb2.Sheets("Sheet1").Range("A1:D35").Interior.ColorIndex = 0
For j = 1 To counter
fin3 = Split(Cells(, j).Address, "$")(1)
If currentWb2.Sheets("Sheet2").Range("" & fin3 & "35") = "1" Then
With currentWb2.Sheets("Sheet1").Range("A1:D35")
comp = currentWb2.Sheets("Sheet2").Range("" & fin3 & "1")
Set c = .Find(comp, LookIn:=xlValues)
If Not c Is Nothing Then
c.Interior.ColorIndex = 3
End If
End With
End If
Next j
MsgBox "The update is now complete, please click on sheet 2 to view the data. All clients in red have not been properly completed"
End Sub
I have 100 names in one column. And next to each name in the next cell is a numerical value that the name is worth.There are 6 positions in a company that each name could potentially hold. And that is also in a cell next to each name.
So the spreadsheet looks something like this.
John Smith Lawyer $445352
Joe Doe Doctor $525222
John Doe Accountant $123192
etc....
I want excel to give me 10 people who make a combined amount between 2 and 3 million dollars. But I require that 2 of the people be doctors 2 be lawyers and 2 be accountants etc. How would I create this?
I set up sheet 1 with the following data:
Goal:
Return 10 people
Salary between 1000000 and 6000000 range
Min 2 each doc, lawyer, accountant
Run this Macro:
Sub macro()
Dim rCell As Range
Dim rRng As Range
Dim rangelist As String
Dim entryCount As Long
Dim totalnum As Long
Set rRng = Sheet1.Range("A1:A12")
Dim OccA As String
Dim OccCntA As Long
Dim OccASalmin As Long
Dim OccASalmax As Long
Dim OccB As String
Dim OccCntB As Long
Dim OccBSalmin As Long
Dim OccBSalmax As Long
Dim OccC As String
Dim OccCntC As Long
Dim OccCSalmin As Long
Dim OccCSalmax As Long
'Set total number of results to return
totalnum = 10
'Set which occupations that must be included in results
OccA = "Accountant"
OccB = "Doctor"
OccC = "Lawyer"
'Set minimum quantity of each occupation to me returned in results
OccCntA = 2
OccCntB = 2
OccCntC = 2
'Set min and max salary ranges to return for each occupation
OccASalmin = 1000000
OccASalmax = 6000000
OccBSalmin = 1000000
OccBSalmax = 6000000
OccCSalmin = 1000000
OccCSalmax = 6000000
'Get total number of entries
entryCount = rRng.Count
'Randomly get first required occupation entries
'Return list of rows for each Occupation
OccAList = PickRandomItemsFromList(OccCntA, entryCount, OccA, OccASalmin, OccASalmax)
OccBList = PickRandomItemsFromList(OccCntB, entryCount, OccB, OccBSalmin, OccBSalmax)
OccCList = PickRandomItemsFromList(OccCntC, entryCount, OccC, OccCSalmin, OccCSalmax)
For Each i In OccAList
If rangelist = "" Then
rangelist = "A" & i
Else
rangelist = rangelist & "," & "A" & i
End If
Next i
For Each i In OccBList
If rangelist = "" Then
rangelist = "A" & i
Else
rangelist = rangelist & "," & "A" & i
End If
Next i
For Each i In OccCList
If rangelist = "" Then
rangelist = "A" & i
Else
rangelist = rangelist & "," & "A" & i
End If
Next i
'Print the rows that match criteria
Dim rCntr As Long
rCntr = 1
Dim nRng As Range
Set nRng = Range(rangelist)
For Each j In nRng
Range(j, j.Offset(0, 2)).Select
Selection.Copy
Range("E" & rCntr).Select
ActiveSheet.Paste
rCntr = rCntr + 1
Next j
'Get rest of rows randomly and print
OccList = PickRandomItemsFromListB(totalnum - rCntr + 1, entryCount, rangelist)
For Each k In OccList
Set Rng = Range("A" & k)
Range(Rng, Rng.Offset(0, 2)).Select
Selection.Copy
Range("E" & rCntr).Select
ActiveSheet.Paste
rCntr = rCntr + 1
Next k
End Sub
Function PickRandomItemsFromListB(nItemsToPick As Long, nItemsTotal As Long, avoidRng As String)
Dim rngList As Range
Dim idx() As Long
Dim varRandomItems() As Variant
Dim i As Long
Dim j As Long
Dim booIndexIsUnique As Boolean
Set rngList = Range("B1").Resize(nItemsTotal, 1)
ReDim idx(1 To nItemsToPick)
ReDim varRandomItems(1 To nItemsToPick)
For i = 1 To nItemsToPick
Do
booIndexIsUnique = True ' Innoncent until proven guilty
idx(i) = Int(nItemsTotal * Rnd + 1)
For j = 1 To i - 1
If idx(i) = idx(j) Then
' It's already there.
booIndexIsUnique = False
Exit For
End If
Next j
Set isect = Application.Intersect(Range("A" & idx(i)), Range(avoidRng))
If booIndexIsUnique = True And isect Is Nothing Then
Exit Do
End If
Loop
varRandomItems(i) = idx(i)
Next i
PickRandomItemsFromListB = varRandomItems
' varRandomItems now contains nItemsToPick unique random
' items from range rngList.
End Function
Function PickRandomItemsFromList(nItemsToPick As Long, nItemsTotal As Long, Occ As String, Salmin As Long, Salmax As Long)
Dim rngList As Range
Dim idx() As Long
Dim varRandomItems() As Variant
Dim i As Long
Dim j As Long
Dim booIndexIsUnique As Boolean
Set rngList = Range("B1").Resize(nItemsTotal, 1)
ReDim idx(1 To nItemsToPick)
ReDim varRandomItems(1 To nItemsToPick)
For i = 1 To nItemsToPick
Do
booIndexIsUnique = True ' Innoncent until proven guilty
idx(i) = Int(nItemsTotal * Rnd + 1)
For j = 1 To i - 1
If idx(i) = idx(j) Then
' It's already there.
booIndexIsUnique = False
Exit For
End If
Next j
If booIndexIsUnique = True And Range("B" & idx(i)).Value = Occ And Range("B" & idx(i)).Offset(0, 1).Value >= Salmin And Range("B" & idx(i)).Offset(0, 1).Value <= Salmax Then
Exit Do
End If
Loop
varRandomItems(i) = idx(i)
Next i
PickRandomItemsFromList = varRandomItems
End Function
Results are printed in column E with the first results meeting the criteria. After those, the rest are random but don't repeat the previous ones:
I'm not doing very much error checking such as what happens if there are not 2 doctors or not enough entries left to meet the required number of results. You'll have to fine tune it for your purposes. You'll probably also want to set up the inputs as a form so you don't have to mess with code every time you change your criteria.