VBA sum each column in another workbook - vba

Hello,
I want to sum each column in another workbook (see example) ,
i did the code below :
Function sumrange(rng As Range)
Summ = 1
For Each cell In rng
Do While cell <> ""
Summ = Summ + cell.Value
Loop
Next
sumrange = Summ
End Function
sub test()
x = sumrange(Workbooks("Clients").Worksheets("Numbers").Range("A:A"))
thisworkbook.worksheets("Result").cells(1,1)=x
MsgBox x
end sub
but i didn't know how to loop throught each column (There will be more that 2 columns) and it blocks because the first cell contain a string : error , how can i define that it should begun from the second cell to end ?

Use SpecialCells method of Range object to sum over numbers only
Should numbers be "constants" (e.g.: not deriving from formulas) only, then use:
Function sumrange(rng As Range) As Double
sumrange = WorksheetFunction.Sum(rng.SpecialCells(xlCellTypeConstants, xlNumbers))
End Function
Should numbers come from formulas only, then use:
Function sumrange(rng As Range) As Double
sumrange = WorksheetFunction.Sum(rng.SpecialCells(xlCellTypeFormulas, xlNumbers))
End Function
Finally, could numbers be both constants or coming from formulas then use:
Function sumrange(rng As Range) As Double
On Error Resume Next
sumrange = WorksheetFunction.Sum(rng.SpecialCells(xlCellTypeFormulas, xlNumbers))
sumrange = sumrange + WorksheetFunction.Sum(rng.SpecialCells(xlCellTypeConstants, xlNumbers))
End Function

You can use the IsNumeric function to decide whether to add the cell's value to the sum:
Function SumRange(rng As Range)
Dim dblSum As Double
Dim rngCell As Range
For Each rngCell In rng
If VBA.IsNumeric(rngCell.Value) Then
dblSum = dblSum + CDbl(rngCell.Value)
End If
Next rngCell
SumRange = dblSum
End Function
That might be quite slow if you are iterating over all the cells in column A and there are only a handful of entries. The following enhancement also checks if the cell IsEmpty and quits the loop if that's the case - otherwise, empty cells may evaluate to zero:
Function SumRange(rng As Range)
Dim dblSum As Double
Dim rngCell As Range
For Each rngCell In rng
If VBA.IsEmpty(rngCell.Value) Then
Exit For
ElseIf VBA.IsNumeric(rngCell.Value) Then
dblSum = dblSum + CDbl(rngCell.Value)
End If
Next rngCell
SumRange = dblSum
End Function

Start at the second cell and specify the column in the function and call like this.
Function sumrange(rng As Range, c as integer)
dim summ as integer
dim I as integer
dim cell as integer
Summ = 1
'Wouldn't you want to start at 0 for your sum?
for i = 2 to 50,000
cell = rng.cells(i,c)
Summ = Summ + cell
Next
sumrange = Summ
End Function
sub test()
x = sumrange(Workbooks("Clients").Worksheets("Numbers").Range("A:A"),'column#')
thisworkbook.worksheets("Result").cells(1,1)=x
MsgBox x
end sub

You can sum range without looping , using the built-in sum function
example:
Sub test()
Dim LastRow As Long
'open the other workbook , clients
Set book = Workbooks.Open("clients.xlsx")
'get last raw starting from B2 down to the last filled cell
LastRow = book.Worksheets("Numbers").Range("B2").End(xlDown).Row
Debug.Print LastRow
Set range2 = book.Worksheets("sheet1").Range("B1:B" & LastRow)
sum1 = Application.WorksheetFunction.Sum(range2)
Debug.Print sum1
'write result to active sheet
ThisWorkbook.Worksheets("result").Cells(1, 1) = sum1
End Sub

Related

VBA Rows.Count in Selection

I'm looking to work out how many rows a user has selected to be displayed at the top of the sheet next to an action button, I.e. Button says "Generate Email" and next to it says "x items selected".
As this is updated everytime the selection is changed, I have the following code:
Private Sub Worksheet_SelectionChange(ByVal Target As Range)
Sheet1.Range("E1") = Target.Rows.Count & " items selected"
End Sub
This works fine if the user selects consecutive rows, for e.g. 7:10 returns 4.
My problem is if a user selected rows 7, and 10. It would only return 1 (the rows in the first part of the selection).
From what I've found, there is no way of just getting this value from a property, but I can't get my head around how to iterate through all parts of the selection/target and calculate the sum of rows. Then there is also the possibility that the user selects say A7, C7, and A10. A7 and C7 relate to the same item, so this should only really be treated as one, not two, which I think my hypothetical code would do...
Has anyone tried to achieve this before and been successful or could point me in the direction of some properties which may help? I tried a separate function to achieve it, but that wasn't working either.
Private Sub Worksheet_SelectionChange(ByVal Target As Range)
Sheet1.Range("E1") = getRowCount(Target) & " items selected"
End Sub
Function getRowCount(selectedRanges As Ranges)
rowCount = 0
For Each subRange In selectedRanges
rowCount = rowCount + subRange.Rows.Count
Next
getRowCount = rowCount
End Function
I think this will work. (Did when I tried it.)
Private Sub Worksheet_SelectionChange(ByVal Target As Range)
'Create a range containing just column A
Dim subRange As Range
Dim r As Range
For Each subRange In Target.Areas
If r Is Nothing Then
Set r = subRange.EntireRow.Columns(1)
Else
Set r = Union(r, subRange.EntireRow.Columns(1))
End If
Next
'Count how many cells in the combined column A range
Sheet1.Range("E1") = r.Cells.Count & " items selected"
End Sub
You need to count the rows in each Area the user has selected.
https://msdn.microsoft.com/en-us/vba/excel-vba/articles/range-areas-property-excel
Private Sub Worksheet_SelectionChange(ByVal Target As Range)
Dim rArea As Range
Dim lCount As Long
For Each rArea In Selection.Areas
lCount = lCount + rArea.Rows.Count
Next rArea
Sheet1.Range("E1") = lCount
End Sub
Sub NumberOfRowsSelected()
Dim vMatch As Variant, aRows() As Long, r As Range, x As Long
ReDim Preserve aRows(x)
aRows(x) = 0
For Each r In Selection.Cells
vMatch = Application.Match(r.Row, aRows, 0)
If IsError(vMatch) Then
x = x + 1
ReDim Preserve aRows(0 To x)
aRows(x) = r.Row
End If
Next r
MsgBox UBound(aRows)
End Sub
Revised Code Converted as Function
Sub NumberOfRowsSelected()
MsgBox RowsCount(Selection)
End Sub
Function RowsCount(rRange As Range) As Long
Dim vMatch As Variant, aRows() As Long, r As Range, x As Long
ReDim Preserve aRows(x)
aRows(x) = 0
For Each r In rRange.Cells
vMatch = Application.Match(r.Row, aRows, 0)
If IsError(vMatch) Then
x = x + 1
ReDim Preserve aRows(0 To x)
aRows(x) = r.Row
End If
Next r
RowsCount = UBound(aRows)
End Function
A different method, building up a string of checked rows seems pretty straight-forward to avoid double counting. See comments for details:
Function getRowCount(rng As Range) As Long
Dim c As Range
' Keep track of which rows we've already counted
Dim countedrows As String: countedrows = ","
' Loop over cells in range
For Each c In rng
' Check if already counted
If Not InStr(countedrows, "," & c.Row & ",") > 0 Then
' Add to counted list
countedrows = countedrows & c.Row & ","
End If
Next c
' Get number of rows counted
Dim rowsarr() As String: rowsarr = Split(countedrows, ",")
getRowCount = UBound(rowsarr) - LBound(rowsarr) - 1
End Function
Private Sub Worksheet_SelectionChange(ByVal Target As Range)
Dim cell As Range
Dim i, currentRow As Long: i = 0
'get row of first cell in range
currentRow = Target.Cells(1, 1).row
For Each cell In Target
'if row is different, then increase number of items, as it's next item
If Not currentRow = cell.row Then
i = i + 1
currentRow = cell.row
End If
Next cell
Range("E1").Value = i
End Sub

How can I write a Macro to sum a Column that has #N/A values in it?

I am looking to create a macro that sums a column that will always contain a few #N/A values, the number of cells in the column will change daily and so will the position of the #N/A values.
I also want to put the result in the first empty cell below that last value i.e. the first empty cell at the bottom of the column.
This is as far as I could get:
Option Explicit
Sub Total()
'
' Total Macro
Dim rg As Range
Dim Cell As Range, Target As Range
Set Target = Range("D65536").End(xlUp)
For Each Cell In Target
Cell.Errors(xlEvaluateToError).Ignore = True
Set rg = Range("D65536").End(xlUp)
rg.Offset(1, 0).Value = "=sumif(D1:D100 " <> 0 & rg.Row & ")"
Range("D65536").End(xlUp).Select
Selection.Font.Bold = True
Next
End Sub
Im using D1:D100 but only because 100 will cover the amount of cells in my Column. This is giving a strange result of more #N/As followed by a True. I dont think I should be trying to use a formula within the code.
Thanks
I would loop through and check if the value is numeric then add it to the total:
Sub mysum()
Dim lstRow As Long
Dim ws As Worksheet
Dim i As Long
Set ws = Worksheets("Sheet5") 'Change to your sheet
With ws
lstRow = .Cells(.Rows.Count, 4).End(xlUp).Row
For i = 1 To lstRow
If IsNumeric(.Cells(i, 4)) Then
.Cells(lstRow + 1, 4) = .Cells(lstRow + 1, 4) + .Cells(i, 4)
End If
Next i
End With
End Sub
You could use SpecialCells():
Sub total()
With Worksheets("SheetName") '<--| Change to your actual sheet name
With .Range("D1", .Cells(.Rows.Count, 4).End(xlUp))
.Cells(.Rows.Count,1).Offset(1).Value = WorksheetFunction.Sum(.SpecialCells(xlCellTypeConstants, xlNumbers))
End With
End With
End Sub

Row increment in paste reference

As below code, the input fields are B3, C18, C20, C22, C24. (Fixed input fields)
These data going to paste starting from B41:F41.
Problem is, how do I make the increment of the output reference B41:F41 as row +1 each time the macro is being used? Consider as, if there are data in B41:F41, then the paste range would be B42:F42 and so on.
Private Sub CommandButton2_Click()
Range("B3").Copy Range("C41")
Range("C18").Copy Range("B41")
Range("C20").Copy Range("D41")
Range("C22").Copy Range("E41")
Range("C24").Copy Range("F41")
If there will be no empty values copied to ColB then:
Private Sub CommandButton2_Click()
Dim sht As WorkSheet
Set sht = ActiveSheet
With sht.Cells(sht.Rows.Count, 2).End(xlUp).Offset(1, 0).EntireRow
sht.Range("B3").Copy .Cells(3)
sht.Range("C18").Copy .Cells(2)
sht.Range("C20").Copy .Cells(4)
sht.Range("C22").Copy .Cells(5)
sht.Range("C24").Copy .Cells(6)
End With
I suggest transfering your data to an array first, then transfering this array to the required section of the worksheet.
Sub Copy_Paste_Macro()
Dim CopyRange As Range, c As Range
Dim HoldArray() As Variant
Dim n As Long, i As Long
With Worksheets("Sheet1")
'Define Non-Contiguous range
Set CopyRange = Range("B3, C18, C20, C22, C24")
'Count of cells in range
n = CopyRange.Cells.Count
'Resize the array to hold the data
ReDim HoldArray(1 To n)
n = 1
'Store the values from that range into array
For Each c In CopyRange.Cells
HoldArray(n) = c.Value
n = n + 1
Next c
End With
'Paste array as contiguous range
If Worksheets("Sheet1").Range("B41") = "" Then
Worksheets("Sheet1").Range("B41").Resize(1, UBound(HoldArray)).Value = HoldArray
Else
Range("B" & Rows.Count).End(xlUp).Offset(1, 0).Resize(1, UBound(HoldArray)).Value = HoldArray
End If
End Sub
you could
gather input values in an array
write them in one shot
like follows:
Option Explicit
Private Sub CommandButton2_Click()
With Worksheets("SheetName") '<--| change "SheetName" to your actual sheet name
.Cells(WorksheetFunction.Max(41, .Cells(.Rows.COUNT, 2).End(xlUp).Offset(1).row), 2).Resize(, 5) = GetValues(.Range("C18,B3,C20,C22,C24")) '<--| list input cells addresses in wanted output order
End With
... other code
End Sub
Function GetValues(rng As Range) As Variant
Dim cell As Range
Dim iCell As Long
ReDim vals(1 To rng.COUNT) As Variant
For Each cell In rng
iCell = iCell + 1
vals(iCell) = cell.Value
Next cell
GetValues = vals
End Function

Why is 005, 05, and 5 same for excel vba even it is converted to text?

I have a code that would alert me(no mather how) if there is a double,tripple... input of INVOICE number(if I try to input an Invoice that has been already stored) in same column. As there are invoices number like 00202, or 0-505, and also 202 and 505, it alerts me like this is the same numbers for program.
Private Sub CommandButton1_Click()
Dim dataRange As Range, oneCell As Range
Set dataRange = ActiveSheet.Range("b7:b15") 'short range for testing
For Each oneCell In dataRange
If 1 < Application.CountIf(dataRange, oneCell.Value) Then
oneCell.Offset(0, 1) = "Double!"
End If
Next oneCell
End Sub
That's how COUNTIF works with numeric data (it will do the same in a cell). You could use SUMPRODUCT instead:
Private Sub CommandButton1_Click()
Dim dataRange As Range, oneCell As Range
Set dataRange = ActiveSheet.Range("b7:b15") 'short range for testing
For Each oneCell In dataRange
' if cell is empty or contains "" don't do anything
If onecell.Value <> vbNullString Then
' evaluate simply evaluates the formula string passed to it.
' and SUMPRODUCT doesn't have the same number conversion issue as COUNTIF
If 1 < ActiveSheet.Evaluate("SUMPRODUCT(--(" & dataRange.Address & "=" & oneCell.Address & "))") Then
oneCell.Offset(0, 1) = "Double!"
End If
End If
Next oneCell
End Sub

Count number of different cells in VBA

I want to count no of different cells which are selected using VBA.
Consider if we select five distinct cells - D5, C2, E7, A4, B1.
Is there a way I can count these number of cells.
Secondly how can I retrieve data in these cells. Lets say I want to store it in an array.
Thank you for the help.
Dim rngCell as Range, arrArray() as Variant, i as integer
Redim arrArray(1 to Selection.Cells.Count)
i = 1
For each rngCell in Selection
arrArray(i) = rngCell.Value
i = i + 1
Next
Looks like you got it mostly figured out, but here is something to load it into an array if you want it:
Public Sub Example()
Dim test() As Variant
test = RangeToArray(Excel.Selection, True)
MsgBox Join(test, vbNewLine)
End Sub
Public Function RangeToArray(ByVal rng As Excel.Range, Optional ByVal skipBlank As Boolean = False) As Variant()
Dim rtnVal() As Variant
Dim i As Long, cll As Excel.Range
ReDim rtnVal(rng.Cells.Count - 1)
If skipBlank Then
For Each cll In rng.Cells
If LenB(cll.Value) Then
rtnVal(i) = cll.Value
i = i + 1
End If
Next
ReDim Preserve rtnVal(i - 1)
Else
For Each cll In rng.Cells
rtnVal(i) = cll.Value
i = i + 1
Next
End If
RangeToArray = rtnVal
End Function
Thankfully I got a way around it by doing - Selection.Cells.Count
It returns me the cell count for selected cells.
But I am still stuck with dynamically assigning this value to an array as in ---
I = Selection.Cells.Count Dim ValArr(I)