Summary: all the occurrences of a UDF recalculate when one of them has a source changed.
I have a fairly simple UDF (code below) that calculates the stableford score of a golf round based on a couple of variables. Now I find that the UDF seems to be semi-volatile, in that as soon as I enter data in the data entry range (HoleScores) ALL of my occurrences of the UDF recalculate, even on other sheets. But if I press F9 (or choose to recalculate) they do not recalculate.
The desired situation is that only the UDF for which the data is entered recalculates. Can anybody help me achieve that?
nb: the HoleScores range is only referenced by one single UDF. All occurrences of the UDF use unique entry ranges. I have tested the recalc with the VBA screen open and closed. I am using Excel 2016
Public Function WACRondeScore(PlayingHandicap, Pars As Range, _
StrokeIndexen As Range, HoleScores As Range, _
Afgelast As String) As Variant
On Error GoTo FuncFail
Dim Hole As Long
Dim StablefordPuntenRonde As Long
Dim StablefordPunten As Long
If PlayingHandicap = "" Then
WACRondeScore = ""
Exit Function
Else
PlayingHandicap = CLng(PlayingHandicap)
End If
' Afgelast
If Not Afgelast = "" Then
WACRondeScore = "A"
Exit Function
End If
If IsEmptyRange(HoleScores) Then
WACRondeScore = ""
Exit Function
End If
For Hole = 1 To 9
If IsInteger(HoleScores(1, Hole)) Then
StablefordPunten = (Pars(1, Hole) + 2 + Int(((PlayingHandicap * 2) - StrokeIndexen(1, Hole) + 18) / 18)) - HoleScores(1, Hole)
If StablefordPunten < 0 Then StablefordPunten = 0
StablefordPuntenRonde = StablefordPuntenRonde + StablefordPunten
End If
Next Hole
WACRondeScore = StablefordPuntenRonde
Debug.Print "wacRONDESCORE"
Exit Function
FuncFail:
WACRondeScore = CVErr(xlErrValue)
End Function
I think I have found the cause of the recalculation. One of the entry values (PlayingHandicap) seems to be culprit. Don't know why, as yet, but am searching for the bug
Related
Hope you're doing well. I'm going to preface this by saying I'm not a programmer and I'm sure the code I have started is riddled with more errors then what I think. Hopefully you can help :D.
I have an Excel sheet that gets generated from another program that comes out like this:
excel sheet
However, the size of this sheet can change with every new generation of this sheet from the other program. (ex, A can have 7 next time, and D could have 9) And the sheet as it is cannot be used easily to do the math required as I only need specific groups of information at a given time, in this example groups B and D only.
What I'm hoping to create is something that will take the sheet as its generated, and turn it into something that looks like this:
result sheet
This is the code I've written so far, but since I don't really know what I'm doing I keep running into numerous problems. Any help would be appreciated.
Option Explicit
Sub Numbers()
Dim matchesFound As Integer
Dim row As Integer
Dim c As Integer
Dim copyRow As Integer
Dim copyLocationColumn As Integer
Dim arr(2) As String
arr(0) = "1"
arr(1) = "2"
arr(2) = "3"
Function arrayContainsValue(array, varValue)
found = false
for each = 0 to array
if array(i) = varValue then
found = true
exit for
arrayContainsValue = found
End Function
row = 1
c = 1
copyLocationColumn = 1
copyRow = 1
matchesFound = 0
Do While matchesFound < 3
if arrayContainsValue(arr, ThisWorkbook.Sheets("Data").Cell(column,row))
matchesFound = matchesFound + 1
Do While ThisWorkbook.Sheets("Data").Cell(column, row)
ThisWorkbook.Sheets("postHere").Cell(copyLocationColumn, copyRow) = _
ThisWorkbook.Sheets("postHere").Cell(c + 1, row)
copyRow = copyRow+1
row = row + 1
Loop
End If
row = row + 1
Loop
End Sub
There are many logic errors to numerate in a comment, Excel highlights them automatically I'll do a summary explaining them:
1. Function can't be "in the middle" of the sub, finish the Sub (take the Function from the sub and paste until it says end sub.
2.array is a forbidden name, try with another variable name
3.For each =0 ? to array? what do you try to mean like that? For Each has to be element in something For each element in Array for example For and To are for something defined in numbers (for counter=1 to 15)
Function arrayContainsValue(***array***, varValue) '2nd problem
found = false
for each = 0 to array '3rd problem
if array(i) = varValue then
found = true
exit for
arrayContainsValue = found
End Function
....
4. you're missing a then at the end
if arrayContainsValue(arr, ThisWorkbook.Sheets("Data").Cell(column,row))
I don't get the coding logic on how relates to the problem stated (?)
Using Excel 2010 & have a list of parts, some of which can have their part number changed. If a part has been changed, it will be in one worksheet (Column A), and its new value (column B). There is also a date time stamp on the change.
In my list of all part numbers, I need to
Check to see if the part has been changed, and
Follow it through each of it's changes until I find the last one.
The "last one" can mean EITHER
The value in Column B (newVal) is not in Column A(oldval) OR
If it refers back to itself (a correction), in which case the newest Date Time stamp is the determining factor.
Corrections to the list are entered by adding a row which would redirect it to the previous (correct) version. An example:
oldVal A changes to newVal B 2016-04-08 11:39:04.765
oldVal B changes to newVal C 2016-04-08 12:21:39.801
(we find out the B--> C change is incorrect)
oldVal C changes to newVal B 2016-04-08 13:44.07.913
I know that a vLookup won't do this. I think that SUMPRODUCT may solve the first part, but I'm not sure how to do the recursion (or if it's possible) for the time part of it.
Any thoughts, ideas, solutions would be appreciated.
EDIT
Additional Info -
The desired output
What I want to show in the NewPartNo column is the last value in the chain for that part number. If the Part number went from A --> B --> C --> D, I would want to see D in the NewPartNo column for the PartNo in column A.
You have a 1960-1 that has a newVal of 25-1960. The problem is that the 25-1960 then has a newVal of OBB1960. How many levels of recursive misplaced vals are there?
That's the issue - in some places a part may be (originally) named any one of the ones in column A. As far as how many levels - I don't know. Possibly 5-10, maybe? I don't know exactly.
EDIT 2
#TMDean's solution mostly worked, except for when I had data like below.
If starting with A001, it maps to B001. B001's newest (most recent date) mapping is to C001. C001's mapping (to itself) has the same date-time stamp as the other one, so A001 should map to C001.
In stepping through the function, it will find the first one (A001 -> B001), but throws an error when it tries to find the second one (B001 -> C001).
In the above, the correct result of the lookup should be A001 -> C001.
Here are two UDFs¹ to retrieve the recursive newVal and it's associated date.
Option Explicit
Function newestVal(lu As Range, rng As Range)
Dim val As Variant
Static app As New Application
'truncate the rng down to the used range
Set rng = Intersect(rng, rng.Parent.UsedRange)
val = lu.Value2
With rng
If IsError(app.Match(lu.Value2, .Columns(1), 0)) Then
val = app.Index(.Columns(2), app.Match(val, .Columns(2), 0), 1)
Else
Do While Not IsError(app.Match(val, .Columns(1), 0))
val = app.Index(.Columns(2), app.Match(val, .Columns(1), 0), 1)
Loop
End If
End With
newestVal = val
End Function
Function latestValDate(lu As Range, rng As Range)
Dim d As Long, val As Variant, dbl As Double
Static app As New Application
'truncate the rng down to the used range
Set rng = Intersect(rng, rng.Parent.UsedRange)
val = lu.Value2
With rng
If IsError(app.Match(lu.Value2, .Columns(1), 0)) Then
val = app.Index(.Columns(2), app.Match(val, .Columns(2), 0), 1)
Else
Do While Not IsError(app.Match(val, .Columns(1), 0))
val = app.Index(.Columns(2), app.Match(val, .Columns(1), 0), 1)
Loop
End If
For d = app.Match(val, .Columns(2), 0) To rng.Rows.Count
If .Cells(d, 2).Value2 = val Then
If .Cells(d, 3).Value2 > dbl Then
dbl = .Cells(d, 3).Value2
End If
End If
Next d
End With
latestValDate = dbl
End Function
In F2:G2 as,
=newestVal(E2,A:C )
=latestValDate(E2,A:C )
¹ A User Defined Function (aka UDF) is placed into a standard module code sheet. Tap Alt+F11 and when the VBE opens, immediately use the pull-down menus to Insert ► Module (Alt+I,M). Paste the function code into the new module code sheet titled something like Book1 - Module1 (Code). Tap Alt+Q to return to your worksheet(s).
This is all psuedo code but it should get you ion the right direction. Give me a couple of hours and I can do it for you, but it might be fun for you to try and figure it out.
If Selection Offset Column + 1 <> vbNullString then
newvalue = Selection Offset Column + 1.value
for i = 1 to 100
If newvalue Offset Column + 1 = vbNullString then
Display (i)a,b,c
Exit For
ElseIf newvalue Offset Column + 1 <> vbNullString then
newvalue = newvalue Offset Column + 1.value
Next i
Inspired by Jeeped's solution, I wondered if it could be more general purpose and wrote a UDF implementation of a recursive version of VLOOKUP.
This function works just like VLOOKUP except it will follow the chain of lookup values until it gets to the end of the table, as described in the problem.
Option Explicit
Function VLOOKUPR(LookupValue As Variant, _
TableArray As Range, _
ColIndexNum As Long, _
RangeLookup As Boolean)
Dim LookupIndex
Set TableArray = Intersect(TableArray, TableArray.Parent.UsedRange)
VLOOKUPR = CVErr(xlErrNA)
Do
LookupIndex = Application.Match(LookupValue, TableArray.Columns(1), _
IIf(RangeLookup, 1, 0))
If IsError(LookupIndex) Then Exit Do
VLOOKUPR = TableArray(LookupIndex, ColIndexNum)
If LookupValue <> TableArray(LookupIndex, 1) Then Exit Do
LookupValue = TableArray(LookupIndex, ColIndexNum)
Set TableArray = Intersect(TableArray, TableArray.Offset(LookupIndex))
Loop Until TableArray Is Nothing
End Function
Since I wanted it to be general purpose, I didn't include the requirement that it return the lookup value instead of #N/A if it exists in column B. You can include this logic in the cell formula you use to call VLOOKUPR. =IFERROR(VLOOKUPR($E2,$A$2:$A$100,2,FALSE),VLOOKUP($E2,$B$2:$B$100,1,FALSE))
I am using the following Index Match function to get the name of a company where the spend data matches that of which I type into cell BF17.
=INDEX($AM$16:$BB$16,MATCH(BF17,AM17:BB17,0))
What I want to be able to do is list multiple results within the same cell and separate these with a comma.
Does anyone know if this is possible and if so can someone please show me how?
Thanks
Code:
Insert this code in a module in your workbook:
Public Function hLookupList(KeyVal, Vals As Range, Ret As Range) As String
Dim i As Long
Dim vw As Worksheet
Dim rw As Worksheet
Dim RetStr As String
Application.Volatile True
Set vw = Vals.Worksheet
Set rw = Ret.Worksheet
If Vals.Rows.Count > 1 Then
hLookupList = "Too Many Value Rows Selected!"
Exit Function
End If
If Ret.Rows.Count > 1 Then
hLookupList = "Too Many Return Rows Selected!"
Exit Function
End If
If Vals.Columns.Count <> Ret.Columns.Count Then
hLookupList = "Value Range and Return Range must be the same size!"
Exit Function
End If
For i = Vals.Column To Vals.Column + Vals.Columns.Count - 1
If vw.Cells(Vals.Row, i) = KeyVal Then
RetStr = RetStr & rw.Cells(Ret.Row, Ret.Column + i - 1) & ", "
End If
Next i
hLookupList = Left(RetStr, Len(RetStr) - 2)
End Function
Then:
Insert this in the cell where you want your list: =hLookupList(BF17, $AM$16:$BB$16, $AM$17:$BB$17)
Unfortunately there is no built-in way to make a vlookup or index/match function return an array. You could do it with a custom formula or if you know there are a limited number of results, a few nested lookups. Lewiy at mrexcel.com wrote a great custom function that I use, which can be found here. This function can be slow if you are looking up a large number of rows.
Since you are looking up columns and want commas separating the results instead of spaces, you will need to modify the code as follows:
Function MYVLOOKUP(lookupval, lookuprange As Range, indexcol As Long)
Dim r As Range
Dim result As String
result = ""
For Each r In lookuprange
If r = lookupval Then
result = result & "," & r.offSet(indexcol, 0)
End If
Next r
result = Right(result, Len(result) - 1)
MYVLOOKUP = result
End Function
Your formula would then be =MYVLOOKUP(BF17,AM17:BB17,-1)
If you want a space after the comma (in the results), change:
result = result & "," & r.offSet(indexcol, 0)
to
result = result & ", " & r.offSet(indexcol, 0)
If you haven't used custom functions before, hit Alt + F11 when in Excel to bring up the VBE, and add a new module to the workbook you are working on (Insert --> Module). Just copy and paste this code in there. I would recommend Paste Special --> Values before sending the workbook to anyone. Let me know if you have any questions implementing it!
I am trying to make a simple sum function that sums until I hit a blank cell. I am not sure why it is not working. I am trying to sum currency and have the output be a currency as well. So far I have:
Function SumContin(X)
Dim Ro As Long
Dim Col As Long
Dim Ro1 As Long
Dim Col1 As Long
Ro = Application.WorksheetFunction.Row(X)
Col = Application.WorksheetFunction.Column(X)
Do While Cells(Ro, Col) <> ""
Sum = Sum + CInt(Cells(Ro, Col))
Ro = Ro - 1
Loop
End Function
UPD:
Follow up from comments:
Can I make the function where it will be =SumContin() and it starts from the cell above?
Function SumContin()
Application.Volatile
SumContin = 0
On Error Resume Next
With Application.ThisCell
If .Row = 1 Then Exit Function
If .Offset(-1) = "" Then Exit Function
SumContin = Application.Sum(Range(.Offset(-1), .End(xlUp)))
End With
End Function
Note: since code using Application.ThisCell, function will work only in case when you call it from worksheet: =SumContin() and won't work if you call it from any code
A UDF for this seems overkill given that in the A provided the requisite range has to be keyed in anyway. A running total something like =SUM(A$1:A1) and double-clicking on the fill handle may be more convenient at times.
It would be most efficient to just define the range withing the UDF based on the cell passed, and then use the WorksheetFunction.Sum command.
Here's one way:
Function SumContin(X As Range)
SumContin = WorksheetFunction.Sum(Range(X.Address & ":" & X.End(xlDown).Address))
End Function
I'm trying to write a function that merges multiple rows of text in a column into a single cell based on a pre determined count. My goal is to generate a flexible function to aid in compiling / interperting large quantaties of data. The code I've written returns #NAME? and I cant figure out where the error is. My code is as follows:
Function vmrg(countref As Integer, datref As Integer) As String
If IsEmpty(ActiveCell.Offset(0, -countref)) Then % check if cell containing count is blank
vertmerge = "N/A" % if blank, state N/A
Else
Dim datlst(0 To ActiveCell.Offset(0, -countref).Value - 1) As String
Dim i As Integer
For i = 0 To ActiveCell.Offset(0, -countref).Value - 1
datlst(i) = ActiveCell.Offset(i, -datref).Text %fill array with data
End
vertmerge = datlst(0)
For i = 1 To ActiveCell.Offset(0, -countref).Value - 1 % merge array to a single string
vertmerge = vertmerge & ", " & datlst(i)
End
End
End Function
I have matlab and some C++ experience but this is the first time I've used VBA so my syntax is probably odd in some areas and wrong in others. Ideally I would like to reference the cells where the data and count info are stored, but for now I'm hoping to correct my syntax and set a jumping off point for further development of this function. Any reccomendations are appreciated.
Code Rev_1: I still have an output of #NAME? but I think I've corrected(?) some of the issues
Function vertmerge(countref As Range, datref As Integer) As String
If IsEmpty(countref) = True Then
vertmerge = "NA"
Else
Dim datlst(0 To countref.Value - 1) As String
Dim i As Integer
For i = 0 To countref.Value - 1
datlst(i) = countref.Offset(i, datref).Text
Next i
vertmerge = datlst(0)
For i = 1 To countref.Value - 1
vertmerge = vertmerge & ", " & datlst(i)
Next i
End
End Function
You are doing some dangerous things here!
First - you are referencing "ActiveCell" from inside a function; but you have NO IDEA what cell will be active when the function runs! Instead, pass the target cell as a parameter:
=vmrg("B6", 5, 6)
and change your function prototype to
Function vmrg(r as Range, countref as Integer, datref as Integer)
Now you can reference things relative to r with
r.Offset(1,2)
etc.
Next - you are never assigning anything to vmrg. In VBA, the way a function returns a value is with (in this case)
vmrg = 23
You are assigning things to a variable called vertmerge - but that is not the name of your function. At least add
vmrg = vertmerge
Just before returning. That might do it. Without a full sample of your spreadsheet I can't help you more.