Which Loop function would better suit to read the cells and get the total? - vba

I have a table with a few cells in blank and the other ones filled in. The ones filled I read as value 12 which I'll later sum up for each row and add to the last column as Total.
At first, I thought on doing a function to read the cells filled in as value = 12 and add to a counter, then create a sub to put the total on the last column. This is the idea of function I had in mind.
Public Function TestValue(ByRef rRng as Range) As Long
Dim rCell as Range
Dim lCont as Long
For Each rCell in rRng.Cells
If isEmpty(rCell.Value) = False then
lCont = lCont + 12
Else
End If
Next rCell
TestValue = lCont
End Function
I came up with a Do While just so you can have an idea on what I'm trying to do.
Public Sub Test()
Do
ActiveSheet.Range("I2:I8").Value = TestValue
Loop While isEmpty(Range("I2:I8")) = False
End Sub

For Each is the less human-error prone loop. I find it's easy to accidentally write an infinite loop in the Do While format For example norie mention's in the comments that this example should get stuck in an infinite loop: IsEmpty(Range("I2:I8")) won't work here - it will always return False

Does it need to be VBA? You could do this with a simple formula.
=COUNTA(I2:I8)*12

Related

How to exclude the #VALUE! or #DIV/0! in a column for calculating average using VBA

I have a situation to calculate the average value for a column (this column have many rows with numbers and some rows with #DIV/0! and #VALUE!).
I have a macro written for calculating average function. If its full of numbers, then it easily calculates the average of the column, but if has some #VALUE! or #DIV/0! in a cell. then it returns blank cell. How can I exclude the #VALUE! and #DIV/0! error and to take average for only numbers.
I have over 5K files to calculate the average.
Private Function data As Boolean
Dim Avg_velocity As String
Dim Avg_length As String
Avg_velocity = Application.WorksheetFunction.Average(Sheets("Data").Range("K5:K650"))
Avg_length = Application.WorksheetFunction.Average(Sheets("Data").Range("I7:I607"))
Sheets("Log").Range("A2:AI2").Insert
Sheets("Log").Cells(2, "AA").value = Avg_velocity
Sheets("Log").Cells(2, "AB").value = Avg_length
End Function
Something like this should work quite ok:
Option Explicit
Public Sub TestMe()
Dim myRng1 As Range
Dim myCell As Range
Dim myRng2 As Range
Set myRng1 = Range("A1:A5")
For Each myCell In myRng1
If Not IsError(myCell) Then
If Not myRng2 Is Nothing Then
Set myRng2 = Union(myRng2, myCell)
Else
Set myRng2 = myCell
End If
End If
Next myCell
If Not myRng2 Is Nothing Then myRng2.Select
End Sub
It goes through the range and kindly picks up only the cells, which are not errors:
You can also just use AverageIf to ignore errors by providing a criteria of a really large number, like this:
Avg_velocity = Application.WorksheetFunction.AverageIf(Sheets("Data").Range("K5:K650"), "<9E307")
Should also be able to use aggregate function
=Application.worksheetFunction.Aggregate(1,6,range)

Making a CountRows function in Excel

I am trying to make a simple countRows function that will count the number of cells I have in a dynamic range. Basically if I have values in cells, say B2:B500, the count would return 499. However next time around values are in cell B2:B501, the count would return 500. But you wouldn't have to do anything to the cell in which you typed in the formula.
I thought if I reference the cell as a Variant, then any value could be accepted. Then find the Address of that cell and return the Count of a Range. But I get a #Value error.
Public Function countRows(startRange As Variant)
Dim rng As Range
Set rng = startRange.Address
If IsEmpty(Range(rng, rng.End(xlDown))) = True Then
countRows = 1
Else
countRows = Range(rng, rng.End(xlDown)).Rows.Count
End If
End Function
This is the code I have used for many years successfully under many different worksheets. It handles many cells, singular cells or empty cells.
Public Function CountRows(ByRef r As Range) As Long
If IsEmpty(r) Then
CountRows = 0
ElseIf IsEmpty(r.Offset(1, 0)) Then
CountRows = 1
Else
CountRows = r.Worksheet.Range(r, r.End(xlDown)).Rows.count
End If
End Function
Public Function CountCols(ByRef r As Range) As Long
If IsEmpty(r) Then
CountCols = 0
ElseIf IsEmpty(r.Offset(0, 1)) Then
CountCols = 1
Else
CountCols = r.Worksheet.Range(r, r.End(xlToRight)).Columns.count
End If
End Function
It's not entirely clear what you are looking for, when you mentioned there are values in cells "B2:B500" and the count should return 499, as there could be a few possible scenarios:
You simply want to count the rows in the range "B2:B500". The code will be:
Range("B2:B500").Rows.Count
You want to count the non-blank cells in the range "B2:B500". In that case, as suggested in the comments:
WorksheetFunction.CountA(Range("B2:B500"))
As indicated in your code rng.End(xlDown), you probably want to the count continuous non-blank cells starting with the range "B2" in the overall range "B2:B500". You may create a function like this:
Public Function countRows(rng As Range) As Long
Dim rw As Range
For Each rw In rng
If IsEmpty(rw) Then Exit For
countRows = countRows + 1
Next
End Function
Clarification:
Based on subsequent comments, I thought it's worth explaining why the variable "countRows" wasn't initialized by adding a line countRows = 0.
Certain programming languages like assembly language, C, C++ require explicit initialization. This was intentionally so designed due to the philosophy in which conflicts between performance and safety were generally resolved in favor of performance.
However, such is not the case with other programming languages like VBA or Java.
Speaking about VBA, during macro run, all the variables are initialized to a value. A numeric variable is initialized to zero, a variable length string is initialized to a zero-length string (""), and a fixed length string is filled with the ASCII code 0. Variant variables are initialized to Empty. An Empty variable is represented by a zero in a numeric context and a zero-length string ("") in a string context.
Therefore a separate line of code countRows = 0 wasn't added in the above code block.
While coding, one need to keep this in perspective as the same might not be true for other languages.

How do I find a value in a row and return the column number with VBA?

My excel sheet is filled with zeroes except for one cell in every row.
I want to find that cell and return the column.
For example: In the cell T616 is a value other than 0. May it be -15400.
I want to find that cell(T616) based on the row(616) and have the column returned(T). May it even be in a MsgBox.
This is my result of many tries and long Google-sessions:
Public Function find_Column(lRange As Range, lValue As String) As Integer
Dim vCell As Range
For Each vCell In lRange.Cells
If vCell.Value = lValue Then
find_Column = vCell.Column
MsgBox (find_Column)
Exit Function
End If
Next vCell
End Function
I found this code somewhere and modified it a little bit, but I can't remember where. So thanks to the creator!
How do I search for a number other than 0?
I'm relatively new to VBA and don't really have an idea what I am doing. Sorry for my bad English (foreigner). I'd appreciate any help. Thank you!
Try this:
Public Function findNonZeroValueInColumn(lRange As Range) As Integer
Dim vCell As Range
For Each vCell In lRange.Cells
If vCell.Value <> 0 Then
find_Column = vCell.Column
Exit Function
End If
Next vCell
End Function
Sub ShowValue()
call MsgBox(findNonZeroValueInColumn(Range("A:A")))
End Sub
Remember that Functions are supposed to return values. Subs (Procedures) do not return values.
The <> symbol is the "Not Equal" Comparison. So if you wanted to check if a cell didn't equal 0, you would write
If vCell.Value <> 0 Then
... ' rest of code here
In your problem:
Public Function find_Column(lRange As Range) As Integer
Dim vCell As Range
For Each vCell In lRange.Cells
If vCell.Value <> 0 Then
find_Column = vCell.Column
MsgBox (find_Column)
Exit Function
End If
Next vCell
End Function
Also since you are only checking against 0, you wouldn't need the extra lValue As String argument.
As the other part of your question has been answered while I was typing, if you want to return the column letter instead of the number...
find_Column = Replace(Replace(vCell.Address, vCell.Row, ""), "$", "")
If this is what you want the function to return, you would have to change the type your function is returning to string

VBA Range.value function causing unexpected end

This must seem like a terribly simple question, but I cannot figure out why my functions are ending unexpectedly on the Range.value = val call. Perhaps I am missing something very basic, but I have tested these out and each one of them are failing to resolve to anything and I don't know how to capture the error.
Here is the initial function:
Function incrementCount(upper As Range, Summed As Range, ParamArray sums() As Variant)
Dim deduct As Integer
Dim summation As Integer
Dim elem As Variant
Dim i As Long
Dim temp As Range
up = upper.Value
summation = Summed.Value
'Initialize the starting points of the increments
For i = LBound(sums) To UBound(sums)
MsgBox IsObject(sums(i)) 'Prints out as an true
MsgBox TypeName(sums(i)) 'Prints out as Rnage
MsgBox sums(i).Value 'Prints out as 0
Set temp = sums(i)
MsgBox temp.Value 'Prints out as 0
Set temp = Summed
MsgBox temp.Value 'Prints out as 1 (which is correct)
temp.value = 3 'Errors here
MsgBox temp.Value 'Never makes it to this line
sums(i).Value = 1 'I have also tried this with the same result
Next i
<more code that is never reached>
End Function
I am at my wits end. I have searched MSDN, stackoverflow, and all the many excel forums and all of them show setting values to a range like this. I have even separated the setting of a range value to a different function like this:
Function testsub(thecell As Range, thevalue As Integer)
thecell.value = thevalue
End Function
Ultimately i would like to be able to do something like discussed in this article where I loop over a random assortment of ranges and will increment them. Any help at all would be greatly appreciated.
You have not specified how IncrementCount() is being called.
If your function is being called from a worksheet cell, then it is "bombing out" at the correct line. A UDF called from a cell cannot modify the contents of other cells, it can only return a value.

Copy cells in a row to another sheet considering a unique reference number

I an trying to extract data from sheet "Record" by matching an entered reference number in sheet "Form" with those numbers in column B of "Record." I was able to come up with the VB code below through command button click. However, it will only return a single value from sheet "Record" column i and coding for each will really be time consuming.
Private Sub CommandButton1_Click()
With Application.WorksheetFunction
Sheets("Form").Range("b:b") = _
.Index(Sheets("Record").Range("h:h"), .Match(Sheets("Form").Range("i13"), Sheets("Record").Range("b:b"), 0), 1)
End With
End Sub
I'm wondering if is it possible to copy values from sheet "Record" columns H-Q to sheet "Form" columns B-K if the reference number in cell I13 of sheet "Form" matches any value on column B of sheet "Record?" Because what i encounter most of the time is returning the entire row.
I would really appreciate any help. Thanks
It might be brute force, but I think the best way is to loop through the data like this:
'Find the last row of data
Public Function Get_Last_Row_Find(ByVal rngToCheck As Range) As Long
Dim rngLast As Range
Set rngLast = rngToCheck.Find(what:="*", searchorder:=xlByRows, SearchDirection:=xlPrevious)
If rngLast Is Nothing Then
Get_Last_Row_Find = rngToCheck.Row
Else
Get_Last_Row_Find = rngLast.Row
End If
If Get_Last_Row_Find <= 1 Then
Get_Last_Row_Find = 2
End If
End Function
Public Sub CommandButton1_Click
x = Get_Last_Row_Find(Sheets("Record").Range("B:B")
for i = 1 to x
if Sheets("Form").Range("I13").Value = Sheets("Record").Range("B:B").Offset(i-1,0).Value then 'match
Worksheets("Record").Range("H"&i&":Q"&i).Copy _
destination:=Worksheets("Form").Range("B"&i&":K"&i)
next i
Note the two methods of "offsetting": you can use the .Offset method or you can use a variable and concatenate it within the Range("") text.
Code not tested.