Runtime error = '1004' - vba

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"))))

Related

VBA referencing data from another sheet is not working

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

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

Faster method to compare two columns from 2 different workbooks

So I have some code using for loops currently doing this and it takes roughly 6 minutes to run...
I have many sheets showing the same columns with some different data.
One column comes in either a named form or a numerical form (Depending on how a user input it to a completely separate database).
Another database contains 2 columns: one being the numerical form of data while the other is named.
My database currently compares my "name" column if numerical with the numerical column in this other database and when it finds a match it changes my "name" cell to match the corresponding name cell in the other database.
Is there any faster way to do this than using for loops?
I have to replicate the code around 12 times for different sheets to do the same task.
As previously stated, overall to run across all 12 its taking around 6 minutes
Sub 6mincode()
Workbooks("1").Activate
N = Workbooks("1").Sheets("Data").Cells(Rows.Count, "B").End(xlUp).Row
N2 = Workbooks("2").Sheets("Data Sheet").Cells(Rows.Count, "B").End(xlUp).Row
For I = 2 To N
If (WorksheetFunction.IsNumber(Sheets("Data").Cells(I, "B").Value)) = True Then
For zz = 8 To N2
If StrComp(Sheets("Data").Cells(I, "B").Value, Workbooks("2").Sheets("Data Sheet").Cells(zz, "B").Value) = 0 Then
Workbooks("1").Sheets("Data").Cells(I, "B").Value = Workbooks("2").Sheets("Data Sheet").Cells(zz, "C").Value
End If
Next zz
End If
Next I
End Sub
You can save the second loop and use Application.Match instead, it will save you a lot of time.
See code below, explanations inside the code's comments:
Option Explicit
Sub Sixmincode()
Dim N As Long, N2 As Long, I As Long
Dim Rng As Range, MatchRow
With Workbooks("1").Sheets("Data")
N = .Cells(.Rows.Count, "B").End(xlUp).Row
End With
With Workbooks("2").Sheets("Data Sheet")
N2 = .Cells(.Rows.Count, "B").End(xlUp).Row ' get last row with data in column B
' set the Range to Match with
Set Rng = .Range("B8:B" & N2)
End With
With Workbooks("1").Sheets("Data")
For I = 2 To N
If IsNumeric(.Cells(I, "B").Value) Then ' use IsNumeric
' use Application.Match, if Not IsError means there is a match found in the second workbook
If Not IsError(Application.Match(.Cells(I, "B").Value, Rng, 0)) Then
MatchRow = Application.Match(.Cells(I, "B").Value, Rng, 0)
.Cells(I, "B").Value = Workbooks("2").Sheets("Data Sheet").Cells(MatchRow, "C").Value
End If
End If
Next I
End With
End Sub

What is the difference between .Value = "" and .ClearContents?

If I run the following code
Sub Test_1()
Cells(1, 1).ClearContents
Cells(2, 1).Value = ""
End Sub
When I check Cells(1, 1) and Cells(2, 1) using formula ISBLANK() both results return TRUE. So I'm wondering:
What is the difference between Cells( , ).Value = "" and Cells( , ).ClearContents?
Are they essentially the same?
If I then run the following code to test the time difference between the methods:
Sub Test_2()
Dim i As Long, j As Long
Application.ScreenUpdating = False
For j = 1 To 10
T0 = Timer
Call Number_Generator
For i = 1 To 100000
If Cells(i, 1).Value / 3 = 1 Then
Cells(i, 2).ClearContents
'Cells(i, 2).Value = ""
End If
Next i
Cells(j, 5) = Round(Timer - T0, 2)
Next j
End Sub
Sub Number_Generator()
Dim k As Long
Application.ScreenUpdating = False
For k = 1 To 100000
Cells(k, 2) = WorksheetFunction.RandBetween(10, 15)
Next k
End Sub
I get the following output for runtime on my machine
.ClearContents .Value = ""
4.20 4.44
4.25 3.91
4.18 3.86
4.22 3.88
4.22 3.88
4.23 3.89
4.21 3.88
4.19 3.91
4.21 3.89
4.17 3.89
Based on these results, we see that the method .Value = "" is faster than .ClearContents on average. Is this true in general? Why so?
From what I have found, if your goal is to simple have an empty cell and you do not want to change anything about the formatting, you should use Value = vbNullString as that is the most efficient.
The 'ClearContents' is checking and changing other properties in the cell such as formatting and the formula (which is technically a separate property than Value). When using Value = "" you are only changing one property and so it is faster. Using vbNullString prompts the compiler that you are using an empty string versus the other way with double quotes, it is expecting a general string. Because vbNullString prompts it to expect an empty string, it is able to skip some steps and you get a performance gain.
when apply both in single cell I don't think there is no any sensible deferent but when you apply it in range Range("A1:Z1000").ClearContents is easier and faster than use cell(i,j).value="" in nested loop or one for loop
I did find one difference that might be of note. ClearContents returns a value, which seems to be of type Boolean in my limited testing (docs mention Variant type).
Option Explicit
Public Sub ClearA1()
Dim a As Range
Dim b As Boolean
Set a = Range("A1")
Debug.Print b 'It's False, the default value
b = a.ClearContents
Debug.Print b 'Set to True, as the action was completed
End Sub
I'd guess some of the overhead is from the fact ClearContents does return a value, where you are just setting a value property in the alternate case.
Ultimately, in terms of outcomes of setting the value both methods appear functionally the same.
Using clearcontent has different behavior on cells with formulas.
When you have just one value, the behavior is the same, but differs when you have formulas in it.
You can notice a big difference in Excel spreadsheet.
Assume that B1 is filled by equation returns blank
A1 = 5
B1 = "=if(A1=5,"","x")
In this case, you have to equations that you can write in C1
(1) C1 = <=isblank(B1)>
(2) C1 =
Solution 1 will return false, as the cell is filled with equation
Solution 2 will return True
I came across this topic a little late, but i would like to share what i have noticed with abit of code of mine, i don't think i can fully explain it but ill do my best.
For Each Cell In ws.Range("D12:D161") 'Order feed colom
Select Case Cell.Value
Case 0
Cell.Interior.Color = Cell.Offset(0, -1).Interior.Color
Case 1
Cell.Interior.Color = 10198015
Case 2
Cell.Interior.Color = 11854022
End Select
Cell.value = ""
Next Cell
This is a bit of code that i have used in order to clear some fields and give some color to the range D12:D161. Nothing special here, If the value is 0 then copy your neighbor if 1 then red if 2 then green. And clear the cell afterwards
But in order for this code to run it took roughly 5-6 seconds for me, which i thought was a fair bit for a small piece of code. Plus i used this on a Private Sub Workbook_SheetActivate(ByVal Sh As Object) which made it for the user unpleasant to wait 5-6 seconds for a screen transition. So i put a loop in to check for empty's in a row and then skip out.
It is noteworthy that this is part of a script, and yes i have my screenupdating off, calculations off, events off during this piece of code.
For Each Cell In ws.Range("D12:D161") 'Order feed colom
Select Case Cell.Value
Case 0
Cell.Interior.Color = Cell.Offset(0, -1).Interior.Color
Erow = Erow +1
Case 1
Cell.Interior.Color = 10198015
Erow = 0
Case 2
Cell.Interior.Color = 11854022
Erow = 0
End Select
Cell.value = ""
if Erow = 10 then exit for
Next Cell
Now instead of having to do 149 rows i did roughly 58 rows, depending on my data in the column. But still it it took 3-4 seconds in order to fully run. During Debug mode i noticed no lag at all. If i manually ran the code when already on the sheet, there was 0 delay. Almost instant, after testing abit more but when using a Private Sub Workbook_SheetActivate(ByVal Sh As Object) with this code it still ran 3-4 seconds.
After testing individual rows of code, i came across the .Value = "". Removing this line from the code made it run 0,5 seconds.... So now i knew where my problem was, using multiple ways of emptying my cells. I noticed that .clearcontents was the fastest for me. Apparently if you move from Sheet to sheet EVENTHOUGH ws. has been declared as my active sheet, it just ook alot of time
For Each Cell In ws.Range("D12:D161") 'Order feed colom
Select Case Cell.Value
Case 0
Cell.Interior.Color = Cell.Offset(0, -1).Interior.Color
Erow = Erow +1
Case 1
Cell.Interior.Color = 10198015
Erow = 0
Case 2
Cell.Interior.Color = 11854022
Erow = 0
End Select
Cell.ClearContents 'DONT USE .Value = "", makes the code run slow
if Erow = 10 then exit for
Next Cell
In conclusiong.
Using the above code with
.value = "" took 4-5 seconds
.value = VbNullstring took 3-4 seconds
.ClearContents took only 0,5 seconds. But only during a worksheet transition
Private Sub Workbook_SheetActivate(ByVal Sh As Object)
If anybody is able to explain why this is or what exactly is going on, i would appreciate it.

Comparing 2 columns in Excel to see "Next High" using VBA

I am fairly new to using VBA code and want to try implementing it in a hypothetical problem I am currently working on. I want to be able to compare 2 columns to see if they have similar numbers, and the first instance of a dissimilar number will become the "Next High." For example:
Client Market
90 87
92 91.25
95 92
95.5 93.5
95
95.5
Next High:
Starting from the bottom of the list, the VBA macro should see if the Market and Client side have the same numbers. The first instance of a number that is on the Market side and not on the client side should be the "Next High". For example, in the above list, 95.5 is on the Market and Client side, and so is 95. Because the next number above 95 on the Market side is 93.5, and this is a higher number than the next number on the Client side(92), this becomes the "Next High."
I would appreciaite some direction with the VBA code! I will eleborate if this is confusing, thank you!
See the attached code and review it. I hope that this will help you get started and allow you to ask any specific questions that you may have.
Code:
Option Explicit
' This sub assumes that the columns are in ascending order already
Sub nextHigh()
' declaring variables before using them
Dim lastRowClient As Long
Dim lastRowMarket As Long
Dim i As Integer
Dim j As Integer
Dim testValue As String
Dim nextHigh As Double
Dim checkValue As String
Dim valueFound As Boolean
' initializing variables to a known value
lastRowClient = 0
lastRowMarket = 0
i = 0
j = 0
testValue = ""
nextHigh = 0
checkValue = ""
valueFound = False
' gets the last row of column 1
With Sheets("Sheet1")
lastRowClient = .Range("A" & .Rows.Count).End(xlUp).Row
lastRowMarket = .Range("B" & .Rows.Count).End(xlUp).Row
End With
' for loop to iterate over column 2. note that you can specify the step value and change it from the default of +1
For i = lastRowMarket To 2 Step -1
testValue = Cells(i, 2).value
' this loop iterates to check the found value against column 1
For j = lastRowClient To 2 Step -1
checkValue = Cells(j, 1).value
' actual check
If testValue = checkValue Then
'value was found and therefore is not "NextHigh"
valueFound = True
Exit For ' value was found so stop searching
Else
'value was notfound and therefore is "NextHigh"
valueFound = False
End If
Next j
If Not valueFound Then
' print out value to next column
Cells(2, 3).value = testValue
Exit Sub 'exits sub to avoid excessive looping
End If
Next i
End Sub
Program output:
Do you absolutely have to use VBA for this? There's a fairly simple formula you could use instead. Using your provided example data, this formula returns the correct result:
=MAX(INDEX((COUNTIF(A2:B7,A2:B7)=1)*A2:B7,))