Do Until loop Syntax error vba excel - excel

I'm trying to write this module to compute a letter grade from a % in the next cell over and loop through the rows until the row is empty. Whats wrong with the syntax of this code? I get error: Runtime error 438: Object doesnt support this property or method at Average = Cells(i, 6).Valve
Sub Grade()
Dim Average As Double
Dim i As Integer
i = 3
Do Until IsEmpty(Cells(i, 6))
Average = Cells(i, 6).Valve
Average = Average * 100
If (Average <= 60) Then
Cells(i, 7).Valve = ("E")
End If
If (Average <= 70) Then
Cells(i, 7).Valve = ("D")
End If
If (Average <= 80) Then
Cells(i, 7).Valve = ("C")
End If
If (Average <= 90) Then
Cells(i, 7).Valve = ("B")
End If
If (Average <= 100) Then
Cells(i, 7).Valve = ("A")
End If
i = i + 1
Loop
End Sub

Change
Dim Average As Double
i As Integer
to
Dim Average As Double
Dim i As Integer
or
Dim Average As Double, i As Integer
or
Dim Average As Double, _
i As Integer
Your code needs a little more work. Use something like this:
Sub Grade()
Dim Average As Double
Dim i As Integer
i = 3
Do Until IsEmpty(Cells(i, 7))
Cells(i, 6).Value = Average
' Perhaps the above should be
' Average = Cells(i,6).Value
If (Average < 60) Then
Cells(i, 7).Valve = ("E")
End If
If (Average < 70) Then
Cells(i, 7).Valve = ("D")
End If
If (Average < 80) Then
Cells(i, 7).Valve = ("C")
End If
If (Average < 90) Then
Cells(i, 7).Valve = ("B")
End If
If (Average < 100) Then
Cells(i, 7).Valve = ("A")
End If
i = i + 1
Loop
End Sub

Just a thought on #zedfoxus post
Single line ifs don't need an end if
Sub Grade()
Dim Average As Double
Dim i As Long
i = 3
Do Until IsEmpty(Cells(i, 7))
Average = Cells(i, 6).Value
Average = Average * 100
If (Average < 60) Then Cells(i, 7).Value = ("E")
If (Average < 70) Then Cells(i, 7).Value = ("D")
If (Average < 80) Then Cells(i, 7).Value = ("C")
If (Average < 90) Then Cells(i, 7).Value = ("B")
If (Average < 100) Then Cells(i, 7).Value = ("A")
i = i + 1
Loop
End Sub
Further to this though, here is my take on the problem. I have put together a condensed routine using a 2 dimensional array and taking advantage of the worksheet function Vlookup. This works because it will find the closest thing (useful when you are using ranges of numbers)
Sub Grade()
Dim Average As Double, i As Long, MyArr As Variant
MyArr = Array(Array(60, "E"), Array(70, "D"), Array(80, "C"), Array(90, "B"), Array(100, "A"))
i = 3
Do Until IsEmpty(Cells(i, 7))
Average = Cells(i, 6).Value * 100 'Why * 100? Anyway just copied what you have done in your code
Cells(i, 7).Value = Application.WorksheetFunction.VLookup(Average, MyArr, 2)
i = i + 1
Loop
End Sub
And lastly, because the Average variable is only used once, it doesn't really need to be there (whilst it could be argued the same for MyArr it would be too bloated to include in the Vlookup, it would become hard to read), you can remove it and just reference its makeup in the Vlookup to condense the code further, and finally, we can remove i=3 and i=i+1 by using a for next loop and polling to the last row of data like so:
Sub Grade()
Dim i as long, MyArr As Variant
MyArr = Array(Array(60, "E"), Array(70, "D"), Array(80, "C"), Array(90, "B"), Array(100, "A"))
For i = 3 To Range("G" & Rows.Count).End(xlUp).Row
Cells(i, 7).Value = Application.WorksheetFunction.VLookup(Cells(i, 6).Value * 100, MyArr, 2)
Loop
End Sub
I am not sure why you are multiplying by 100 and I don't have your test data. I made my own test data but had to remove the *100 to make it work, my data was in column F.
40
50
60 E
65 E
70 D
75 D
80 C
85 C
90 B
95 B
100 A
This is the code I used:
Sub Grade2()
Dim i As Long, MyArr As Variant
MyArr = Array(Array(60, "E"), Array(70, "D"), Array(80, "C"), Array(90, "B"), Array(100, "A"))
For i = 3 To Range("F" & Rows.Count).End(xlUp).Row
Cells(i, 7).Value = Application.WorksheetFunction.VLookup(Cells(i, 6).Value, MyArr, 2)
Next
End Sub

I wonder if you want to use Formula instead of VBA.
Vlookup can do this. As below, hope this help.

Related

Maintaining a "library" with new data

So I am trying to maintain a library I have in excel. My library is kinda identical to the table shown below. This will store data for several year on 45000 lines. But every month i extract new hours which I want to include in the data. Though it is possible to change data back in time, so I am always extracting T, t-1, t-2, and t-3. So first I want to take last months data and subtract it from my library, load in new data, and then add on the new hours. But with the new data there will always be new Combinations, which I want to add in the bottom of the library. I have tried to solve this, and came to a solution, but it took forever as I have a big library but also extracting 85k lines every month. The reason for the combination is that several people can list time on a proj, but I do not care who does it, just the combination of these things. This is also why I fewer lines in my library. Can anybody help me? I provided the code I have made, which is doing the correct thing, but is way to slow.
Combination
Hours
ProjID
Planning
Approval
Month
Year
Hour type
Charge status
Proj1Planned42022Fixed
12
Proj1
Planned
4
2022
Fixed
Sub UpdateHours()
Dim data1 As Variant, data2 As Variant
Dim StartTime As Double
Dim MinutesElapsed As String
Application.ScreenUpdating = False
StartTime = timer
lastRow = Worksheets("TimeReg_Billable").Cells.Find("*", SearchOrder:=xlByRows, SearchDirection:=xlPrevious).row
lastRowTRB = Worksheets("TimeRegistrations_Billable").Cells.Find("*", SearchOrder:=xlByRows, SearchDirection:=xlPrevious).row
data1 = Worksheets("TimeReg_Billable").Range("A2:I" & lastRow).Value
data2 = Worksheets("TimeRegistrations_Billable").Range("A2:W" & lastRowTRB).Value
For i = 1 To lastRow
If i > UBound(data1, 1) Then Exit For
For k = 1 To lastRowTRB
If k > UBound(data2, 1) Then Exit For
If data1(i, 1) = data2(k, 23) Then
data1(i, 2) = data1(i, 2) - data2(k, 15)
End If
Next k
Next i
Worksheets("TimeReg_Billable").Range("A2:I" & lastRow).Value = data1
'Load data
'Workbooks.Open "C:\Users\jabha\Desktop\Projekt ark\INSERTNAMEHERE.xls"
'Workbooks("INSERTNAMEHERE.xls").Worksheets("EGTimeSearchControllingResults").Range("A:AA").Copy _
Workbooks("Projekt.xlsm").Worksheets("TimeRegistrations_Billable").Range("A1")
'Workbooks("INSERTNAMEHERE.xls").Close SaveChages = False
'Insert the new numbers
lastRow = Worksheets("TimeReg_Billable").Cells.Find("*", SearchOrder:=xlByRows, SearchDirection:=xlPrevious).row
lastRowTRB = Worksheets("TimeRegistrations_Billable").Cells.Find("*", SearchOrder:=xlByRows, SearchDirection:=xlPrevious).row
myarray = Worksheets("TimeReg_Billable").Range("A2:A" & lastRow)
data1 = Worksheets("TimeReg_Billable").Range("A2:I" & lastRowTRB).Value
data2 = Worksheets("TimeRegistrations_Billable").Range("A2:W" & lastRowTRB).Value
i = 1
Do While i <= lastRow
If i > UBound(data1, 1) Then Exit Do
k = 1
Do While k <= lastRowTRB
If k > UBound(data2, 1) Then Exit Do
If data1(i, 1) = data2(k, 23) Then
data1(i, 2) = data1(i, 2) + data2(k, 15)
End If
If Not data1(i, 1) = data2(k, 23) Then
Teststring = Application.Match(data2(k, 23), myarray, 0)
If IsError(Teststring) Then
data1(lastRow, 1) = data2(k, 23)
data1(lastRow, 3) = data2(k, 11)
data1(lastRow, 4) = data2(k, 16)
data1(lastRow, 5) = data2(k, 17)
data1(lastRow, 6) = data2(k, 20)
data1(lastRow, 7) = data2(k, 21)
data1(lastRow, 8) = data2(k, 22)
data1(lastRow, 9) = data2(k, 7)
lastRow = lastRow + 1
myarray = Application.Index(data1, 0, 1)
End If
End If
k = k + 1
Loop
If data1(i, 9) = "#N/A" Then
data1(i, 9) = ""
End If
i = i + 1
Loop
Worksheets("TimeReg_Billable").Range("A2:I" & lastRowTRB).Value = data1
MinutesElapsed = Format((timer - StartTime) / 86400, "hh:mm:ss")
MsgBox "This code ran succesfully in " & MinutesElapsed & " minutes", vbInformation
End Sub
Your code only turns off ScreenUpdating but, since the line below
Worksheets("TimeReg_Billable").Range("A2:I" & lastRow).Value = data1
updates a worksheet after the first set of nested loops it potentially triggers Excel's calculation engine to run, so it would be sensible, at the outset, also to include the line
Application.Calculation = xlCalculationManual
While I don't have any evidence for it, UsedRange.Find should, in theory, be more efficient than Cells.Find since the UsedRange is, necessarily, a much smaller area of the worksheet.
It is also more efficient to use .Value2 than .Value.
The code below
For i = 1 To lastRow
If i > UBound(data1, 1) Then Exit For
For k = 1 To lastRowTRB
If k > UBound(data2, 1) Then Exit For
If data1(i, 1) = data2(k, 23) Then
data1(i, 2) = data1(i, 2) - data2(k, 15)
End If
Next k
Next i
could be improved by the declaration of 2 additional variables
Dim outerLimit as Long, innerLimit as Long
outerLimit = Application.Min(lastRow, UBound(data1,1))
innerLimit = Application.Min(lastRowTRB, UBound(data2,1))
For i = 1 To outerLimit
For k = 1 To innerLimit
If data1(i, 1) = data2(k, 23) Then
data1(i, 2) = data1(i, 2) - data2(k, 15)
End If
Next k
Next i
which eliminates 1 test from the inner-loop body and 1 test from the outer-loop body.
(since you have similar tests in your 2nd set of nested loops you can replicate this optimisation there also)
In your 2nd set of nested loops you could replace the code below
If data1(i, 1) = data2(k, 23) Then
data1(i, 2) = data1(i, 2) + data2(k, 15)
End If
If Not data1(i, 1) = data2(k, 23) Then
with that below
If data1(i, 1) = data2(k, 23) Then
data1(i, 2) = data1(i, 2) + data2(k, 15)
Else
(obviously also deleting one of the later End If lines)
since the data1(i, 1) = data2(k, 23) test is Boolean, such that it only needs to be evaluated once.
They're the improvements I would suggest for your code, but I would also question your approach:
In the first set of nested loops the code is, effectively, testing every cell in column A of TimeReg_Billable for equality with every cell in column W of TimeRegistrations_Billable - with 85k rows this is potentially over 7 billion loop iterations (!).
Based on the sample table you posted, while you may have 85k unique rows, I don't believe you have 85k unique values in either of those 2 columns. Accordingly, I would suggest
using an Advanced Filter to isolate the unique values in columns O through W of TimeRegistrations_Billable on a new worksheet
loop through each of the unique items in column W of TimeRegistrations_Billable, making it the filter criterion for an AutoFilter of column A of TimeReg_Billable
loop through each of the SpecialCells(xlCellTypeVisible) in column A of TimeReg_Billable and make the required updates (using .Offset() as necessary)
you will likely have far fewer than 7 billion loop iterations, and you won't have to do any testing because, if a cell is one of the visible cells then it has, by definition, already satisfied the test.
Your 2nd set of nested loops is more involved, but is broadly using similar logic, such that I believe you can also use filtering to your advantage there too.
FYI
Teststring = Application.Match(data2(k, 23), myarray, 0)
will be quite slow compared to
Dim rng As Range
Set rng = Worksheets("TimeReg_Billable").Range("A2:A" & lastRow)
'...
'...
Teststring = Application.Match(data2(k, 23), rng, 0)
Match() against a worksheet range is much faster than against an array. See (eg) https://stackoverflow.com/a/61396675/478884 or this:
Sub Tester()
Const DATA_ROWS As Long = 50000
Dim i As Long, rng As Range, t, arr, m
'create some test data....
For i = 1 To DATA_ROWS
Cells(i, 1).Value = "Val_" & Format(i, "0000000")
Next i
Set rng = Range("A1:A" & DATA_ROWS)
t = Timer
arr = rng.Value
For i = 1 To 1000
m = Application.Match("Val_" & Format(i, "0000000"), arr, 0)
Next i
Debug.Print "Array", Timer - t
DoEvents
t = Timer
For i = 1 To 1000
m = Application.Match("Val_" & Format(i, "0000000"), rng, 0)
Next i
Debug.Print "Range", Timer - t
End Sub
output:
Array 9.148438
Range 2.734375E-02
The more data you have, the bigger the difference between the two methods.

How do I fill a series in VBA from a higher number to another?

I am trying to fill a series from a higher number in cell G2 (e.g "512") down to a lower number in Cell H2 (e.g "500"). I need the VBA code to run in Column J, producing the following this series as an example 512, 511, 510, 509, 508 ... down the column.
Here's a screenshot that describes what I need
Most examples I've found seems to be built for an increasing series (512, 513, 514....)
Any help to get this right will be helpful
For a VBA general solution, try this
Sub CreateSequence(StartValue As Long, EndValue As Long, OutputStart As Range, Optional ByVal StepBy As Long = 1)
Dim NumValues As Long
Dim dat As Variant
Dim i As Long
StepBy = Abs(StepBy)
If StepBy <= 0 Then Exit Sub
NumValues = Abs(StartValue - EndValue) \ StepBy + 1
ReDim dat(1 To NumValues, 1 To 1)
For i = 0 To NumValues - 1
dat(i + 1, 1) = StartValue + i * IIf(StartValue > EndValue, -StepBy, StepBy)
Next
OutputStart.Resize(UBound(dat, 1), 1).Value = dat
End Sub
Use it like this
Sub Demo()
CreateSequence Range("G2").Value, Range("H2").Value, Range("K2")
End Sub
A formula solution (Excel version 365)
=SEQUENCE(G2-H2+1,1,G2,-1)
hlRange = Sheets(2).Cells(2, 7).Value - Sheets(2).Cells(2, 8).Value + 1
' Fill series from max to min value
For j = 1 To hlRange
Sheets(2).Cells(j + 1, 10).Value = Sheets(2).Cells(2, 7).Value - j + 1
Next j
You can use the following code, it lets you add a a values to specify the steps you want to have.
https://i.stack.imgur.com/vSan5.jpg
Sub createNumList()
Dim i As Integer
Dim count As Integer
Dim cellValue As Integer
count = (ActiveSheet.Cells(2, 1).Value - ActiveSheet.Cells(2, 2).Value) / ActiveSheet.Cells(2, 3).Value
For i = 1 To count + 1
If i = 1 Then
ActiveSheet.Cells(i + 2, 5).Value = ActiveSheet.Cells(2, 1).Value
Else
ActiveSheet.Cells(i + 2, 5).Value = ActiveSheet.Cells(i + 1, 5).Value - ActiveSheet.Cells(2, 3).Value
End If
Next i
End Sub

IF, Else, ElseIF Loop within a certain Date

I want to set the following condition but i can only get 2 condition to be done
The three condition are less than 7 days multiple by one value, the range between 2 dates multiply by one value and more than 30 days multiply by another value.
Not able to get all to work
Not sure what went wrong
' To create the following condition
'If less than 7 days interest = 0%
' if 8 to 30 days interest = 7%
'if more than 31 days interest = 9%
Sub Workbook_Open()
For i = 1 To 3 'Rows.Count
xdate = Cells(i, 1)
nulldate = DateAdd("d", -7, Date)
irate7late = DateAdd("d", -8, Date)
irate7early = DateAdd("d", -30, Date)
If Day(nulldate) < Day(xdate) Then
result = Cells(i, 2) * 1
ElseIf Day(irate7early) <= Day(xdate) And Day(xdate) <= Day(irate7late) Then
'30/9/2015 20/10/2015 20/10/2015 22/10/2015
result = Cells(i, 2) * 1.07
ElseIf Day(irate7early) > Day(xdate) Then
result = Cells(i, 2) * 1.09
End If
Cells(i, 3).Value = result
Next i
End Sub
Reversing your test may simplify them sometimes:
Sub Workbook_Open()
Dim delta as Long
Dim xdate as Date
For i = 1 To 3 'Rows.Count
xdate = Cells(i, 1).Value
delta = DateDiff("d", xdate, Date)
If delta > 30 Then
Cells(i,3).Value = Cells(i,2).Value * 1.09
ElseIf delta > 7 Then
Cells(i,3).Value = Cells(i,2).Value * 1.07
Else 'delta <= 7
Cells(i,3).Value = Cells(i,2).Value
End If
Next i
End Sub
And don't forget Option Explicit, it may save you a lot of time in debugging.
Try this below code for your requirement
Sub Workbook_Open()
Dim diffdate As Variant
For i = 1 To 3 'Rows.Count
xdate = Cells(i, 1).Value
diffdate = (DateDiff("d", xdate, Now()))
If diffdate < 7 Then
result = Cells(i, 2) * 1
ElseIf diffdate < 31 And diffdate > 7 Then
result = Cells(i, 2) * 1.07
Else
result = Cells(i, 2) * 1.09
End If
Cells(i, 3).Value = result
Next
End Sub

Excel VBA rowheigt from value in range

I have an Excel-Sheet with values in column D.
I would like to set the row height in relation to the value of cell D of each row.
Values in D are small %-values like 0.0593 %, except of the first (D4 = 31 %) and last (D92 = 40 %)
To get the small values at a reasonable height I'd like to multiply them with 10'000 - but there comes the problem with the 409 max height.
I have a script that works until it comes to the high values so I tried a if formula. But to be frankly: I have no Idea what I am doing here... I copied it together.
So the problems: working only in the range of D5-D91 and if a value should go over 409 give him something like 15px.
Thanx for your Help!
Private Sub Worksheet_SelectionChange(ByVal Target As Range)
Dim i As Long
For i = 4 To Cells(Rows.Count, 1).End(xlUp).Row - 1
With Cells(i, 4)
If .Cells(i, 4).Value * 10000 > 409 Then
Rows(i).RowHeight = 12
Else
Rows(i).RowHeight = Cells(i, 4).Value * 10000
End If
End With
Next i
End Sub
Copy the below code to any standard module & Run. You may have to tweak the code as per your requirement.
Sub sample()
Dim i As Long
For i = 4 To Cells(Rows.Count, 1).End(xlUp).Row
If Cells(i, 4).Value * 10000 > 409 Then
Rows(i).RowHeight = 12
Else
Rows(i).RowHeight = Cells(i, 4).Value * 10000
End If
Next
End Sub
Sub sample()
Dim i As Long
For i = 4 To Cells(Rows.Count, 1).End(xlUp).Row
If Cells(i, 4).Value * 100000 > 409 Then
Rows(i).RowHeight = 20
ElseIf Cells(i, 4).Value * 100000 < 10 Then
Rows(i).RowHeight = 12
Else
Rows(i).RowHeight = Cells(i, 4).Value * 100000
End If
Next
End Sub

Multiplying an integer with a currency in Excel VBA

In my excel table i have one row (5th row) as number of items and another (6th row) as the price of the items. For example i want to multiply 200 with $56.50 but I am having a problem with this script. Can anyone please help.
Sub calcprice()
Dim i As Integer
Dim iRowNumber As Integer ' Integer to store result in
Dim val As Double
iRowNumber = InputBox(Prompt:="Number of Rows", _
Title:="Rows: ", Default:="# of Rows")
For i = 1 To iRowNumber
If Cells(i, 5).Value >= 0 And Cells(i, 6).Value >= 0 And IsEmpty(Cells(i, 5)) = False And IsEmpty(Cells(i, 5)) = False Then
val = FormatCurrency(Cells(i, 5).Value) * Cells(i, 6).Value
Cells(i, 7).Value = val
End If
Next i
End Sub
it says runtime error 13
type mismatch
here is the image:
Here is the link: https://www.dropbox.com/s/lla2cuz8hqu5qyp/test.xlsm
also i cannot use the =a*b i have to use macros!
You don't need a loop
You can work with a single shot range in colunm G that
Adds a formula from G5 to the user entered iRowNumber to test whether a result > 0 happens in each row (or adds "" for a 0 result)
overwrite the formulae with the values
Sub calcprice()
Dim iRowNumber As Long ' Integer to store result in
iRowNumber = InputBox(Prompt:="Number of Rows", _
Title:="Rows: ", Default:="# of Rows")
With Range(Cells(5, 7), Cells(iRowNumber, 7))
.FormulaR1C1 = "=IF(N(RC[-1]*RC[-2]),RC[-1]*RC[-2],"""")"
.Value = .Value
End With
End Sub
Try this out. Remember to format the 5th cell and the 6th cell to the currency you like!
Sub calcprice()
Dim iRowNumber As Integer
Dim val As Double
iRowNumber = InputBox(Prompt:="Number of Rows", _
Title:="Rows: ", Default:="# of Rows")
For i = 1 To iRowNumber
If Cells(i, 5).Value >= 0 And Cells(i, 6) >= 0 Then
If Cells(i, 6).Value > 0 Then
val = Cells(i, 5).Value * FormatCurrency(Cells(i, 6).Value)
Cells(i, 7).Value = val
End If
End If
Next i
End Sub
You have For i = 1 to iRowNumber
The image shows that the data starts in row 5. The previous rows contain text.
Therefore, try For i = 5 to iRowNumber

Resources