Excel VBA Index/Match within IF function - vba

I'm trying to setup a VBA code that loops through each cell in column M and returns the value of Column L if the cell contains a number, and goes through an index/match function if Column L doesn't contain a number. Then if the index/match doesn't find what it's looking for, it goes through another vlookup. I'm having trouble with the syntax of the third part of this (the vlookup at the end). I'm not sure if it should be another Else statement or an if statement or an ISerror or something different entirely. Right now I have it set up as the second if/else. I'm also wondering if I will have problems due to the fact that the index/match function has text as the input and should return a number. Any suggestions/advice on this is much appreciated. Below is what I have so far.
Sub Ranking_2()
Dim cell As Range, rng As Range
Set rng = Range("L2:L120")
For Each cell In rng
If WorksheetFunction.IsNumber(cell.Value) Then
cell.Offset(0, 1).Value = cell.Value
Else: cell.Offset(0, 1).Value = WorksheetFunction.Index(ThisWorkbook.Sheets(1).Range("K:K"), WorksheetFunction.Match(cell.Offset(0, 1) & cell.Offset(0, 5), ThisWorkbook.Sheets(1).Range("A:A") & ThisWorkbook.Sheets(1).Range("H:H"), 0))
If:cell.Offset(0,1).Value= WorksheetFunction.IsError(
Else: cell.Offset(0, 1).Value = WorksheetFunction.VLookup(cell.Offset(0, -11), ThisWorkbook.Sheets(2).Range("A1:D136"), 3, 0)
End If
Next
End Sub

you may want to adopt these changes to your code
Option Explicit
Sub Ranking_2()
Dim cell As Range
Dim lookUp1Sht As Worksheet
Dim lookUp2Rng As Range
Dim val1 As Variant
Set lookUp1Sht = ThisWorkbook.Worksheets("LookUp1Sht") '<--| set the worksheet you're making the first lookup
Set lookUp2Rng = ThisWorkbook.Worksheets("LookUp2Sht").Range("A1:C136") '<--| since you're this range returning column "C" value it suffices limiting it to column "C"
For Each cell In Range("L2:L120").SpecialCells(xlCellTypeConstants) '<--| limit looping through wanted range not blank cells only
With cell '<--| reference current cell
Select Case True
Case IsNumeric(.Value) '<--| if current cell value can be evaluated as "number"...
.Offset(0, 1).Value = CDbl(.Value)
Case Not IsError(LookUp1(lookUp1Sht, .Offset(0, 1).Value, .Offset(0, 5).Value, val1)) '<-- if "first" lookup doesn't return an "error"...
.Offset(0, 1).Value = val1 '<--| then write the 3rd argument passed from LookUp1() function
Case Else '<-- if all preceeding "cases" failed...
.Offset(0, 1).Value = Application.VLookup(.Offset(0, -11), lookUp2Rng, 3, 0) '<-- write "second" lookup return value
End Select
End With
Next
End Sub
Function LookUp1(sht As Worksheet, val1 As Variant, val2 As Variant, val As Variant) As Variant
Dim f As Range
Dim firstAddress As String
With sht '<--| reference passed worksheet
Set f = .Range("A:A").Find(what:=val1, LookIn:=xlValues, lookat:=xlWhole) '<-- look for first passed value in its column "A"
If Not f Is Nothing Then '<--| if found...
firstAddress = f.Address '<--| store found cell address to stop subsequent FindNext() loop upon wrapping back to it
Do '<--| loop
If f.Offset(, 7).Value = val2 Then '<--| if corresponding value in column "H" matches val2...
val = .Cells(f.row, "K") '<-- set 3rd argument to value in column "K" corresponding to the "double" match
Exit Function '<--| exit function
End If
Set f = .Range("A:A").FindNext(f) '<-- go on looking for val1 in column "A"
Loop While f.Address <> firstAddress '<-- stop looping upon wrapping back on first cell found
End If
End With
LookUp1 = CVErr(xlErrValue) '<-- if no "double" match occurred then return "#VALUE!" error
End Function
please note that:
change "LookUp1Sht" and "LookUp2Sht" to your actual worksheets names
Match and LookUp Application functions handle possible errors without halting the macro and simply returning the error value
This I only used in .Offset(0, 1).Value = Application.VLookup(.Offset(0, -11)..., so that if the "last chance lookup" ever returned an error you would have it written in your .Offset(0,1) cell
use SpecialCells() method to return a filtered group of the range you call it on: for instance using xlCellTypeConstants as its Type parameter you'd get back not empty cell only
use IsNumeric() function instead of [WorksheetFunction.IsNumber()[(https://msdn.microsoft.com/en-us/library/office/ff840818(v=office.15).aspx) since the former will recognize string "5" as a number, while the latter would not

Related

VBA If statement in For Loop and Undefined Ranges

I am new to coding and am starting off on VBA. This isn't a homework assignment but a little project my dad challenged to me figure out. His instructions were:
Given an unspecified number of values in column A, determine if each value is less than OR is greater than or equal to 5. If the number is less than 5, print "Yes" in the cell next to it in column B. If the number is greater than or equal to 5, print "No". If the value in column A is not a numerical value or is blank, print "Non numeric entry".
Here is my problem: I can't seem to get the For loop to work with the nested If Statement. Do I need a counter? And what would I set as the range for the new entries in column B?
Here is my current code:
Sub practice()
Range (Cells(1,1), Cells(Rows.Count, 1). End(xlUp)).Select
For Each cell In Selection.Cells
If cell.value < 5 Then
ThisWorkbook.Sheets("Sheet3").Range().Value = "Yes"
Else cell.value >= 5 Then
ThisWorkbook.Sheets("Sheet3").Range().Value = "no"
End If
Next
End Sub
You should try to avoid using Select and Seletion, and use fully qualified Range instead. You can do this by fully qualify your Range with Worsheets("Sheet3").
You can use C.Offset(, 1).Value to modify the value in the cell to the right (Column B).
Try the code below:
Sub practice()
Dim Rng As Range
Dim C As Range
Set Rng = Worksheets("Sheet3").Range("A1:A" & Worksheets("Sheet3").Cells(Worksheets("Sheet3").Rows.Count, "A").End(xlUp).Row)
For Each C In Rng
If Not IsNumeric(C.Value) Or IsEmpty(C.Value) Then
C.Offset(, 1).Value = "Non numeric entry"
Else
If C.Value < 5 Then
C.Offset(, 1).Value = "Yes"
Else
If C.Value >= 5 Then
C.Offset(, 1).Value = "No"
End If
End If
End If
Next C
End Sub

Looping through column based on comparing dates, if less than today then lookup previous columns

I understand that a match function is needed to look up values to the left rather than a right (VLOOKUP).
My want to click the macro button to display the items of the previous two columns, if the cell (is past its due date), and build an array of items which are past its due date.
Sub ItemRegister()
Application.Workbooks("Current.xlsm").Worksheets("Sheet1").Activate
On Error GoTo MyErrorHandler:
Dim Today As Date
Dim InspectionDate As Range
Dim ItemRow As Long
Dim ItemCol As Long
Dim Check As Variant
Today = Date
Set InspectionDate = [G4:G500]
Set TableC = [A4:A500]
Set TableS = [B4:B500]
Set DateArray = [G4:G500]
ItemRow = [G4].Row
ItemCol = [G4].Column
For Each Cell In InspectionDate
Check = Application.Match(Cell, DateArray, 0) 'need to fix match up
If Cell = "" Then
Item = ""
Serial = ""
If Cell <= Today Then
Item = Application.WorksheetFunction.Index(TableC, Check)
Serial = Application.WorksheetFunction.Index(TableS, Check)
Else
Item = ""
Serial = ""
End If
ItemRow = ItemRow + 1
End If
Next Cell
Exit Sub
MyErrorHandler:
If Err.Number = 1004 Then
MsgBox "An error has occured - please ensure that cells have not been altered in anyway - Something is wrong with code, Debug It" 'Remove this, when process is completed
Else
MsgBox "The item(s) that need inspection is/are: " & vbNewLine & vbNewLine & Item & "-" & Serial
End If
End Sub
Thanks in advance.
you could adopt an AutoFilter() approach;
Option Explicit
Sub main()
Dim area As Range
Dim iCell As Long
With Application.Workbooks("Current.xlsm").Worksheets("Sheet1") '<--| reference relevant worksheeu
With .Range("G3", .Cells(.Rows.COUNT, "G").End(xlUp).Offset(1)) '<-- reference its column "G" cell from row 3 down to last not empty cell
.AutoFilter Field:=1, Criteria1:="<=" & CDbl(Date) '<--| filter referenced column on dates preceeding or equal today's date
If Application.WorksheetFunction.Subtotal(103, .Cells) > 1 Then '<--| if any cell filtered other than header (which is in row 3)
With .SpecialCells(xlCellTypeVisible) '<--| reference columnn "G" filtered cells
ReDim Item(1 To .COUNT) '<--| size Item array to the number of referenced (i.e. filtered) cells
ReDim Serial(1 To .COUNT) '<--| size Serial array to the number of referenced (i.e. filtered) cells
For Each cell In .Cells '<--| loop through referenced (i.e. filtered) cells
iCell = iCell + 1 '<--| update cell counter
Item(iCell) = cell.Offset(, -6).Value '<--| retrieve value in column "A" cell at current filtered cell row
Serial(iCell) = cell.Offset(, -5).Value '<--| retrieve value in column "G" cell at current filtered cell row
Next cell
End With
End If
End With
.AutoFilterMode = False '<--| show all rows back
End With
End Sub

Conditional formatting range based on 2 conditions (other cell's format/ value)

I'm new to VBA and English isn't my native language so here goes.
I want to conditional format rows/ range (giving them green-colored background) if cell C in that row have duplicate value in column C and also if there's a cell in column O that equals 0, but if the cell in column C has no similar value, don't apply the conditional format to that cell (eventhough cells in column O has the value of 0).
Note: Cells that have same values in column C will always be above and below each other, for example it's possible that C1=C2=C3 but not C1<>C2, C1=C3
I know I'm not explaining it clearly, so please just let me know if you want more information.
Update (more information): I may have 3 or more rows with same C column value above and below each other, and the zero value in column O will always be the bottom row.
Example:
If C1=C2=C3=C4=C5 and O5=0 , Rows 1 2 3 4 5 become green colored.
I prefer using conditional format even if it needs vba code so I dont have to run it everytime there's new 0 in column O.
I've used this code but it doesn't work (obviously), but maybe it's a little different with my question because the real data is more complicated than what I illustrated. My data table starts at 4th row (header on 3rd). This code only formats 1 row (above the row that has zero column O value) and what I need is all rows with same column C value are formatted. Please keep in mind that I'm a newbie in vba :(
With Range("A4:r8000").FormatConditions.Add( _
Type:=xlExpression, _
Formula1:="=AND($C4=$C5,$O5=0,$F4<>0)")
.Interior.Color = 13551615
.Font.Color = -16383844
End With
Try this as the formula for the CFR,
=and(countif(c:c, c1)>1, o1=0, len(o1))
'alternate for part that I am not sure I understand
=and(countif(c$1:c1, c1)>1, o1=0, len(o1))
This will go through and highlight duplicate cells if any of the duplicate cells' rows have '0' in column O. I am still working on a way that will make this auto update whenever a change happens in Column O, but can't quite figure that out. Will update when I do.
Sub ConditionalFormatSE()
Application.ScreenUpdating = False
Dim lastRow As Long
Dim myCell As Range
Dim colCVals As Range
lastRow = Cells(Rows.Count, 3).End(xlUp).Row
Set colCVals = Range("C1", "C" & lastRow)
colCVals.clearformats
For Each myCell In colCVals
If Cells(myCell.Row, 15).Value = "0" Then
If WorksheetFunction.CountIf(colCVals, myCell.Value) > 1 Then
Set c = colCVals.Find(myCell.Value)
If Not c Is Nothing Then
firstAddress = c.Address
Do
c.Interior.color = RGB(198, 239, 206)
c.Font.color = RGB(0, 97, 0)
Set c = colCVals.FindNext(c)
Loop While Not c Is Nothing And c.Address <> firstAddress
End If
End If
End If
Next myCell
Set colCVals = Nothing
Set myCell = Nothing
Set c = Nothing
Application.ScreenUpdating = True
End Sub
As for making it run automatically, put this in: VBAProject([workbookname].xlsm)->Microsoft Excel Objects->Sheet1([sheetname]) and it should run whenever a value in column 'O' is changed
Private Sub Worksheet_Change(ByVal Target As Range)
Dim KeyCells As Range
Set KeyCells = Columns(15)
If Not Application.Intersect(KeyCells, Range(Target.Address)) Is Nothing Then
Call ConditionalFormatSE
End If
Set KeyCells = Nothing
End Sub
If cells with same values are always grouped (one below the other), following code might do what you want.
Sub Test()
Dim lLastRow As Long
Dim i As Integer
lLastRow = Worksheets("Sheet1").Cells(Rows.Count, 3).End(xlUp).Row
For i = 1 To lLastRow
If ((Worksheets("Sheet1").Cells(i + 1, 3).Value = Worksheets("Sheet1").Cells(i, 3).Value) And (Worksheets("Sheet1").Cells(i, 15).Value = "0")) Then
Worksheets("Sheet1").Cells(i, 3).Interior.Color = vbGreen
End If
Next i
End Sub

Use User-defined range as input for cell parsing

I'm writing a macro in Excel 2010 in order to remove line breaks in multiple cells of a column. This cells need to be selected by the user. Following this previous post I was able to create an InputBox to let the user select the range but now, I am unable to process the data within the selection.
My previous code without the selection range parsed an entire column with a regexp to find a pattern in the string within the cells and change its contents.
I did this with a For i To Rows.Count block of code like this:
For i = 1 To Rows.Count
If Not IsEmpty(Cells(i, 5).Value) Then
varString = Sheets(ActiveSheet.Name).Cells(i, 5).Text
Sheets(ActiveSheet.Name).Cells(i,5).Value=objRegExp.Replace(varString, "$1 ")
End If
Next i
Now I want to replace the static column so I can process only the user range.
In order to achieve that I tried this:
Set selection = Application.InputBox(Prompt:= _
"Please select a range to apply the remove break lines procedure.", _
Title:="Remove Line Breaks", Type:=8)
If selection Is Nothing Then
Exit Sub
End If
Set RowsNumber = selection.CurrentRegion -> This line gives me an error: "Object required"
Set RowsNumber = RowsNumber.Rows.Count
For i = 1 To RowsNumber
If Not IsEmpty(Cells(i, 5).Value) Then
varString = Sheets(ActiveSheet.Name).Cells(i, 5).Text
Sheets(ActiveSheet.Name).Cells(i, 5).Value = objRegExp.Replace(varString, "$1 ") 'Replace pattern found with regular expression in the same line
End If
Next i
How can I access the cells in the range returned by the InputBox?
I also tried changing RowsNumber with selection.Rows.Count but that way, although it doesn't gives an error, the cells used have blank string within them when I run the debugger. I think this is because I try to access row = 5 when the range could be less, i.e 3 if user just selects 3 cells.
I tried a For Each Next loop but then again, I know not how to access the cells withing the selection range.
You can iterate through the cells of a range by using For Each loop.
Below is your code modified. I have changed the name of variable Selection to rng, because Selection is Excel library built-in function and this name should be avoided.
Sub x()
Dim rng As Excel.Range
Dim cell As Excel.Range
Set rng = Application.InputBox(Prompt:= _
"Please select a range to apply the remove break lines procedure.", _
Title:="Remove Line Breaks", Type:=8)
If rng Is Nothing Then
Exit Sub
End If
For Each cell In rng.Cells
If Not IsEmpty(cell.Value) Then
varString = cell.Text
cell.Value = objRegExp.Replace(varString, "$1 ") 'Replace pattern found with regular expression in the same line
End If
Next cell
End Sub

Find value in many tables, then act on first blank row within matched table

Background: I am writing a macro that copies information from a change log into the matched row within a master file made of several sheets which each contain one table. I have the changes written and working, but need to also include a procedure for when the change request is to add a new row. I have a list in Col A that matches with the request, so I need to search for a matched Col A value within the file, then go to the first blank row in the table (there is a lot of empty space still held within the tables), then copy over the relevant information.
Function AddMatch(LOBID As String) As Range
Dim arrSheets, s As Worksheet, f As Range
Dim addr1 As String
arrSheets = Array("Sheet names all go here")
For Each s In arrSheets
Set s = ActiveWorkbook.Sheets(s)
Set f = s.Columns("A").Find(LOBID, Cells(Rows.Count, "A"), xlValues, xlWhole)
If Not f Is Nothing Then
addr1 = f.Address()
Do
Set AddMatch = f.EntireRow
Exit Function
Set f = s.Columns("A").Find(LOBID, f, xlValues, xlWhole)
Loop While f.Address() <> addr1
End If
Next s
Set AddMatch = Nothing
End Function
I have this function which finds me the row within the sheets where LOBID is a match. It may be overcomplicated, but I used the same function with two criteria for another action so it was an easy copy-paste. I need to act upon the first blank row within the table where that match was found, which is where I'll be adding the new information (copied from the change log).
This returns the first row with a match in Col A.
Set destRNG = AddMatch(CStr(changeWS.Cells(i, "A")))
I have a feeling there's a very simple addition to get me from this row to the first blank within the table, but I keep getting a Method 'Range' of object'_Global' error on just about everything I've tried. Is there a simpler way to do this? Thank you in advance for your help.
FOLLOW-UP: Trying to copy a range from the change log to the new row. This throws a Method 'Range' of object'_Global' error, not sure how to adjust it to work.
rw.Range(Cells(1, 1), Cells(1, 10)) = changeWS.Range(Cells(i, 1), Cells(i, 10))
Something like this:
Sub Tester()
Dim rw As Range
Set rw = FirstEmptyRow("AAA")
If Not rw Is Nothing Then
Debug.Print "Found empty row on " & rw.Parent.Name, rw.Address()
Else
Debug.Print "Not found..."
End If
End Sub
Function FirstEmptyRow(LOBID As String) As Range
Dim arrSheets, s, rv As Range
arrSheets = Array("Sheet1", "Sheet2", "Sheet3")
For Each s In arrSheets
Set s = ActiveWorkbook.Sheets(s)
'any match on this sheet?
If Not IsError(Application.Match(LOBID, s.Columns("A"), 0)) Then
Set rv = s.Rows(2) 'start here and work down...
Do While Application.CountA(rv) > 0
Set rv = rv.Offset(1, 0)
Loop
Exit For
End If
Next s
Set FirstEmptyRow = rv
End Function
EDIT:
rw.Range(Cells(1, 1), Cells(1, 10)) = changeWS.Range(Cells(i, 1), Cells(i, 10))
Here you have a problem because Cells() without a worksheet qualifier always points to the ActiveSheet, so you're essentially trying to create a range across multiple sheets, hence the error.
rw.Range(rw.Cells(1, 1), rw.Cells(1, 10)).Value = _
changeWS.Range(changeWS.Cells(i, 1), changeWS.Cells(i, 10)).Value
would be more correct, but I would prefer this type of approach:
rw.Cells(1, 1).Resize(1, 10).Value = changeWS.Cells(i, 1).resize(i, 10).Value