VBA: match of a variable range - vba

I have a problem. For a certain spreadsheet I want to find out the position (only column) of a value smaller than threshold (further called maxt). I have to solve this in VBA as I need them on a different worksheet to give out accumulated numbers.
I am able to retrieve the max smaller than threshold but the vba match function gives back an error that the number couldnt be found.
However, if the value maxt is copied to a cell and I use the the normal match function on the sheet with the cell containing maxt as condition (=MATCH(cell of maxt; range), it works without any issues.
Problem (I only have A to C filled in my example; irrelevant as it doesnt work on only a few constellations).
A B C
8 5 6 -> doesn't work (Error: 1004)
5 6 7 -> works
7 6 7 -> works
4 8 5 -> works
Below is the code.
Dim myVar As Double
Dim myVarAdress As Long
For I = 1 To 10
myVar = Evaluate("=MAX(IF(A" & I & ":M" & I & "<6, A" & I & ":M" & I & "))")
myVarAdress = Application.WorksheetFunction.Match(myVar, Range("A" & I & ":M" & I))
Next I
End Sub
Thanks in advance

Change myVarAdress = Application.WorksheetFunction.Match(myVar, Range("A" & I & ":M" & I))
to myVarAdress = Application.WorksheetFunction.Match(myVar, Range("A" & I & ":M" & I), 0)
That "0" means that you are looking for the exact match. Also you should add a conditon that will skip the 0 value of your "myVar" variable. For example:
If myVar > 0 Then
myVarAdress = Application.WorksheetFunction.Match(myVar, Range("A" & i & ":M" & i), 0)
End If

Your setup has a couple of different problems that must be addressed before a true solution can be found.
1) You are trying to evaluate a MAX() function, that only has one argument. Your IF() function will either return a value less than 6, or FALSE (0). So either your MAX() function is irrelevant, your IF() function is irrelevant, or you left out one or more arguments on either/both of those functions. In any case, there is no set behavior for what to do when there is no item less than 6 in a row. This raises the possibility that myVar is 0, which is likely to cause faulty results because:
2) You left off the third argument in the MATCH() function. Because your range is (currently) larger than your data set, when you leave off the third argument for MATCH() it will fail any time the data is not organized correctly. This is particularly problematic when you are returning FALSE from your IF() function (treated as 0 by MAX()) because MATCH() is matching to the blanks in your data. Which means that the size of your data set does matter. If you had all 13 rows filled in, your first line would (probably) not fail, but it would not actually match to the 5 you want it to if there were any values LOWER than 5 to the right of column B. Also, it potentially causes some of the other lines to fail if suddenly there aren't any values below 6 in any of the columns and there are no blanks for MATCH() to find and treat as a 0.
All that being said, without further clarification of how you want to clean up these problems, here is a proposed solution (that assumes you want the first occurrence of your max value less than 6, regardless of how many occurrences there are):
Sub MatchSub()
Dim myVar As Double
Dim myVarAdress As Long
Dim rngMaxT As Range
Dim wsFindMax As Worksheet
Set wsFindMax = ActiveSheet
For i = 1 To 10
myVar = Evaluate("=IF(A" & i & ":M" & i & "<6, A" & i & ":M" & i & ")")
Set rngMaxT = wsFindMax.UsedRange.Rows(i)
If rngMaxT(1, 1).Value = myVar Then
myvaraddress = 1
Else
Set rngMaxT = rngMaxT.Find(myVar, , xlValues, xlWhole, xlByRows, xlNext, False)
If rngMaxT Is Nothing Then
'There is no value in the row less than 6
Else
myVarAdress = rngMaxT.Column
End If
End If
Next i
End Sub

Related

Conditional Formatting - how to reference a cell itself?

I was trying to write VBA code to set conditional formatting for a column, so that when the cell contains mix of lower-case and upper-case letter, fill the cell with yellow color. Column number will be determined by variable c. And last row number will be determined by variable last_row.
The following code didn't work as desired. It looks like LCase(xlCellValue) always returns "1" instead of the cell content in lower case format. Thus the condition becomes "CellValue <> 1", which didn't do what I wanted it to do.
c = 1
last_row = 10
Range(Cells(1, c), Cells(last_row, c)). _
FormatConditions.Add(xlCellValue, xlNotEqual, LCase(xlCellValue)). _
Interior.ColorIndex = 6
So I guess I'll have to use xlExpression instead of xlCellValue. But how shall I write the formula so it'll take variables c and last_row? Thank you!
Here, try this:
Sub test()
c = 1
last_row = 10
Range(Cells(1, c), Cells(last_row, c)). _
FormatConditions.Add(xlExpression, , "=NOT(Exact(" & Cells(1, c).Address(RowAbsolute:=False) & ",Lower(" & Cells(1, c).Address(RowAbsolute:=False) & ")))"). _
Interior.ColorIndex = 6
End Sub
I don't know if the comparison with the xlCellValue, xlNotEqual would be case-sensitive; however, Excel has a function Exact which is a case-sensitive comparison. I added a Not to cause the return from the formula to be true when the string is not all lower case. Since this is set-up using Excel Functions, I also replaced Lcase with Lower.
Note:
Using the logic you were following, if the entire string is upper-case, it also gets colored yellow. If you only want the conditioning when it's a mix of cases, you should use the below code instead:
Sub test()
c = 1
last_row = 10
Range(Cells(1, c), Cells(last_row, c)). _
FormatConditions.Add(xlExpression, , "=NOT(OR(Exact(" & Cells(1, c).Address(RowAbsolute:=False) & ",Lower(" & Cells(1, c).Address(RowAbsolute:=False) & ")),Exact(" & Cells(1, c).Address(RowAbsolute:=False) & ",Upper(" & Cells(1, c).Address(RowAbsolute:=False) & "))))"). _
Interior.ColorIndex = 6
End Sub
You're adding the format condition to [ActiveSheet.]Range([ActiveSheet.]Cells(1, c), [ActiveSheet.]Cells(last_row, c)) - you can extract that into a local variable:
Dim target As Range
Set target = ActiveSheet.Range(ActiveSheet.Cells(1, c), ActiveSheet.Cells(last_row, c))
And then use that variable to refer to the cell:
Dim fCondition As FormatCondition
Set fCondition = target.FormatConditions.Add(xlCellValue, xlNotEqual, LCase$(target.Value))
It looks like LCase(xlCellValue) always returns "1" instead of the cell content in lower case format.
That's because xlCellValue is a constant, with a value of 1. LCase(xlCellValue) is therefore equivalent to LCase(1), which will always only ever return "1".

Print Value Based on Iteration and condition

I Want to Print Values from 1 to 10 or less in blank cells from range(A1:A10)
If we find any non blank cell in range(A1:A10) then we have to skip the cell and print the values without disturbing the series i.e, 1,2,3,A(non Blank Cell),4,5 etc
i tried
Dim i As Integer
For i = 1 To 10
If Sheets("Data").cell("K" & i).Value Is Nothing Then
Sheets("Data").Range("K" & i).Value = i
i = i + 1
End If
Next i
You would be best using a For Each loop and manually incrementing the i variable each time you do print a number, using the iterated cells row as the row to print the value of i to.
Dim i As Integer, c As Range
i = 1
For Each c In Range("A1:A10")
If Len(c.Value) = 0 Then
Sheets("Data").Range("K" & c.Row).Value = i
i = i + 1
End If
Next c
If Sheets("Data").cell("K" & i).Value Is Nothing Then
In your question text, you mention that the range is from A1:A10. In the code, you mention "K". Unless it's a typo, that may one of the reasons. In addition, replace the above if condition with :
If ((Sheets("Data").cell("K" & i).Value Is Nothing) Or (Trim(Sheets("Data").cell("K" & i).Value) = "")) Then

Count rows for merged cells

I have a problem to count the number of rows for each block of merged cells, in Excel.
I have a value on A1. If I merge cells A1 to A4 the value appears centered, on the range A1-A4.
Then I have another value in A5. If I merge cells A5 to A12, this second value appears centered on this second block of cells.
What I want is to count number of rows for each block of merged cells.
I have tried to use VBA programming to detect these number of rows, with function "MergeArea" and "CurrentRegion.Count" but the program detects that the two blocks are contiguous and counts 12 rows, instead of 4 and then 8.
If it detects "4" first, I could put the correct instruction on a loop and then detect "8".
There are several downsides to merged cells in terms of VBA but here is a simple method to try.
My sheet looks like this:
Code:
Sub CountMergedRows()
For i = 1 To 20
RowCount = Range("A" & i).MergeArea.Rows.Count
If RowCount > 1 Then
MsgBox ("Cell [A" & i & "] has " & RowCount & " merged rows")
i = i + RowCount
End If
Next i
End Sub
Results are two message boxes that appear like this:
Method Range("A" & i).MergeArea.Rows.Count suggested by Portland Runner works fine, however the function has slightly incorrect logic as it is missed that Next also increments i, so it is more correct to write:
Sub CountMergedRows()
For i = 1 To 20
RowCount = Range("A" & i).MergeArea.Rows.Count
If RowCount > 1 Then
MsgBox ("Cell [A" & i & "] has " & RowCount & " merged rows")
i = i + RowCount - 1 'note -1 here
End If
Next i
End Sub

Am I using the isnumeric function correctly?

This program is to convert a column of data from cumulative to non-cumulative. On my sheet I have A1, B1, and C1 with the text Non-Cumulative, Cumulative, and Converted, respectively. I have numbers 1 to 10 beneath A1, then them summed cumulatively beneath B1. C1 is where I want to convert column B back to non-cumulative.
The IsNumeric is used to make the first row of data in C equal to the first row of data in B. It should detect that the title is above the number it is evaluating, thus knowing that no calculations have to be performed. For the rest of them, it'll see that the number above the one it is evaluating is a number, and thus the calculation has to be done.
My problem is that it isn't working. I think the reason is because IsNumeric() keeps coming back as false. Is there a different function I should be using? Do cell references not work in IsNumeric?
Here's the program!
Option Explicit
Dim i As Variant
Sub Conversion()
Sheets("Test Sheet").Select
For i = 1 To 10
If IsNumeric("B" & i) = False Then
Range("C" & i + 1) = Range("B" & i + 1)
Else: Range("C" & i + 1) = Range("B" & i + 1) - Range("B" & i - 1)
End If
Next
End Sub
The way you wrote your code is logical, just a minor syntax changes you need initially. However,
It's also best to check if the range is empty first...
Then check on if the value is numeric.
Better even, if you set the Range into a Range object and use offset
Code:
Option Explicit '-- great that you use explicit declaration :)
Sub Conversion()
Dim i As Integer '-- integer is good enough
Dim rngRange as Range
'-- try not to select anything. And for a cleaner code
Set rngRange = Sheets("Test Sheet").Range("B1")
For i = 1 To 10
If (rangeRange.Offset(i,0).value) <> "" then '-- check for non-empty
If IsNumeric(rangeRange.Offset(i,0).value) = False Then
rangeRange.Offset(i+1,1) = rangeRange.Offset(i+1,0)
Else
rangeRange.Offset(i+1,1) = rangeRange.Offset(i+1,0) - rangeRange.Offset(i-1,0)
End If
End if
Next i '-- loop
End Sub
To make your code more dynamic:
Another suggestion, you may simply Application.WorkSheetFunction.Transpose() the entire B column range that you need to validate into a variant array
Process the array and Transpose back to the Range with column B and C.
By doing so, you may omit setting for loop size manually but setting it using Lower and Upper bound of the array ;)
You need to check if the range of B i is numeric, not the string "B" & i
and rather than selecting the sheet, simply using a parent identifier like:
sheets("sheet1").range("B" & i)
This will help you avoid errors in your code
For i = 1 To 10
If IsNumeric(sheets("test sheet").range("B" & i).value) = False Then
Range("C" & i + 1) = Range("B" & i + 1)
Else: Range("C" & i + 1) = Range("B" & i + 1) - Range("B" & i - 1)
End If
Next

400 Error Excel Macro

I'm trying to run a macro that will delete rows that don't contain a particular value in column B. Here's my code:
Sub deleteRows()
Dim count As Integer
count = Application.WorksheetFunction.CountA(Range("AF:AF"))
Dim i As Integer
i = 21
Do While i <= count
If (Application.WorksheetFunction.IsNumber(Application.WorksheetFunction.Search("OSR Platform", Range("B" & i))) = False) Then
If (Application.WorksheetFunction.IsNumber(Application.WorksheetFunction.Search("IAM", Range("B" & i))) = False) Then
Rows(i).EntireRow.Delete
i = i - 1
count = count - 1
End If
End If
i = i + 1
Loop
End Sub
Now what it SHOULD be doing is the following:
1.) Find the number of rows to go through and set that as count (this works)
2.) Start at row 21 and look for "OSR Platform" and "IAM" in column B [this kind of works (see below)]
3.) If it finds neither, delete the entire row and adjust the count and row number as necessary (this works)
For some reason, whenever the code gets to the first If statement, an error window with a red X pops up that just says "400." As far as I can tell, I have written everything syntactically soundly, but clearly there's something wrong.
You may want to start by looping the other way. When you delete a line, all the previous lines are shifted. You account for this, but a reverse loop is simpler (for me anyways) to understand than keeping track of when I've offset the current position within the loop:
For i = count To 21 Step -1
Also, you're relying too much on Application.WorksheetFunction:
(Application.WorksheetFunction.IsNumber(Application.WorksheetFunction.Search("OSR Platform", Range("B" & i))) = False)
to
InStr(Range("B" & i).value, "OSR Platform") > 0
Application.WorksheetFunction takes much more processing power, and depending on what you are trying to accomplish, this can take a significantly longer amount of time. Also for this suggested change, the code size is reduced and becomes easier to read without it.
Your count can also be obtained without A.WF:
Excel 2000/03: count = Range("AF65536").End(xlUp).Row
Excel 2007/10: count = Range("AF1048576").End(xlUp).Row
Version independent: count = Range("AF" & Rows.Count).End(xlUp).Row
One more thing is that you can do (and should do in this case) is combine your If statements into one.
Making these changes, you end up with:
Sub deleteRows()
Dim count As Integer
count = Range("AF" & Rows.Count).End(xlUp).Row
Dim i As Integer
For i = count To 21 Step -1
If Len(Range("B" & i).value) > 0 Then
If InStr(Range("B" & i).value, "OSR Platform") > 0 Or InStr(Range("B" & i).value, "IAM") > 0 Then
Range("B" & i).Interior.Color = RGB(255, 0, 0)
End If
End If
Next i
End Sub
If this does not help, then can you step through the code line by line. Add a breakpoint, and step through with F8. Highlight the variables in your code, right-click, choose "add Watch...", click "OK", (Here's an excellent resource to help you with your debugging in general) and note the following:
Which line hits the error?
What is the value of i and count when that happens? (add a watch on these variables to help)
This worked for me. It uses AutoFilter, does not require looping or worksheet functions.
Sub DeleteRows()
Dim currentSheet As Excel.Worksheet
Dim rngfilter As Excel.Range
Dim lastrow As Long, lastcolumn As Long
Set currentSheet = ActiveSheet
' get range
lastrow = currentSheet.Cells(Excel.Rows.Count, "AF").End(xlUp).Row
lastcolumn = currentSheet.Cells(1, Excel.Columns.Count).End(xlToLeft).Column
Set rngfilter = currentSheet.Range("A1", currentSheet.Cells(lastrow, lastcolumn))
' filter by column B criteria
rngfilter.AutoFilter Field:=2, Criteria1:="<>*OSR Platform*", Operator:= _
xlAnd, Criteria2:="<>*IAM*"
' delete any visible row greater than row 21 which does not meet above criteria
rngfilter.Offset(21).SpecialCells(xlCellTypeVisible).EntireRow.Delete
' remove autofilter arrows
currentSheet.AutoFilterMode = False
End Sub
This code applies AutoFilter to column B to see which rows contain neither "OSR Platform" nor "IAM" in column B. Then it simply deletes the remaining rows greater than 21. Test it on a copy of your workbook first.
With a huge nod to this OzGrid thread, because I can never remember the proper syntax for selecting visible cells after filtering.