VBA code optimization - excel

I have a set of VBA codes which work really perfectly with around of 20 000 x 16 cells.
However, I need to use the codes with max 80 000 x 16 cells.
I have identified two types of codes which run really slow:
c = 2 'this is the first row where your data will output
d = 2 'this is the first row where you want to check for data
Application.ScreenUpdating = False
Do Until Range("A" & c) = "" 'This will loop until column U is empty, set the column to whatever you want
'but it cannot have blanks in it, or it will stop looping. Choose a column that is
'always going to have data in it.
ws1.Range("U" & d).FormulaR1C1 = "=RC[-20] & RIGHT(""0000"" & RC[-14], 6)"
c = c + 1 'Advances a and b when there is a matching case
d = d + 1
Loop
Application.ScreenUpdating = True
End Sub
Sub OpenValue()
Dim l As Integer
Dim k As Integer
Dim m As Integer
m = Sheets("Input").Range("AC:AC").End(xlDown).Row
For l = 2 To m
If Range("AC" & l) = "Delievered" Then
Range("AD" & l) = 0
ElseIf Range("AC" & l) = "Cancelled" Then
Range("AD" & l) = 0
Else
Range("AD" & l) = Val(Range("Z" & l)) * Val(Range("J" & l))
End If
Next
End Sub
What can I do to poptimize them ....

The link provided by #GSerg is an awesome way to cut the running time of your script down. I found myself using:
Application.ScreenUpdating set to False
Application.Calculation set to xlCalculationManual
Application.EnableEvents set to False
Application.DisplayAlerts set to False
so often that I combined them into a single public subroutine. #Garys-Student provided the inspiration:
'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
'INPUT : True or False (i.e. fast or slow)
'DESCRIPTION : this sub turns off screen updating and alarms then
' sets calculations to manual
'
Public Sub GoFast(OnOrOff As Boolean)
Dim CalcMode As XlCalculation
CalcMode = Application.Calculation
With Application
.ScreenUpdating = Not OnOrOff
.EnableEvents = Not OnOrOff
.DisplayAlerts = Not OnOrOff
If OnOrOff Then
.Calculation = xlCalculationManual
Else
.Calculation = CalcMode
End If
End With
End Sub
In practice, you can now add the one-liner:
Call GoFast(True)
at the beginning of your script as part of the setup, then add:
Call GoFast(False)
at the end of your script as part of the teardown. Modify as you see fit!

The Do Until can be replaced with a one liner:
ws1.Range("A2", ws1.Range("A2").End(xlDown)).Offset(0,20).FormulaR1C1 = _
"=RC[-20] & RIGHT(""0000"" & RC[-14], 6)"
Note that this will fail if A3 is empty. If you have headers in row 1 you can change the second A2 to A1.
For the other Sub I'm not sure if you are doing something special with Val but if not you could change it to something similar:
Sub OpenValue()
Dim r As Range
Set r = Sheets("Input").Range("AD2:AD" & Sheets("Input").Range("AC1").End(xlDown).Row)
With r
.FormulaR1C1 = "=IF(OR(RC[-1]=""Delivered"",RC[-1]=""Cancelled""),0,RC10*RC26"
'If you want these as values uncomment the following lines
'.Calculate
'.Copy
'.PasteSpecial xlPasteValues
End With
End Sub
Sprinkle Application stuff around if needed (Calculation, ScreenUpdating, DisplayAlerts, EnableEvents).
Why is this faster:
To put it simply, VBA and Excel have to open a 'channel' to communicate between each other and this costs some time. So looping through a Range and adding formulas one-by-one is much slower for large ranges than doing it all at once since you'll only open the 'channel' once.

Related

Splitting column on multiple spaces

I have a bunch of entries in a spreadsheet which I want to split into two different columns.
The data looks something like this:
102483 STEIN LOKK B4-702
102482 STEIN LOKK BF-701
102413 RINGFUGEKULL 352X353X214 POS 2 Å1
102412 RINGFUGEKULL 352X353X135 POS 1 ÅI
102388 STEIN ISOLER MOSCONI MSB-475 500x250x 76
102387 STEIN ISOLER MOSCONI MSB-475 500x250x152
102384 OVNSFUNDAMENT CRADLE
102383 STEIN PLATE HA-040 KVAL,HSU95
102382 STEIN PLATE HA-039 KVAL,HSU95
102376 OLJE SYNT. MITRA 220
102341 KULL BUNN ÅI/ÅIIC D 3365 x 550 x 490
102291 OLJE 10W-40 HAVOLINE FORMULA 3 DIESEL
102241 FETT MINERALSK PATRON STARPLEX EP 2
102231 OLJE FYRINGSOLJE NR.1 (F)
102211 CALDE SRRIX 14
102141 STEIN ISOLER AAM HIPOR 230X114X 76
102103 STAMPEMASSE ILDFAST AL-85-F
102102 STEIN BORGESTAD INSULATING FIREBRICKS
102101 STAMPEMASSE TYPE T-JUSTERT ELKEM
101964 PAKNING LEX THERMOSEAL PGF-1 LEX Ø12mm
101939 BOKS KOMPENSASJON F/OVN 4 OG 4B 1170
The delimiter is the bunch of spaces between the product number and name.
Trying to use Excel's text to columns function, there doesn't seem to be a way to specify more than one character as a delimiter, and if I only use one space it creates issues with splitting up the product name as well.
I wrote a small macro to do it for me (see below), but I feel like I may be making things overly complicated. Is there some simpler way to do this? Are there any obvious ways my approach can fail? I am not overly familiar with regexes, so I'm not sure if the pattern I've chosen is the best...
Sub split_column()
Dim ws As Worksheet
Dim regexp As Object
Dim reMatches As Object
Dim c As Range
Call deaktiver
Set regexp = CreateObject("VBScript.RegExp")
Set ws = År_2017
With regexp
.Global = False
.MultiLine = False
.IgnoreCase = False
.Pattern = "^(\d+)\s{2,}(.+)$"
End With
For Each c In ws.Range("A2:A" & ws.Range("A2").End(xlDown).Row)
Set reMatches = regexp.Execute(c.Value2)
If reMatches.Count > 0 Then
c = Trim(reMatches(0).SubMatches(0))
c.Offset(0, 1) = Trim(reMatches(0).SubMatches(1))
End If
Next c
Call reaktiver
End Sub
Private Sub deaktiver()
Application.EnableEvents = False
Application.ScreenUpdating = False
Application.DisplayStatusBar = False
Application.Calculation = xlCalculationManual
End Sub
Private Sub reaktiver()
Application.EnableEvents = True
Application.ScreenUpdating = True
Application.DisplayStatusBar = True
Application.Calculation = xlCalculationAutomatic
End Sub
Your data has a nice fixed format. Rather than TextToColumns or VBA, in cell B1 enter:
=LEFT(A1,6)
and in C1 use:
=MID(A1,20,99)
EDIT#1:
For non-regular data, use:
=LEFT(A1,FIND(" ",A1)-1)
=MID(TRIM(A1),FIND(" ",TRIM(A1))+1,99)
for B1 and C1 respectively.
EDIT#2:
Siddharth has a good point. It is better to use:
=MID(TRIM(A1),FIND(" ",TRIM(A1))+1,LEN(A1))
rather than:
=MID(TRIM(A1),FIND(" ",TRIM(A1))+1,99)
Yes, there is much simpler... and faster.
Dim input_, output_, i&
input_ = ws.Range("A2:A" & ws.Range("A2").End(xlDown).Row).Value2
ReDim output_(LBound(input_) To UBound(input_), 1 To 2)
For i = LBound(input_) To UBound(input_)
output_(i, 1) = Split(input_(i, 1), " ")(0)
output_(i, 2) = Split(input_(i, 1), " ")(1)
Next
ws.Range("A2:B" & ws.Range("A2").End(xlDown).Row) = output_

Be more efficient in VBA coding --> efficient VBA looping

could you assist me in writing the code below more efficiently please? I am working on a Master List, where I am copying data from various sources for each month into the columns Z, AC, AF, AI etc. (always separated by 2 columns). Then I copy that cell all the way down to update the values for each row. As you can see in the code below, the only difference from one section of the code to the next is:
Change column (here Z to AC)
Change paths which are stored in different cells (e.g. fromPath changed to fromPath2.
How can I make it more efficient? Any idea would be greatly appreciated.
Take care
' Update Jan 2018
fromPath = Sheets("Filepaths for P25 2017").Range("G2")
vbaPath = Sheets("Filepaths for P25 2017").Range("F2")
vbaFile = Sheets("Filepaths for P25 2017").Range("H2")
Orderlist2017 = Sheets("Filepaths for P25 2017").Range("I2")
With ThisWorkbook.Sheets("Orderlist P25 2017")
Range("Z10").Formula = "=VLookup(C10, '" & vbaPath & vbaFile & Orderlist2017 & "'!C14:Z90, 8, False)"
Range("Z10").Select
Selection.Copy
Range("Y10").Select
Selection.End(xlDown).Select
Range("Z85").Select
Range(Selection, Selection.End(xlUp)).Select
ActiveSheet.Paste
Application.CutCopyMode = False
End With
' Update Feb 2018
fromPath2 = Sheets("Filepaths for P25 2017").Range("G3")
vbaPath2 = Sheets("Filepaths for P25 2017").Range("F3")
vbaFile2 = Sheets("Filepaths for P25 2017").Range("H3")
Orderlist2017 = Sheets("Filepaths for P25 2017").Range("I3")
With ThisWorkbook.Sheets("Orderlist P25 2017")
Range("AC10").Formula = "=VLookup(C10, '" & vbaPath2 & vbaFile2 & Orderlist2017 & "'!C14:Z90, 8, False)"
Range("AC10").Select
Selection.Copy
Range("Y10").Select
Selection.End(xlDown).Select
Range("AC85").Select
Range(Selection, Selection.End(xlUp)).Select
ActiveSheet.Paste
Application.CutCopyMode = False
End With
In my experience, the most effective way to speed up most macros is to turn off screen updating. Also, turning off automatic calculation can help a lot, if your worksheet contains a lot of formulas. I created a method to do this, called "FastMode" which I use in every VBA project I create. At the top of your macro, call it with the parameter set to "True" to make your code run fast, then at the end, call it with "False" to restore the default Excel settings.
Public Sub FastMode(ByVal blnMode As Boolean)
'set workbook to fast mode (or back to normal mode) to speed up any process
'that modifies the worksheets
On Error Resume Next
With Application
Select Case blnMode
Case True
.ScreenUpdating = False
.Calculation = xlCalculationManual
Case False
.ScreenUpdating = True
.Calculation = xlCalculationAutomatic
End Select
End With
End Sub
Try something like this. Untested, but should get you started.
Sub TT()
Dim fromPath, vbaPath, vbaFile, Orderlist2017
Dim shtPaths As Worksheet, shtOrders As Worksheet
Dim i As Long, rngFormula
Set shtPaths = Sheets("Filepaths for P25 2017") 'ThisWorkbook?
Set shtOrders = ThisWorkbook.Sheets("Orderlist P25 2017")
Set rngFormula = shtOrders.Range("Z10") '<< first vlookup goes here
For i = 1 To 12 'for example...
fromPath = shtPaths.Range("G2").Offset(i - 1, 0).Value
vbaPath = shtPaths.Range("F2").Offset(i - 1, 0).Value
vbaFile = shtPaths.Range("H2").Offset(i - 1, 0).Value
Orderlist2017 = shtPaths.Range("I2").Offset(i - 1, 0).Value
'you can assign the formula directly to the required range
' (exactly what you want here is not clear from your posted code...)
rngFormula.Resize(76, 1).Formula = "=VLookup(C10, '" & vbaPath & vbaFile & Orderlist2017 & "'!C$14:Z$90, 8, False)"
Set rngFormula = rngFormula.Offset(0, 2) 'move over two columns
Next i
End Sub

Read Wildcard As Asterisk

I had a piece of code commissioned earlier this week (cheaper to have an expert write it than for me to spend a week trying to!). However, when putting it use I've hit a bit of a snag.
The macro looks at a name on one excel worksheet, matches it to a list of names and associated ID numbers on a different worksheet, then inserts the ID on the first worksheet. This was all fine until I started using it on real data.
Here's some sample data (all of this information is in one cell...):
WARHOL*, Andy
PETO, John F
D3 GRECO, Emilio -20th C
HASELTINE, William Stanley
D3 DALI, Salvador
D3 SOSNO, Sacha
WEGMAN**, WILLIAM
One asterisk means it's a print, two a photograph, D3 a sculpture, and nothing a painting.
When I run the code with this data, it sees * as a wildcard, and so will always insert the ID of the first variation of the artist in the sheet. What I need is a way for the macro to not read it as a wildcard.
I did some research, and found that inserting ~ before * negates the wildcard properties. How would I make my code do this? I've discovered the main issue of having code written by someone else... You might not understand it!
Here is the code:
Public Sub match_data()
'ctrl+r
On Error GoTo errh
Application.ScreenUpdating = False
Application.Calculation = xlCalculationManual
Dim r1, r2, i, exc As Long
Dim fp As Range
Sheets("Data").Activate
r1 = Cells(Rows.Count, "B").End(xlUp).Row
r2 = Sheets("List").Cells(Sheets("List").Rows.Count, "B").End(xlUp).Row
'MsgBox r1 & r2
exc = 0
For i = 2 To r1
If Range("B" & i).Value <> "" Then
With Sheets("List").Range("B2:B" & r2)
Set fp = .Find(Range("B" & i).Value, LookIn:=xlValues, lookat:=xlWhole)
If Not fp Is Nothing Then
Range("B" & i).Interior.Color = xlNone
Range("A" & i).Value = Sheets("List").Range("A" & fp.Row).Value
Else
Range("B" & i).Interior.Color = xlNone
Range("B" & i).Interior.Color = vbYellow
exc = exc + 1
End If
End With
End If
Next i
MsgBox "There are " & exc & " exceptions."
errh:
If Err.Number > 0 Then
MsgBox Err.Description
End If
Application.ScreenUpdating = True
Application.Calculation = xlCalculationAutomatic
End Sub
Oh also, I would need to do this for the list of names and ID's wouldn't I? If so, that only needs doing once, so if you could give me a hint about that too, I'd be so grateful!
Thanks!
PS I know the system we are using at the moment absolutely sucks (definitely not 3rd form normalised!), but we are fast running out of time and money, and need to get our product up and running ASAP!
EDIT: To clarify, here is a pic of the spreadsheets I'm working with... Obviously in cells A14 and A15 I wanted the ID numbers 11 & 12 respectively
Here is one way to tell the stars from the planets:
Sub StaryNight()
Dim s As String, OneStar As String, TwoStar As String, ThreeStar As String
OneStar = "*"
TwoStar = "**"
ThreeStar = "***"
t = Range("A1").Text
ary = Split(t, ",")
s = ary(0)
If Right(s, 3) = ThreeStar Then
MsgBox "scupture"
Exit Sub
ElseIf Right(s, 2) = TwoStar Then
MsgBox "photograph"
Exit Sub
ElseIf Right(s, 1) = OneStar Then
MsgBox "print"
Exit Sub
End If
MsgBox "Painting"
End Sub
Okay, I have solved the problem! I had a play around with changing the variables in the Find and Replace box.
If I put ~* in both the find AND replace boxes, and uncheck Match entire cell contents, I can replace all of the * with ~* (really don't understand that but oh well!)
So I do this on the Data worksheet, but NOT on the List worksheet, run the macro as normal and the problem is solved!

Reduce Macro Time

I have been worked on a VBA code for past couple of days and everything seems to be working fine till one fine day when I added the below code to it. It marco executed time increased to such an extent that I myself don't when it is going to complete. I have waited for almost 2 hours but it continues to run.
This datasheet that I have is about 15 MB in size and contains around 47,000 rows with 25 columns filled with data. I have running this code to delete rows basis the multiple criteria on Columns "H".
Here is the code. Any help to reduce the runtime is highly appreciated.
Thanks...
Application.ScreenUpdating = False
Application.EnableEvents = False
Application.Calculation = xlCalculationManual
Application.DisplayStatusBar = False
Workbooks("Vivar_Template_Blank.xlsx").Sheets("Main & PCO Working").Activate
Dim ws As Worksheet, i&, lastRow&, value$
Set ws = ActiveWorkbook.ActiveSheet
lastRow = ws.Range("H" & ws.Rows.Count).End(xlUp).Row
For i = lastRow5 To 2 Step -1
value = ws.Cells(i, 8).value
If Not (value Like "*Supplier Name*" _
Or value Like "*[PO]Supplier (Common Supplier)*" _
Or value Like "*ACCENTURE LLP*" _
Or value Like "*COGNIZANT TECHNOLOGY SOLUTIONS US CORP*" _
Or value Like "*INFOSYS LIMITED*" _
Or value Like "*INFOSYS TECHNOLOGIES LTD*" _
Or value Like "*INTERNATIONAL BUSINESS MACHINES CORP DBA IBM CORP*" _
Or value Like "*MINDTREE LIMITED*" _
Or value Like "*SYNTEL INC*" _
Or value Like "*TATA AMERICA INTERNATIONAL CORPORATION*") _
Then
ws.Rows(i).Delete
End If
Next
Application.EnableEvents = True
Application.ScreenUpdating = True
Application.Calculation = xlCalculationAutomatic
Application.DisplayStatusBar = True
Or is not short-circuited so each Like expression will be executed, an alternative to halt on the first match (you don't actually need Like in this case, you can use the more efficient InStr):
Dim lookup(9) As String
lookup(0) = "Supplier Name"
lookup(1) = "[PO]Supplier (Common Supplier)"
lookup(2) = "ACCENTURE LLP"
lookup(3) = "COGNIZANT TECHNOLOGY SOLUTIONS US CORP"
lookup(4) = "INFOSYS LIMITED"
lookup(5) = "INFOSYS TECHNOLOGIES LTD"
lookup(6) = "INTERNATIONAL BUSINESS MACHINES CORP DBA IBM CORP"
lookup(7) = "MINDTREE LIMITED"
lookup(8) = "SYNTEL INC"
lookup(9) = "TATA AMERICA INTERNATIONAL CORPORATION"
For i = lastRow5 To 2 Step -1
value = ws.Cells(i, 8).value
For j = 0 To UBound(lookup)
If InStr(Value, lookup(j)) Then
ws.Rows(i).Delete
Exit For
End If
Next
Next
If any values are empty or there is a large distribution of a constant non-matching value, you should check and exclude them first.
Deleting Rows (Row by Row) is slow , try to use Union and delete all Rows by one time.
Application.ScreenUpdating = False
Application.EnableEvents = False
Application.Calculation = xlCalculationManual
Application.DisplayStatusBar = False
Workbooks("Vivar_Template_Blank.xlsx").Sheets("Main & PCO Working").Activate
Dim ws As Worksheet, i&, lastRow&, value$
Dim uRng As Range
Set ws = ActiveWorkbook.ActiveSheet
lastRow = ws.Range("H" & ws.Rows.Count).End(xlUp).Row
For i = lastRow5 To 2 Step -1 ' !!! maybe lastRow not lastRow5 because there is no value for lastRow5 in your code!!!
value = ws.Cells(i, 8).value
If Not (value Like "*Supplier Name*" _
Or value Like "*[PO]Supplier (Common Supplier)*" _
Or value Like "*ACCENTURE LLP*" _
Or value Like "*COGNIZANT TECHNOLOGY SOLUTIONS US CORP*" _
Or value Like "*INFOSYS LIMITED*" _
Or value Like "*INFOSYS TECHNOLOGIES LTD*" _
Or value Like "*INTERNATIONAL BUSINESS MACHINES CORP DBA IBM CORP*" _
Or value Like "*MINDTREE LIMITED*" _
Or value Like "*SYNTEL INC*" _
Or value Like "*TATA AMERICA INTERNATIONAL CORPORATION*") _
Then
'ws.Rows(i).Delete
If uRng Is Nothing Then
Set uRng = ws.Rows(i)
Else
Set uRng = Union(uRng, ws.Rows(i))
End If
End If
Next
If Not uRng Is Nothing Then uRng.Delete
Application.EnableEvents = True
Application.ScreenUpdating = True
Application.Calculation = xlCalculationAutomatic
Application.DisplayStatusBar = True
You could build a set of nested if/else constructs such that your logic terminates when the first true condition is encountered.
If Not (value Like "*Supplier Name*") then
ws.Rows(i).Delete
else if value Like "*[PO]Supplier (Common Supplier)*" then
ws.Rows(i).Delete
else if ...
End If
After you do this, another level of optimization would be to order the 'if' statements from most prevalent to least, thereby reducing the number of expected comparisons.

Excel. List every variation of a value range

I'm trying to get Excel to list every variation of a certain value.
If A1= ABC1904
& A2= ABC1910
I'd like column B to list.
ABC1904
ABC1905
ABC1906
ABC1907
ABC1908
ABC1909
ABC1910
This is the best I could do w/ a purely formula solution:
=LEFT(A$1,3) & MID(A$1,MIN(FIND({0,1,2,3,4,5,6,7,8,9},A$1&"0123456789")),LEN(A$1))+MIN(RIGHT($A$2,1)+0,ROWS(A$1:A1)-1)
It leaves a bit to be desired because you'll have a bunch of duplicates if you drag the formula too far down.
If you're not opposed to a VBA solution, you could give this a go:
Sub VariationOfValue()
Dim startNumber As Long, _
endNumber As Long, _
counter As Long
Dim leadingString As String
With Application
.ScreenUpdating = False
.Calculation = xlCalculationManual
End With
counter = 1
With Sheet1
leadingString = Left(Sheet1.Range("A1").Value, 3)
startNumber = Evaluate("=MID(A$1,MIN(FIND({0,1,2,3,4,5,6,7,8,9},A$1&""0123456789"")),LEN(A$1))") + 0
endNumber = Evaluate("=MID(A$2,MIN(FIND({0,1,2,3,4,5,6,7,8,9},A$2&""0123456789"")),LEN(A$2))") + 0
Do While startNumber <= endNumber
.Range("B" & counter).Value = leadingString & startNumber
counter = counter + 1
startNumber = startNumber + 1
Loop
End With
With Application
.ScreenUpdating = True
.Calculation = xlCalculationAutomatic
End With
End Sub
Does basically the same thing, but only until the last number is reached. Either way, hope one or both of these helps out a bit.
If you are willing to have a few columns to achieve this then it is really not very difficult.
Col A is your starting data. [ABC1904 and ABC1910}
Col B contains equations =VALUE(RIGHT(A1,LEN(A1)-3)) which gives the numeric part of the strings. [1904 and 1910]
Col C contains equations =IF(OR(C2=B$2,C2=""),"",C2+1) - except C1 which is just =b1
- this gives the series of numbers you want {1904 to 1910]
Col D contains equations =IF(C1="","",LEFT(A$1,3)&C1)
- this puts the text back on the front of the numbers [ABC1904 to ABC1910]
..this would be clearer with a screenshot but I apparently do not have enough reputation to post one

Resources