How to change a value in a Excel table based on a value found through the match function? - excel

I want to change a value in a table based on an array of values.
These values are found through the application.match function.
Dim i As Integer
For i = 16 To 29
If ThisWorkbook.Sheets("General").Range("A" & i) = "" Then
Else
MsgBox ThisWorkbook.Sheets("General").Range("A" & i)
'Find value in Table
Dim Assignments As Worksheet
Dim TargetTable As ListObject
Dim TargetRW As Variant
Set Opdrachten = ThisWorkbook.Sheets("Jobs")
Set TargetTable = Opdrachten.ListObjects("Assignments")
TargetRW = Application.Match(i, TargetTable.ListColumns(1), 0)
MsgBox TargetRW 'I get an error at this point'
'Change value in Table
If Not IsError(TargetRW) Then
TargetTable.DataBodyRange.Cells(TargetRW, 6) = "Yes"
Else
MsgBox "Error, allready verified."
End If
End If
Next i

A VBA Lookup in an Excel Table
Tips
When writing to the same rows of cells of a column range, you usually want to loop through its cells, lookup the values in another column range and write to the same rows in another column, not the other way around.
A type mismatch error will occur if you try to return an error in a message box. You could use an if statement to check if the value is an error and then use CStr to return its string representation, e.g.:
If IsError(TargetRW) Then MsgBox CStr(TargetRW) Else MsgBox TargetRW
I've opted for the shorter IIf version of the same in the code.
Of course, you could also just do:
MsgBox CStr(TargetRW)
since the number is converted to a string anyways.
TargetTable.ListColumns(1) is a ListColumn object. To reference its range with the header or without it, you need to use either TargetTable.ListColumns(1).Range
or TargetTable.ListColumns(1).DataBodyRange (in this particular case) respectively.
The Code
Option Explicit
Sub LookupJobs()
' Source (read, compare)
Const swsName As String = "General"
Const sRangeAddress As String = "A16:A29"
' Destination (loop, compare, write)
Const dwsName As String = "Jobs"
Const dtblName As String = "Assignments"
Const dlcLookupIndex As Long = 1
Const dlcValueIndex As Long = 6
' Reference the workbook.
Dim wb As Workbook: Set wb = ThisWorkbook
' Reference the source worksheet and the source column range.
Dim sws As Worksheet: Set sws = wb.Worksheets(swsName)
Dim srg As Range: Set srg = sws.Range(sRangeAddress)
' Reference the destination worksheet, table,
' lookup column range (compare) and value column range (write).
Dim dws As Worksheet: Set dws = wb.Worksheets(dwsName)
Dim dtbl As ListObject: Set dtbl = dws.ListObjects(dtblName)
Dim dlrg As Range: Set dlrg = dtbl.ListColumns(dlcLookupIndex).DataBodyRange
Dim dvrg As Range: Set dvrg = dtbl.ListColumns(dlcValueIndex).DataBodyRange
Dim srIndex As Variant ' current index of match in source range or error
Dim dlCell As Range ' current cell of the destination lookup range
Dim dValue As Variant ' value of the current cell
Dim drIndex As Long ' index of the current cell
For Each dlCell In dlrg.Cells
drIndex = drIndex + 1
dValue = dlCell.Value
If Not IsError(dValue) Then ' the cell contains an error value
If Len(dValue) > 0 Then ' the cell is not blank
srIndex = Application.Match(dValue, srg, 0)
'MsgBox IIf(IsError(srIndex), CStr(srIndex), srIndex)
If IsNumeric(srIndex) Then ' match found; write
dvrg.Cells(drIndex).Value = "Yes"
Else ' match not found
'MsgBox "Error, allready verified."
End If
'Else ' the cell is blank; do nothing
End If
'Else ' the cell contains an error value; do nothing
End If
Next dlCell
' Inform.
MsgBox "Jobs lookup finished.", vbInformation
End Sub

Related

Select first 800 visible cells only form a column, even if there are more then 800 visible filtered cells

I need a VBA code, that will allow me to select and copy custom number of visible rows only.
For example: I filtered a column data, and the count of all the visible cells is 1000. However, I want to copy only the first 800 visible cells only out of the 1000 visible cells.
One idea is to get all visible cells using SpecialCells(xlCellTypeVisible) and then loop through and collect them one by one using Application.Union to limit them to your desired amount.
Option Explicit
Public Sub Example()
Dim Top800Cells As Range
Set Top800Cells = GetTopVisibleRows(OfRange:=Range("A:A"), TopAmount:=800)
Top800Cells.Select
End Sub
Public Function GetTopVisibleRows(ByVal OfRange As Range, ByVal TopAmount As Long) As Range
Dim VisibleCells As Range
Set VisibleCells = OfRange.SpecialCells(xlCellTypeVisible)
If VisibleCells Is Nothing Then
Exit Function
End If
Dim TopCells As Range
Dim Count As Long
Dim Row As Range
For Each Row In VisibleCells.Rows
If TopCells Is Nothing Then
Set TopCells = Row
Else
Set TopCells = Application.Union(TopCells, Row)
End If
Count = Count + 1
If Count = TopAmount Then Exit For
Next Row
Set GetTopVisibleRows = TopCells
End Function
If you want to use it as a UDF (user defined function) in a formula SpecialCells(xlCellTypeVisible) is known to fail there (see SpecialCells(xlCellTypeVisible) not working in UDF). And you need to check visibility yourselft:
Public Function GetTopVisibleRows(ByVal OfRange As Range, ByVal TopAmount As Long) As Range
Dim TopCells As Range
Dim Count As Long
Dim Row As Range
For Each Row In OfRange.Rows
If Not Row.EntireRow.Hidden Then
If TopCells Is Nothing Then
Set TopCells = Row
Else
Set TopCells = Application.Union(TopCells, Row)
End If
Count = Count + 1
If Count = TopAmount Then Exit For
End If
Next Row
Set GetTopVisibleRows = TopCells
End Function
Copy First n Rows of SpecialCells(xlCellTypeVisible)
This is usually done to more columns as illustrated in the code.
To apply it just to column A, replace Set rg = ws.Range("A1").CurrentRegion with
Set rg = ws.Range("A1").CurrentRegion.Columns(1)
assuming that the header is in the first worksheet row.
In a nutshell, it loops through the rows (rrg) of each area (arg) of the range (MultiRange, dvrg) counting each row (r) and when it hits the 'mark' (DataRowsCount), it uses this row (Set SetMultiRangeRow = rrg, lrrg) and the first row (frrg) as arguments in the range property to set the required range and reapply the same type of SpecialCells to finally reference the required amount of rows.
Sub ReferenceFirstMultiRangeRows()
' Define constants
Const CriteriaColumn As Long = 1
Const CriteriaString As String = "Yes"
Const DataRowsCount As Long = 800
' Reference the worksheet ('ws').
Dim ws As Worksheet: Set ws = ActiveSheet ' improve!
If ws.AutoFilterMode Then ws.AutoFilterMode = False
' Reference the ranges.
Dim rg As Range ' the range (has headers)
Set rg = ws.Range("A1").CurrentRegion ' you may need to use another way!
Dim drg As Range ' the data range (no headers)
Set drg = rg.Resize(rg.Rows.Count - 1).Offset(1)
' Apply the auto filter to the range.
rg.AutoFilter CriteriaColumn, CriteriaString
' Attempt to reference the visible data range ('vdrg').
Dim vdrg As Range
On Error Resume Next
Set vdrg = drg.SpecialCells(xlCellTypeVisible)
On Error GoTo 0
' Reference the required amount of visible rows ('vdrg').
' Reference the partial range ('vdrg') from the first row
' to the DataRowsCount-th row of the visible range
' and reapply special cells to this range.
If Not vdrg Is Nothing Then ' filtered rows found
Dim lrrg As Range: Set lrrg = SetMultiRangeRow(vdrg, DataRowsCount)
If Not lrrg Is Nothing Then ' there are more rows than 'DataRowsCount'
Dim frrg As Range: Set frrg = vdrg.Rows(1)
Set vdrg = ws.Range(frrg, lrrg).SpecialCells(xlCellTypeVisible)
'Else ' the visible data range is already set; do nothing
End If
'Else ' no filtered rows found; do nothing
End If
ws.AutoFilterMode = False ' remove the auto filter
If vdrg Is Nothing Then
MsgBox "No filtered rows.", vbExclamation
Exit Sub
End If
' Continue using vdrg e.g.:
Debug.Print vdrg.Address ' only the first <=257 characters of the address
'vdrg.Select
'vdrg.Copy Sheet2.Range("A2")
End Sub
Function SetMultiRangeRow( _
ByVal MultiRange As Range, _
ByVal MaxRowNumber As Long) _
As Range
Dim rCount As Long
rCount = MultiRange.Cells.CountLarge / MultiRange.Columns.Count
If rCount < MaxRowNumber Then Exit Function
Dim arg As Range
Dim rrg As Range
Dim r As Long
Dim lrrg As Range
For Each arg In MultiRange.Areas
For Each rrg In arg.Rows
r = r + 1
If r = MaxRowNumber Then
Set SetMultiRangeRow = rrg
Exit For
End If
Next rrg
Next arg
End Function

Select each of 'filtered' values in a column of Sheet 1 and find their occurences in all values of a column in Sheet 2

I have an Excel worksheet comprised of two sheets.
One (Sheet 1) with a list of products, their respective serial numbers and a part number for a specific part - the users enters one or more serial numbers to filter the complete big list to end up with a smaller list of items
One seperate sheet (Sheet 2) that has only one column, a list of part numbers that need to be replaced
Now I want to write a VBA script that on Worksheet_Calculate() (not reflected below) compares the filtered values of a specific column in Sheet 1 (the column containing the part numbers) with the list/column in Sheet 2 and shows a message box for each product containing a part with a number found in the list of sheet2
But I'm having trouble finding a solution for collecting all filtered cells in Sheet 1
I assume I have to somehow make use of the ListObjects property to collect the specific visible/filtered cells and to compare only those to the list in sheet 2
But I don't really know how to select those specific, auto-filtered, cells or write an iteration that accounts for only those cells but still compares to all rows in the list/column of sheet 2
Right now, despite making use of col1 and col2 as ranges with the 'SpecialCells(xlCellTypeVisible)' attribute it always selects all cells of col1
I'm surprised that this selector
prod1 = Cells(r, col1.Column).Value
despite using col1 (which is a limited range) iterates all values, not just the filtered ones
Sub CompareTwoColumns()
Dim col1 As Range, col2 As Range, prod1 As String, lr As Long
Dim incol1 As Variant, incol2 As Variant, r As Long
Set col1 = ActiveSheet.ListObjects("Tabel1").ListColumns.DataBodyRange.SpecialCells(xlCellTypeVisible)
Set col2 = Worksheets("Tabel2").Range("A1").CurrentRegion.SpecialCells(xlCellTypeVisible)
lr = Worksheets("Tabel1").UsedRange.Rows.Count
Dim cell As Range
For r = 2 To lr
prod1 = Cells(r, col1.Column).Value
If prod1 <> "" Then
Set incol2 = col2.Find(prod1, LookIn:=xlValues, LookAt:=xlWhole, MatchCase:=True)
If incol2 Is Nothing Then
MsgBox CStr(prod1) + " Not in List"
Else
MsgBox CStr(prod1) + " Is in List!"
End If
End If
Next r
End Sub
Anyone able to nudge me in the right direction?
Match Value in Range
Adjust the worksheet, table, and column names.
Option Explicit
Sub ComparePartNumbers()
' Often you loop through the cells of the destination worksheet
' and try to find a match in the source worksheet (read, copy from)
' and then in another column of the destination worksheet you write
' e.g. Yes or No (write, copy to).
' The analogy doesn't quite apply in this case but I used it anyway.
' Reference the workbook ('wb').
Dim wb As Workbook: Set wb = ThisWorkbook ' workbook containing this code
' Reference the source range ('srg').
Dim sws As Worksheet: Set sws = wb.Worksheets("Sheet2")
Dim sTbl As ListObject: Set sTbl = sws.ListObjects("Table2")
Dim sLc As ListColumn: Set sLc = sTbl.ListColumns("Part Number")
Dim srg As Range: Set srg = sLc.DataBodyRange
' Attempt to reference the destination range ('drg').
Dim dws As Worksheet: Set dws = wb.Worksheets("Sheet1")
Dim dTbl As ListObject: Set dTbl = dws.ListObjects("Table1")
Dim dLc As ListColumn: Set dLc = dTbl.ListColumns("Part Number")
Dim drg As Range
On Error Resume Next
Set drg = dLc.DataBodyRange.SpecialCells(xlCellTypeVisible)
On Error GoTo 0
' Validate the destination range.
If drg Is Nothing Then ' no visible cells
MsgBox "No filtered values.", vbCritical
Exit Sub
'Else ' there are visible cells; do nothing i.e. continue
End If
' Declare additional variables.
Dim dCell As Range ' current destination cell
Dim dPartNumber As String ' current part number read from the cell
Dim sIndex As Variant ' the n-th cell where the value was found or an error
' Loop.
For Each dCell In drg.Cells
dPartNumber = CStr(dCell.Value)
If Len(dPartNumber) > 0 Then ' is not blank
sIndex = Application.Match(dPartNumber, srg, 0)
If IsNumeric(sIndex) Then ' is a match
'MsgBox "'" & dPartNumber & "' is in list!", vbInformation
Debug.Print "'" & dPartNumber & "' is in list!"
Else ' is not a match (VBA: 'Error 2042' = Excel: '#N/A')
'MsgBox "'" & dPartNumber & "' is not in list!", vbExclamation
Debug.Print "'" & dPartNumber & "' is not in list!"
End If
'Else ' is blank; do nothing
End If
Next dCell
End Sub

VBA cannot AutoFilter a Range for a certain criteria

I am trying to make a Range object of all entrys and than apply a filter, which searches for a number in there.
I want the Range to hold only the matching entrys afterwards, but I always get the error 1004...
Here the code:
Dim rSearch As Range
Dim rResult As Range
Set rSearch = wbMe.Sheets(iCurSheet).Range("F2:F1000")
rSearch.AutoFilter Field:=iColKey, Criteria1:="=" & wbMe.Sheets(iCurSheet).Cells(iLine, iColKey).Value
The last line throws the exception. I found out that the AutoFilter has to be applied to the first line, so .Range("A1:K1"), but I still don't get why I am not able to Filter on a Range, maybe i get the Object wrong?
Thanks in advance!
Edit:
So I tried some stuff:
Set rSearch = wbMe.Sheets(iCurSheet).Range("A2:K1000")
rSearch.AutoFilter Field:=11, Criteria1:="=" & wbMe.Sheets(iCurSheet).Cells(iLine, iColKey).Value
MsgBox "Count Rows rSearch:" & rSearch.Rows.Count
I expected the MsgBox to say smth less, but I get 999, so it hasn't filtered anything.
My guess that I was filtering the wrong column, but I wanna filter on Col K (I need Col F afterwards to search once more, sry for mixing stuff up).
Now I don't get the AutoFilter exception anymore. But for some reason my rSearch range does not shrink.
How do I shrink my Range?
Count Visible Data Cells (Criteria Cells)
A quick fix could be something like
MsgBox "Count Rows rSearch:" & rSearch.Columns(11).SpecialCells(xlCellTypeVisible).Cells.Count - 1
Note that the headers need to be included in the range for AutoFilter to work correctly.
Using SpecialCells
Sub CountVisibleDataCells()
' Define constants.
Const CriteriaIndex As Long = 11
' Reference the workbook ('wb').
Dim wb As Workbook: Set wb = ThisWorkbook ' workbook containing this code
' Reference the worksheet ('ws').
Dim ws As Worksheet: Set ws = wb.Worksheets("Sheet1")
' Turn off AutoFilter.
If ws.AutoFilterMode Then ws.AutoFilterMode = False
' Reference the range ('rg').
Dim rg As Range: Set rg = ws.Range("A1:K21")
' Store the criteria value from the cell, converted to a string ('CStr'),
' in a string variable ('Criteria'). AutoFilter 'prefers' this.
Dim Criteria As String: Criteria = CStr(ws.Range("M1").Value)
' Filter the range.
rg.AutoFilter Field:=CriteriaIndex, Criteria1:=Criteria
' Reference the visible cells in the criteria column ('vrg').
Dim vrg As Range
Set vrg = rg.Columns(CriteriaIndex).SpecialCells(xlCellTypeVisible)
' Turn off the worksheet auto filter.
ws.AutoFilterMode = False
' Store the number of visible cells of the criteria column
' in a long variable (subtract 1 to not count the header).
Dim CriteriaCount As Long: CriteriaCount = vrg.Cells.Count - 1
' Inform.
MsgBox "Count of '" & Criteria & "': " & CriteriaCount
End Sub
Using Application Count
Sub CountCriteriaCells()
' Define constants.
Const CriteriaIndex As Long = 11
' Reference the workbook ('wb').
Dim wb As Workbook: Set wb = ThisWorkbook ' workbook containing this code
' Reference the worksheet ('ws').
Dim ws As Worksheet: Set ws = wb.Worksheets("Sheet1")
' Turn off AutoFilter.
If ws.AutoFilterMode Then ws.AutoFilterMode = False
' Reference the range ('rg').
Dim rg As Range: Set rg = ws.Range("A1:K21")
' Store the criteria value from the cell, converted to a string ('CStr'),
' in a string variable ('Criteria').
Dim Criteria As String: Criteria = CStr(ws.Range("M1").Value)
' You may need to modify this because 'CountIf' works differently.
' Reference the criteria data range ('cdrg') (no headers).
Dim cdrg As Range
With rg.Columns(CriteriaIndex)
Set cdrg = .Resize(.Rows.Count - 1).Offset(1)
End With
' Store the number of cells containing the criteria ('CriteriaCount')
' in a long variable.
Dim CriteriaCount As Long
CriteriaCount = Application.CountIf(cdrg, Criteria)
' Inform.
MsgBox "Count of '" & Criteria & "': " & CriteriaCount
End Sub

Incompatibility type when using range

I'm trying to to run a command if these arguments checks , but it's giving me incompatibily type on that block, what am I doing wrong?
Dim rn as range
For Each rg In Columns("X")
If rg.Value Like "?*#?*.?*" And _
LCase(Cells(rg.Row, "U").Value) = "Demande de création d'intervention" _
And LCase(Cells(rg.Row, "V").Value) <> "envoyé" Then
Comparing Strings
If you loop through the cells of the whole column, it will take forever. Calculate the last row, the row of the last non-empty cell in the column, instead.
LCase(Something) will never be equal to Demande.... Use demande... instead.
If you use CStr to convert a value to a string, you won't have to worry about the value being incompatible when comparing it to another string.
The Code
Option Explicit
Sub Test()
Const FirstRow As Long = 2
Dim ws As Worksheet: Set ws = ActiveSheet ' improve!
Dim LastRow As Long: LastRow = ws.Cells(ws.Rows.Count, "X").End(xlUp).Row
' If LastRow < FirstRow Then
' MsgBox "No data in column.", vbCritical
' Exit Sub
' End If
' Reference the column range ('rg').
Dim rg As Range
Set rg = ws.Range(ws.Cells(FirstRow, "X"), ws.Cells(LastRow, "X"))
' Reference the other column ranges ('rg2', 'rg3')
Dim rg2 As Range: Set rg2 = rg.EntireRow.Columns("U")
Dim rg3 As Range: Set rg3 = rg.EntireRow.Columns("V")
' Note that all three ranges are of the same size.
' Declare additional variables to be use in the loop.
Dim Cell As Range ' Current cell in the first range
Dim cString As String ' String Representation of the Current Cell's Value
Dim r As Long ' Index of the Current Cell
' Use 'CStr' to convert the values to strings to avoid an error occurring
' if the cell contains an error value.
For Each Cell In rg.Cells ' note '.Cells'
r = r + 1 ' count the cells (in this case rows)
cString = CStr(Cell.Value)
If cString Like "?*#?*.?*" _
And LCase(CStr(rg2.Cells(r).Value)) _
= "demande de création d'intervention" _
And LCase(CStr(rg3.Cells(r).Value)) <> "envoyé" Then
' Do your thing, e.g.
Debug.Print r, cString
End If
Next Cell
End Sub
Results in the Immediate window (Ctrl+G).
8 FY#I.NV
11 MF#X.UT
14 EU#X.IF

How to bypass code if criteria don't match?

The code works when the criteria exists. I get an error when the criteria doesn't exist.
' Define constants.
Const srcName As String = "wfm_rawdata"
Const srcFirst As String = "D2" ' Location for Group
Const dstName As String = "bond_insurance"
Const dstFirst As String = "A2" ' do not change the 'A' (entire row).
'This function will transfer rows from one worksheet to another worksheet
' if the value = specified critiera
' Define workbook.
Dim wb As Workbook: Set wb = ActiveWorkbook ' Workbook containing this code.
' Define Source Range
Dim LastRow As Long
Dim srg As Range
' Define worksheet and column am working on and
' getting the range of last used cell using(LastRow)
With wb.Worksheets(srcName).Range(srcFirst)
LastRow = .Offset(.Worksheet.Rows.Count - .Row).End(xlUp).Row
Set srg = .Resize(LastRow - .Row + 1, 10)
End With
'Combine' critical cells into a range.
Dim brg As Range ' Built Range --> Range in the new sheet
Dim cel As Range ' Current Cell Range --> Range in the current sheet(rawdata)
'for every cell in group within wfm_rawdata sheet if the value = GO
For Each cel In srg.Cells
If cel.Value = "BOND INSURANCE" Then
' If the range in the new sheet have nothing then
' add specific criteria from the group in wfm_rawdata
If brg Is Nothing Then
Set brg = cel
' if there is range in there combine the new and
' old range together using -> Union function
Else
Set brg = Union(brg, cel)
End If
End If
Next cel
Application.ScreenUpdating = False
' Copy and delete critical rows of Source Range.
With wb.Worksheets(dstName).Range(dstFirst)
.Resize(.Worksheet.Rows.Count - .Row + 1, _
.Worksheet.Columns.Count).clear
Set brg = brg.EntireRow ' 'Convert' cells into rows.
brg.Copy .Offset ' Copy. 'Offset' because range is in 'With'.
brg.Delete ' Delete.
End With
How can I use a Boolean or other function to bypass the above code if the criteria doesn't exist?
For example if criteria "dog" exists then run the code and if it doesn't exist bypass the code.
I use this code to run three modules with code similar to the top code.
Sub master()
Call report1
Call report2
Call report3
End Sub
One you've assigned srg you can use Match() to check whether it contains any instances of the term you're interested in:
'...
'...
' Define worksheet and column am working on and getting the range of last used cell using(LastRow)
With wb.Worksheets(srcName).Range(srcFirst)
LastRow = .Offset(.Worksheet.Rows.Count - .Row).End(xlUp).Row
Set srg = .Resize(LastRow - .Row + 1, 10)
End With
'Exit if "BOND INSURANCE" is not found in `srg`
If IsError(Application.Match("BOND INSURANCE", srg, 0)) Then Exit Sub
'...
'...

Resources