VBA referencing data from another sheet is not working - vba

would appreciate the help here as this is something that I come across a lot when I'm coding. I know there must be a way to do this without selecting or activating that sheet and I really cannot see why my code isn't working.``
WHAT IM ATTEMPTING....Sheet(4) column L contains various asset types, column D contains market values. I am looping through L to sum only market values where "Equities" is the asset type. I then wish to display this sum in a cell on sheet(2).
PROBLEM...the below code only works when I have sheet(4) selected and then run it, but sheet(2) is the one that will be selected before running
WHAT HAVE I TRIED....as well as the below, I have tried with Worksheets(4)....end with but same problem
Dim LastRow As Integer
Dim EquitySum As LongLong
LastRow = 91
EquitySum = 0
For i = 2 To LastRow
If Worksheets(4).Cells(i, 12).Value = "Equities" Then
EquitySum = EquitySum + Cells(i, 4).Value
Else
EquitySum = EquitySum + 0
End If
Next

Related

Runtime error = '1004'

I'm trying to write to a program that will indicate an alphabet grade to each student in accord with their marks. Basically, the indicated grade will be shown in column C. From the data table, John will receive a grade "A" as his mark is higher than or equal to 85. Plus, the cell of his grade will be filled green and center aligned. However, for students whose mark is lower than 35, the students will receive a grade "F" in column C. Moreover, the whole row(Column A,B,C) of that student will be filled red. When I try to run the program, I get a
run time error = '1004'.
Does anyone know how to solve the problem?
**Also is my method of counting the row and column with data right?
Sub Green()
Dim Mark As Integer
Dim c As Integer
Dim r As Integer
Dim i As Integer
Dim j As Integer
r = Cells(Rows.Count, 1).End(xlUp).row
c = (Selection.End(xlToRight).row) / r
For i = 0 To r
For j = 0 To c
Mark = Range(i, "B").Value
If Mark >= 85 And Mark <= 100 Then
Range(i, "C").Value = "A"
Range(i, "C").Interior.Color = RGB(0, 255, 0)
Range(i, "C").HorizontalAlignment = xlCenter
ElseIf Mark >= 0 And Mark <= 35 Then
Range(i, "C").Value = "F"
Range(i, j).Interior.Color = RGB(255, 0, 0)
Range(i, "C").HorizontalAlignment = xlCenter
Else
End If
Next j
Next i
MsgBox ("Rows = " & r)
MsgBox ("Columns =" & c)
End Sub
My recommendation is that you handle conditional formatting to the application's built-in features. If you disagree, feel free to modify this code.
Function LastRow(ByVal ws As Worksheet, Optional ByVal col As Variant = 1) As Long
With ws
LastRow = .Cells(.Rows.Count, col).End(xlUp).row
End With
End Function
Sub assignLetterGrade()
Dim ws As Worksheet, i As Long, ltr As String
Set ws = ThisWorkbook.Worksheets(1)
For i = 1 To LastRow(ws, "B")
Select Case ws.Cells(i, "B")
Case 85 To 100
ltr = "A"
Case 75 To 84
ltr = "B"
Case 65 To 74
ltr = "C"
Case 55 To 64
ltr = "D"
Case 0 To 54
ltr = "F"
Case Else
MsgBox "Unknown Grade! Aborting!"
Exit Sub
End Select
ws.Cells(i, "C").Value = ltr
Next
End Sub
For conditional Formatting, you could try these steps:
Select the entire column of which you plan to format conditionally
(in my example, it's column C.
Let's create a new rule.
Now enter your Rules, one at a time. In this example, I am highlighting all cells containing "A" in green.
The question remains. Why not format within VBA?
The first thing you should understand about VBA is that it's very inefficient. While minimal code can take fractions of a second, once your loops get larger and larger the more you will notice that it takes longer to complete its task.
Also, you run into issues where your code is not pristine and may have minor errors. Minor errors can turn into major errors when you start building your project (example: using ActiveSheet comes to mind. It might work for your small project, but next thing you know you are applying formats to the wrong sheet, referencing the wrong cell's value, so on and so forth).
If you can avoid VBA, then do it. Take advantage of Microsoft's excellent conditional formatting UI. It's very efficient and much easier to manage.
Also, again this entire VBA project could have been done without using VBA at all. VBA is slow. You could have just as easily used formulas in column C to provide you with the letter grade.
A less-advanced version of a formula is to use nested If Statements.
=IF(B1>=85,"A", IF(B1>=75,"B", IF(B1>=65,"C", IF(B1>=55,"D","F"))))

Average with If condition

I want to calculate the average of the cells in column E, only for the rows that have 0 in column I. It needs an if condition and then perform the standard average line of code.. I am providing the code I have written to calculate the average for all cells in column E. This code needs editing to include this if condtion. If someone knows what to add to have this if condition I would appreciate it !
Also, I am providing a screenshot
lastrow = Cells(Rows.Count, "D").End(xlUp).Row
Range("C1").Formula = "=AVERAGE(E2:E" & lastrow & ")"
formula:
=AVERAGEIF(I:I,0,E:E)
or in vba:
WorksheetFunction.AverageIf(Range("I:I"), 0, Range("E:E"))
As far as I know you cannot do this with an excel function unless you make it an array function. Array functions are powerful but can be very slow at calculating. Here is a VBA solution using a VBA collection.
The answer you selected is definitely a more efficient way of getting the answer. But this code may be useful if you are wanting to manipulate those numbers in other ways since it puts them into a collection.
I made a VBA collection and added to it all values in E that corresponded to 0 values in D. Then I summed it into a variable called f and then divided it by the count of the collection. Then dropped it in the range you wanted.
Sub test()
Dim a As Collection
Dim lastrow As Integer
Set a = New Collection
lastrow = Cells(Rows.Count, "D").End(xlUp).Row
For x = 1 To lastrow
If Cells(x, 9) = 0 Then
y = Cells(x, 5).Value
a.Add (y)
End If
Next x
For Z = 1 To a.Count
f = a.Item(Z) + f
Next Z
Range("C1").Value = (f / a.Count)
End Sub

Summing a variable range excluding certain values VBA

I am new to VBA and am trying to create a report template for work. I also want to see how this would be coded for my own personal understanding instead of using a formula.
Put simply I have a variable set of values in column A and dates in column B. Column D is a variable range of dates (user input. I would like to have this as an array within my code.)
I would like to sum column A while excluding the dates specified in column D, and have this sum output into cell G1. I have attached a picture below.
Thanks in advance!
Picture of the sheet
Try out this code, this is self explanatory.
Sub sumVariables()
Dim i As Long, j As Long, sum As Long, match As Boolean
sum = 0
match = False
For i = 2 To Cells(Rows.Count, 1).End(xlUp).Row
For j = 2 To Cells(Rows.Count, 4).End(xlUp).Row
If Cells(i, 2) = Cells(j, 4) Then
match = True
End If
Next j
If match = False Then
sum = sum + Cells(i, 1)
End If
match = False
Next i
Cells(1, 7) = sum
End Sub
This code would work for n number of rows in your sheet and give the total in G1. Let me know if you need any help.

Sum Values based on unique ID

Just started a new job. I'm automating a month-end report and I'm new at VBA. Been googling most of my issues with success, but I've finally run into a wall. In essence I'm downloading some data from SAP and from there I need to build a report.
My question is: How to do a sumif function using loops in VBA?
Data pull:
Sheet1 contains a product code and purchase amounts (columns A & B) respectively. One product code can have several purchases (several rows with the same product code).
Steps so far:
I arranged the data sheet1 to be in ascending order.
Copied unique values for the product codes onto another sheet (sheet2). So Sheet2 has a list of all the products (in ascending order).
I want to get the sum of all purchases in sheet2 column B (per product code). I know how to do this using formulas, but I need to automate this as much as possible. (+ I'm genuinely interested in figuring this out)
This is what I did in VBA so far:
Sub Macro_test()
Dim tb As Worksheet
Dim tb2 As Worksheet
Dim x As Integer
Dim y As Integer
Dim lrow As Long
Set tb = Sheets("sheet1")
Set tb2 = Sheets("sheet2")
lrow = tb.Cells(Rows.Count, "A").End(xlUp).Row
For x = 2 To lrow
For y = 2 To lrow
If tb2.Cells(x, 1).Value = tb.Cells(y, 1).Value Then
tb2.Cells(x, 2).Value = tb.Cells(y, 2).Value
End If
Next y
Next x
End Sub
If i'm not mistaken, for each product_code in sheet2 col A, I'm looping through all the product codes in sheet1 and getting back the LAST value it finds, instead of the sum of all values... I understand why it doesn't work, I just don't know how to fix it.
Any help would be much appreciated. Thanks!
This statement overwrites the value of tb2.Cells(x, 2).Value at each iteration:
tb2.Cells(x, 2).Value = tb.Cells(y, 2).Value
Instead, I think you need to keep adding to it:
tb2.Cells(x, 2).Value = tb2.Cells(x, 2).Value + tb.Cells(y, 2).Value
But I don't like the looks of your double-loop which uses only one lrow variable to represent the "last row" on the two different worksheets, that could be causing some issues.
Or, in your loop do something like this which I think will avoid the duplicate sum. Still, assumes the second worksheet doesn't initially have any value in
' Base our lRow on Sheet2, we don't care how many rows in Sheet1.
lrow = tb2.Cells(tb2.Rows.Count, 1).End(xlUp).Row
Dim cl as Range
Set cl = tb.Cells(2,1) 'Our initial cell value / ID
For x = 2 to lRow '## Look the rows on Sheet 2
'## Check if the cell on Sheet1 == cell on Sheet2
While cl.Value = tb2.Cells(x,1).Value
'## Add cl.Value t- the tb2 cell:
tb2.Cells(x, 2).Value = tb2.Cells(x, 2).Value + cl.Offset(0,1).Value
Set cl = cl.Offset(1) '## Reassign to the next Row
Wend
Next
But it would be better to omit the double-loop and simply use VBA to do 1 of the following:
1. Insert The Formula:
(See Scott Holtzman's answer).
This approach is better for lots of reasons, not the least of which is that the WorksheetFunction is optimized already, so it should arguably perform better though on a small dataset the difference in runtime will be negligible. The other reason is that it's stupid to reinvent the wheel unless you have a very good justification for doing so, so in this case, why write your own version of code that accomplishes what the built-in SumIf already does and is specifically designed to do?
This approach is also ideal if the reference data may change, as the cell formulas will automatically recalculate based on the data in Sheet1.
2. Evaluate the formula & replace with values only:
If you prefer not to retain the formula, then a simple Value assignment can remove the formula but retain the results:
With .Range(.Range("B2"), .Range("A2").End(xlDown).Offset(, 1))
.FormulaR1C1 = "=SUMIF(Sheet1!C[-1]:C[-1],RC[-1],Sheet1!C:C)"
.Value = .Value 'This line gets rid of the formula but retains the values
End With
Use this approach if you will be removing Sheet1, as removing the referents will break the formula on Sheet2, or if you otherwise want the Sheet2 to be a "snapshot" instead of a dynamic summation.
If you really need this automated, take advantage of VBA to place the formula for you. It's very quick and easy using R1C1 notation.
Complete code (tested):
Dim tb As Worksheet
Dim tb2 As Worksheet
Set tb = Sheets("sheet1")
Set tb2 = Sheets("sheet2")
Dim lrow As Long
lrow = tb.Cells(tb.Rows.Count, 1).End(xlUp).Row
tb.Range("A2:A" & lrow).Copy tb2.Range("A2")
With tb2
.Range("A2").CurrentRegion.RemoveDuplicates 1
With .Range(.Range("B2"), .Range("A2").End(xlDown).Offset(, 1))
.FormulaR1C1 = "=SUMIF(Sheet1!C[-1]:C[-1],RC[-1],Sheet1!C:C)"
End With
End With
Note that with R1C1 notation the C and R are not referring to column or row letters . Rather they are the column and row offsets from the place where the formula is stored on the specific worksheet. In this case Sheet!C[-1] refers to the entire A column of sheet one, since the formula is entered into column B of sheet 2.
I wrote a neat little algorithm (if you can call it that) that does what you want them spits out grouped by totals into another sheet. Basically it loops through the first section to get unique names/labels and stores them into an array. Then it iterates through that array and adds up values if the current iteration matches what the current iteration of the nested loop position.
Private Sub that()
Dim this As Variant
Dim that(9, 1) As String
Dim rowC As Long
Dim colC As Long
this = ThisWorkbook.Sheets("Sheet4").UsedRange
rowC = ThisWorkbook.Sheets("Sheet4").UsedRange.Rows.Count
colC = ThisWorkbook.Sheets("Sheet4").UsedRange.Columns.Count
Dim thisname As String
Dim i As Long
Dim y As Long
Dim x As Long
For i = LBound(this, 1) To UBound(this, 1)
thisname = this(i, 1)
For x = LBound(that, 1) To UBound(that, 1)
If thisname = that(x, 0) Then
Exit For
ElseIf thisname <> that(x, 0) And that(x, 0) = vbNullString Then
that(x, 0) = thisname
Exit For
End If
Next x
Next i
For i = LBound(that, 1) To UBound(that, 1)
thisname = that(i, 0)
For j = LBound(this, 1) To UBound(this, 1)
If this(j, 1) = thisname Then
thisvalue = thisvalue + this(j, 2)
End If
Next j
that(i, 1) = thisvalue
thisvalue = 0
Next i
ThisWorkbook.Sheets("sheet5").Range(ThisWorkbook.Sheets("Sheet5").Cells(1, 1), ThisWorkbook.Sheets("Sheet5").Cells(rowC, colC)).Value2 = that
End Sub
Yay arrays

Excel VBA : assign formula to multiples dynamic range table in same sheet

I am new and learning Excel VBA. I am now having this problem
There is more than 10 tables in a worksheet (number of tables is not consistent)
The number of columns are consistent but not the rows in each tables
I would like to apply a total row to the end of every table
After that, I will apply the same formula to every table and put the results on the right side of each table
This could be easy but the core problem is that the range is unknown.
- As it is not an actual table in Excel, so I tried to first define the range of the data by creating table for it, then again, I don't have idea on how to create the table without knowing the range.
Below is something I came up with (which is not very "dynamic")
Sub plsWork()
Set u = ThisWorkbook.Worksheets("Sheet2")
Set f = u.Range("A").Find(what:="Name", lookat:=xlPart)
a = f.Address
Set sht = u.Range(a)
'trying to insert this at the end of the table
Total = Sum(u.Offset(2, 1) + u.Offset(3, 1) + u.Offset(4, 1))
If Cells(i, 2) = vbNullString Then 'this is already not applicable as the top 2 row in colB has null string
u.Offset(i, 1).Value = Total
'putting the table name at F2
u.Offset(-2, 5).Value = u.Offset(-3, 0).Value
u.Offset(-2, 6).Value = Total
u.Offset(-1, 5).Value = u.Offset(2, 0).Value
u.Offset(-1, 6).Value = Sum(u.Offset(2, 1) + u.Offset(2, 2) + u.Offset(2, 3))
u.Offset(0, 5).Value = u.Offset(3, 0).Value
u.Offset(0, 6).Value = Sum(u.Offset(3, 1) + u.Offset(3, 2) + u.Offset(3, 3))
u.Offset(1, 5).Value = u.Offset(4, 0).Value
u.Offset(1, 6).Value = Sum(u.Offset(4, 1) + u.Offset(4, 2) + u.Offset(4, 3))
End Sub
Oh, and when I run above code, I got error "Sub or Function not defined" on "SUM"
Here is the image of the tables in a sheet
yellow highlighted is what going to be there after executing the sub.
It was quite easy applying formula in Excel sheet and copy paste the formula to each tables,
but it was tedious, so I try to come out with a vba code to help so that the macro could run based on schedule.
I'm scratching my head and searching to and fro for the past two days,
I still haven't got a clue on how to code this.
So can any expert tell me if this is possible? like without knowing the range?
If so, could you guys shed me with some info on how to achieve this?
Thank you. I really want to know if this can be done or not.
Here is an image of my attempt using provided answer
You may try something like this...
The code below will insert a Total Row for each table which has more than one row and four columns in it.
Sub InsertTotalInEachTable()
Dim ws As Worksheet
Dim rng As Range
Dim i As Integer, r As Long, j As Long
Application.ScreenUpdating = False
Set ws = ActiveSheet
For Each rng In ws.UsedRange.SpecialCells(xlCellTypeConstants, 3).Areas
If rng.Rows.Count > 1 And rng.Columns.Count = 4 Then
j = 2
r = rng.Cells(rng.Rows.Count, 1).Row + 1
Cells(r, rng.Columns(1).Column).Value = "Total"
For i = rng.Columns(2).Column To rng.Columns(2).Column + 2
Cells(r, i).Formula = "=SUM(" & rng.Columns(j).Address & ")"
j = j + 1
Next i
End If
Next rng
Application.ScreenUpdating = True
End Sub