Check if a value is present in a range or not with VBA - vba

I'm looking to check if a value is present in a range or not. If it's not there then I want it to jump to WriteProcess else I want it to give a message box saying it's present and exit the sub.
This is code,
'Write the Selected Value in the Range - Next Available row in the Column of Source
For i = TableStartingRow + 1 To AddNewEntrow
If Range(EntryColLett & i).Value = wb21Tool.Sheets("Home").ComboBox1.Value Then
MsgBox "The data exists in the Table"
GoTo StopSub
Else
GoTo WriteProcess
End If
Next
WriteProcess:
wbTool.Sheets("Home").Range(EntryColLett & AddNewEntrow).Value = wb21Tool.Sheets("Home").ComboBox1.Value
StopSub:
'Turn on the ScreenUpdate
Application.ScreenUpdating = True
Please share your thoughts. Thanks.

Your problem is that if the loop expires (exhausts all of the iterations) there is no control to prevent it from entering the WriteProcess.
This is one problem with using GoTo statements. Preferably keep these to a minimum. For example, although this doesn't check every row, just an example of how you might avoid the extra GoTo.
'Write the Selected Value in the Range - Next Available row in the Column of Source
For i = TableStartingRow + 1 To AddNewEntrow
If Range(EntryColLett & i).Value = wb21Tool.Sheets("Home").ComboBox1.Value Then
MsgBox "The data exists in the Table"
GoTo StopSub
Else
wbTool.Sheets("Home").Range(EntryColLett & AddNewEntrow).Value = wb21Tool.Sheets("Home").ComboBox1.Value
End If
Next
StopSub:
'Turn on the ScreenUpdate
Application.ScreenUpdating = True
However, a brute-force iteration over the table data seems unnecessary and if you need to check all rows int he table it's probably better to just use the Find method.
Assuming EntryColLet is a string representing the column letter:
Dim tblRange as Range
Dim foundRow as Range
Set tblRange = Range(EntryColLet & (TableStartingRow+1) & ":" & EntryColLet & AddNewEntRow)
Set foundRow = tblRange.Find(wb21Tool.Sheets("Home").ComboBox1.Value)
If foundRow Is Nothing Then
'The value doesn't exist in the table, so do something
'
Else
'The value exists already
MsgBox "The data exists in the Table"
GoTo StopSub
End If
'More code, if you have any...
StopSub:
Application.ScreenUpdating = True
And regarding the remaining GoTo -- if there's no more code that executes after the condition If foundRow Is Nothing then you can remove the entire Else clause and the GoTo label:
Dim tblRange as Range
Dim foundRow as Range
Set tblRange = Range(EntryColLet & (TableStartingRow+1) & ":" & EntryColLet & AddNewEntRow)
Set foundRow = tblRange.Find(wb21Tool.Sheets("Home").ComboBox1.Value)
If foundRow Is Nothing Then
'The value doesn't exist in the table, so do something
End If
Application.ScreenUpdating = True
End Sub

Alternate solution if you need to check every row before performing the "WriteProcess":
Dim bExists As Boolean
bExists = False
'Write the Selected Value in the Range - Next Available row in the Column of Source
For i = TableStartingRow + 1 To AddNewEntrow
If Range(EntryColLett & i).Value = wb21Tool.Sheets("Home").ComboBox1.Value Then
bExists = True
MsgBox "The data exists in the Table"
Exit For
End If
Next
If Not bExists Then wbTool.Sheets("Home").Range(EntryColLett & AddNewEntrow).Value = wb21Tool.Sheets("Home").ComboBox1.Value
'Turn on the ScreenUpdate
Application.ScreenUpdating = True

Related

Excel VBA crashing due to size

I made a script in VBA that should read a very long Pivot Table with over 190,000 entries in the "Data" sheet, and according with the value in the column "J", it should write the info from that row in a sheet called "Temp".
When the value from column "A" changes, it should read from sheet "Regioner" a list of over 600 entries and check if each value is presented in the previous arrays of values.
The code I wrote works, but it takes forever to write down the expected 220,000 entries in the "Temp" sheet. In my laptop, i5 6th generation with 8Gb RAM, it simply crashes.
The current code is as per below.
Many thanks to all!
Public Sub FindWithoutOrder()
Dim DataRowCounter As Long
Dim TempRowCounter As Long
Dim RegiRowCounter As Long
Dim DataOldCounter As Long
Dim DataNewCounter As Long
Dim loopCounter As Long
Dim DataOldProd As Range
Dim DataNewProd As Range
Dim DataPurchase As Range
Dim RegiButikk As Range
Dim ButikkFlag As Boolean
'Code optimization to run faster.
Application.ScreenUpdating = False
Application.DisplayAlerts = False
'Initialize variables.
'----------------------------------------------------------------------------------------------------------
DataRowCounter = 11
TempRowCounter = 1
DataOldCounter = 11
DataNewCounter = 11
Set DataOldProd = ActiveWorkbook.Sheets("Data").Range("A" & DataRowCounter)
Set DataNewProd = ActiveWorkbook.Sheets("Data").Range("A" & DataRowCounter)
Set DataPurchase = ActiveWorkbook.Sheets("Data").Range("J" & DataRowCounter)
'Start of loop that verifies all values inside "Data" sheet.
'----------------------------------------------------------------------------------------------------------
Do Until (IsEmpty(DataOldProd) And IsEmpty(DataNewProd))
'Verify if the product of new line is still the same or different.
'------------------------------------------------------------------------------------------------------
If DataNewProd.Value = DataOldProd.Value Then
DataNewCounter = DataNewCounter + 1
Else
'Initialize variables from "Regioner" sheet.
'------------------------------------------------------------------------------------------
ButikkFlag = False
RegiRowCounter = 11
Set RegiButikk = ActiveWorkbook.Sheets("Regioner").Range("C" & RegiRowCounter)
'Verify list of supermarkets and match them with purchases list.
'--------------------------------------------------------------------------------------------------
Do Until IsEmpty(RegiButikk)
'Check all supermarkets in the product range.
'----------------------------------------------------------------------------------------------
For loopCounter = DataOldCounter To DataNewCounter - 1
'Compare both entries and register them if it doesn't exist in the product list.
'------------------------------------------------------------------------------------------
If RegiButikk.Value = ActiveWorkbook.Sheets("Data").Range("D" & loopCounter).Value Then
ButikkFlag = True
RegiRowCounter = RegiRowCounter + 1
Set RegiButikk = ActiveWorkbook.Sheets("Regioner").Range("C" & RegiRowCounter)
Exit For
Else
ButikkFlag = False
End If
Next loopCounter
'Add to list supermarkets not present in the purchases list.
'------------------------------------------------------------------------------------------
If ButikkFlag = False Then
ActiveWorkbook.Sheets("Temp").Range("B" & TempRowCounter & ":D" & TempRowCounter).Value = ActiveWorkbook.Sheets("Regioner").Range("A" & RegiRowCounter & ":C" & RegiRowCounter).Value
ActiveWorkbook.Sheets("Temp").Range("A" & TempRowCounter).Value = ActiveWorkbook.Sheets("Data").Range("A" & DataRowCounter - 1).Value
TempRowCounter = TempRowCounter + 1
RegiRowCounter = RegiRowCounter + 1
Set RegiButikk = ActiveWorkbook.Sheets("Regioner").Range("C" & RegiRowCounter)
End If
Loop
'Reset the product range.
'--------------------------------------------------------------------------------------------------
DataOldCounter = DataNewCounter
DataNewCounter = DataNewCounter + 1
End If
'Validate if item was purchased in the defined period and copy it.
'------------------------------------------------------------------------------------------------------
If DataPurchase.Value = 0 Then
ActiveWorkbook.Sheets("Temp").Range("A" & TempRowCounter & ":D" & TempRowCounter).Value = ActiveWorkbook.Sheets("Data").Range("A" & DataRowCounter & ":D" & DataRowCounter).Value
TempRowCounter = TempRowCounter + 1
End If
'Update row counter and values for previous and new product readed.
'------------------------------------------------------------------------------------------------------
Set DataOldProd = ActiveWorkbook.Sheets("Data").Range("A" & DataRowCounter)
DataRowCounter = DataRowCounter + 1
Set DataNewProd = ActiveWorkbook.Sheets("Data").Range("A" & DataRowCounter)
Set DataPurchase = ActiveWorkbook.Sheets("Data").Range("J" & DataRowCounter)
Loop
'Code optimization to run faster.
Application.ScreenUpdating = True
Application.DisplayAlerts = True
End Sub
Instead of having this code scattered all over the place:
'Code optimization to run faster.
Application.ScreenUpdating = False
Application.DisplayAlerts = False
Use this procedure:
Public Sub ToggleWaitMode(ByVal wait As Boolean)
Application.Cursor = IIf(wait, XlMousePointer.xlWait, XlMousePointer.xlDefault)
Application.StatusBar = IIf(wait, "Working...", False)
Application.Calculation = IIf(wait, XlCalculation.xlCalculationManual, XlCalculation.xlCalculationAutomatic)
Application.ScreenUpdating = Not wait
Application.EnableEvents = Not wait
End Sub
Like this:
Public Sub DoSomething()
ToggleWaitMode True
On Error GoTo CleanFail
'do stuff
CleanExit:
ToggleWaitMode False
Exit Sub
CleanFail:
'handle errors
Resume CleanExit
End Sub
Disabling automatic calculation and worksheet events should already help quite a lot... but it's by no means "optimizing" anything. It simply makes Excel work [much] less, whenever a cell is modified.
If your code works but is just slow, take it to Code Review Stack Exchange and present it to the VBA reviewers: they'll go out of their ways to help you actually optimize your code. I know, I'm one of them =)

VBA Code to Autofill

Have a column H with alphanumeric characters. Some cells in this column have the content (RAM) followed by 5 digits starting from 00000 to 99999.
If cell H219 has the content (RAM) 23596 then i have to fill cell A219 with a comment "completed".
This has to be done for all cells with the content "(RAM) followed by 5 digits"
Sub Macro16_B()
' ' Macro16_B Macro ' '
intRowCount = Worksheets("Reconciliation").UsedRange.Rows.Count
For i = 11 To intRowCount
If InStr(Range("H" & i).Value, "(RAM 00000-99999") Then
Range("A" & i).Value = "Completed"
End If
Next i
End Sub
A non-VBA answer could be (if the cell doesn't have extra text other than (RAM) & 5 numbers):
=IFERROR(IF(LEN(VALUE(TRIM(SUBSTITUTE(H1,"(RAM)",""))))=5,"completed",""),"")
My VBA answer would be:
Sub Test()
Dim rLastCell As Range
Dim rCell As Range
With Worksheets("Reconciliation")
Set rLastCell = .Columns(8).Find("*", , , , xlByColumns, xlPrevious)
If Not rLastCell Is Nothing Then
For Each rCell In .Range(.Cells(1, 8), rLastCell)
If rCell Like "*(RAM) #####*" Then
rCell.Offset(, -7) = "complete"
End If
Next rCell
End If
End With
End Sub
Cheers #Excelosaurus for heads up on the * would've forgotten it as well. :)
One way is to use the Like operator. The precise format of your string is not clear so you may have to amend (and assuming case insensitive). # represents a single number; the * represents zero or more characters.
Sub Macro16_B()
Dim intRowCount As Long, i As Long
' ' Macro16_B Macro ' '
intRowCount = Worksheets("Reconciliation").UsedRange.Rows.Count
For i = 11 To intRowCount
If Range("H" & i).Value Like "(RAM) #####*" Then
Range("A" & i).Value = "Completed"
End If
Next i
End Sub
Well, there are already 2 good answers, but allow me to paste my code here for good measure, the goal being to submerge #user2574 with code that can be re-used in his/her next endeavors:
Sub Macro16_B()
'In the search spec below, * stands for anything, and # for a digit.
'Remove the * characters if you expect the content to be limited to "(RAM #####)" only.
Const SEARCH_SPEC As String = "*(RAM #####)*"
Dim bScreenUpdating As Boolean
Dim bEnableEvents As Boolean
'Keep track of some settings.
bScreenUpdating = Application.ScreenUpdating
bEnableEvents = Application.EnableEvents
On Error GoTo errHandler
'Prevent Excel from updating the screen in real-time,
'and disable events to prevent unwanted side effects.
Application.ScreenUpdating = False
Application.EnableEvents = False
'Down with business...
Dim scanRange As Excel.Range
Dim cell As Excel.Range
Dim content As String
Dim ramOffset As Long
With ThisWorkbook.Worksheets("Reconciliation").Columns("H")
Set scanRange = .Worksheet.Range(.Cells(11), .Cells(.Cells.Count).End(xlUp))
End With
For Each cell In scanRange
content = CStr(cell.Value2)
If content Like SEARCH_SPEC Then
cell.EntireRow.Columns("A").Value = "Completed"
End If
Next
Recover:
On Error Resume Next
'Restore the settings as they were upon entering this sub.
Application.ScreenUpdating = bScreenUpdating
Application.EnableEvents = bEnableEvents
Exit Sub
errHandler:
MsgBox Err.Description, vbExclamation + vbOKOnly, "Error"
Resume Recover
End Sub

Find value in column and change cell to left with an if statment

This VBA script should take the value in the cell A37 and check if its in the C column of another worksheet. When the number is found the column to the left should be changed to 0. If it is already 0 then a message box will inform the user and if the number does not exist another message box will inform them of this.
This is the VBA I am using to accomplish this. However, every time I try to run it there is a "compile error: Next without For"
Update This issue now is that I need to activate the cell that the fcell is in before doing an Active.cell offset
Sub Cancelled()
Dim x As Long
Dim regRange As Range
Dim fcell As Range
x = ThisWorkbook.Sheets("Welcome").Range("A37").Value
Set regRange = ThisWorkbook.Sheets("Registration").Range("C:C")
For Each fcell In regRange.Cells
If fcell.Value = x Then
ActiveCell.Offset(0, -1).Select
If ActiveCell.Value = 1 Then
ActiveCell.Value = 0
MsgBox "Changed to zero"
Exit Sub
Else
MsgBox "That registration number is already cancelled"
Exit Sub
End If
End If
Next fcell
MsgBox "That number does not exist"
End Sub
Edit for new question: No need to use Select and ActiveCell
If fcell.Value = x Then
If fcell.Offset(0,-1).Value = 1 Then
fcell.Offset(0,-1).Value = 0
...
Edit 2: A further suggestion: You could also use the Range.Find method. This will throw an error if nothing is found so you have to catch that:
On Error Resume Next 'If an error occurs, continue with the next line
Set fcell = regRange.Find(x)
On Error GoTo 0 'disable the error handler
If fcell Is Nothing Then 'If Find failed
MsgBox "That number does not exist"
Else
'do your stuff with fcell here
End If
Hope this is not too late to answer your question:
Sub Cancelled()
Dim x As Long
Dim regRange As Range
Dim fcell As Range
x = ThisWorkbook.Sheets("Welcome").Range("A7").Value
Set regRange = ThisWorkbook.Sheets("Registration").Range("C:C")
For Each fcell In regRange.Cells
If fcell.Value = x Then
If fcell.Offset(0, -1).Value = 1 Then
fcell.Offset(0, -1).Value = 0
MsgBox "Changed to zero"
Else
MsgBox "That registration number is already cancelled"
End If
Exit Sub
End If
Next fcell
MsgBox "That number does not exist"
End Sub
Instead of
Set regRange = ThisWorkbook.Sheets("Registration").Range("C:C")
its better to get the last row in Column C and then set your range as:
Dim lastRow As Long
lastRow = ThisWorkbook.Sheets("Registration").Cells(Rows.Count, "C").End(xlUp).Row
Set regRange = ThisWorkbook.Sheets("Registration").Range("C1:C" & lastRow)

Need to copy certain data over workbooks

new to VBA here. I've been stuck on this problem for a while now:
Essentially, I need to create a macro that copies over specific data from one sheet to another, that is up to the user to specify. The catch is that while all the data is in one column (B), not all rows of the column have relevant entries; some are blank and some have other data that I don't want.
Only entries that begin with 4 numbers are wanted. I can't seem to get how the iterated copy-pasting works; what I've come up with is as follows:
'defining input
Dim dater As Date
dater = Range("B2")
If dater = False Then
MsgBox "Date not specified"
Exit Sub
End If
Dim sheetin As String
sheetin = Range("B5")
If sheetin = "" Then
MsgBox "Input Sheet not specified"
Exit Sub
End If
Dim wbin As String
wbin = Range("B4")
If wbin = "" Then
MsgBox "Input workbook not specified"
Exit Sub
End If
Dim sheetout As String
sheetout = Range("B9")
If sheetout = "" Then
MsgBox "Output Sheet not specified"
Exit Sub
End If
Dim wbout As String
wbout = Range("B8")
If wbout = "" Then
MsgBox "Output Workbook not specified"
Exit Sub
End If
Windows(wbout).Activate
Dim sh As Worksheet, existx As Boolean
For Each sh In Worksheets
If sh.Name Like sheetout Then existx = True: Exit For
Next
If existx = True Then
If Sheets(sheetout).Visible = False Then Sheets(sheetout).Visible = True
Else
Sheets.Add.Name = CStr(sheetout)
End If
'copy pasting values
Windows(wbin).Activate
Sheets(sheetin).Select
'specify maximum row
iMaxRow = 500
For iRow = 1 To iMaxRow
With Worksheets(sheetin).Cells(iRow, 2)
'Check that cell is not empty.
If .Value = "####*" Then
.Copy Destination:=Workbooks(wbout).Worksheets(sheetout).Range("A" & i)
'Else do nothing.
End If
End With
Next iRow
End Sub
Subsequently i'll have to match data to these entries that have been copied over but I figure once i get the hang of how to do iterated stuff it shouldn't be too much of a problem. But right now i'm really stuck... Please help!
It looks like it should work, except for that part :
With Worksheets(sheetin).Cells(iRow, 2)
If .Value = "####*" Then
.Copy Destination:=Workbooks(wbout).Worksheets(sheetout).Range("A" & i)
End If
End With
The third line contains an unknown variable : i.
You need to define it to contain the number of the line to which you're copying. For example, if you want to copy to the first available line, try this :
Set wsOut = Workbooks(wbout).Worksheets(sheetout)
With Worksheets(sheetin).Cells(iRow, 2)
If .Value = "####*" Then
i = wsOut.Cells(wsOut.Rows.Count, 1).End(xlUp).Row + 1
.Copy Destination:=wsOut.Range("A" & i)
End If
End With

VBA: error 91 Object variable or with block variable not set

I faced a weird issue of VBA error 91. I saw many other people have this problem because they didn't use keyword "Set" for object, whereas that is not my case.
Following is my code:
Dim eventWS As Worksheet
Set eventWS = Worksheets("Event Sheet")
Dim eventRange As Range
Set eventRange = eventWS.Columns("A:A").Find(240, , xlValues, xlWhole)
If Not eventRange Is Nothing Then
Dim eventFirstAddress As String
eventFirstAddress = eventRange.Address
Do
If eventWS.Range("L" & eventRange.Row).Value = busId Then
If commuter = True Then
Count = Count + Affected(eventWS.Range("Q" & eventRange.Row).Value)
Else
Count = Count + 1
End If
End If
MsgBox("Before call move next: " & eventRange.Row )
Set eventRange = eventWS.Columns("A:A").FindNext(eventRange)
MsgBox("After call move next: " & eventRange.Row )
Loop While Not eventRange Is Nothing And eventRange.Address <> eventFirstAddress
End If
Affected() is a function I can call to do internal processing. And if I removed this "Count = Count + Affected(....)", the code was working fine. If I added it, "Loop While " would throw error 91. If I added a message box to print out the row number before and after moving eventRange, it turned out that "MsgBox("After call move next: " & eventRange.Row)" would throw error 91.
Hence, I'm confuse whether the issue is caused by the internal function or the eventRange now. Hope someone can point my mistakes out. Thank you very much.
Following are the codes of internal function:
Function Affected(markerId As Integer) As Integer
'initialized return value'
AffectedCoummters = 0
'get total financial sheets'
Dim totalFinancial As Integer
totalFinancial = 0
For Each ws In Worksheets
If InStr(ws.Name, "Financial") > 0 Then
totalFinancial = totalFinancial + 1
End If
Next
Dim i As Integer
'run through all financial sheets'
For i = 1 To totalFinancial
'get current financial sheet'
Dim financialWS As Worksheet
Set financialWS = Worksheets("Financial Sheet" & i)
'get total rows of current operation sheet'
Dim rowSize As Long
rowSize = financialWS.Range("A" & financialWS.Rows.Count).End(xlUp).Row
'if reach the maximum number of rows, the value will be 1'
'reInitialize rowSize based on version of Excel'
If rowSize = 1 Then
If Application.Version = "12.0" Then
'MsgBox ("You are using Excel 2007")'
If InStr(ThisWorkbook.Name, ".xlsx") > 0 Then
rowSize = 1048576
Else
'compatible mode'
rowSize = 65536
End If
ElseIf Application.Version = "11.0" Then
'MsgBox ("You are using Excel 2003")'
rowSize = 65536
End If
End If
'filter by marker id first inside current financial sheet'
Dim findMarker As Range
Set findMarker = financialWS.Columns("K:K").Find(markerId, , xlValues, xlWhole)
'if found any given marker id'
If Not findMarker Is Nothing Then
Dim firstAddress As String
firstAddress = findMarker.Address
'check all matched marker id'
Do
AffectedCommuters = AffectedCommuters + financialWS.Range("O" & findMarker.Row).Value
'move to next'
Set findMarker = financialWS.Columns("K:K").FindNext(findMarker)
Loop While Not findMarker Is Nothing And findMarker.Address <> firstAddress
End If
Next i
End Function
Sorry I dont have enough rep to comment so I have to answer here :(
Just want to say that although it is standard procedure to use
Loop While Not eventRange Is Nothing And eventRange.Address <> eventFirstAddress
in this type of procedure, if eventRange is actually Nothing, the line will throw Error 91, because eventRange.address does not exists. What this means is that once you have found something, you can't modify the row in such a way that it will not be found again using .findnext.
After you exit the do...loop, you can modifiy the range to suit...
Perhaps you want to use an array to hold all the rows from your .find...findnext results, and then manipulate them after the Do...loop