I keep getting a "compile error: next without For" when I try to run this code. However, after checking everything over multiple times, I do not see how it does not recognize their presences. This is my first VBA code, so any help would be greatly appreciated.
Sub Naming()
'
' Naming Macro
' Assigns a category name in a cell based on values in a cell one column over
'
Dim number As Double
For i = 9 To 200
number = Cells(i, 3).Value
If number = 0 Then
GoTo Line1
Else
If number <= 199999 And number > 0 Then
Cells(i, 2) = "EP-GEARING"
Else
If number <= 399999 And number > 199999 Then
Cells(i, 2) = "DRIVES"
Else
If number <= 499999 And number > 399999 Then
Cells(i, 2) = "FLOW"
Else
If number <= 599999 And number > 499999 Then
Cells(i, 2) = "SPARES"
Else
If number <= 699999 And number > 599999 Then
Cells(i, 2) = "REPAIR"
Else
If number <= 799999 And number > 699999 Then
Cells(i, 2) = "FS"
Else
If number <= 899999 Then
Cells(i, 2) = "GC-GEARING"
Else
GoTo Line1
Line1:
End If
Next i
End Sub
ElseIf is one word in VB.
If number = 0 Then
'Do nothing
ElseIf number <= 199999 And number > 0 Then
Cells(i, 2) = "EP-GEARING"
ElseIf number <= 399999 And number > 199999 Then
...
Else
'Do nothing
End If
However, Select Case would fit better here:
Select Case number
Case 0
'Do nothing
Case 1 To 199999
Cells(i, 2) = "EP-GEARING"
Case 200000 To 399999
...
Case Else
'Do nothing
End Select
Your code should look like this:
Sub Naming()
'
' Naming Macro
' Assigns a category name in a cell based on values in a cell one column over
'
Dim number As Double
For i = 9 To 200
number = Cells(i, 3).Value
If number <= 199999 And number > 0 Then
Cells(i, 2) = "EP-GEARING"
ElseIf number <= 399999 And number > 199999 Then
Cells(i, 2) = "DRIVES"
ElseIf number <= 499999 And number > 399999 Then
Cells(i, 2) = "FLOW"
ElseIf number <= 599999 And number > 499999 Then
Cells(i, 2) = "SPARES"
ElseIf number <= 699999 And number > 599999 Then
Cells(i, 2) = "REPAIR"
ElseIf number <= 799999 And number > 699999 Then
Cells(i, 2) = "FS"
ElseIf number <= 899999 Then
Cells(i, 2) = "GC-GEARING"
End If
Next i
End Sub
The problem with your code as originally written is that, regardless of the Else clauses, the compiler still expects an End If for every If, and is getting confused because they are not there. The single keyword ElseIf only requires one End If statement at the end.
Goto's are seldom advisable. 99 percent of the time, there's a better and cleaner way to write it, without using a Goto.
The other answers indicate how you could fix your If statement so that VBA recognizes your For and Next pair up.
Now, personally, I would suggest using Select Case as GSerg indicated, if your loop were necessary.
But here is probably what I would do. In Cell B9 place the following formula: =IF(C9=0,"",IF(C9<=199999,"EP-GEARING",IF(C9<=399999,"DRIVES",IF(C9<=499999,"FLOW",IF(C9<=599999,"SPARES",IF(C9<=699999,"REPAIR",IF(C9<=799999,"FS",IF(C9<=899999,"GC-GEARING","")))))))) then copy it down where you need it.
Or if you want to do it with code you could replace your whole sub with no looping I could have written this as a 1 liner, but I wanted it to be legible:
Sub Naming()
'
' Naming Macro
' Assigns a category name in a cell based on values in a cell one column over
Dim theRange As Range
Set theRange = Range(Cells(9, 2), Cells(200, 2))
theRange.Value = "=IF(RC[1]=0,""""," & _
"IF(RC[1]<=199999,""EP-GEARING""," & _
"IF(RC[1]<=399999,""DRIVES""," & _
"IF(RC[1]<=499999,""FLOW""," & _
"IF(RC[1]<=599999,""SPARES""," & _
"IF(RC[1]<=699999,""REPAIR""," & _
"IF(RC[1]<=799999,""FS""," & _
"IF(RC[1]<=899999,""GC-GEARING"",""""))))))))"
'Optional if you want only the values without the formula, uncomment next line
'theRange.Value = theRange.Value
Set theRange = Nothing
End Sub
It is generally faster and cleaner to solve things like this using Excel formulas rather than writing out the logic in VBA and looping through cells.
Related
I'm currently working on a Quality Concern log that will be used at my place of work to track the quality concerns, and output specific data to the management via a dashboard.
One of the calculations i have, go through rows in a log and counts the number of rows that meet a certain criteria. It is essentially a CountIf function, but with a For loop. The count is then dumped into a cell, and the calculation moves onto the next value in the range.
I've currently got 95 entries into the log and the counts are running pretty slowly. As we get more quality concerns, its inevitable that the code will start to run even slower.
This is a sample of the code i'm running:
For Each cell In mnthRng
monthVal = cell.value
YearVal = cell.Offset(-1, 0).value
num = 1
Total_prjctCount = 0
For i = LBound(prjcts) To UBound(prjcts)
PrjctName = prjcts(i)
included_in_calcs = prjctYesNo(num, 1)
If included_in_calcs = "YES" Then
Total_Count = 0
For j = 8 To IDLastRow
If QCRLogSheet.Range("AI" & j) = monthVal _
And QCRLogSheet.Range("AK" & j) = YearVal _
And QCRLogSheet.Range("D" & j) = PrjctName Then
Total_Count = Total_Count + 1
Else
End If
Next j
Total_prjctCount = Total_Count + Total_prjctCount
End If
num = num + 1
Next i
cell.Offset(1, 0).value = Total_prjctCount
Next cell
Just to give you some more information on the code:
mnthRng is a cell range containing different months.
The array prjcts contains the name of the various different projects we have onsite, and allows me to sort the data out by project is someone unticks the "include in calculations" box on the dashboard
I've read that to speed up calculations of this nature, instead of looping per cell, i could add the range to an array, and do the count in the array. Unfortunately i'm not sure how i go about adding my data range into an array and then looping through it.
Any help would be much appreciated!
Untested:
Dim arrMonth, arrYear, arrProj
arrMonth = QCRLogSheet.Range("AI8:AI" & IDLastRow)
arrYear = QCRLogSheet.Range("AK8:AK" & IDLastRow)
arrProj = QCRLogSheet.Range("D8:D" & IDLastRow)
For Each cell In mnthRng
monthVal = cell.Value
YearVal = cell.Offset(-1, 0).Value
num = 1
Total_prjctCount = 0
For i = LBound(prjcts) To UBound(prjcts)
PrjctName = prjcts(i)
included_in_calcs = prjctYesNo(num, 1)
If included_in_calcs = "YES" Then
Total_Count = 0
For j = 1 To UBound(arrMonth, 1)
'nested if's are faster...
If arrMonth(j, 1) = monthVal Then
If arrYear(j, 1) = YearVal Then
If arrProj(j, 1) = PrjctName Then Total_Count = Total_Count + 1
End If
End If
Next j
Total_prjctCount = Total_Count + Total_prjctCount
End If
num = num + 1
Next i
cell.Offset(1, 0).Value = Total_prjctCount
Next cell
I'm attempting to make a user-defined function that mimics my formula. I need something more efficient than my formula.
I've tried VBA and using the above formula as is. This is ineffective for the larger datasets I'm working with.
=IF((AND(B2>=65,A2>=7)),"Greenbox",IF((AND(B2>10,A2=0,B2= "")),"Balance",IF((AND(B2>=65,A2<3)),"Yellowbox",IF((AND(B2<65,A2>=7)),"Purplebox",IF((AND(B2<65,A2<=3,A2>=1)),"Orangebox",IF(AND(B2>=65,A2<7,A2>=3),"Bluebox",IF(AND(A2<7,A2>=3,B2<65),"Redbox")))))))
A VBA function that mimics the formula.
If you do not want to supply an input to the UDF, you can grab the row number and worksheet using Application.Caller. Otherwise, you can add two arguments for the A2 & B2 range and compare the value there.
I have made no performance tests regarding the two methods, but I would imagine the one that does not use Application.Caller would be the one that has greater performance - but I figured another example wouldn't hurt.
Application.Caller Method
Function myFunc() As String
Dim r As Long, ws As Worksheet
Set ws = Application.Caller.Worksheet
r = Application.Caller.Row
If ws.Cells(r, "B").Value >= 65 And ws.Cells(r, "A").Value >= 7 Then
myFunc = "Greenbox"
ElseIf ws.Cells(r, "B").Value > 10 And ws.Cells(r, "A").Value = 0 Then
myFunc = "Balance"
'.... Continue
End If
End Function
Which the worksheet formula would look like: =myFunc(). (no arguments needed)
Function with Arguments Method
Function myFunc(rngA As Range, rngB As Range) As String
If rngB.Value >= 65 And rngA.Value >= 7 Then
myFunc = "Greenbox"
ElseIf rngB.Value > 10 And rngA.Value = 0 Then
myFunc = "Balance"
'.... Continue
End If
End Function
Which the worksheet formula would look like: =myFunc($A2, $B2).
As already mentioned in the comments by Scott Craner, AND(B2>10,A2=0,B2= "") isn't logically correct. B2>10 and B2="" will never be True when used together with AND, so you may need to figure your intentions out with that one.
Here's a go. Total is the value returned in the cell with the function called.
It would be cell a2 and B would be cell b2 in the example
=caseTest(A2,B2)
Per Scott's note, <10 is used rather than null. Feel free to edit.
There is also a default value if none of the conditions are met. Happy coding.
Function caseTest(A, B)
Dim scoreA As Integer, scoreB As Integer, result As String
scoreA = A.Value
scoreB = B.Value
If ((scoreA >= 7) And (scoreB >= 65)) Then
Total = "Greenbox"
ElseIf ((scoreA = 0) And (scoreB <10)) Then
Total = "Balance"
ElseIf ((scoreA < 3) And (scoreB >= 65)) Then
Total = "Yellowbox"
ElseIf ((scoreA >= 7) And (scoreB < 65)) Then
Total = "Purplebox"
ElseIf ((scoreA <= 3) And (scoreA >= 1) And (scoreB < 65)) Then
Total = "Orangebox"
ElseIf ((scoreA >= 3) And (scoreA < 7) And (scoreB >= 65))) Then
Total = "Bluebox"
ElseIf ((scoreA >= 3) And (scoreA < 7) And (scoreB < 65))) Then
Total = "Redbox"
Else
Total = "default"
End If
caseTest = Total
End Function
if you need help on creating custom functions below link is an useful guide
https://support.office.com/en-us/article/create-custom-functions-in-excel-2f06c10b-3622-40d6-a1b2-b6748ae8231f.
To answer your question, I have created a custom function based on what I deciphered from your formula.
Function Cust_SetBox(A as Range, B as Range) As String
'function will receive two parameters A and B as ranges and return back a string
Application.Volatile 'this ensures that formula will update when cell values are modified
'Original formula
'=IF((AND(B2>=65,A2>=7)),"Greenbox",IF((AND(B2>10,A2=0,B2= "")),"Balance",IF((AND(B2>=65,A2<3)),"Yellowbox",IF((AND(B2<65,A2>=7)),"Purplebox",IF((AND(B2<65,A2<=3,A2>=1)),"Orangebox",
'IF(AND(B2>=65,A2<7,A2>=3),"Bluebox",IF(AND(A2<7,A2>=3,B2<65),"Redbox")))))))
'adding .value="" condition as emtpy cells will show up as true while checking for X.Value<n
If B.Value = "" And A.Value = "" Then
Cust_SetBox = "Unknown"
ElseIf (B.Value = "" Or B.Value > 10) And A.Value = 0 Then 'you might want to check this condition as it is not clear from your formula
Cust_SetBox = "Balance"
ElseIf B.Value >= 65 And B.Value <> "" Then
If A.Value >= 7 Then
Cust_SetBox = "Greenbox"
ElseIf A.Value < 3 Then
Cust_SetBox = "Yellowbox"
ElseIf A.Value < 7 And A.Value >= 3 Then
Cust_SetBox = "Bluebox"
End If
ElseIf B.Value < 65 And B.Value <> "" Then
If A.Value >= 7 Then
Cust_SetBox = "Purplebox"
ElseIf A.Value <= 3 And A.Value >= 1 Then
Cust_SetBox = "Orangebox"
ElseIf A.Value < 7 And A.Value >= 3 Then
Cust_SetBox = "Redbox"
End If
Else
Cust_SetBox = "Unknown"
End If
End Function
To quickly add this function to your workbook.
use Alt+F11, insert a new module and add above code to the module. You should be able to use this new function as a formula.
Go to any cell and type the below
=Cust_SetBox(A1,B1)
The cell will show the value based on the logic above. Detailed explanation in the link above.
Note
Make sure Calculation is set to automatically calculate (in Formulas menu -> Calculation options) else press F9 to calculate
The workbook has to be saved as a macro enabled file else the function will not be available later.
I have a code that searches an excel fill for the word distance, takes the value of the cell next to it, paste it into a new cell, then sums all the cells up. Which works great, but i now need to find a way to only sum ever even row number. Does that make sense?
Sub Distance_Check()
Dim DistanceCheck As String
Dim DistanceNumber As String
Dim DistanceSum As String
Dim DistanceTotal As String
DistanceCheck = MsgBox("Would you like to check the distance?", vbYesNo)
If DistanceCheck = vbYes Then
If IsArray(fname) Then Workbooks.OpenText fname(1)
i = 1
findStr = "Distance"
Set foundCel = Range("A:A").Find(what:=findStr)
If Not foundCel Is Nothing Then
firstAddress = foundCel.Address
Do
Range("J" & i).Value = foundCel.Offset(0, 1).Value
Set foundCel = Range("A:A").FindNext(foundCel)
i = i + 1
Loop While Not foundCel Is Nothing And foundCel.Address <> firstAddress
End If
Set wkbSourceBook = ActiveWorkbook
DistanceNumber = i - 2
DistanceSum = WorksheetFunction.Sum(Range(Cells(2, 15), (Cells(DistanceNumber + 1, 15))))
DistanceTotal = DistanceSum / DistanceNumber
If DistanceNumber = Cells(2, 12) Then
MsgBox ("No error found wihin distance")
Else
MsgBox ("Error found with distance")
End If
Else
End If
Call Save_Data
End Sub
Would the way youd go about this be using a for loop on the
cells(DistanceNumber(j,+1)
Where j = 0,
j = j +2 ,
Until j > DistanceNumber,
Would that work? If so how would you go about it?
Thanks
A quick way to step through a loop in the desired increments is to use the Mod operator which divides two numbers and returns any remainder (e.g. 7 mod 2 = 1, as two sixes fit into seven, leaving one).
You can use the row property of the range you identify with the Find method, and since you want to jump by two the modulo should be zero:
If foundcel.Row Mod 2 = 0 Then Range("J" & i).value = foundcel.Offset(0, 1).Value
That said, there is a 'built in' way to step through a loop if using a For loop like this
For x = 2 to 10 Step 2
' Do stuff
Next x
You can also use this method to step backwards, e.g.
For x = 100 to 0 Step -10
' Do stuff backwards!
Next x
I've got a macro that works perfectly but that I now need to customize it and add complexity.
The macro is basically the following code repeated numerous times for a variety of ranges.
For i = 2 To n
Range(Cells(13, i), Cells(19, i)).Value = Sheets(i).Range("J13:J19").Value
Next i
The logic/complexity that I need to add to this should go as follows:
if the sum of the range O13:O19 on sheet i is greater than zero, then the value of the range cells(13,i),cells 19,i) on this sheet are equal to the value of the range p13:p19 on sheet i.
If the value of the sum of range O13:O19 on sheet i is not greater than 0, then set the value of the target range equal to each cell in (range sheet(i).range("I13:I19")-sheet(i).range("K13:K19")*4).value
In simpler terms, if the sum of the range is 0, set the value of every cell in range A to the value of every cell in range b less the (value of every cell in range C * 4)...
Sub Op_ex_analysis_macro()
ActiveWorkbook.Sheets.Add Before:=Worksheets(1)
ActiveSheet.Name = "Control Panel"
Range("A:A").ColumnWidth = 36
Range("A12").Value = "Property Code"
Range("A13:A16") = Sheets(2).Range("A13:A16").Value
Range("A17") = Sheets(2).Range("B17").Value
Range("A18") = Sheets(2).Range("A18").Value
Range("A19") = Sheets(2).Range("B19").Value
Range("A20:A29") = Sheets(2).Range("A21:A30").Value
Range("A30") = Sheets(2).Range("B31").Value
Range("A31") = Sheets(2).Range("A33").Value
Range("A32:A36") = Sheets(2).Range("A35:A39").Value
Range("A37:A38") = Sheets(2).Range("A41:A42").Value
Range("A40").Value = "Analyst"
Range("A41").Value = "Number of Units"
Range("A42").Value = "Asset Manager"
Range("A43").Value = "Tenancy"
Range("A44").Value = "Year Built/Type"
Range("A45").Value = "Management Company"
Range("A46").Value = "End of Compliance Year"
Range("A47").Value = "Property Name"
Range("A48").Value = "Number of Properties"
Range("A49").Value = "City"
Range("A50").Value = "State"
'Consolidate Property Codes
n = ActiveWorkbook.Sheets.Count
For i = 2 To n
Z = Sheets(i).Range("P49").Value
Cells(12, i) = Z
Next i
'Consolidate rows 13-19
For i = 2 To n
Select Case Application.Sum(ThisWorkbook.Sheets(i).Range("O13:O33"))
Case Is > 0
Range(Cells(13, i), Cells(19, i)).Value = Sheets(i).Range("P13:P19").Value
Case Is = 0
Range(Cells(13, i), Cells(19, i)).Value = ThisWorkbook.Sheets(i).Range("I13:I19") - ThisWorkbook.Sheets(i).Range("K13:K19") * 4
End Select
Next i
In this case i think the best option is to use a Select case statement.
For i = 2 To n
Select Case Application.Sum(ThisWorkbook.Sheets(i).Range("O13:O33"))
Case Is > 0
Range(Cells(13, i), Cells(19, i)).Value = Sheets(i).Range("P13:P19").Value
Case Is < 0
Range(Cells(13, i), Cells(19, i)).Value = ThisWorkbook.Sheets(i).Range("I13:I19") - ThisWorkbook.Sheets(i).Range("K13:K19") * 4
End Select
Next i
Hope this helps :)
EDIT If ou want to account for whent it's "0" then just add a Case Is 0
After a lot of trial and error, I was able to solve the problem through via a different route.
As A.S.H correctly noted above, you can't do arithmetic on VBA arrays.
The first half of my code was basically moving an array, as Scott Craner noted on a different page, which is simple.
Directing VBA to perform calculations requires the coder to send the formula through a range cell by cell.
Ultimately, the code that performed as required was as follows:
Dim rng As Range
n = ActiveWorkbook.Sheets.Count
With ActiveSheet
For i = 2 To n
If Application.Sum(Sheets(i).Range("O13:O33")) > 0 Then
.Range(.Cells(13, i), .Cells(19, i)).Value = Sheets(i).Range("P13:P19").Value
Else
For Each rng In .Range(.Cells(13, i), .Cells(19, i))
rng.Value = Sheets(i).Cells(rng.Row, "I") - (4 * Sheets(i).Cells(rng.Row, "K"))
Next rng
End If
Next i
End With
If the condition of the first 1/2 of the if statement is met, then it's just set these values equal to those values. If the condition is not met, then the Else statement directs Excel to move through the range performing the calculation as it goes.
The code I have once worked to add/delete groups of rows (requirements). I needed to modify the code so that if the 1st row of the group met certain criteria (i.e, the requirement was not one we wanted to consider), (1) we would not count it and (2), we would hide the group (current and subsequent 2 rows). This all works fine.
The problem is that now that I incorporated these changes, I get an error in another section of the code and for the life of me I cannot figure out why. I have stepped through this and am extremely frustrated. I am reaching out for help, and am hoping someone can see my error(!)
We calculate the start and finish row numbers within a grouping, and store these calculations in Arrays called "Start" and "Finish." I use the ReDim statement to initialize my arrays, because I thought that could be part of the problem, but no.
Any insight as to why my "subscripts are out of range" would be appreciated. I have traced through the logic, investigated this error, and read about the syntax/usage of VBA arrays. I don't know what else to do. Thanks in advance. Here are the relevant lines:
Sub Button1_Click()
Cells.Select
Selection.ClearOutline
If Cells.EntireRow.Hidden Then Cells.EntireRow.Hidden = False
Dim Start() As Integer
Dim Finish() As Integer
Dim p As Integer, q As Integer
ReDim Start(0, 50)
ReDim Finish(0, 50)
The following is embedded in logic that loops through all the rows in the spreadsheet:
i = 1
For Row = 4 To Cells(1, 6).Value - 1
If Begin Then
If Cells(Row, 3).Interior.ColorIndex = 44 Then
Start(i) = Row + 1
j = Cells(Row, 2).Value
Begin = False
End If
Else
If Cells(Row, 2).Value = j + 1 Or Cells(Row, 2).Interior.ColorIndex = 37 Then
Finish(i) = Row - 1
Begin = True
i = i + 1
Row = Row - 1
End If
End If
Next
The block I changed is as follows (code I added is last block where I attempt to hide rows). It precedes the previous. I am wondering how my change could have affect the above(?!)
If Cells(Row, 5).Value = "Requirement" Then
Range(Cells(Row, 4), Cells(Row, 4)).Interior.ColorIndex = 40
Rows(Row).Font.Bold = True
Rows(Row).Font.Italic = False
Rows(Row).Font.ColorIndex = 1 'Black
If Cells(Row - 3, 4).Value = "" Then 'this is requirement #1
Cells(Row, 4).Value = 1
Else
Cells(Row, 4).Value = Cells(Row - 3, 4).Value + 1
End If
p = Row
q = p + 2
Rows(p & ":" & q).Select
If Cells(p, 19).Value = "4" Then
Selection.EntireRow.Hidden = True
Else
Selection.EntireRow.Hidden = False
End If
Redim Start(0,50) makes the array dimensions 0 to 0, 0 to 50 i.e. a 2d array.
This means that when you call the array you need to provide parameters for both dimensions I.E: Start(0,i) or Finish(0,i). Calling Start(i) will result in the error you mentioned.
The easiest way to get rid of the error is to change your Redim lines to
ReDim Start(50)
ReDim Finish(50)
which is what I assume you meant to do in the first place.
Note: Based upon the format you used, you may have been meaning to do Start(0 to 50) and Finish(0 to 50) initially. The comma in the dimensioning indicates another dimension instead of a separation between lower and upper bounds.