I need to apply MATCH function to a range of dates (in VBA. The standard MATCH function written as cell formula does work as intended.) looking for the largest possible index whose corresponding date is <= the looked-up date. Here's my code and a minimum working example:
Function test_debug_func(x As Date, arr As Range) As Integer
Debug.Print "entered function"
test_debug_func = WorksheetFunction.Match(x, arr, 1)
End Function
And I checked that the function is indeed entered by printing the debug statement. However, it cannot proceed with the WorksheetFunction.Match function. I also checked that all dates are indeed Date and not other formats.
It simply doesn't make any sense to me why this wouldn't work. Could anyone kindly help? Thanks!
Since dates are just numbers, you can use CLng:
test_debug_func = WorksheetFunction.Match(CLng(x), arr, 1)
Related
In Excel, I often have complicated formulas where I need to know if the result of an expression is true before I use the value. Often times, I have to put the result in a separate cell, and then use that result in another calculation / formula.
I would like to create a UDF that allowed me to pass:
Function UseIf(left_expression, comparison_expression, value_if_false)
If the overall item is true, I want to return the value for the left expression, much the same way that IfError() returns the evaluation if it doesn't yield an error. Here, I want to return the evaluation if it meets another criteria. I want to pass in the matching criteria or comparison expression, much like you do in SUMIIF, etc. Then for good measure, accept a value to return if the comparison is false.
Yes, in Excel, I can simply create an Excel formula (in the worksheet) with an IF statement and I include the entire expression if the If Test and in the If Results, but that generates a lot of duplication in a formula.
I tried having the UDF insert the expressions as range.formula for cells in my Personal.xlsb, to have excel do the expression parsing & calculation for me. But Excel won't allow a UDF to update another cell's value or formula (it always goes to my OnError condition - The same functionality works in a macro subroutine, but not in a udf.) Also, I seem to run into a lot of problems with volatility in UDFs, with it recalculating much more often than I think it should. If it didn't recalculate when anything on the spreadsheet was updated, then the idea of using temporary cells and updating the associated formulas might work (if Excel allowed me to that is). However, if it is volatile, then the cells might have different values in them at a later point.
Any ideas? Is this possible? Or do I have to stick with long and messy simple IF statements?
Update: Thanks to Scott's help, I came up with the following solution. Please let me know if you see anything that needs improvement. Thanks again!
Function UseIf(left_expression As Variant, _
comparison_expression As Variant, _
Optional value_if_false As Variant, _
Optional value_if_error As Variant) As Variant
Dim vResult As Variant
On Error GoTo ErrorHandler
If (Not InStr("=><", Left(comparison_expression, 1)) > 0) Then comparison_expression = "=" & comparison_expression
If IsMissing(value_if_false) Then value_if_false = ""
vResult = Evaluate(left_expression)
UseIf = IIf(Evaluate(vResult & comparison_expression), vResult, value_if_false)
Exit Function
ErrorHandler:
UseIf = IIf(Not IsMissing(value_if_error), value_if_error, "#ERROR")
End Function
I have a form that is supposed to show the total sum of different numeric values between two dates, a start date and a finish date. For this, I thought the best option would be to use the SumIfs WorkSheetFunction. However, after trying to code this, the function is not working properly. I am not sure of what is wrong. If I type the exact same formula on the worksheet that has the table with my sample data, it works perfectly.
So, the form I designed is the following:
A second label and textbox will be added for the finish (or end) date. However, I thought it would be better to do that once I get the code to work with a single date in the beginning. The textbox where the user will insert the start date is called tbxDate and the textbox that will show the resulting sum is called tbxBalance. The button that triggers the SumIfs functions is called cmdCalculate.
Also, the table that stores the data (which only has one row of data so far for testing purposes) is this one:
The table name is Sales, and the worksheet name is SalesWS. I thought the code should be pretty simple, unless there is something I am missing. What I did was:
Private Sub cmdCalculate_Click()
Set SalesRange = Worksheets("SalesWS").Range("Sales[TOTAL]")
Set DatesRange = Worksheets("SalesWS").Range("Sales[DATE]")
tbxBalance = Application.WorksheetFunction.SumIfs(SalesRange , DatesRange , ">=" & tbxDate)
End Sub
The issue is that the >= part of the criteria is failing. I only get proper results using only the greater than or less than conditions. For example, if I enter the date 09/08/2020 in the textbox the result in the balance textbox is 0, but if I enter the date 08/08/2020 or anything before it works. It just ignores the condition to sum the values if the date is equal to what is entered. It only works with dates greater or less than what the user inputs in the textbox, excluding the chosen date.
I already checked that the column with the dates in the table is formatted properly.
The version of your code given below should work provided your DATE range contains true dates and tbxDate contains a string VBA can recognise as a date.
Private Sub cmdCalculate_Click()
Dim Fun As Double
Set SalesRange = Worksheets("SalesWS").Range("Sales[TOTAL]")
Set DatesRange = Worksheets("SalesWS").Range("Sales[DATE]")
Fun = Application.WorksheetFunction.SumIfs(SalesRange, DatesRange, ">=" & CLng(CDate(tbxDate.Value)))
tbxBalance = Format(Fun, "#,##0.00")
End Sub
Remember that tbxBalance will cotnain a text string, not a number. Use Excel's NUMBERVALUE function to convert the formatted number you have in tbxBalance after the above code back to a number you can calculate with.
I am trying out a simple summation vba code. I want to find the sum of the first 2 rows in a defined range. However, I get a #VALUE error return upon running the following code:
Function test(prices As Range)
test = prices.Rows.Value + prices.Rows.Offset(1, 0).Value
End Function
Please advise, thanks v much!
Value will only look at a single cell while Rows is looking at all rows in your range so your code is trying to add a single cell that thinks it's the entire row and coming back with the computer equivalent of a shoulder shrug.
Try using:
Function test(prices As Range) As Double
test = Application.WorksheetFunction.Sum(prices.Rows(1).Resize(2))
End Function
It will look at the first two rows of whichever range you give it.
This would also work - now looking up as not sure of difference of leaving out the Worksheetfunction bit:
Function test(prices As Range) As Double
test = Application.Sum(prices.Rows(1).Resize(2))
End Function
I am trying to store all the values of an excel column in an array.
set rangeDate to {value of range "A14:A100"}
repeat with date in rangeDate
if (date as string is equal to "01/01/2001") then
log "It works"
end if
end repeat
In my Excel I do have an exact date of 01/01/2001 formatted in the specified columns. When I remove the range and it is just cell A14 (where the date is) it works. But when I include the range A14:A100 it doesn't work.
I am new to applescript, I guess that it doesn't store the values as array values and instead a string object? Any help would be appreciated
You have 4 issues :
1) value of range should not be between {}, but between ()
2) 'Date' is a reserved word in Applescript, so you should not use it as the variable in the loop. I replaced it with 'myDate'.
3) instead of converting your date to string to compare with "01/01/2001", it is quicker to keep comparing 2 dates, and then, compare with the date "01/01/2001"
4) I think it is a bug (at least with my Excel version), but the rangeDate variable is not a list of dates as expected, but for me a list of list : {{01/02/01},{02/02/01},………} Therefore, each member of 'rangeDate' is not a date, but a list made on one item which is a date ! I am not sure, but it could also be that range definition could be a list of ranges... So I am using item 1 of sub list.
Anyway, script bellow is working :
tell application "Microsoft Excel"
activate
tell active sheet of document 1
set rangeDate to (value of range "A14:A100")
repeat with mydate in rangeDate
set TheDate to item 1 of mydate
if TheDate = (date "lundi 1 janvier 2001 00:00:00") then
log "It works"
end if
end repeat
end tell
end tell
Quickly getting the values of a range of cells is great news! But even better is that you can fill in the values of a range by defining the value of that range. This is SO MUCH FASTER than doing it one cell at a time.
When I tried getting the value of a column (a range of cells), I received a list of lists. Each item in the list had only one value - that is the value of the cell.
To speed up complex operations, once you've got the list of values, take the process out of the "tell Excel" block and let AppleScript do the calculations. Then turn the result back into a list of lists and define the value of the range in Excel.
I had a problem reading ranges with some cells containing #VALUE! (failed formulas). I didn't find a solution on the Internet, so I thought it would be a good idea to share my solution here. Comments & improvement are surely welcome. I'm inclined to think there is a more straightforward solution to the problem than this. :)
Getting all values with value of range can lead to a problem messing up the output of the script. AppleScript doesn't consider a cell's content "#VALUE!" (= missing values) a value since it is, well, missing. Therefore the script doesn't include the cell's content in the list of values. This obviously messes up the cell order in the values list, since it has less items than the actual range has cells. In this situation it is quite impossible to return each value to its original cell in the workbook. Adding ”of ranges” to the code includes all cells with missing values solving the problem.
N.B. The values will be displayed as a one-dimensional array. Handling multi-column ranges requires more work. Nonetheless the missing values are included.
set celVals to (value of ranges of range "A1:A4")
E.g. {2.2.2022, 1.1.2011, missing value, 3.3.2033}
In order to return the values back to the workbook it is required to build back the list of lists. A missing value will be written to its cell as an empty string. Of course the original (failed) formula can be written instead, if needed.
N.B. again. This code applies to one column situation only. A little more is needed to put back a multi-column range. I'm sure you'll manage. :D
set returningCelVals to {}
repeat with i from 1 to count of celVals
set end of returningCelVals to {item i of celVals}
end repeat
set value of range ("A1:A4") to returningCelVals
EDIT: I knew there is a better solution. Here it is:
set celVals to string value of range "A1:A4"
String value gives a two-dimensional array of values and error messages of the range. String value gives also e.g. cell's currency symbols, so it is perhaps not suitable to all situations.
I am using the following code to sum all the numbers in different columns. The formula always gives me wrong answer. Please help! Thanks
Sub TOTALVALNEW(colNumber As Integer)
Dim StartOfTheRANGE As Range
Dim EndOfTheRange As Range
Set StartOfTheRANGE = Evaluation.Cells(3, colNumber)
Set EndOfTheRange = StartOfTheRANGE.End(xlDown)
'Evaluation is the name of the sheet.
Evaluation.Cells(3, colNumber).End(xlDown).Offset(1, 0) = Application.Sum(StartOfTheRANGE, EndOfTheRange)
End Sub
VBA's Application.Sum works exactly the same as the worksheet function SUM() in a cell formula. It takes an object, or a list of objects. If you specify two objects namely the first and last cells, it will just take the sum of the values of the contents first and last cells only (a 2-term sum), not anything in between. If you want to include all cells between the first cell and last cell inclusive, you need to make make a range object by using the Range() function. So changing to this:
Evaluation.Cells(3, colNumber).End(xlDown).Offset(1, 0) = Application.Sum(Range(StartOfTheRANGE, EndOfTheRange))
should work.