I have worksheets A, B and C. Worksheet A contains a column with dates. B and C each contain two columns: one with a date and one with a value. For example
worksheet A:
A B
1 2001-01-01 ---
2 2001-01-02 ---
worksheet B:
A B
1 2001-01-01 1
worksheet C:
A B
1 2001-01-02 2
I'd like to have a function =Search(W, date) that when run from worksheet A returns a value assigned to date in worksheet W. For example Search(C, "2001-01-02")=2.
This is an abstract version of searching for currency rates at given dates: multiple worksheets contain rates for currencies, so when we search, we know what worksheet (currency) to pick.
How to define such a function? I tried passing parameters to a custom macro, but excel keeps giving me cryptic errors. It's seems easy to use a macro that uses the selected cell as a source, but a function would be better.
EDIT: my attempt, doesn't work
Function FindRate()
Dim FindString As String
Dim Rate As String
Dim Src As Range
Dim Found As Boolean
MsgBox sheet_name
Rate = "Not found "
Set Src = Application.ActiveCell
FindString = "2006-12-19"
Sheets("cur CHF").Activate
Found = False
For Each c In [A1:C2000]
If c.Value = FindString Then
Rate = c.Offset(0, 1).Value
Found = True
Exit For
End If
Next
MsgBox Rate
'FindRate = Rate
End Function
Function Rate(cname As String)
Dim sheet_name As String
Dim c2s As New Collection
c2s.Add "cur worksheet name", "cur"
sheet_name = c2s.Item(cname)
Call FindRate(sheet_name)
End Function
What you are really doing is a lookup. There is a VLOOKUP function built into Excel that will do exactly what you want. The syntax is
VLOOKUP(lookup_value, table_array, col_index_num, [range_lookup])
This will look up the value lookup_value in the table table_array. It will find an exact match in the first column if range_lookup is false, otherwise it will find the closest value (faster, but data must be sorted).
It will return the value in the col_index_num column.
In your case, if you want the value from sheet B corresponding to "2012-01-01", you would do
=VLOOKUP("2012-01-01", Sheet2!A2:B1000, 2, false)
You may have to mess around with converting the date string into a date value, etc. If you had added the values on Sheet2 as dates, you would want to use
=VLOOKUP(DATEVALUE("2012-01-01"), Sheet2!A2:B1000, 2, false)
since that function correctly converts the string "2012-01-01" to something Excel recognizes as a DATE.
Now if you don't know a priori which sheet you will need to access (because that's a variable), you may have to write yourself a VBA function:
Function myLookup(value, curr)
Dim dval As Long, luTable As Range, s As Worksheet, c As Range
' if user types date as string, convert it to date first...
If VarType(value) = vbString Then
dval = DateValue(value) ' this doesn't work if dval hasn't been declared as `long`!
Else
dval = value
End If
' see if `curr` is the name of a defined range; if so, use it
On Error GoTo notArange
' if the next line doesn't generate an error, then the named range exists:
Set luTable = Range(curr)
' so let's use it...
GoTo evaluateFunction
notArange:
' If we got here, "curr" wasn't the name of a range... it must be the name of a sheet
' first, tell VBA that we're done handling the last error:
Resume here
here:
On Error GoTo noSheet
Set s = ActiveWorkbook.Sheets(curr)
Dim firstCell As Range, lastCell As Range
Set firstCell = s.Range("a1")
Set lastCell = s.Range("b1").End(xlDown) ' assuming data in columns A and B, and contiguous
Set luTable = Range(firstCell, lastCell)
evaluateFunction:
myLookup = Application.WorksheetFunction.VLookup(dval, luTable, 2, False)
Exit Function
noSheet:
' get here if currency not found as either sheet or range --> return an error message
myLookup = curr & " not found!"
End Function
This has been tested on a small sample, and it worked. A few things to note:
You can name the range where the conversion is kept ("euro", "dinar", "yen", ...) instead of keeping each on a separate sheet. You can then pass the name of the range (make it the same as the name of the currency for convenience) as a parameter to your function, and access it with Range(currency). This also gets around the problem of "hard-wiring" the size of the range
The function will check for the existence of a named range, and use it if it exists. If it doesn't, it will look for a sheet with the correct name
If you use an "invalid currency name", this will be reflected in the return value (so myLookup("01-01-2012", "Florins") will return "Florins not found!"
Instead of assuming a lookup table of a certain length, I determine the size of the table dynamically, using the End(xlDown) construct
I allow date to be passed in as a String, or as a DATEVALUE. The function notices the string and converts it
Right now I am setting the range_lookup parameter to False. This means that there must be an exact match, and values that are not present will generate errors. If you prefer to return "the best match", then you set the parameter to True. Now the risk is that you will return garbage when the date requested is outside of your limits. You could solve this by setting the first and last value of the exchange rate column to "no valid data". When the lookup function returns, it will show this value.
This is a simple FindCell function I use a lot is simply extends Excels search function but from what you have got should suit fine. It returns a range however it is simple enough to get the value from the return range. I use it as follows (with comments added for your sake):
Function FindCell(SearchRange As Range, SearchText As Variant, OffsetDown As Integer, OffsetRight As Integer) As Range
'Do a normal search range call using the passed in range and text.
'First try looking formula
Set FindCell = SearchRange.Find(What:=SearchText, LookAt:=xlWhole, LookIn:=xlFormulas, _
MatchCase:=True, SearchOrder:=xlByRows).Offset(OffsetDown, OffsetRight)
'If nothing is found then look in values
If FindCell Is Nothing Then
Set FindCell = SearchRange.Find(What:=SearchText, LookAt:=xlWhole, LookIn:=xlValue, _
MatchCase:=True, SearchOrder:=xlByRows).Offset(OffsetDown, OffsetRight)
End If
End Function
This can be used a function for the rate (you could of course combine the two functions but I use FindCell for many applications so have kept it seperate):
Function GetRate(sWorksheetName As String, theDate As Date) As Double
Dim returnRange As Range
'Call the FindCell function specifying the range to search (column A), and the date and then offset one cell to the right for the value
Set returnRange = FindCell(ThisWorkbook.Worksheets(sWorksheetName).Columns("A:A"), sDate, 0, 1)
'Check if we've found something. If its Nothing then we haven't
If Not returnRange Is Nothing Then GetRate = returnRange.Value
End Function
You can test it in a Sub like so:
Sub Test()
MsgBox "Value is " & GetRate("Sheet2", "2001-01-01")
End Sub
By accepting the GetRate as a date type it shouldn't matter what format the date is in the worksheet.
Related
EDIT: Short version (longer explanation below):
I have a variable date value in Sheet8.Range("s1")
I have a named range in Sheet3 called "HolidaySchedule"
I am trying to use the variable date from Sheet8 to find the corresponding column of the date in "HolidaySchedule" where dates are listed in one row.
I'm trying to get the column number in "HolidaySchedule" where the variable date appears stored in a value (e.g. Dim iTargetCol as Integer)
Sub Scheduler()
'The below code sample correctly grabs the date value (e.g. 21/08/2019) and correctly references the named range location
Dim vCurrentDate As Variant
Dim rCurrentDateCol As Range
Dim lCurrentDateCol As Long
Dim iTargetCol As Integer
Dim rRng As Range
vCurrentDate = Sheet8.Range("s1").Value
Set rRng = Sheet3.Range("HolidaySchedule")
Long version:
Our team uses Excel sheets to allocate work schedules. I'm trying to create some VBA code so that it automatically creates a work schedule for our team based on a date entered in a different sheet.
I'm new to VBA and trying to use the find method to locate the date of one sheet to the corresponding date in a named range of a different sheet. I can't seem to get VBA to allow me to use the .Find to locate the date in the named range. Ideally, I'm able to locate the column number of the date from the different sheet that's located in the named range (example code below)
Sheet8 includes a date listed in cell S1
Sheet3 includes a list of dates for the corresponding month of the date listed in cell S1
The named range of Sheet3 called "HolidaySchedule" (C5:AJ9) has the dates listed horizontally across row 5 (but this may change): namely F5:AJ5 (the first few columns are for the names/position/etc)
The dates in F5:AJ5 are calculated using the first day of the month and each corresponding cell to the right is just the value of the cell to the left +1 (for instance: the formula in Cell G5 is F5+1)
The dates in F5:AJ5 are listed in Chinese text corresponding to the month and the date (e.g. 8月5日 which translates to 8th Month 5th Day = Aug 5)
For example purposes, Cell s1 in Sheet8 has the value of 21/8/2019.
Sheet3 F5:AJ5 has Aug 1 in cell F5 (actual text: 8月1日), Aug 2 in cell F5 (actual text: 8月2日).
I'm quite proficient in using "regular" excel coding language and have tried to use a similar Application.Match in VBA to find the column number of the corresponding date in the named range, but that doesn't seem to be working as well.
I've tried on stackoverflow to find similar problems with finding dates in ranges of cells that contain dates but their solutions don't seem to work for me for some reason.
I've also taken a look at the Excel help on how the find function accepts its different parameters but can't seem to get it to work.
I originally thought that the cells in Sheet3 F5:AJ5 were not properly formed as values but if I copied and pasted the values into a blank row, they appropriately show up as 43678, 43679, 43680, etc
Sub Scheduler()
Dim vCurrentDate As Variant
Dim rCurrentDateCol As Range
Dim lCurrentDateCol As Long
Dim iTargetCol As Integer
Dim rRng As Range
vCurrentDate = Sheet8.Range("s1").Value
Set rRng = Sheet3.Range("HolidaySchedule")
'NOTE - Only 1 of either attempt a) or attempt b) or attempt c is active at a time. If a is active, the code for attempt b and attempt c is commented out
Attempt :
using Application.Match to find the date in the named range
lCurrentDateCol = Application.Match(CLng(CDate(vCurrentDate)), rRng, 0)
using the .Find method after looking on Stackoverflow
'Note I've also tried ThisWorkbook.Sheet3.Range(rRng).Find(.......)
Set rCurrentDateCol = rRng.Find(what:=CDate(vCurrentDate), LookIn:=xlFormulas, lookat:=xlWhole)
If Not rCurrentDateCol Is Nothing Then
iTargetCol = rCurrentDateCol.Column
End If
Message iTargetCol
using the .Find method after looking on Stackoverflow
Dim cell as Range
For Each cell In rRng
Set rCurrentDateCol = rRng.Find(what:=CDate(vCurrentDate), LookIn:=xlFormulas, lookat:=xlWhole)
If Not rCurrentDateCol Is Nothing Then
iTargetCol = rCurrentDateCol.Column
Exit For
End If
Next
MsgBox iTargetCol
End Sub
Expected: return column number within the named range once it finds the date from the other sheet in this named range (i.e. supposed to return 24)
e.g.
21/8/2019 as Sheet 8.Range("s1").value;
In Sheet 3
Cell F5 contains value of 43678 (equiv to Aug 1, 2019 formatted as "8月1日")
Cell G5 contains value of 43679 (equiv to Aug 2, 2019 formatted as "8月2日")
...
Cell Z5 contains value of 43698 (equiv to Aug 21, 2019 formatted as "8月21日")
Actual results if using attempt a:
Run-time error '13': Type mismatch
Actual results if using attempt b and c:
Msgbox "Not found"
Like this?
Sub Scheduler()
vCurrentDate = Sheets("Sheet3").Range("A2").Value
Set rRng = Sheets("Sheet3").Range("HolidaySchedule")
lCurrentDateCol = Application.Match(vCurrentDate * 1, rRng, 0)
Sheets("Sheet3").Range("C2").Value = lCurrentDateCol
End Sub
EDIT:
If you have trouble with the variable types, either delete them altogether (and don't forget to delete "option explicit" at the start of your code. Or set them like this:
Sub Scheduler()
Dim vCurrentDate As Long
Dim rCurrentDateCol As Range
Dim lCurrentDateCol As Long
Dim iTargetCol As Integer
Dim rRng As Range
vCurrentDate = 1 * (Sheets("Sheet3").Range("A2").Value)
Set rRng = Sheets("Sheet3").Range("HolidaySchedule")
lCurrentDateCol = Application.Match(vCurrentDate, Range(rRng.Address), 0)
Sheets("Sheet3").Range("C2").Value = lCurrentDateCol
End Sub
I'm trying to write a code that would look up a string value (that I would get from the user) from a table located in another sheet of my workbook.
I've tried using the Range.Find function as well as Application.WorksheetFunction.Vlookup.
Sub ID_Lookup()
Dim name As Variant
Dim FoundID As Range
Dim test_tbl As ListObject
Set test_tbl = Sheets("Sheet1").ListObjects("Full_List")
name = InputBox("Enter ID name:")
'Snippet used to find user input value above located in table from another sheet.
On Error Resume Next
FoundID = test_tbl.DataBodyRange.Find(name, LookAt:=xlWhole)
' FoundID = Application.WorksheetFunction.VLookup(name, test_tbl .DataBodyRange, 1, False)
On Error GoTo 0
End Sub
While the logic above seems to work well for numerical values, it returns an error (in case of Vlookup) and Null (in case of Range.Find) while trying to find string values.
The string values I need to lookup are sometimes a combination of letters, numbers and a period character (for example: AB.CDE2) and I have tried setting the user input to both String and Variant datatypes to no avail. The column from where I would search is also Text ('General' format in Excel).
Any direction would be helpful.
I have a table in Excel as shown in the attached image (Until Column F) and would like to see the final column "Result":
ExcelData:
The Result column should retrieve Column Names where the row values is "IF".
I can use the IF function in Excel, but that would be a long statement that I would have to write to retrieve the final output if there are like 20 columns that I would have to check.
If you are okay with creating a UDF (User Defined Function), then you can essentially create your own worksheet function to do this.
In this function, you will do a quick loop in the input range, and check if any cell in that range is equal to "IF". If so, you will strip the column letter out of the .Address property.
Public Function getIFCols(ByVal rng As Range) As String
Dim retVal As String, cel As Range
For Each cel In rng.Cells
If cel.Value = "IF" Then
retVal = Trim(retVal & " " & colLtr(cel))
End If
Next
getIFCols = Replace(retVal, " ", ", ")
End Function
Private Function colLtr(colRng As Range) As String
colLtr = Split(colRng.Address, "$")(1)
End Function
You will call this function as such:
=getIFCols(B1:F1)
The great thing about this function is that it doesn't require a set amount of columns. So if you need 3 columns checked on one row and 10 on another, it's pretty dynamic.
For Excel Online you can try the texjoin
=TEXTJOIN(",",TRUE,IF(B2:G2="IF",$B$1:$G$1,""))
I have an excel with predefined column headers.The problem is these column headers can be at any position for every iteration that is only column headers are fixed not their position(index). So, I need to get the column index based on the column name for further processing.
I found another way to access the column as described in the question, so I though I should change my first answer accordingly. But I thought maybe that answer works for other cases.
Here is a quicker way to get hold of a column containing the value:
Sub Macro1()
ColumnByValue("age").Offset(5, 0).Value = "17"
End Sub
Function ColumnByValue(col As String) As Range
Set ColumnByValue = Range("1:1").Find(col)
End Function
Use .Find() to locate the cell containing the column you are looking for.
I wrote the following example to add a value at a certain column, on a specified row number. You might want to change the code so that it adds the value to the bottom of the existing values instead. For simplicity, the code below will find the column, go down to the specified row and change the value to the value specified by the caller.
Sub Macro1()
Dim result As Boolean
result = add_value_to_column("age", atRow:=3, newValue:=17)
'was that successful?
If result = True Then
MsgBox "value was added"
Else
MsgBox "value NOT added. maybe column does not exist"
End If
End Sub
Function add_value_to_column(col_name As String, _
atRow As Integer, _
newValue As String) As Boolean
Dim c As Range
'Assuming the header row is the first row at the top (1:1)
With Range("1:1")
'try to find the column specified in the function parameters
Set c = .Find(col_name, LookIn:=xlValues)
'If the column was found then it will not be nothing
'NOT NOTHING = THING :)
If Not c Is Nothing Then
'access the row specified in the parameter, at the column,
'and set the value for that location
Cells(atRow, c.Column).Value = newValue
'Optioal: inform the caller that the value was set
add_value_to_column = True
Else
'optional: inform the caller that column was not found
add_value_to_column = False
End If
End With
End Function
Here is an image showing a sample data set. Running the code above has added the value 17 to the age column for the row number 3
I am currently searching a HR sheet for a column header that contains specific text. This text can change each week, for example, a column called "State/Province" could have no spaces in it one week, but the following week it could be "State / Province" (notice the space between the words).
I am currently using the below code that looks for one condition
StateProvince = WorksheetFunction.Match("State/Province", hr.Sheets("Open").Rows(1), 0)
This works fine, but I am looking to add to this so that it looks for the second example containing the spaces if this is the case for the column header. Any suggestions?
Use:
StateProvince = WorksheetFunction.Match("State*/*Province", hr.Sheets("Open").Rows(1), 0)
This answer is specific to your question. If you want more generic solution you'll have to provide other examples.
Since the unpredictable appearance of spaces you'd better ignore them altoghether by means of a custom MyMatch() function to which pass "unspaced" string, like follows:
Function MyMatch(textToSearch As String, rng As Range) As Long
Dim txtRng As Range, cell As Range
MyMatch = -1 '<--| default value for no match found
On Error Resume Next
Set txtRng = rng.SpecialCells(xlCellTypeConstants, xlTextValues) '<--| consider only cells with text values
On Error GoTo 0
If Not txtRng Is Nothing Then '<--| If there's at least one cell with text value
For Each cell In txtRng '<--| loop through selected "text" cells
If WorksheeyFunction.Substitute (cell.Value, " ", "") = textToSearch Then '<--|remove every space occurrence from cell value and compare it to your "nospace" searched value
MyMatch = cell.Column - rng.Columns(1).Column + 1
Exit For
End If
Next cell
End If
End With
To be used like follows:
Dim StateProvince As Long
StateProvince = MyMatch("State/Province", hr.Sheets("Open").Rows(1)) '<--| pass an "unspaced" string to search
If StateProvince > 0 Then
' code for handling found StateProvince
Else
' code for handling NOT found StateProvince
End If