Maintaining a "library" with new data - excel

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.

Related

Create two rows for each unique value in column and subtract

I have a list of projects in Excel. Each project has three rows (act, plan, fcst) and many columns (one column = one month).
What I would like to do is following:
A) for each unique value in column D (project #) add two rows | Completed
B) subtract plan - actual in one of new rows
C) subtract fcst - actual in second of new rows
A*) create two new rows and copy
data from columns A:AE for each unique value in column D (project #)
| Optional - I can handle option A), but A* would be a better one.
Does anyone know how to write a code to do points B, C, A*? I have no clue how to tackle that.
This is the final output that I would like to see (yellow and orange rows are new ones that I want macro to create for each unique project# in column D):
Text in AF is always either "Plan $000's" or "Actual $000's" or "Forecast $000's", for each project (i.e. each single project has these three rows; no less, no more).
Data is sorted per impact # (column D). Meaning that first three rows are related to project #123, next three are related to project #129, next three to project #761, etc.
We are allowed to play (sort, filter, etc.) with the data as long as we get the desired result. :-)
Below is the code I have right now... it is quite poor:
Sub CreateAndCompare()
Dim rng As Range
Dim cl As Range
Dim dic As Object
Dim ky As Variant
Set dic = CreateObject("Scripting.Dictionary")
With Sheets("Impact")
Set rng = .Range(.Range("D2"), .Range("D" & .Rows.Count).End(xlUp))
End With
For Each cl In rng
If Not dic.exists(cl.Value) Then
dic.Add cl.Value, cl.Value
End If
Next cl
For Each ky In dic.keys
lastrow = ActiveSheet.Range("d2").CurrentRegion.Rows.Count
Cells(lastrow + 1, 4).Value = dic(ky)
Cells(lastrow + 2, 4).Value = dic(ky)
Next ky
End Sub
thank you!
I think I have found the solution. :-)
I have created an extra column AG which concatenates Impact# & Purpose (columns D&AF).
However, it takes ~15 minutes to execute the code.
Is anyone able to suggest what should I change to make it faster?
Sub CreateAndCompare()
Dim rng As Range
Dim cl As Range
Dim dic As Object
Dim ky As Variant
Set dic = CreateObject("Scripting.Dictionary")
With Sheets("Impact")
Set rng = .Range(.Range("D2"), .Range("D" & .Rows.Count).End(xlUp))
End With
For Each cl In rng
If Not dic.exists(cl.Value) Then
dic.Add cl.Value, cl.Value
End If
Next cl
For Each ky In dic.keys
lastrow = ActiveSheet.Range("d2").CurrentRegion.Rows.Count
Cells(lastrow + 1, 4).Value = dic(ky)
Cells(lastrow + 1, 32).Value = "Act-Plan"
Cells(lastrow + 1, 33).Value = "Plan $000's"
For i = 2 To 43
mylookupvalue = Cells(lastrow + 1, 4) & "Actual $000's"
mylookupvalue_2 = Cells(lastrow + 1, 4) & Cells(lastrow + 1, 33)
myfirstcolumn = 33
mylastcolumn = 43
mycolumnIndex = i
myfirstrow = 2
mylastrow = lastrow
mytablearray = Worksheets("Impact").Range(Cells(myfirstrow, myfirstcolumn), Cells(mylastrow, mylastcolumn))
On Error Resume Next
value_1 = Application.WorksheetFunction.VLookup(mylookupvalue, mytablearray, mycolumnIndex, False)
value_2 = Application.WorksheetFunction.VLookup(mylookupvalue_2, mytablearray, mycolumnIndex, False)
Cells(lastrow + 1, i + 32).Value = value_1 - value_2
Cells(lastrow + 2, 4).Value = dic(ky)
Cells(lastrow + 2, 32).Value = "Act-Fcst"
Cells(lastrow + 2, 33).Value = "Forecast $000's"
mylookupvalue_3 = Cells(lastrow + 2, 4) & "Actual $000's"
mylookupvalue_4 = Cells(lastrow + 2, 4) & Cells(lastrow + 2, 33)
value_3 = Application.WorksheetFunction.VLookup(mylookupvalue_3, mytablearray, mycolumnIndex, False)
value_4 = Application.WorksheetFunction.VLookup(mylookupvalue_4, mytablearray, mycolumnIndex, False)
Cells(lastrow + 2, i + 32).Value = value_3 - value_4
Next i
Next ky
Worksheets("Impact").Range("AH2:BW10024").NumberFormat = "_(* #,##0_);_(* (#,##0);_(* ""-""??_);_(#_)"
End Sub
I suggest the following:
Loop through all data rows
Find the rows plan/actual/forecast for the current impact no
Then write the calculations to the end of the worksheet
So you would end up with something like that:
Option Explicit
Public Sub CreateAndCompare()
Dim ws As Worksheet
Set ws = ThisWorkbook.Worksheets("Impact")
'we assume here that the sheet is already sorted by column D "Impact #"
Dim LastDataRow As Long 'find last used row
LastDataRow = ws.Cells(ws.Rows.Count, "D").End(xlUp).Row
Dim LastDataColumn As Long 'find last used column
LastDataColumn = ws.Cells(1, ws.Columns.Count).End(xlToLeft).Column
Dim NextEmptyRow As Long
NextEmptyRow = LastDataRow + 1
Dim iRow As Long, PlanRow As Long, ActualRow As Long, ForcastRow As Long
For iRow = 2 To LastDataRow 'loop through all data rows
Select Case ws.Cells(iRow, "AF").Value 'check which row type the current iRow is and remember
Case "Plan $000's": PlanRow = iRow
Case "Actual $000's": ActualRow = iRow
Case "Forecast $000's": ForcastRow = iRow
End Select
'detect change of impact no
If ws.Cells(iRow, "D").Value <> ws.Cells(iRow + 1, "D").Value Or iRow = LastDataRow Then
'check if plan/actual/forecast rows were found (if one is missing we cannot calculate
If PlanRow > 0 And ActualRow > 0 And ForcastRow > 0 Then
'copy column A-AE to next 2 empty rows
ws.Cells(NextEmptyRow, "A").Resize(RowSize:=2, ColumnSize:=31).Value = ws.Cells(iRow, "A").Resize(ColumnSize:=31).Value
'write purpose
ws.Cells(NextEmptyRow, "AF").Value = "Act - Plan"
ws.Cells(NextEmptyRow + 1, "AF").Value = "Act - Fcst"
'calculate
Dim iCol As Long
For iCol = 33 To LastDataColumn
ws.Cells(NextEmptyRow, iCol).Value = ws.Cells(ActualRow, iCol).Value - ws.Cells(PlanRow, iCol).Value
ws.Cells(NextEmptyRow + 1, iCol).Value = ws.Cells(ActualRow, iCol).Value - ws.Cells(ForcastRow, iCol).Value
Next iCol
NextEmptyRow = NextEmptyRow + 2 'initialize for next impact no
End If
PlanRow = 0: ActualRow = 0: ForcastRow = 0 'initialize for next impact no
End If
Next iRow
End Sub

VBA Looping to compare multiple values

I have created a nested for loop to compare 3 different cell values within 2 sheets. The loop works fine when the data is small, but when I run on 5,000 rows its too slow and crashes excel. Any idea of how to run this more efficiently.
Sub RowMatch()
Dim x As Integer
' Make sure we are in the right sheet
Worksheets("Q416").Activate
' Set numrows = number of rows of data.
NumRows = Range("C2", Range("C2").End(xlDown)).Rows.count
' find the reference range
Worksheets("Q415").Activate
NumRows2 = Range("C5", Range("C5").End(xlDown)).Rows.count
Worksheets("Q416").Activate
MsgBox ("Total # of Rows on this sheet = " & NumRows & " and " & NumRows2 & " in Ref Range")
Range("A1").Select
' Establish "For" loop to loop "numrows" number of times.
For x = 1 To NumRows
'MsgBox NumRows2
For y = 1 To NumRows2
'MsgBox (ActiveCell.Offset(x, 0).Value & " & " & Worksheets("Q415").Cells(y + 1, 1))
If ActiveCell.Offset(x, 0).Value = Worksheets("Q415").Cells(y + 1, 1).Value _
And ActiveCell.Offset(x, 2).Value = Worksheets("Q415").Cells(y + 1, 3).Value Then
If ActiveCell.Offset(x, 5).Value = Worksheets("Q415").Cells(y + 1, 6).Value Then
'If NumRows(i).Value = ActiveCell.Offset(1, 0).Value Then
ActiveCell.Offset(x, 10).Value = "Same"
Else
ActiveCell.Offset(x, 10).Value = ActiveCell.Offset(x, 5).Value - Worksheets("Q415").Cells(y + 1, 6).Value
End If
End If
Next y
Next x
End Sub
Reading and writing to cells is one of the slowest operations you can do in Excel VBA. Instead, you should place the values contained in the worksheets into arrays and work with them there, Here is an excellent reference: http://www.cpearson.com/excel/ArraysAndRanges.aspx. Use your NumRows variables and either a column letter or number to define the ranges that will consitute the arrays e.g:
myRange = Range("A1:C" & NumRows)
myArray = myRange.value
From the link to Chip Pearsons site:
Dim Arr() As Variant
Arr = Range("A1:B10")
Dim R As Long
Dim C As Long
For R = 1 To UBound(Arr, 1) ' First array dimension is rows.
For C = 1 To UBound(Arr, 2) ' Second array dimension is columns.
Debug.Print Arr(R, C)
Next C
Next R

VBA - Deleting duplicate rows and merging cells with unique data

I have would like some help regarding the following problem. Every quarter we a have excel sheets sent to us with client info containing rows often more than a 1000. I've managed to write a code that deletes duplicate rows that are a 100% match, however, a considerable portion still remains due to the following:
A new code I have found kinda works, however I would need some help tweaking it, as it does the following:
It deletes the duplicate and merges the cells, however, if one cell value (in this case Marketing) appears both times it keeps it twice. Also, it does not retain other info like mail/name/phone etc.
Here's the code itself:
Sub Main()
Dim Source As Worksheet: Set Source = ThisWorkbook.Worksheets("Sheet1")
Dim Destination As Worksheet: Set Destination = ThisWorkbook.Worksheets("Sheet2")
Dim Records As Object: Set Records = CreateObject("Scripting.Dictionary")
Dim Data As Variant
Dim Index As Long
Dim Row As Integer: Row = 1
Data = Source.Range("A1", "E" & Source.Rows(Source.UsedRange.Rows.Count).Row).Value2
For Index = LBound(Data, 1) To UBound(Data, 1)
If Records.Exists(Data(Index, 1)) Then
Destination.Cells(Records(Data(Index, 1)), 5).Value2 = Destination.Cells(Records(Data(Index, 1)), 5).Value2 & ", " & Data(Index, 5)
Else
Records.Add Data(Index, 1), Row
Destination.Cells(Row, 1).Value2 = Data(Index, 1)
Destination.Cells(Row, 5).Value2 = Data(Index, 5)
Row = Row + 1
End If
Next Index
Set Records = Nothing
End Sub
I was wondering if there is a way to tackle this problem, or is it too complicated? If the latter, no problems, only deleting the duplicates works fine and reduces work hours a lot.
Thank you for any input and comment!
I use a Dictionary to remove duplicates in a comma delimit string. Email, code and Country are also copied over to the Destination worksheet.
Sub Main()
Dim Source As Worksheet: Set Source = ThisWorkbook.Worksheets("Sheet1")
Dim Destination As Worksheet: Set Destination = ThisWorkbook.Worksheets("Sheet2")
Dim Records As Object: Set Records = CreateObject("Scripting.Dictionary")
Dim Data As Variant
Dim Index As Long
Dim Row As Integer: Row = 1
Data = Source.Range("A1", "E" & Source.Rows(Source.UsedRange.Rows.Count).Row).Value2
With Destination
For Index = LBound(Data, 1) To UBound(Data, 1)
If Records.Exists(Data(Index, 1)) Then
Destination.Cells(Records(Data(Index, 1)), 5).Value2 = removeDuplicates(Destination.Cells(Records(Data(Index, 1)), 5).Value2 & ", " & Data(Index, 5))
Else
Records.Add Data(Index, 1), Row
Destination.Cells(Row, 1).Value2 = Data(Index, 1)
Destination.Cells(Row, 2).Value2 = Data(Index, 2)
Destination.Cells(Row, 3).Value2 = Data(Index, 3)
Destination.Cells(Row, 4).Value2 = Data(Index, 4)
Destination.Cells(Row, 5).Value2 = Data(Index, 5)
Row = Row + 1
End If
Next Index
End With
Set Records = Nothing
End Sub
Function removeDuplicates(values As String)
Dim v As Variant
Dim d As Object
Set d = CreateObject("Scripting.Dictionary")
For Each v In Split(values, ",")
If v <> "" Then d(v) = 1
Next
removeDuplicates = Join(d.Keys, ", ")
Set d = Nothing
End Function
Try the following
If Records.Exists(Data(Index, 1)) Then
If InStr(Destination.Cells(Records(Data(Index, 1)), 5).Value2, Data(Index, 5)) = 0 Then
Destination.Cells(Records(Data(Index, 1)), 5).Value2 = Destination.Cells(Records(Data(Index, 1)), 5).Value2 & ", " & Data(Index, 5)
End if
...
InStr searches for a certain String in another, and returns the position at which the string is found. Hence, if Marketing not found, instr will return 0 and it will be added to the cell. If it is already there, Instr will return something largern than 0 and it will not be added again.
Update If you have more records with more than one unit, try this
UnitFull = Data(Index, 5)
Do Until Len(UnitFull) = 0
If InStr(UnitFull, ",") > 0 Then
Unit = Left(UnitFull, Instr(UnitFull, ",") - 1)
UnitFull = Trim(Right(UnitFull, Len(UnitFull) - InStr(UnitFull, ",")))
Else
Unit = UnitFull
UnitFull = ""
End If
Destination.Cells(Records(Data(Index, 1)), 5).Value2 = Destination.Cells(Records(Data(Index, 1)), 5).Value2 & ", " & Unit
Unit = ""
Loop

Applying if statement to range of cells using VBA

I have a small range of cells, C6:C10. I'm trying to apply an if statement to this range of cells using VBA code. Currently, my code takes the output of the if statement for the first cell (C6), and replicates that value for cells C7:C10. The if statement is correct, I'm just not sure how to apply it to a range of cells in a column.
Sub Cleanup()
Dim Segment As String
Dim i As Integer
Segment = ActiveCell(6, 3).Value
For i = 6 To 10
If Right(Left(Segment, 6), 1) = "/" Then
ActiveCell(i, 3).Value = Left(Segment, 5)
Else
ActiveCell(i, 3).Value = Left(Segment, 6)
End If
Next i
End Sub
It should be fine if you use Cells instead of ActiveCell, except that you'll have to change your loop to go from 7 to 10 or else it will over-write the original cell as well as C7:C10.
Sub Cleanup()
Dim Segment As String
Dim i As Integer
Segment = Cells(6, 3).Value
For i = 7 To 10
If Right(Left(Segment, 6), 1) = "/" Then
Cells(i, 3).Value = Left(Segment, 5)
Else
Cells(i, 3).Value = Left(Segment, 6)
End If
Next i
End Sub
Sub Cleanup()
Dim Segment As String
Dim i As Integer
Segment = Cells(i, 3).Value
For i = 7 To 10
If Right(Left(Segment, 6), 1) = "/" Then
cells(i, 3).Value = Left(Segment, 5)
Else
Cells(i, 3).Value = Left(Segment, 6)
End If
Next i
End Sub
here three (out of many other) possible codes, in simplicity order (the last being more simple than the first):
Option Explicit
Sub Cleanup()
Dim Segment As String
Dim i As Integer
For i = 6 To 10
Segment = Cells(i, 3).Value '<== Cells(i, 3) is the current cell as per the current row (i)
If Mid(Segment, 6, 1) = "/" Then
Cells(i, 3).Value = Left(Segment, 5)
Else
Cells(i, 3).Value = Left(Segment, 6)
End If
Next i
End Sub
Sub Cleanup2()
Dim i As Integer
For i = 6 To 10
With Cells(i, 3) 'avoid repetitions (and a variable, too) by using 'With' keyword and implying 'Cells(i, 3)' preceeds every dot (".") till next 'End With" statement
If Right(Left(.Value, 6), 1) = "/" Then
.Value = Left(.Value, 5)
Else
.Value = Left(.Value, 6)
End If
End With
Next i
End Sub
Sub Cleanup3()
Dim i As Integer
For i = 6 To 10
With Cells(i, 3)
.Value = Left(.Value, IIf(Mid(.Value, 6, 1) = "/", 5, 6)) ' use Iif function to avoid multiple lines. Also use 'Mid' function in lieu of 'Right(Left(..))'
End With
Next i
End Sub

Do Until loop Syntax error vba 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.

Resources