Is there a better alternative than a loop to reduce processing time? - excel

I am cycling through values on a pending data list and then updating a master list if any of those values are new. The pending list will normally have 100-200 rows of data and each row will have about 10 variables that it will need to make sure are updated on the master list. The Master list is about 10,000 rows.
I have written code that will loop through each row of the pending list, assign values to variables and then perform a find on the master list looking for a matching record and then update it accordingly. My code works fine and does exactly what I want but the processing time is about 4 minutes and that's 3 minutes and 50 seconds longer than the people that use it are willing to allow without complaining.
Is there alternative coding that I could use to help decrease processing time?
The code I have is very long and so I am not going to paste it all here but instead paste snip-its of it so you can have an idea of what I am currently doing:
Application.Screenupdating = False
Applicaiton.Enableevents = False
Application.Calculation = xlCalculationManual
PendingBRow = ThisWorkbook.Sheets("PendingLog").Range("A65000").End(xlUp).Row
MasterBRow = ThisWorkbook.Sheets("MasterLog").Range("A65000").End(xlUp).Row
For D = 2 To PendingBRow
With ThisWorkbook.Sheets("PendingLog").Range("A" & D)
PendingRecordNumber = .Value
PendingIR = .offset(0, 5).Value
PendingVal = .offset(0, 6).Value
End With
With ThisWorkbook.Sheets("MasterLog").Range("B2:B" & MasterBRow)
Set c = .Find(PendingRecordNumber, LookIn:=xlValues)
If Not c Is Nothing Then
firstAddress = c.Address
Do
DaysSinceLastWorkedStatic = c.offset(0, 22).Value
MasterIRValue = c.offset(0, 16).Value
If PendingIR <> 0 Then
If PendingIR <> MasterIRValue Then
c.offset(0, 16).Value = PendingIR
DaysSinceLastWorkedStatic = 0
c.offset(0, 22).Value = DateVal
End If
End If
c.offset(0, 24).Value = POorLA
c.offset(0, 25).Value = FinalizedFlag
Set c = .FindNext(c)
Loop While Not c Is Nothing And c.Address <> firstAddress ' in rare cases a record number could be duplicated on the master list.
end if
end with
ThisWorkbook.Sheets("PendingLog").Range("A" & D).offset(0, 15).Value = DaysSinceLastWorkedStatic
Next D
One alternative I had considered was to filter the master list by the record number and update the visible row only and then unfilter for each record number on the pending list. I have not yet tested this method.
Could this method be better than what I have?

So I dont know if this will be an answer but I think this might at least give you a look at another type of find/update code.
This is used to update the times people are scheduled to work
In this example I have a Row A with Names B with Times. I paste the updated Name and Time in rows H and I. Some times its all 98 some times it just 5 and this program looks at the list finds the name and takes the time from I and writes it over its corresponding time in B.
Option Explicit
Sub Update_Holiday()
Dim ws As Worksheet
Dim SrcRng As Range
Dim schRng As Range
Dim c As Range
Dim search As Range
Set ws = ThisWorkbook.Sheets(3)
Set SrcRng = ws.Range("H2:H98")
Set schRng = ws.Range("A2:A98")
For Each c In SrcRng
Set search = schRng.Find(c.Value, LookIn:=xlValues, SearchDirection:=xlNext)
If Not search Is Nothing Then
c.Offset(, 1).Copy search.Offset(, 1)
End If
Next c
End Sub

Related

VBA code too slow - takes 6 hours to execute output

I have a lengthy code unable to share the 8000+ liner code completely, The code runs through loops multiple times row by row, if there are 10000+ rows then loop runs 10000+ times.
Since the code is too lengthy I am sharing a part of it were I feel it can shorten the time taken, But I am missing a loop in it and how do I include that Is my query for now.
I’ll be sharing the original code and very next is the replacement code kindly check and let me know we’re and how to include.
Original code:
For i = 2 To endlineMM
If worksheets(“MM Source”).cells(I,9).value = “registered locked” or worksheets(“MM Source”).cells(i,9).value = “registered unlocked” Then
For j = 3 to endlineDHDO
If instr(Lcase(worksheets(“DHDO”).cells(j,2).value),Lcase(Worksheets(“MM Source”).cells(i,2).value)) <> 0 Then
If Lcase(Worksheets(“MM Source”).cells(i,2).value) = Lcase(worksheets(“DHDO”).cells(j,2).value) Then
Found missing = True
Exit For
Else if j= EndlineDHDO And Lcase(Worksheets(“MM Source”).cells(i,2).value)<>
Lcase(worksheets(“DHDO”).cells(j,2).value) Then
Foundmissing = false
End if
Next j
If foundmissing = False Then
Etc......
Replacement code:
For i = 2 to endlineMM
If worksheets(“MM Source”).cells(I,9).value = “registered locked” or worksheets(“MM Source”).cells(i,9).value = “registered unlocked” Then
Test_ID = Worlsheets(“MM Source”).cells(i,2).value
With sheets(“DHDO”).Range(“B:B“)
Set prg = .Find(Test_ID, LookIn:=xlvalues)
If prg is nothing then
Foundmissing =true
Exit for
Else
Foundmissing = false
End if
End with
If foundmissing = false Then
Etc......
If you observe above from original code it has “i“ as well as “j” but in replacement code I am missing “j”
How can I fix my Replacement code
let me know how to edit the Replacement code please
Generally speaking, your code will run a lot faster if you use Ranges and Arrays rather than individual Cells.
For example, if you were to take a spreadsheet and fill columns A1:B10000, with numeric data, and then compare the performance of the two following codes:
Dim data As Variant
Dim output(10000) As Double
Dim i As Integer
data = Application.Transpose(Application.Transpose(Range("A1", "B10000")))
For i = 1 To 10000
output(i - 1) = data(i, 1) + data(i, 2)
Next
Range("C1", "C10000").Value = Application.Transpose(output)
and
Dim i As Integer
For i = 1 To 10000
Cells(i, 3).Value = Cells(i, 1).Value + Cells(i, 2).Value
Next
You will notice that the first variation is considerably faster.
By way of explanation Application.Transpose is necessary to assign the range to an array. It needs to be doubled in the first case, because it is a two-dimensional array.
Here is a sample that will filter the MM Source sheet, then loop through the visible cells finding cells in DHDO sheet
Sub Do_It()
Dim sh As Worksheet, ws As Worksheet
Dim rng As Range, c As Range
Dim a As Range
Set sh = Sheets("MM Source")
Set ws = Sheets("DHDO")
Application.ScreenUpdating = False
With sh
Set rng = .Range("I2:I" & .Cells(.Rows.Count, "I").End(xlUp).Row)
.Columns("I:I").AutoFilter Field:=1, Criteria1:= _
"=registered locked", Operator:=xlOr, Criteria2:="=registered unlocked"
For Each c In rng.SpecialCells(xlCellTypeVisible).Cells
Set a = ws.Range("B:B").Find(c.Offset(, -7), LookIn:=xlValues)
If Not a Is Nothing Then
'MsgBox "Do nothing"
Else
'MsgBox "Do something"
c.Interior.Color = vbGreen
End If
Next c
.AutoFilterMode = False
End With
End Sub

Add values from multiple cells to a Listbox and get the First selected value back

I am currently using the below code to populate a (userform) listbox with items on loan based on a person's name(Column B). I need to also include the Item ID number as a reference for further functions. I am wondering what is the best approach to return two values from a row (Item name(Column C) and ID number(Column L)) based on a reference value (Person's name) into a listbox.
Private Sub cboName_Change()
Me.ListItem.Clear
Dim ws As Worksheet
Dim i As Range
Set ws = Worksheets("Loans")
With ws
LastRow = .Range("B" & .Rows.Count).End(xlUp).Row
End With
Set i = ws.Range("B1:A" & LastRow)
With i
Set c = .Find(cboName.Value, LookIn:=xlValues, Lookat:=xlWhole)
If Not c Is Nothing Then
firstAddress = c.Address
Do
Set c = .FindNext(c)
ListItem.AddItem .Cells(c.Row, 3).Value
Loop While Not c Is Nothing And c.Address <> firstAddress
End If
End With
End Sub
If this question is very similar to others already asked (always getting in trouble for that), any direction would be appreciated - I may just need some context (very new at coding). Thanks!
I 'll put up an answer for you:
There are 2 ways for this
Use Multicolumn ListBox
Concatenate you string
Second one I ll describe here as i prefer this one.
So in your code use: ListItem.AddItem .Cells(c.Row, 3).Value & ":" & .Cells(c.Row, 12).Value
Now to get the selected Item back use: Split(ListItem.list(ListItem.ListIndex), ":")(0)
So if you have Name in one cell & Mikku in the Other, Listbox will display
Name:Mikku
and the second Line of code will give you Name
Demo:

VBA multiple For loops - works right only first two times

So I have been working on this little piece of code for a couple of days now and have hit a snag that I just cannot figure out. Basically I have a nested for loop that is supposed to take values from one Sheet and put them in a data sheet so that those value can be saved and averaged out over time.
I want the data from the cells M5 - M35 to get put into the data sheet like this: from left to right A3 - AC200, but I also want the loop to stop once the M5 - M35 range runs out of values and when the user next pushes the button to store data I want the loop to start on the next line down.
So This Spreadsheet is for my work, it is for a Butchery Yield Test and I want it to have it's owned database stored on a hidden sheet that will be averaged out over time so I or others can come back to it and update it every few months to make the averages more accurate and to get a better understanding of the profitability of certain cuts of meat.
I have tried setting the loop values back to their starting values once the loop has filled out all the data which worked and I thought I'd solved the issue until I ran the test multiple time and found that the first and second time work as expected from the third onwards however it doesn't start the loop down at the next row it just continues through the range
Sub subData1()
Dim rng As Range
Dim rcell As Range
Dim ws As Worksheet
Dim Tws As Worksheet
Set Tws = Worksheets("Test")
Set ws = Worksheets("data")
For i = 3 To 200 'range is from cell A3 - A200
'if start cell already has value go down a row
If ws.Cells(i, 1).Value <> "" Then
i = i + 1
End If
'set the range for data sheet
Set rng = ws.Range(ws.Cells(i, 1), ws.Cells(i, 30).End(xlToRight))
For f = 5 To 35
For Each rcell In rng 'loop through each cell in data sheet range
If rcell.Value = "" Then 'if cell is blank input data
If Tws.Cells(f, 13).Value <> "" Then
'Check the selected Cell has a value
rcell.Value = Tws.Cells(f, 13).Value
f = f + 1
End If
Else
If f > 5 Then
MsgBox "Data Storage Updated", , "Data Storage"
f = 5
Exit Sub
'Else
'MsgBox "Value Must Be Greater Than Zero", , f
'f = 5
'Exit Sub
End If
End If
Next
Next
Next
End Sub
The reason it's working the first and second time is because of this line
If ws.Cells(i, 1).Value <> "" Then
i = i + 1
End If
But, if you'll notice, it's only running once for each iteration. Which means that on a blank sheet, it will evaluate false on the first iteration of the first run and continue, on the first iteration of the second run, it will evaluate true and move down one row to row 4, but on the first iteration of the third run it will also evaluate true and move down to row 4 and then place data starting at ws.Range(ws.Cells(i, 1), ws.Cells(i, 30).End(xlToRight))
You need to change your If-End If to a function that will retrieve the last used row in Column A of your data sheet. Additionally, you probably don't need your outer For-Next loop since that's looping through your output sheet and would output the same M5:M35 values for every single row.
If I understand your needs correctly, I believe you could utilize this to accomplish what you're looking for
Dim rng As Range
Dim ws As Worksheet
Dim Tws As Worksheet
Dim endRow As Long
Set Tws = Worksheets("Test")
Set ws = Worksheets("data")
endRow = ws.Range("A" & Rows.Count).End(xlUp).Row + 1
Set rng = ws.Cells(endRow, 1)
For f = 5 To 35
If Tws.Cells(f, 13).Value <> "" Then
rng.Value = Tws.Cells(f, 13).Value
Set rng = rng.Offset(0, 1)
End If
Next
With this, every sequential button click would insert data at a new row, looping through Tws.Cells(f,13) and placing that value in the right-most column by incrementally setting the range.

Most efficient way of offseting cell that contains "x"

I've been using Columns(2).Copy Destination:=Columns(1) provisionally to offset all cells that contain "x" one column to the left, however it seems to be very memory consuming and not very reliable as I can't choose to offset only "x" values.
Is there any way I can make only the cells on which this contition was met to be offset, and on a more efficient way?
Similar to the below answer, but with a For-Each loop and for the whole range:
Sub MoveOver()
Dim rng As Range
Dim c As Range
Set rng = ThisWorkbook.Worksheets("Sheet1").Range("B:B")
For Each c In rng
If c.Value = "x" Then c.Offset(0, -1) = c.Value
Next c
End Sub
you would probably want to try something like this
For i = 1 To 100
If ActiveSheet.Cells(i, 2).Value = "x" Then
ActiveSheet.Cells(i, 1).Value = ActiveSheet.Cells(i, 2).Value
End If
Next
just set the 100 for however many rows you have. I'm not even sure this would run any faster than what you have. Unless I am missing your goal here.
and if you want to go through all cells in the sheet
For Each rcell In ActiveSheet.Cells
If recll.Column >= 1 And rcell.Value = "x" Then
ActiveSheet.Cells(rcell.Row, rcell.Column - 1).Value = ActiveSheet.Cells(rcell.Row, rcell.Column).Value
End If
Next
but this one would presumably take a really long time. Ideally you should use nested loops if you know the last row and column you are using. This way you only iterate of cells that you believe contain data.

Paste N Times Based on Column Value Isn't Working

Thanks to a previous post and some excellent direction I was able to create successfully working code for my needs. However, the table/data has changed a bit and I have not yet reached my ultimate goal of iterating the paste function based on another cell's value. I have seen this post on SO but it has not helped me. I found other resources on using loops in Excel VBA and created the very simple Do While code you see below.
My problem: I cannot get the code to stop looping when it reaches the value from Column F. It finds the first available case and outputs the correct information endlessly without stopping at n value and moving on.
Here is a look at the data table:
A B C D E F
R1 Name Num Status #Orig #Act #Rem
R2 ABC 032 Complete 22 0 11
R3 LMN 035 In Prog 25 21 4
Here is my code:
Sub Copy_Pending_Status()
Dim srcrange As Range
Dim wb As Workbook
Dim ws1 As Worksheet, ws2 As Worksheet
Dim i As Integer
Set wb = ActiveWorkbook
Set ws1 = wb.Worksheets("SIS Agregate")
Set ws2 = wb.Worksheets("Center Detail")
Set srcrange = ws2.Range("C2:C61")
For Each Row In srcrange.Cells
Select Case Row.Value
Case "In Progress"
Do While i <= Row.Offset(0, 3).Value
Set LastCell = ws1.Cells(ws1.Rows.Count, "C").End(xlUp).Offset(1)
LastCell.Offset(0, 0).Value = Row.Offset(0, -2).Value
LastCell.Offset(0, 12).Value = Row.Offset(0, -1).Value
LastCell.Offset(0, 4).Value = "Not Yet Scanned"
Loop
Case "Complete"
Do While i <= Row.Offset(0, 3).Value
Set LastCell = ws1.Cells(ws1.Rows.Count, "C").End(xlUp).Offset(1)
LastCell.Offset(0, 0).Value = Row.Offset(0, -2).Value
LastCell.Offset(0, 12).Value = Row.Offset(0, -1).Value
LastCell.Offset(0, 4).Value = "Purged"
Loop
End Select
Next Row
End Sub
I have tried setting the Do While i <= Row.Offset(0, 3).Value to (0,3) (0,4) and (0,6) as I am not sure if it is counting the number of columns from "A" as one, "C" as one, or "C" as zero and offset is three (to the right).
I want to note that eliminating the Do While and Loop lines of code from each case make this function correctly, but only once per row, per case - as opposed to however many times Column F indicates.
Have I oversimplified this and am missing some obvious key information? What change do I need to make to get this little loop to function correctly?
Nothing within your loops changes the value of i, and so the "Do While" conditions are always met. You need to iterate through values of i so that eventually the conditions will not be met. At the end of each loop, add:
i = i + 1
This will allow it to count how many times the loop has been run and stop after a set number of iterations based on Column F.
You'll also want to re-set i to zero in between each row. Do this between "End Select" and "Next Row":
End Select
i = 0
Next Row

Resources