VBA EXCEL Range syntax - vba

I don't understand syntax for range.
Why does this work:
For i = 1 To 10
Range("A" & i & ":D" & i).Copy
Next
But this doesn't work:
For i = 2 To lastRow
num = WorksheetFunction.Match(Cells(i, 1), Range("A" & lastRow), 0)
Next
Why do I need to use
For i = 2 To lastRow
'num = WorksheetFunction.Match(Cells(i, 1), Range("A1:A" & lastRow), 0)
Next
What A1:A mean? Why can't I use
Range("A" & lastRow), 0

There is nothing wrong with your syntax and your code should've work just fine.
The problem with using worksheet function like Match, Vlookup and other look up functions is that if the value being searched is not found, it throws up an error.
In your case, you are trying to search multiple values in just one cell.
So let us say your lastrow is 9. You're code will loop from Cell(2,1) to Cell(9,1) checking if it is within Range("A" & lastrow) or Range("A9").
If your values from Cell(2,1) through Cell(9,1) is the same as your value in Range("A9"), you won't get an error.
Now, if you use Range("A1:A" & lastrow), it will surely work cause you are trying to match every element of that said range to itself and surely a match will be found.
WorksheetFunction.Match(Cells(2,1), Range("A1:A9")) 'will return 2
WorksheetFunction.Match(Cells(3,1), Range("A1:A9")) 'will return 3
'
'
'And so on if all elements are unique
It doesn't matter if you use Range("A9") or Range("A1:A9").
What matters is that you handle the error in case you did not find a match.
One way is to use On Error Resume Next and On Error Goto 0 like this:
Sub ject()
Dim num As Variant
Dim i As Long, lastrow As Long: lastrow = 9
For i = 2 To lastrow
On Error Resume Next
num = WorksheetFunction.Match(Cells(i, 1), Range("A" & lastrow), 0)
If Err.Number <> 0 Then num = "Not Found"
On Error GoTo 0
Debug.Print num
Next
End Sub
Another way is to use Application.Match over WorksheetFunction.Match like this:
Sub ject()
Dim num As Variant
Dim i As Long, lastrow As Long: lastrow = 9
For i = 2 To lastrow
num = Application.Match(Cells(i, 1), Range("A" & lastrow), 0)
Debug.Print num
'If Not IsError(num) Then Debug.Print num Else Debug.Print "Not Found"
Next
End Sub
Application.Match works the same way but it doesn't error out when it returns #N/A. So you can assign it's value in a Variant variable and use it later in the code without any problem. Better yet, use IsError test to check if a value is not found as seen above in the commented lines.
In both cases above, I used a Variant type num variable.
Main reason is for it to handle any other value if in case no match is found.
As for the Range Syntax, don't be confused, it is fairly simple.
Refer to below examples.
Single Cell - All refer to A1
Cells(1,1) ' Using Cell property where you indicate row and column
Cells(1) ' Using cell property but using just the cell index
Range("A1") ' Omits the optional [Cell2] argument
Don't be confused with using cell index. It is like you are numbering all cells from left to right, top to bottom.
Cells(16385) ' refer to A2
Range of contiguous cell - All refer to A1:A10
Range("A1:A10") ' Classic
Range("A1", "A10") ' or below
Range(Cells(1, 1), Cells(10, 1))
Above uses the same syntax Range(Cell1,[Cell2]) wherein the first one, omits the optional argument [Cell2]. And because of that, below also works:
Range("A1:A5","A6:A10")
Range("A1", "A8:A10")
Range("A1:A2", "A10")
Non-Contiguous cells - All refer to A1, A3, A5, A7, A9
Range("A1,A3,A5,A7,A9") ' Classic

Without any specific details about the error, I assume that Match does not return the value you expect, but rather an #N/A error. Match has the syntax
=match(lookup_value, lookup_range, match_type)
The lookup_range typically consists of a range of several cells, either a column with several rows or a row with several columns.
In your formula, you have only one cell in the lookup_range. Let's say Lastrow is 10. The first three runs of the loop produce the formula
=Match(A2,A10,0)
=Match(A3,A10,0)
=Match(A4,A10,0)
It is a valid formula but in most cases the result won't be a match but an error. Whereas what you probably want is
=Match(A2,A1:A10,0)
Looking again at your code, stitch it together and find why you need A1:A as a string constant in your formula:
For i = 2 To lastRow
num = WorksheetFunction.Match(Cells(i, 1), Range("A1:A" & lastRow), 0)
Next

Related

Issue trying to filter long numbers

I have a column with lots of longer numbers in it. I want to filter my column for all numbers that DO NOT begin with 2. I tried writing it in code to select all of the 2's first, but that didn't work. Then I tried to do it by hand in Excel by saying filter, custom rule, does not begin with 2. But nothing changes. I don't see, for example, a zero before the numbers either. I tried to reformat them into numbers but they were already numbers. I am not sure why it isn't selecting either all the numbers that start with 2 or all the numbers that DON'T start with 2.
Here is the code I tried to get numbers that START with 2.
With ws1.Range("A1:Z" & UsdRws)
.autofilter Field:=6, Criteria1:="2*", Operator:=xlFilterValues
On Error Resume Next
UsdRws = ws1.Range("C" & Rows.Count).End(xlUp).Row
If UsdRws > 1 Then
Range("A2:A" & UsdRws).SpecialCells(xlCellTypeVisible).EntireRow.Delete
End If
On Error GoTo 0
.autofilter Field:=9
Cells.EntireColumn.AutoFit
End With
The filter does not change anything even though I have a lot of numbers beginning with 2. Here is a sample of the numbers I am using.
2190190000
1410650400
1410720000
1410511900
1410650000
1410511900
1410650500
1410680100
1410650300
2190160000
1410650100
2190160000
2020110008
1410511900
1410650400
2020110002
2020180000
2010110100
1410671000
2190190000
1410650200
2020180000
2190190000
1410650400
1410720000
1410511900
1410650000
1410511900
1410650500
So like I said, I am trying to get either all the numbers beginning with 2 or all the other ones, but it seems like neither is working, even when doing it by hand using a custom filter rule.
If all of your numbers are 10 digit numbers, then the following seems to work:
Option Explicit
Sub FilterTwos()
Dim ws1 As Worksheet
Dim r As Range
Set ws1 = Worksheets("sheet1")
With ws1
Set r = .Range("a2", Cells(.Rows.Count, "A").End(xlUp))
End With
ws1.AutoFilterMode = False
r.AutoFilter Field:=1, Criteria1:=">=2000000000", Operator:=xlAnd, _
Criteria2:="<3000000000"
Set r = r.SpecialCells(xlCellTypeVisible)
r.Copy Worksheets("sheet2").Range("B2")
End Sub
In your With statement you use UsdRws but don't give UsdRws a value until within the 'With'. May this be causing your problem?
You could also try treating the numbers as strings (change format to text?). This way Excel shouldn't get confused by what it thinks you should or shouldn't be able to do with numbers.
This works regardless of how many digits your numbers contain. Your inputRange should just be the column with the long numbers in, include headers too.
Sub Remove2()
Dim inputRange As Range
Dim v As Variant
Dim i As Long, deleteCount As Long, min As Long
Set inputRange = Sheet1.Range("A1:A10001")
v = inputRange.Value
min = Application.WorksheetFunction.min(inputRange) - 1
For i = 1 To UBound(v)
If Left(v(i, 1), 1) = "2" Then
v(i, 1) = min
deleteCount = deleteCount + 1
End If
Next i
With inputRange
.Value = v
.Sort key1:=.Resize(1, 1), order1:=xlAscending, Header:=xlYes 'xlNo if no headers
.Resize(deleteCount, 1).Offset(1, 0).EntireRow.Delete 'remove offset if no headers
End With
End Sub
tested with 10,000 numbers taking 0.4 seconds

First blank ("") cell in column with IF formula

I have a macro that exactly copies one sheet's data into another.
Sub QuickViewRegMgmt()
("Reg Management").Select
Cells.Select
Selection.Copy
Sheets("Quick View Reg Mgmt").Select
Cells.Select
ActiveSheet.Paste
End Sub
I would like for this macro to also go to the last non-blank cell in Column C (or first blank, I really don't care either way). I tried simple end/offset code, e.g.
Range("A1").End(xldown).Offset(1,0).Select
My problem, however, is that the direct copy macro also copies the underlying formulas, which for Column C is an IF formula. Therefore, no cell in the column is actually empty, but rather they all have an IF formula resulting in a true/false value (respectively, a "" or VLOOKUP).
=IF(VLOOKUP('Reg Management'!$Y260,'Reg Guidance'!$A:$V,3,FALSE)=0,"",VLOOKUP('Reg Management'!$Y260,'Reg Guidance'!$A:$V,3,FALSE))
That means the end/offset code goes to the last cell in the column with the formula (C1000) instead of going to the first cell that has a value of "" (which is currently C260).
What code can I add to this macro to select the first cell that contains an IF formula resulting in a value of "" ---- which has the appearance of being blank?
After trying to be fancy with SpecialCells(), or using Find() or something I couldn't get it ...so here's a rather "dirty" way to do it:
Sub test()
Dim lastRow As Long, lastFormulaRow As Long
lastRow = Range("A" & Rows.Count).End(xlUp).Row
Dim i As Long
For i = lastRow To 1 Step -1
If Cells(i, 1).Formula <> "" And Cells(i, 1).Value = "" Then
lastFormulaRow = i
Exit For
End If
Next i
End Sub
Edit2: Here's one using .SpecialCells(). Granted I think we can whittle this down more, I like it better:
Sub lastRow()
Dim tempLastRow As Long
tempLastRow = Range("C" & Rows.Count).End(xlUp).Row
Dim lastRow As Range
Set lastRow = Columns(3).SpecialCells(xlCellTypeFormulas).Find(What:="", LookIn:=xlValues, LookAt:=xlWhole, searchdirection:=xlPrevious, after:=Range("C" & tempLastRow))
Debug.Print lastRow.Row
End Sub
It returns 10 as the row.
Edit: Be sure to add the sheet references before Range() and Cells() to get the last row. Otherwise, it's going to look at your active sheet to get the info.

VBA find cell of closest value

I have an excel file that looks like:
12123 51212
12123.5 45832
12124 37656
12124.5 32987
12125 42445
and so on, where column A is always 0.5 increasing and column B has a certain output.
Now I have a specific value in cell E2, say 12124,23 and I want a VBA code to return, in this case, that the best matching value is in cell A3, because I need this cell location in further code, I don't need the corresponding value in column B. I don't know how to start, however. The file can be up to 30000 rows big.
I'd only like to know first which method to use, then I will try to write the code myself of course :)
JV
You don't have to use VBA for your problem, Excel will do it perfectly fine!
Try this
=vlookup(E2;A:A;2;true)
and for what you are trying to do, you HAVE TO sort your A column in an ascending fashion, or else you will get an error!
And if you do need that in VBA,
a simple for+if structure with a test like this
Function pr24(ByVal Value_To_Match As Double) As Range
For i = 2 To ActiveSheet.Cells(Rows.Count, 1).End(xlUp).Row
If Cells(i, 1) > Value_To_Match Then
If Abs(Cells(i - 1, 1) - Value_To_Match) >= Abs(Cells(i, 1) - Value_To_Match) Then
pr24 = Range(Cells(i, 1))
Else
pr24 = Range(Cells(i - 1, 1))
End If
Exit For
Else
End If
Next i
End Function
or you can use the worksheet function Vlookup
Application.WorksheetFunction.VLOOKUP()
You could use VLOOKUP function for this:-
Application.WorksheetFunction.VLOOKUP(lookup_value, table_array, column_index, range_lookup)
Set your values as below:-
lookup_value = 12124.23
table_array = would be the range Ax:Bx containing your values
column_index = 2 (the second column of table_array)
range_lookup = true
Setting range_lookup to true means that if the vlookup doesn't find the exact value it will return the closest match.
Note this will only work if the values in column A are sorted in ascending order.
Hope this helps.
You need to sort your data in column A first (smallest to largest), and then you can use a simple lookup formula:
=LOOKUP(E2,A:A)
If you don't want to sort the data, then you can use a VBA loop like so - however this is very inefficient - you should always use worksheet formulas where you can:
Sub SO()
Dim resultCell As Excel.Range
Dim checkCell As Double
Dim bestDiff As Double
checkCell = Range("E2").Value
bestDiff = checkCell
For i = 1 To Range("A" & Rows.count).End(xlUp).Row
If Range("A" & i).Value <= checkCell Then
If (checkCell - Range("A" & i).Value) < bestDiff Then
bestDiff = checkCell - Range("A" & i)
Set resultCell = Range("A" & i)
End If
End If
Next i
MsgBox "Best match is in " & resultCell.Address
Set resultCell = Nothing
End Sub
You dont'need VBA, a call co VLOOKUP Excel function will do the trick; remember to set the last parameter to true, to find a non exact match with the searched value
It should be like something similar to:
= VLOOKUP(E2, A:B, 2, true)

VBA countif statement only returns 0

I'm working on a macro that is supposed to count the number of times the term "GM" appears in a column. I decided to use a countif statement, as I have before and it worked well. However, for some reason when I run my code it outputs 0 every time, which definitely is not correct. I've run this same code with other columns and strings and it has worked fine, but for some reason if I search this certain column for the term "GM" it fails. The only thing I can think of is maybe countif only works if the string you're searching for is the only string in a cell, because in all cases where this is true the code works fine. In this particular case the string I'm looking for is not the only string in the cell and the code is failing. I've tried to find more info on whether or not this is true but I can't find anything online. Here's the code if anyone would like to take a look:
Function OemRequest() As Long
Sheets("CS-CRM Raw Data").Select
Sheets("CS-CRM Raw Data").Unprotect
Dim oem As Long
Dim LastRow As Long
Dim LastColumn As Long
'Determines size of table in document
LastRow = Range("A" & Rows.Count).End(xlUp).row
LastColumn = Cells(1, Columns.Count).End(xlToLeft).Column
oem = Application.WorksheetFunction.CountIf(Range(2 & "2:" & 2 & LastRow), "gm")
OemRequest = oem
End Function
You are correct that the COUNTIF as written will only match cells where the whole content is "gm". The criteria in the COUNTIF function will also accept wildcards, so to match on cells that contain "gm" do:
.CountIf(Range(2 & "2:" & 2 & LastRow), "*gm*")
Update
As you noted there is also an issue with your Range call. As it is, the expression inside the parens will evaluate to "22:2<LastRow>" (where <LastRow> is the value of the LastRow variable).
The 2's in there should be a variable containing the column name you're interested in. Something like:
Dim col as String
col = "B"
... Range(col & "2:" & col & LastRow) ...
This will evaluate to "B2:B<LastRow>", which is what you want.
Another possibility:
oem = WorksheetFunction.CountIf(Columns(LastColumn).Cells(2).Resize(rowsize:=LastRow - 1), "gm")
This will count cells containing "gm" (use wilcards if needed) in the LAST column of the table, except the one in the first row. (It assumes the table upper left corner is in cell "A1")
Of course you can create a variable if you would like to count any other column:
Dim lngCol as Long
lngCol = ...
oem = WorksheetFunction.CountIf(Columns(lngCol).Cells(2).Resize(rowsize:=LastRow - 1), "gm")
I think in this way
Sub Main()
Application.ScreenUpdating = 0
Dim Count As Double
Range("C1").Activate 'Firs row in the column
Do While ActiveCell.Value <> ""
If InStr(ActiveCell.Value, "MyText") Then
Count = Count + 1
End If
ActiveCell.Offset(1, 0).Activate
Loop
Application.ScreenUpdating = 1
End Sub
This will work, only if the data cell is not empty, if there is an empty space in middle of the worksheet, do this:
Sub Main()
Application.ScreenUpdating = 0
Dim Count As Double
Range("C1").Activate
Do While ActiveCell.Row <> Rows.Count ' This wil evaluate all the rows in the 'C' Column
If InStr(ActiveCell.Value, "MyText") Then
Count = Count + 1
End If
ActiveCell.Offset(1, 0).Activate
Loop
Application.ScreenUpdating = 1
End Sub
Hope it's work for you.

Setting Range in For Loop

I am trying to set the range in For loop. My code works fine when I do this:
For Each i in Range("A1":"A5")
'Some process code
Next i
But I do not get the same results when I do this:
For Each i in Range("A1").End(xlDown)
'Some Process
Next i
Arent the two codes equivalent? What changes should I make to the second one that it perfoms the same way as the first one but doesn't make me hardcode the Range in the code?
The second one you have only gets the last cell in the range, which I believe would me A5 from the first example. Instead, you need to do something like this.
I structured this like a small test so you can see the first option, the corrected second, and an example of how I would prefer to do this.
Option Explicit
Sub test()
Dim r As Range
Dim x As Range
' Make sure there is stuff in Range("A1:A5")
Range("A1") = 1
Range("A2") = 2
Range("A3") = 3
Range("A4") = 4
Range("A5") = 5
' Your first option
For Each x In Range("A1:A5")
Debug.Print x.Address & ", " & x
Next
' What you need to do to get the full range
For Each x In Range("A1", Range("A1").End(xlDown))
Debug.Print x.Address & ", " & x
Next
' My preferred method
Set r = Range("A1").End(xlDown)
For Each x In Range("A1", r)
Debug.Print x.Address & ", " & x
Next
End Sub
The cleanest way to do it would probobly be to store the lastRow number in a variable like so. You can do the concatenation in the for each line:
Dim cell as range
Dim lastRow As Long
lastRow = Range("A" & Rows.Count).End(xlUp).row
For Each cell In Range("A1:A" & lastRow)
Please note that it makes a difference between using xlUp and xlDown.
xlUp gives you last cell used in column A (so you start at rows.count)
XlDown gives you last non-blank cell (you can use range("A1").End(xlDown).Row)
You'll notice a lot of people use "A65536" instead of rows.count, but 65536 is not the limit for some versions of Excel, so it's always better to use rows.count.