Looping through multiple column : optimization - excel

I am completely new to Macro/VBA. We have a requirement ,where I need to check different condition against multiple column and if the conditions are met, then update the final column.
I was able to code it(copied and edited) and it is working fine against small no. of records. The problem is that ,my file has 20000+ records and now the VBA is taking around 40-45 min to update "Call Down Start Date_CUSTOM" column.
Is there anyway to optimize the code or make it to run faster?
Sub Macro3_Call_down_start_date_custom()
Dim i As Long
With ActiveSheet.ListObjects(1)
For i = 1 To .DataBodyRange.Rows.Count
If .ListColumns("Activation Status").DataBodyRange(i).Value = "No" And _
.ListColumns("Call Down Start Date").DataBodyRange(i).Value = "" Then
.ListColumns("Call Down Start Date_CUSTOM").DataBodyRange(i).Value = .ListColumns("Date Added").DataBodyRange(i).Value + 3
ElseIf .ListColumns("Activation Status").DataBodyRange(i).Value = "Yes" And _
.ListColumns("Publish Date").DataBodyRange(i).Value = "" And _
.ListColumns("Time to 1st Publish POC Status").DataBodyRange(i).Value = "No First POC" Then
.ListColumns("Call Down Start Date_CUSTOM").DataBodyRange(i).Value = .ListColumns("Activation Date").DataBodyRange(i).Value
Else
.ListColumns("Call Down Start Date_CUSTOM").DataBodyRange(i).Value = .ListColumns("Call Down Start Date").DataBodyRange(i).Value
End If
Next i
End With
End Sub

set lc = ActiveSheet.ListObjects(1).ListColumns
set range1 = lc("Activation Status").DataBodyRange
set range2 = lc("Call Down Start Date").DataBodyRange
set range3 = lc("Call Down Start Date_CUSTOM").DataBodyRange
set range4 = lc("Date Added").DataBodyRange
on error goto CATCH
Application.ScreenUpdating = False
With ActiveSheet.ListObjects(1)
For i = 1 To .DataBodyRange.Rows.Count
if range1(i).Value = "No" And _
range2(i).Value = "" then
range3(i).Value = range4(i).Value + 3
elseif
...
else
...
end if
next i
end with
CATCH: Application.ScreenUpdating = True
end sub

Related

For each code not making check box value true

I am trying to create code that runs through a list of user ID's (B Numbers) and then when it finds the corresponding ID it checks to see if there's an X in the column directly next to it for a certain subject named SBB005 See image. If there is an X, I want the check box value to be true. The for each loop ends when it reaches a blank cell.
I have declared the 'RowYear2' and 'Year2CourseRange' ranges as public variables, and when running the code, nothing happens and the check box remains unticked! Any idea why the checkbox isn't being ticked as expected?
I am planning on setting up multiple checkboxes once this is working for all the subjects in each column:
See image
Hoping that someone can help me to get this working or may even introduce an easier way to do so for another 20 checkboxes!
Many thanks :)
Private Sub UserForm_Initialize()
Set Year2CourseRange = Sheets("Year2").Range("A:A")
For Each RowYear2 In Year2CourseRange.Cells
If RowYear2.Value = BNumberTxt Then
If RowYear2.Offset(0, 1) = "x" Then
Me.CHKSBB005.value = True
Else
Me.CHKSBB005.value = False
End If
ElseIf IsEmpty(RowYear2) Then
Exit For
End If
Next RowYear2
LoggedInTxt = Row.Offset(0, -3)
BNumberTxt = Row.Offset(0, -7)
CourseTxt = Row.Offset(0, -1)
CourseNumTxt = Row.Offset(0, -2)
End Sub
Private Sub EnterBtn_Click()
Dim LIMatch As Boolean
Dim Win As Boolean
Email = Me.EmailTxt
Password = Me.PasswordTxt
Set UserRange = Sheets("StudentInformation").Range("H:H")
For Each Row In UserRange.Cells
If Me.EmailTxt = "" And Me.PasswordTxt = "" Then
MsgBox ("Please enter an email and password")
LIMatch = False
Win = True
Exit For
ElseIf Me.EmailTxt = "" Then
MsgBox ("Please enter an email address")
LIMatch = False
Win = True
Exit For
ElseIf Me.PasswordTxt = "" Then
MsgBox ("Please enter a password")
LIMatch = False
Win = True
Exit For
Else
If UCase(Row.Value) = UCase(Email) Then
If UCase(Row.Offset(0, -6)) = UCase(Password) Then
MsgBox "Welcome"
LIMatch = True
Win = True
Attempts = 0
Exit For
ElseIf IsEmpty(Row) Then
Exit For
Win = False
Else
LIMatch = False
Win = False
Exit For
End If
Else
LIMatch = False
Win = False
End If
End If
Next Row
If LIMatch = True And Win = True Then
Unload Me
NewForm.Show
ElseIf LIMatch = False And Win = False Then
MsgBox ("Incorrect login")
Attempts = Attempts + 1
Else
End If
If Attempts >= 3 Then
MsgBox ("You have entered the incorrect login 3 times")
Unload Me
End If
End Sub
Once you fix your problem with the Row global, you can do something like this:
Private Sub UserForm_Initialize()
Dim shtData As Worksheet
Dim Year2CourseRange As Range, HeaderRange As Range, m, c As Range
Set shtData = ThisWorkbook.Sheets("Year2")
With shtData
Set Year2CourseRange = .Range("A:A")
Set HeaderRange = .Range(.Range("B2"), .Cells(2, 500).End(xlToLeft))
End With
'you'll need to fix this part....
BNumberTxt = Row.Offset(0, -7)
'etc
'find a matching row: Match() is a good approach here
m = Application.Match(BNumberTxt, Year2CourseRange, 0)
'loop over all the column headers
For Each c In HeaderRange.Cells
'Assumes all checkboxes are named "CHK[ColumnHeaderHere]"
With Me.Controls("CHK" & c.Value)
If IsError(m) Then
.Value = False 'clear all if no match
Else
.Value = (UCase(shtData.Cells(m, c.Column)) = "X") 'set if "x"
End If
End With
End If
End Sub
Ranges and Ranges
This is your code squashed a little bit and below is your data:
Private Sub UserForm_Initialize()
Set Year2CourseRange = Sheets("Year2").Range("A:A")
For Each RowYear2 In Year2CourseRange.Cells
If RowYear2.Value = BNumberTxt Then
If RowYear2.Offset(0, 1) = "x" Then
Me.CHKSBB005.value = True
Else: Me.CHKSBB005.value = False: End If
ElseIf IsEmpty(RowYear2) Then
Exit For: End If: Next RowYear2
LoggedInTxt = Row.Offset(0, -3): BNumberTxt = Row.Offset(0, -7)
CourseTxt = Row.Offset(0, -1): CourseNumTxt = Row.Offset(0, -2): End Sub
Take a look for a while, you might see the error yourself.
The CheckBox Tick Mystery
When you write Range("A:A") that refers to the whole column including Range("A1") which appears to be EMPTY. The code never even enters the If RowYear2.Offset... line but exits via the ElseIf line.
The Row Variable
I hate the idea of declaring a variable Row. But it is valid. Since there is an Offset involved, Row should be a range, probably a cell. As in the comments indicated, it has to 'survive' from another UserForm let's say UserFormX. If it has 'survived' you have to refer to it like this:
UserFormX.Row
or you have to declare it in a 'not-object' module to use only Row.
Another EnterBtn_Click
Probably useless now but here is the code I worked on the other day:
Option Explicit
Public intAttempts As Integer
Private Sub CancelBtn_Click()
Unload Me
End Sub
Private Sub EnterBtn_Click()
Const strEmail = "Please enter email address." ' Email Input Message
Const strPassword = "Please enter a password." ' Password Input Message
Const strLoginCorrect = "Welcome" ' Correct Login Message
Const strLoginIncorrect = "Incorrect Login." ' Incorrect Login Message
Const strAttempts = "Too many login attempts." ' Login Attempts Message
' Use worksheet name or index e.g. "SInfo" or 1.
Const vntWsName As String = "StudentInformation" ' Worksheet
' Use column letter or column number e.g. "F" or 6.
Const vntEmailColumn As Variant = "F" ' Email Column
Const intFirstRow As Integer = 2 ' Email Column First Row
Const intPasswordColumnOffset As Integer = -4 ' Password Column Offset
Const intMaxAttempts As Integer = 3 ' Maximum Login Attempts
Dim lngCounter As Long ' Email Column Row Counter
Dim lngLastrow As Long ' Email Column Last Row
' Check number of login attempts.
If intAttempts >= intMaxAttempts Then
MsgBox strAttempts
Exit Sub
End If
' Show annoying text messages if nothing was entered.
If Me.EmailTxt.Text = "" Then
Me.EmailTxt.Text = strEmail: Exit Sub
ElseIf Me.EmailTxt.Text = strEmail Then Exit Sub
End If
If Me.PasswordTxt.Text = "" Then
Me.PasswordTxt.Text = strPassword: Exit Sub
ElseIf Me.PasswordTxt.Text = strPassword Then Exit Sub
End If
' Check for data in specified worksheet.
With ThisWorkbook.Worksheets(vntWsName)
' Determine last row of data in Email Column.
lngLastrow = .Cells(Rows.Count, vntEmailColumn).End(xlUp).Row
For lngCounter = intFirstRow To lngLastrow
' Ceck for email in Email Column.
If UCase(.Cells(lngCounter, vntEmailColumn).Value) _
= UCase(EmailTxt.Text) Then ' Correct email.
' Check if correct password in Password Column
If UCase(.Cells(lngCounter, vntEmailColumn) _
.Offset(0, intPasswordColumnOffset).Value) _
= UCase(PasswordTxt.Text) Then ' Correct password.
Exit For
Else ' Wrong password. Set "counter" to "end".
' Faking that the loop was not interrupted.
lngCounter = lngLastrow
End If
' Else ' Wrong Email. Do nothing. Not necessary.
End If
Next
' When the loop wasn't interrupted, "lngcounter = lnglastrow + 1".
End With
' Check if loop was NOT interrupted.
If lngCounter = lngLastrow + 1 Then ' Loop was NOT interrupted.
intAttempts = intAttempts + 1
MsgBox strLoginIncorrect
Else ' Loop was interrupted. Correct email and password.
MsgBox strLoginCorrect
Unload Me
NewForm.Show
End If
End Sub

How to efficiently run through rows of data to match a criterion using excel VBA?

Normally what I do on my program is using logic like:
Total_rows_worksheet = Worksheets("Sample").Range("A" & Rows.Count).End(xlUp).Row
For i = 2 to Total_rows_worksheet
If Worksheets("Sample").Cells(i,1)=Criteria Then
[Perform this action]
End If
Next i
However, when the number of rows get large, code that runs through the entire sheet gets a bit slow. Also, I remember my programmer friend telling me that it is a common mistake for beginner programmers to run through all the data. The correct way according to him was to point to the rows of interest, but I do not know exactly how to do that in Excel.
Disable screen updating and calculation before the loop and it will increase the speed immensely.
Sub testing()
'Disable screen updating and calculation
Dim uRange As Range
Application.ScreenUpdating = False
Application.Calculation = xlCalculationManual
Total_rows_worksheet = Worksheets("Sample").Range("A" & Rows.Count).End(xlUp).Row
For i = 1 To Total_rows_worksheet
If Worksheets("Sample").Cells(i, 1) = "OK" Then
If uRange Is Nothing Then
Set uRange = Worksheets("Sample").Cells(i, 1)
Else
Set uRange = Union(uRange, Worksheets("Sample").Cells(i, 1))
End If
End If
Next i
uRange.Value = "THIS USED TO SAY OK"
'Enable screen updating and calculation when done
Application.Calculation = xlCalculationAutomatic
Application.ScreenUpdating = True
End Sub
Edit 1: Depending on the task, another way to speed things up is to change all at once by adding ranges to a Union.
Note: Screen updating should be the first to be disabled and the last to be enabled when going for speed in Excel-VBA. There are other things that can be disable like Events that also help if you have event specific triggers.
You might use something similar as:
Public roww
Sub Main()
Dim sht
roww = InputBox("Start Row", "Row to start", "", 8000, 6000)
If roww <> "" Then
Set sht = Sheets("Your sheet")
'this is a validation
Do Until sht.Cells(roww, "A").Value = ""
'this is other optional validation
If sht.Cells(roww, "Y").Text <> "" Then
[Perform this action]
roww = roww + 1
Else
roww = roww + 1
End If
Loop
Else
End If
MsgBox ("The Process has been completed")
End Sub
It should stop until the the last row with data.

What is causing this Run-time Error 13 "Type Mis-Match"?

The code below increments a variable based upon which cells contain "Yes". It works just until I use auto-fill to set the value for any of the cells in F13:F59 to "Yes". When I type in the word "Yes" manually, the run-time error does not occur. However auto-fill, seems to be causing the issue.
Select Case (changedcell)
'Issues Mgmt
Case Range("F15"), Range("F19"), Range("F23"), Range("F27"), Range("F30"), Range("F39"), Range("F42"), Range("F45"), Range("F50"), Range("F53"), Range("F54") 'Issues
If Range("F15").Value = "Yes" Then
issues = issues + 1
End If
If Range("F19").Value = "Yes" Then
issues = issues + 1
End If
If Range("F23").Value = "Yes" Then
issues = issues + 1
End If
If Range("F27").Value = "Yes" Then
issues = issues + 1
End If
If Range("F30").Value = "Yes" Then
issues = issues + 1
End If
If Range("F39").Value = "Yes" Then
issues = issues + 1
End If
If Range("F42").Value = "Yes" Then
issues = issues + 1
End If
If Range("F45").Value = "Yes" Then
issues = issues + 1
End If
If Range("F50").Value = "Yes" Then
issues = issues + 1
End If
If Range("F53").Value = "Yes" Then
issues = issues + 1
End If
If Range("F54").Value = "Yes" Then
issues = issues + 1
End If
If issues > 1 Then
Application.EnableEvents = False
MsgBox "Issues Management has already been selected under another old solution category.", vbCritical, "Duplicate Use Case Selected"
Range(target.Address).Value = ""
End If
End Select
This first part of the code is where the error occurs. Any thoughts?
Case Range("F15"), Range("F19"), Range("F23"), Range("F27"), Range("F30"), Range("F39"), Range("F42"), Range("F45"), Range("F50"), Range("F53"), Range("F54") 'Issues
Try this:
Dim rIssues As Range
Dim rCll As Range
Set rIssues = Range("F15,F19,F23,F27,F30,F39,F42,F45,F50,F53,F54")
For Each rCll In rIssues
If rCll.Value = "Yes" Then Issues = 1 + Issues
Next
If Issues > 1 Then
Application.EnableEvents = False
MsgBox "Issues Management has already been selected under another old solution category.", _
vbCritical, "Duplicate Use Case Selected"
Range(Target.Address).Value = ""
End If
Suggest to read the following pages:
For Each...Next Statement,
Select Case Statement

When reading down a column of Excel file, how to define cell coordinates without selecting a cell?

Can anyone tell me how to improve this macro?
All the macro does is it just reads an Excel file for a list a accounts to update in an application (SmarTerm Beta). It technically already accomplishes the goal, but is there a way to code it so that while it’s reading the Excel file, the coordinates of the cells from which to read the account numbers and also the coordinates of the cells in which to write an output don’t depend on a "pre-selected" a cell? The risk with selecting a cell is that if someone were to accidentally select a different cell while the macro is running, everything will get screwed up.
Here's my current code:
Public oExcelObj As Object
Function WaitSystem(Optional NoDialog as Variant) As Boolean
Dim nContinue as Integer
Dim nTimeOut as Integer 'In seconds.
'The default timeout for each command is 3 minutes.
'Increase this value if your host requires more time
'for each command.
nTimeOut = 10
If IsMissing(NoDialog) then NoDialog = False
'Wait for response from host.
Session.EventWait.Timeout = nTimeOut
Session.EventWait.EventType = smlPAGERECEIVED
Session.EventWait.MaxEventCount = 1
WaitSystem = True
If Session.EventWait.Start = smlWAITTIMEOUT Then
If NoDialog Then
WaitSystem = False
Else
nContinue = QuerySyncError()
If nContinue <> ebYes then WaitSystem = False
End If
End If
Set LockStep = Nothing
End Function
'Establish link. Search for Excel.
Function OleLinkConnection
Const XlMaximized = &HFFFFEFD7
Titlebar$ = AppFind$("Microsoft Excel")
If Titlebar$ <> "" Then
bIsExcelActive = True
If AppGetState(Titlebar$) = ebMinimized Then
AppSetState 2, Titlebar$
End If
Else
bIsExcelActive = False
End If
If bIsExcelActive Then
'Create Excel Object using current instance of Excel.
Set oExcelObj = GetObject(, "Excel.Application")
Else
'Create Excel Object using a new instance of Excel.
Set oExcelObj = CreateObject("Excel.Application")
End If
Version = oExcelObj.Application.Version
oExcelObj.ScreenUpdating = True
oExcelObj.Displayalerts = True
oExcelObj.Visible = true
End Function
Sub JPBmacro
Dim AccountNumber As String
Dim Temp As Integer
Begin Dialog StartDialogTemplate ,,211,74,"Run JPBmacro?"
OKButton 60,12,92,20,.Proceed
CancelButton 60,40,92,20,.Exit
End Dialog
Dim StartDialog As StartDialogTemplate
r% = Dialog(StartDialog)
If r% = 0 Then End
g$ = "G:\DATA\outputfile.xlsx"
oleCode = OleLinkConnection
oExcelObj.Workbooks.Open g$
oExcelObj.Range("A1").Select ‘<----This selects the cell from which all coordinates are based off of. The coordinates of oExcelObj.ActiveCell.Offset(Y,X).Value VBA depend on selecting a cell.
NEXTACCOUNT:
Temp = 0
AccountNumber = oExcelObj.ActiveCell.Offset(Temp,0).Value
While AccountNumber <> ""
Session.SendKey "CLEAR"
If WaitSystem = False Then End
Session.Send "ACTU " & AccountNumber
Session.SendKey "ENTER"
If WaitSystem = False Then End
If Trim(Session.ScreenText(4,6,1,22)) = "INVALID ACCOUNT NUMBER" Or Trim(Session.ScreenText(4,6,1,19)) = "ACCOUNT NOT ON FILE" Then
oExcelObj.ActiveCell.Offset(Temp,1).Value = Trim(Session.ScreenText(4,6,1,22))
GoTo RESTARTLOOP
End If
UPDATEIOV:
If Trim(Session.ScreenText(13,76,1,1)) = "Y" Then
oExcelObj.ActiveCell.Offset(Temp,1).Value = "Account already flagged as institutional."
Else
Session.Row = 13
Session.Column = 76
Session.send "Y"
Session.SendKey "ENTER"
If WaitSystem = False Then End
oExcelObj.ActiveCell.Offset(Temp,1).Value = Trim(Session.ScreenText(24,2,1,50))
End If
RESTARTLOOP:
Temp = Temp + 1
AccountNumber = oExcelObj.ActiveCell.Offset(Temp,0).Value
Wend
ENDNOW:
oExcelObj.Workbooks.Close
MsgBox "All Done!"
End Sub
Why not keep the reference to the first cell?
Dim rng as Range
Set rng = oExcelObj.Range("A1")
i=1
...
x = rng.Cell(i,1).Value
'Or faster yet is reading all the values into an variant array.
Dim array() as Variant
array = rng.Resize(N,M).Value
' Work with array as
x = array(i,1)
Given the comment from assylias and that another poster has since "answered" with this approach:
I can't see where oExcelObj is instantiated? Or how you are referring to a specific sheet.
Regardless of which,
you can avoid select by setting a range, ie Set rng1 = oExcelObj.Sheets(1).Range("A1")
and then use offsets from rng1.
The user won't be able to interfere while the code is running

Using Input Box to Filter Text - VBA

I want to filter a pre-determined column using a query from a user (Input Box)
Ex: Filter column "L" with a user input text "Joey".
Can anyone guide me as to what needs to be done? This is the code I was trying...
Set wSheetStart = ActiveSheet
Set rFilterHeads = Range("L1", Range("IV1").End(xlToLeft))
With wSheetStart
.AutoFilterMode = False
rFilterHeads.AutoFilter
strCriteria = InputBox("Enter Criteria")
If strCriteria = vbNullString Then Exit Sub
rFilterHeads.AutoFilter Field:=3, Criteria1:=strCriteria
End With
Use a loop + Like to limit entry to whatever you need.
Do
strCriteria = UCase(InputBox("Enter criteria"))
Loop Until Len(strCriteria) = 0 Or strCriteria Like "?????"
If Len(strCriteria) = 0 Then Exit Sub
'continue...

Resources