Nested for and nested if - vba

I'm trying to implement a nested for and a nested if statement together. I have the following column below. It needs to look at the column if the range is between 500-1000 it should give recommendation a (i.e. write the recommendation in another column) if it is more than 1000 it should give another recommendation in the responding column.
Income Recommendation
550 a
1200 b
750 a
1400 b
600 a
Dim i As Integer
Dim j As Integer
For i = 2 To Range(i, 1).End(xlDown).Row
If Cells(i, 1).Value > 1000 Then
Cells(i, 10).Value = "b"
i = i + 1
Else
If Cells(i, 1).Value < 1000 Then
If Cells(i, 1).Valie > 500 Then
Cells(i, 10).Value = "a"
End If
End If
i = i + 1
End If
Next i
End Sub

Several errors:
Don't rely on i having a value while it is setting the start and end values of the For loop - there is a good chance that it is 0 while calculating Range(i, 1). (Edit: Tested and confirmed that it is still 0 at the point when the end value is being calculated.) Using Range(0, 1) will give a 1004 error.
Don't increment the loop counter within the loop (i.e. don't do i = i + 1) - it will almost certainly confuse things. If you really only want to process every second row, use Step 2 on the For statement.
.Valie should be .Value
Don't use Integer data types for rows - these days Excel can handle 1048576 rows, which is more than an Integer can cope with.
Range(1, 1) is invalid syntax. When passing two parameters to the Range property, they need to be cell references. Passing a row and column is what is used when using the Cells property. (So Range(1, 1) will need to be Cells(1, 1), or Range("A1").)
Refactoring your code would give:
Dim i As Long
For i = 2 To Cells(1, "A").End(xlDown).Row
If Cells(i, "A").Value > 1000 Then
Cells(i, "J").Value = "b"
ElseIf Cells(i, "A").Value > 500 Then
Cells(i, "J").Value = "a"
Else
Cells(i, "J").Value = ""
End If
Next i
End Sub

You can do it like this with Select Case:
Public Sub TestMe()
Dim i As Long
Dim j As Long
With ActiveSheet
For i = 2 To .Cells(1, 1).End(xlDown).Row
Select Case True
Case .Cells(i, 1) > 1000
.Cells(i, 10) = "b"
Case .Cells(i, 1) < 1000 And .Cells(i, 1) > 500
.Cells(i, 10).value = "a"
End Select
Next i
End With
End Sub
It is more visible and a bit more understandable. Also, make sure that you refer to the Worksheet (in this case with ActiveSheet), to avoid reference problems in the future.

Related

What am I doing wrong? Removing duplicates using Excel VBA

I'm new to VBA so this is probably a very obvious mistake.
To keep it short, I am trying to delete rows based on two criteria: In Column A, if they have the same value (duplicate) and in Column B, the difference is less than 100, then one row is deleted from the bottom.
Example data:
Column A Column B
1 300
1 350 SHOULD be deleted as second column diff. is <100 compared to row above
2 500
2 700 Should NOT be deleted as second column diff. is not <100
Here is the code I have come up with:
Sub deduplication()
Dim i As Long
Dim j As Long
Dim lrow As Long
Application.ScreenUpdating = False
With Worksheets("Sheet1")
lrow = .Range("A" & .Rows.Count).End(xlUp).Row
For i = lrow To 2 Step -1
For j = i To 2 Step -1
If .Cells(i, "A").Value = .Cells(j, "A").Value And .Cells(i, "B").Value - .Cells(j, "B").Value < 100 Then
.Cells(i, "A").EntireRow.Delete
End If
Next j
Next i
End With
End Sub
This largely works, but only if the second criterion is greater than (>) rather than less than (<). When it is less than, it deletes every row. What am I doing wrong? Is there an easy fix?
Thank you
Not
If .Cells(i, "A").Value = .Cells(j, "A").Value And .Cells(i, "B").Value - .Cells(j, "B").Value < 100 Then
Here in the second part of the statement, you're just comparing .Cells(j, "B").Value to const 100 !
But
If .Cells(i, "A").Value = .Cells(j, "A").Value And Abs(.Cells(i, "B").Value - .Cells(j, "B").Value) < 100 Then
Abs() may help, else keep just the ( )
Something like this should work for you:
Sub tgr()
Dim ws As Worksheet
Dim rDel As Range
Dim rData As Range
Dim ACell As Range
Dim hUnq As Object
Set ws = ActiveWorkbook.Sheets("Sheet1")
Set hUnq = CreateObject("Scripting.Dictionary")
Set rData = ws.Range("A2", ws.Cells(ws.Rows.Count, "A").End(xlUp))
If rData.Row = 1 Then Exit Sub 'No data
For Each ACell In rData.Cells
If Not hUnq.Exists(ACell.Value) Then
'New Unique ACell value
hUnq.Add ACell.Value, ACell.Value
Else
'Duplicate ACell value
If Abs(ws.Cells(ACell.Row, "B").Value - ws.Cells(ACell.Row - 1, "B").Value) < 100 Then
If rDel Is Nothing Then Set rDel = ACell Else Set rDel = Union(rDel, ACell)
End If
End If
Next ACell
If Not rDel Is Nothing Then rDel.EntireRow.Delete
End Sub
Sticking to the format of your code, you can do this using one For loop as well.
For i = lrow To 3 Step -1
If .Cells(i, "A") = .Cells(i - 1, "A") And (.Cells(i, "B") - .Cells(i - 1, "B")) < 100 Then
.Cells(i, "A").EntireRow.Delete
End If
Next i
Every first j-cycle starts off by comparing a row to itself since you start with j = i. The difference between a value and itself is always zero. (It also compares row 2 with itself as the very last step.)
However, if you switch:
For i = lrow To 2 Step -1
For j = i To 2 Step -1
to:
For i = lrow To 3 Step -1
For j = i - 1 To 2 Step -1`
the code will compare all the various rows without the self-compares.
Another point (which #Proger_Cbsk 's answer brought to mind), is that doing the comparison with just the subtraction .Cells(i, "B").Value - .Cells(j, "B").Value < 100 will sometimes cause unexpected results.
For example, assume .Cells(i, "B").Value = 1 and .Cells(j, "B").Value = 250. We can tell by just looking, that there is a difference of at least 100, so you would expect this part of the expression to evaluate to False. However, from straight substitution, you get the expression: 1 - 250 < 100. Since 1 - 250 = -249, and since -249 < 100, the expression would actually evaluate to True.
However, if you were to change .Cells(i, "B").Value - .Cells(j, "B").Value < 100 to Abs(.Cells(i, "B").Value - .Cells(j, "B").Value) < 100, the expression will now be looking at if the difference is greater or less than 100, instead of looking at if the subtraction result is greater or less than 100.
Why not to use the built-in command:
Worksheets("Sheet1").Range("$A:$A").RemoveDuplicates Columns:=1, Header:=xlYes
Range.RemoveDuplicates Method (Excel)

LEN() returning wrong value in VBA

I have this simple data set:
230
16000
230
230000
230000
230000
16000000
230000
230000
all i want is to get the length of each cell but when i write this code:
Sub LengthOfCell()
Dim c As Long
Dim result As Integer
c = ActiveCell.Value
result = Len(c)
Debug.Print (result)
End Sub
it gives me 2 for the first cell (230) when it should be 3 and 4 for any number having more than 3 digits. dont know what i am doing wrong. tis is just for proof of concept for a larger SUB:
Public Sub SortMyData()
'approach: convert line to string and concatenate to that as it's a lot less picky than Excel's formats, then replace cell value with the new string.
' Excel will then define the string type as either Percentage or Scientific depending on the magnitude.
Dim i As Integer
Dim N_Values As Integer
N_Values = Cells(Rows.Count, 2).End(xlUp).Row
'Range("B6", Range("B5").End(xlDown)).Count
For i = 6 To N_Values 'iteration loop from 6 (first row of value) to N_Values (last filled row)
Cells(i, 3).NumberFormat = "0"
If Cells(i, 2).NumberFormat <> "0.0%" Then
Cells(i, 2).NumberFormat = "0.0%"
Cells(i, 2).Value = Cells(i, 2).Value / 100
ElseIf Len(Cells(i, 3).Value > 3) Then
Cells(i, 3).Value = Cells(i, 3).Value / 1000
ElseIf Cells(i, 3).Value = Null Then
Cells(i, 3).Value = 0
Else
Cells(i, 2).Value
Cells(i, 3).Value
End If
' If Len(Cells(i, 3) > 3) Then
' Cells(i, 3).Value = Cells(i, 3).Value / 1000
' ElseIf Cells(i, 3).Value = Null Then
'Cells(1, 3).Value = 0
' Else
' Debug.Print
' End If
Next
End Sub
The closing ) is in the wrong place.
If Len(Cells(i, 3).Value > 3) Then
should be
If Len(Cells(i, 3).Value) > 3 Then
Len(Cells(i, 3).Value > 3) will evaluate to Len("True") or Len("False"), so it will always be True (any non-zero number is True)
Len is a String type function
#Shai Rado, please, be careful with such statements in answers for newbies...
F1: Len Function
Returns a Long containing the number of characters in a
string or the number of bytes required to store a variable.
Not sure why you're including the Dim c As Long piece at all - why not try this:
Sub LengthOfCell()
Dim result As Integer
result = Len(ActiveCell.Value)
Debug.Print (result)
End Sub
That works fine for me..
Since you are looking for the number of characters (digits) in the cell, you need to change to Dim c As String and modify your code a little, it will give you the Result that you are looking for.
See short-sub below:
Sub LengthOfCell()
Dim c As String
Dim i As Long
Dim result As Integer
For i = 1 To 9
c = CStr(Cells(i, 1).Value)
result = Len(c)
Debug.Print result
Next i
End Sub
There seems to be a confusion between Value and Display Text. Range().Value will return the ranges raw value, where as, Range().Text or Cstr(Range().Value) will return the formatted value.
Sub Demo()
Dim r As Range
For Each r In Range("A2:A9")
r.Value = 230
r.Offset(0, 1) = r.NumberFormat
r.Offset(0, 2) = Len(r.Value)
r.Offset(0, 3) = Len(r.Text)
r.Offset(0, 4) = Len(CStr(r.Value))
Next
End Sub

Loop Crashing Excel VBA

i have been having problems with getting my code to run through its conditional loop and stopping. Here is what I have:
Do While True
Dim i As Integer
i = 2
If Cells(i, 1).Value <> "" And Not IsError(Cells(i, 2).Value) Then
Range(Cells(i, 1), Cells(i, 22)).Copy
Range(Cells(i, 1), Cells(i, 22)).PasteSpecial
i = i + 1
Else
Exit Do
End If
Loop
What I'm trying to do is to get the program to check if one cells isn't empty and if another doesn't have an error in it, if that condition is met, then the program would copy a certain row and re-paste it as just it's values since some of the cells in the row is a formula. For some reason the loop doesn't exit and Excel crashes, am I missing something?
the i = 2 should be outside
Dim i As Integer
i = 2
Do While True
If Cells(i, 1).Value <> "" And Not IsError(Cells(i, 2).Value) Then
Range(Cells(i, 1), Cells(i, 22)).Copy
Range(Cells(i, 1), Cells(i, 22)).PasteSpecial
i = i + 1
Else
Exit Do
End If
Loop
Two points :
The i = 2 must be outside the while loop.
Don't use Copy and PasteSpecial. Using the clipboard will give lots of problems later on. Additionally PasteSpecial likes you to be specific with "what" PasteSpecial action you're using. Rather assign values directly.
Dim i As Integer, Dataset As Variant
i = 2
Do While True
If Cells(i, 1).Value <> "" And Not IsError(Cells(i, 2).Value) Then
'This seems silly, but it overwrites the cell with its value.
Dataset = Range(Cells(i, 1), Cells(i, 22)).Value
Range(Cells(i, 1), Cells(i, 22)).Value = Dataset
i = i + 1
Else
Exit Do
End If
Loop

'If ... Then' statement with loop

I've what seems like a pretty simple application with looping and 'If..Then' statements but need some help on structuring it.
In very a basic example, I have a list numbers in column A and the values PM or AM listed in column B. I want to write a loop that will search every value in column B until the end of the data set, and add 12 to each value in column A each time column B has a value of PM. In a nutshell, it would look like this:
If column B = PM
then add 12 to its corresponding cell in column A
else move down to the next row and do the same thing until you reach an empty cell
There are many ways, here is a typical one:
Sub dural()
Dim i As Long
i = 1
Do While Cells(i, "B").Value <> ""
If Cells(i, "B").Value = "PM" Then
Cells(i, "A").Value = Cells(i, "A").Value + 12
End If
i = i + 1
Loop
End Sub
you can set it with For next loop and 2 variables. one for last row and the 2nd for the row count:
Sub Macro1()
Dim LastRow As String
Dim i As Integer
LastRow = Cells(Rows.Count, "A").End(xlUp).Row
For i = 1 To LastRow
If Cells(i, 2).Value = "PM" Then Cells(i, 1).vlaue = Cells(i, 1).vlaue + 10
Next i
End
'
End Sub
This is another way to do this.
Option Explicit
Sub Add()
Dim rData As Range
Dim r As Range
Set rData = Cells(1, 1).CurrentRegion.Columns("B").Cells
For Each r In rData
If UCase$(r.Value) = "PM" Then
r.Offset(, -1).Value = r.Offset(, -1).Value + 12
End If
Next r
End Sub

Copy certain values from one to another column and deleting the original value

I want to copy values from one column to another column (into the same row) if the cell contains the word IN and delete the original value. If not, the code should proceed to the next row and perform a new test. Thus the cell in the target column will remain empty.
When I run the code in Excel nothing happens, so I don't know what is wrong.
Ideally the code should jump to the next column (8) and do the same search and paste the value into the same column (5) when it is done with the first column, but this I haven't started with yet. So I do appreciate tips for that as well :)
Sub Size()
Dim i As Integer, a As String
i = 2
a = "IN"
Do While Cells(i, 7).Value <> ""
If InStr(Cells(i, 7), a) Then
'copying the value to another column but within the same row
Cells(i, 7).Copy Cells(i, 5)
Cells(i, 7).Clear
i = i + 1
Else
i = i + 1
End If
Loop
End Sub
I found out that my first cell in column 7 was empty and thus the Do While Cells(i, 7).Value <> "" wasn't working. Hence I'm refering to a different column that always contain data. Note that the solution code also jumps to the 2 next columns in order to search for the same word.
Sub Size()
Dim i As Integer, a As String
j = 0
i = 1
a = "IN"
Range("A1").Offset(i, 0).Select
For j = 0 To 2
Do Until Selection.Value = ""
If InStr(Range("G1").Offset(i, j).Value, a) Then
Range("E1").Offset(i, 0).Value = Range("G1").Offset(i, j).Value
Range("G1").Offset(i, j).Clear
i = i + 1
Range("A1").Offset(i, 0).Select
Else
i = i + 1
Range("A1").Offset(i, 0).Select
End If
Loop
i = 1
Range("A1").Offset(i, 0).Select
Next j
End Sub