I am trying to write a function in Excel that:
Iterates through each worksheet
Checks if the supplied string exists in the supplied cell
Adds a predetermined cell value to the return value if step 2 evaluates to True
I've been stepping through my function and am getting the correct values until the function has to add the 4th cell to my return value.
Anyone have any idea what's going on? Thanks!
Function Revenue(row As Integer, col As Integer, str As String) As Integer
Dim i As Integer
For i = 2 To Worksheets.Count
If Worksheets(i).Cells(row, col) = str Then
Revenue = Revenue + Worksheets(i).Cells(21, 2) // Bug occurs on 4th iteration
Debug.Print Revenue
End If
Next i
End Function
You need to establish values for row and col prior to using them.
Related
I need to find the value not equal 1 in the column A and get all the position in the same field.
I want to get the all not equal 1 position into the B11.
I want to use the excel formula function and don't use vba or other code. How can I do ? Is possible ?
Since you are working with Excel-2007, unfortunately you won't be able to use the new TEXTJOIN() and CONCAT() functions (nor power query). However you can get quite creative with a function like CONCATENATE(), it will most likely mean at some point you'll have to do some manual 'labour'. Who wants to do manual labour? ;)
So in this case I would prefer to go the UDF way. Below a tested example:
Function GetPos(RNG As Range, VAL As Double) As String
Dim CL As Range, ARR() As String, X as double
X = 1
For Each CL In RNG
If CL.Value <> VAL Then
ReDim Preserve ARR(X)
ARR(X) = CL.Address(False, False)
X = X + 1
End If
Next CL
If IsEmpty(ARR) Then
GetPos = "No hits"
Else
GetPos = Join(ARR, ",")
GetPos = Right(GetPos, Len(GetPos) - 1)
End If
End Function
This one takes two criteria, a range and a numeric value indicating what the cells in your range must NOT be. It will return a string value.
Call it from your worksheet through =GETPOS(A1:A10,1) and it should return A2,A7,A9
EDIT
If you are fine using a helper column you could do it like so:
Formula in B1:
=IF(A1<>1,"A"&ROW()&",","")
Formula in B11:
=LEFT(B1&B2&B3&B4&B5&B6&B7&B8&B9&B10,SUM(LEN(B1:B10))-1)
Enter through CtrlShiftEnter
Note: If you don't want to use a helper column you'll have to use TRANSPOSE() to 'load' an array of text values but it involves manual labour and IMO you'll surpass your goal.
I'm developing a VBA function for Excel. It will take input parameters of an integer (we'll call it ref_num), and a range. It will search through the range, looking for ref_num as the value of a cell. When it finds ref_num (which may or may not be present), it will go to the second row of the column that ref_num is in, and store that value as a string in the return variable (the value is a date, and 1-31 each have their own column). Every time ref_num is found in a column, the value in the second row will be appended to the return string.
Slightly more concrete example:
ref_num is 2, and 2 occurs in columns A, B, and C. The values in A2, B2, and C2 are 1, 2, and 3, respectively, so the function must return "1, 2, 3".
This is my pseudo-code, but I need some help filling in the blanks...
Note that this currently does not work, and that the algorithm is very much brute force. I just want to get something working.
Function GetDays(ref_num As Integer, range_o_cells As Range) As String
Dim num_dates As Integer
Dim day As String
Set num_dates = 0
'iterate through all the cells and see if the value is ref_num
For Each c In range_o_cells
If c.Value = ref_num Then
'get the cell's column, then reference the second row and get the value. Note that this will be an int that we need to convert to a string
'I seriously doubt the following line will work
day = CStr(Cells(c.Column, 2).Value)
'Once you have the value, append it to the return value
If num_dates = 0 Then
'This is the first value we've found, so we don't need to prepend a comma
GetDays = day
num_dates = 1
Else
'This is probably not valid VBA syntax...
GetDays = GetDays & ", " & day
End If
Next c
End Function
Note that currently, if I call it like this: =GetDays(AG39, $P$3:$W$500) where AG39 is the cell containing ref_num, I get #NUM!
There are multiple issues in your code
You don't use Set for integers
Missing an End If
As you suspected, your indexing into Cells is iffy
You should build your return string into day and assign it to the function in one place
Looping over a range is Slow
You should declare all variables
Better approach is to move the data to a variant array, and loop that. Also include the header data in the range passed to range_o_cells (I'm guessing thats $P$1:$W$500)
Here's your code refactored
Function GetDays( _
ref_num As Long, _
range_o_cells As Range, _
Optional Sep As String = ", ") As String
Dim dat As Variant
Dim rw As Long, col As Long
Dim day As String
dat = range_o_cells.Value
For col = 1 To UBound(dat, 2)
For rw = 3 To UBound(dat, 1)
If dat(rw, col) = ref_num Then
day = day & dat(2, col) & Sep
End If
Next rw, col
If Len(day) > 0 Then day = Left$(day, Len(day) - Len(Sep))
GetDays = day
End Function
I would like to ask your help to resolve an issue I'm facing building an algorithm.
Available data
We have a table of data with 10 rows and 30 columns.
Each row corresponds to the variable linkied to a specific parameter (for example weight of Apples, weight of Pears,weight of Oranges... Paramter10)
Each column corresponds to the value the variable can take.
This how we have 30 possible values for each variable/parameter.
Goal of the algorithm
Give the sum of all possible combinations of these parameters (Apples+Pears+ Oranges+...+Paramter10), normally there should be 30^10 possible combinations
Description of my version of the algorithm
It is to create an array of 10, let's say, indexes. Each index is corresponding to the variable/parameter. Each index is filled by one out of 30 possible values.
Create a loop passing through the columns in the same row. Each value of the cell in the table is placed in the corresponding index (row 1, for index 1)
Each change corresponds to the next available value (on the right) in the same row but with a different column. While the column is terminated, the algorithm starts to do the same for the next row.
Every change in the array varlues has to give as result a new sum, copied in a new cell somewhere in the table.
Sub AlgoSum()
Dim rw As Long, column1 As Long
Dim Sum1(9) As Long 'The array with 10 index 'First difficulty is to find a way how to synchornize the sum with every change in array Sum1.
Do While i <= 8 'My version is to place it in first but not sure it is right
For rw = 1 To 9
For column1 = 1 To 30
If Not IsEmpty(Worksheets("Sheet1").Cells(rw, column1)) Then 'verify that the cell is not empty
Sum1(i) = Worksheets("Sheet1").Cells(rw, column1).Value
Worksheets("Sheet1").Cells(rw+30, column1+30).Value = Application.WorksheetFunction.Sum(Sum1)
Else 'normally if it is empty it should move to the next value but I didn't find the appropriate way to do it, this why I thought about this compromise
Sum1(i) = 0
Worksheets("Sheet1").Cells(rw+30, column1+30).Value = Application.WorksheetFunction.Sum(Sum1)
End If
Next column1
Next rw
i = i + 1
Loop
End Sub
This code is not giving the appropriate results. It's not giving the sum for every change in the array, and it's not making the change in the array in the way it has to.
I've seen possible solution with a recursive, but didn't find the right way to do it.
Some help is very welcome!
Thank you in advance!
Try this. It's recursive (although it does use a loop as well).
Use the constants at the top to adjust to your data.
Try it with a few rows and a few columns.
The current constants assume that your data starts in cell A1, and that there are 3 "variables" and 2 "values".
It will print the results to the immediate window as it goes, but you could certainly adjust it to store in an array or print to another sheet.
' number of "variables"
Const ROWS As Integer = 3
' number of "values"
Const COLS As Integer = 2
' upper left cell of table
Const CELL_UPPER_LEFT As String = "A1"
' Recursive method to sum the values of all the combinations of all the rows.
' Method will print the result in the immediate window for now.
' #param row_number Integer Number of rows of data
' #param sum Double Running sum of previous rows
Private Sub recursiveSum(ByVal row_number As Integer, ByVal sum As Double)
' if we've reached the bottom, then print the result and go back up
If row_number = ROWS Then
Debug.Print sum
Exit Sub
End If
' loop over the number of columns
Dim col_number As Integer
For col_number = 0 To COLS - 1
' make a recursive call, increasing the sum by the current row and increasing the row number by 1
recursiveSum row_number + 1, Range(CELL_UPPER_LEFT).Offset(row_number, col_number).Value + sum
' when we return from the recursive call we will be here - ready to start the next time through the loop
Next
End Sub
' Wrapper function for the recursive method.
Public Sub recursiveSumWrapper()
' make the initial recursive call
recursiveSum 0, 0
End Sub
Thank you really for your reply. Your solution is working perfectly with debug.print!
I was able to add an automatic dimensioning of the table:
' number of "variables"
Dim ROWS As Integer
' number of "values"
Dim COLS As Integer
' upper left cell of table
Const CELL_UPPER_LEFT As String = "A1"
ROWS = Worksheets("Sheet1").UsedRange.ROWS.Count
COLS = Worksheets("Sheet1").UsedRange.Columns.Count
a = (ROWS) ^ (COLS) 'The number of possible combinations
However I spent all the morning to change debug.print for another option:
If row_number = ROWS Then
Debug.Print sum
Exit Sub
End If
For a more convenient possibility for the following use of obtained data
If row_number = ROWS Then
For i = 1 To a
Worksheets("Sheet2").Cells(20, a).Value = sum
Exit Sub
Next
End If
As I understand, it's not the right place to use a loop, because I get the error: «Subscript out of range». My intuition is telling me it's ROWS which doesn't correspond to a.
Do you have any idea about this detail? Is it possible to use debug.print information directly?
I'm trying myself on vba with little success. I would like to achieve a simple function that sums the content of a range of cells based on the beginning of the year till today. Unfortunately, I get back a "circular reference" error when I call the function, and I just can't see why. Any help will be appreciated.
Public Function til2day(r As Integer) As Long ''supposed to receive cell("row") as parameter
Dim c As Integer
Dim c1 As Integer
Dim c_here As Integer
Application.Volatile True
c_here = ActiveCell.Column
c = 0
c1 = 34 ''column contains 1/1/2013 date
Range("AH4:OM4").Activate ''contains a timeline
Do While ActiveCell.Offset(0, c).Value <> Date
c = c + 1
Loop
If ActiveCell.Offset(0, c).Value = Date Then
c = ActiveCell.Offset(0, c).Column
End If
til2day = Application.WorksheetFunction.Sum(Range(Cells(r, c1).Address, Cells(r, c).Address))
Range(Cells(r, c_here).Address).Activate
End Function
It is a really bad idea to use "activate" in a function; I can't explain exactly why this is, except that you are changing the selection of the cell during the calculation. In the following scenario this is going to cause a problem:
multiple cells are being calculated with this function, and
you use `Application.Volatile`, and
you refer to the active cell inside your function, and
you allow multi-threaded calculation,
Things will not happen in the order you expect, and at some point the active cell will be different than you thought. Function ends up referring to the cell it's in, and you have a circular reference. This doesn't happen when you run the debugger since it by definition runs as a single thread - which is why you can't find the problem then...
Here is a suggested rewrite of your function - it doesn't do any activating of cells, but attempts to maintain the same functionality:
Public Function til2day(r As Integer) As Long ''supposed to receive cell("row") as parameter
Dim c As Integer
Dim c1 As Integer
Dim dateRange as Range
Dime dateCell as Range
Application.Volatile True
c = 0
c1 = 34 ''column contains 1/1/2013 date
set dateRange = Range("AH4:OM4")
For Each dateCell in dateRange
If dateCell.Value = Date Then Exit For
Next dateCell
c = dateCell.Column
til2day = Application.WorksheetFunction.Sum(Range(Cells(r, c1).Address, Cells(r, c).Address))
End Function
Note: I attempted to reproduce the functionality of your function - but without a good example of the worksheet you are using, and the values you are expecting to return, it's hard to test. Please try to run this on your worksheet - and let me know if things don't work as you expected.
Note also that the SUMIF function could be used with good effect:
=SUMIF(range, criteria, sum_range)
In your case, use
=SUMIF($AH$4:$OM$4, "<=" & NOW(), $AH18:$OM18)
Where "18" is whatever row you need it to be (and when you drag the formula to a different row, it will continue to refer to the date row because of the $4 absolute reference, but calculate the sum for a different row because of the relative row reference in $AH18:$OM18.
An example of the use of this function (simplified range...)
As you can see, the function is summing columns C through F only since I did this on June 15th.
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.