I have a workbook where I want to find the differences of two sheets by looking at the company name and their corporate registration number and then type the differences on the third sheet.
I have tried the code in another workbook with only 143 rows, which works perfectly, but when I try it on the real workbook with 10,000 rows I get a "type mismatch error". Also if I use other columns than the CVR and Firm columns the code also works.
The CVR is numbers and Firms are strings (firm names). I get the
type mismatch error
on the line I marked **. Does somebody know why I get this error?
Sub ComCVR()
Dim CVR1()
Dim CVR2()
Dim Firm1()
Dim Firm2()
Dim n As Long, m As Long
Dim i As Double, j As Double
Dim intCurRow1 As Integer, intCurRow2 As Integer
Dim rng As Range, rng1 As Range
Set rng = ThisWorkbook.Sheets("Last month").Range("A11")
Set rng1 = ThisWorkbook.Sheets("Current month").Range("A11")
n = rng.CurrentRegion.Rows.Count
m = rng1.CurrentRegion.Rows.Count
ReDim CVR1(n)
ReDim Firm1(n)
ReDim CVR2(m)
ReDim Firm2(m)
ThisWorkbook.Sheets("CVR").Range("A1") = "Flyttet CVR"
ThisWorkbook.Sheets("CVR").Range("B1") = "Flyttet Firmanavn"
ThisWorkbook.Sheets("CVR").Range("A1:B1").Interior.ColorIndex = 3
ThisWorkbook.Sheets("CVR").Range("C1") = "Nye CVR"
ThisWorkbook.Sheets("CVR").Range("D1") = "Nye Firmanavn"
ThisWorkbook.Sheets("CVR").Range("C1:D1").Interior.ColorIndex = 4
ThisWorkbook.Sheets("CVR").Range("A1:D1").Font.Bold = True
' Inset data to arrays
For i = 0 To n
CVR1(i) = ThisWorkbook.Sheets("Last month").Cells(12 + i, 5)
Firm1(i) = ThisWorkbook.Sheets("Last month").Cells(12 + i, 4)
Next
For i = 0 To m
CVR2(i) = ThisWorkbook.Sheets("Current month").Cells(12 + i, 5)
Firm2(i) = ThisWorkbook.Sheets("Current month").Cells(12 + i, 4)
Next
intCurRow1 = 2
intCurRow2 = 2
'Old
For i = 0 To n
For j = 0 To m
If Firm1(i) = ThisWorkbook.Sheets("Current month").Cells(12 + j, 4) Then '** Error raised here
Exit For
End If
If j = m Then
ThisWorkbook.Sheets("CVR").Cells(intCurRow1, 1) = CVR1(i)
ThisWorkbook.Sheets("CVR").Cells(intCurRow1, 2) = Firm1(i)
intCurRow1 = intCurRow1 + 1
End If
Next j
Next i
'new
For i = 0 To m
For j = 0 To n
If Firm2(i) = ThisWorkbook.Sheets("Last month").Cells(12 + j, 4) Then
Exit For
End If
If j = n Then
ThisWorkbook.Sheets("CVR").Cells(intCurRow2, 3) = CVR2(i)
ThisWorkbook.Sheets("CVR").Cells(intCurRow2, 4) = Firm2(i)
intCurRow2 = intCurRow2 + 1
End If
Next j
Next i
Columns("A:B").Select
ActiveSheet.Range("$A:$B").RemoveDuplicates Columns:=1, Header:=xlNo
Application.DisplayAlerts = False
Columns("C:D").Select
ActiveSheet.Range("$C:$D").RemoveDuplicates Columns:=1, Header:=xlNo
Application.DisplayAlerts = False
End Sub
Whenever an error happens, the best way is to google it. This is what it says in the documentation of VBA for Type mismatch:
Cause: The variable or property isn't of the correct type. For example, a variable that requires an integer value can't accept a string value unless the whole string can be recognized as an integer.
In the case of the code, it happens, when an array is compared with excel cell. Now the trick - in order to see why it happens, see what is in these:
Debug.Print ThisWorkbook.Sheets("Last month").Cells(12 + i, 4)
Debug.Print Firm1(i)
and the after the error runs, take a look at the immediate window (Ctrl+G). It it quite possible, that there is an error in the excel cell, thus it cannot be compared. This is some easy way to avoid it, if this is the case:
Sub TestMe()
Dim myRange As Range
Set myRange = Worksheets(1).Cells(1, 1)
myRange.Formula = "=0/0"
If Not IsError(myRange) Then
Debug.Print CBool(myRange = 2)
Else
Debug.Print myRange.Address; " is error!"
End If
End Sub
Related
I want to start second for from next current first for counter I run this code and this error prevent to run code.
ERROR : type mismatch
This code should show shortest distance cells as when find next cell(short distance) this cell should remove from search
also I want to return the address(ROW NUMBER) of next cell(shortest distance)
`Sub distance()
Dim j, i As Integer, ws As Worksheet
Set ws = Worksheets("activesheet")
For i = 2 To 87
For j = i+1 To 87
If j <> i Then
Worksheets("activesheet").Cells(j, 11).Value = Sqr(((Worksheets("activesheet").Cells(i,
8).Value) - (Worksheets("activesheet").Cells(j, 8).Value)) ^ 2 +
((Worksheets("activesheet").Cells(i, 9).Value) - (Worksheets("activesheet").Cells(j,
9).Value)) ^ 2)
Next j
ws.Range("l" & i) = Application.WorksheetFunction.Small(ws.Range("k2:k87"), 1)
Next i
End Sub`
I have reformatted your code so I could read it, it would not compile because you were missing an End If (as indicated by a comment in the formatted code below). The code runs fine on a test workbook I have just created. However of course I don't know what source data you have.
Sub distance()
Dim j, i As Integer, ws As Worksheet
Set ws = Worksheets("activesheet")
For i = 2 To 87
For j = i + 1 To 87
If j <> i Then
Worksheets("activesheet").Cells(j, 11).Value = Sqr(((Worksheets("activesheet").Cells(i, 8).Value) - (Worksheets("activesheet").Cells(j, 8).Value)) ^ 2 + ((Worksheets("activesheet").Cells(i, 9).Value) - (Worksheets("activesheet").Cells(j, 9).Value)) ^ 2)
End If 'this was missing
Next j
ws.Range("l" & i) = Application.WorksheetFunction.Small(ws.Range("k2:k87"), 1)
Next i
End Sub
Given that this runs okay, I recommend checking the data being used from the worksheet on this line
Worksheets("activesheet").Cells(j, 11).Value = Sqr(((Worksheets("activesheet").Cells(i, 8).Value) - (Worksheets("activesheet").Cells(j, 8).Value)) ^ 2 + ((Worksheets("activesheet").Cells(i, 9).Value) - (Worksheets("activesheet").Cells(j, 9).Value)) ^ 2)
and this line
ws.Range("l" & i) = Application.WorksheetFunction.Small(ws.Range("k2:k87"), 1)
and make sure you are getting data of the correct type. Do you have any words or letters or non numeric data of any kind being used?
To demonstrate the problem, below I am trying to divide the variable A (a string) by the variable B (an integer) and get a type mismatch error as a result.
Sub test()
Dim a As String
a = "test"
Dim b As Integer
b = 1
Debug.Print a / b
End Sub
I got an initial code but it's not working correctly, if you guys have any suggestions on how to achieve it and make the code better (cleaner/faster) I would really appreciate it.
Sub CountByError()
Dim rangeArr() As Variant
Dim xcharFlag As Boolean
Dim tester2 As Worksheet
Set tester2 = Worksheets("tester2")
rangeArr = Worksheets("tester").Range("a2").Resize(3169, 30).Value2
Dim i As Long, j As Long
For i = 1 To 29
Select Case i
Case 1
For j = 1 To 3168
xcharFlag = False
For k = 1 To Len(rangeArr(j, i))
If Not Mid(Len(rangeArr(j, i)), k, 1) Like "[a-zA-Z0-9-]" Then
xcharFlag = True
If xcharFlag = True Then Exit For
End If
Next k
If xcharFlag = True Then
tester2.Range("d4") = tester2.Range("d4") + 1
End If
Next j
End Select
Next i
Worksheets("tester").Range("a2").Resize(3169, 30).Value2 = rangeArr
End Sub
It's always good to split the code into smaller pieces. In your case, I would suggest you move the check if a string contains invalid characters into a boolean function. That makes it much easier to test and debug.
Function containsInvalidChar(ByVal s As String) As Boolean
Dim k As Long
For k = 1 To Len(s)
If Not Mid(s, k, 1) Like "[a-zA-Z0-9-]" Then
containsInvalidChar = True
Exit Function
End If
Next k
containsInvalidChar = False
End Function
Now open the immediate window and enter something like (the TRUE and FALSE is the response).
? containsInvalidChar("ABC")
FALSE
? containsInvalidChar("12-34 56")
TRUE
? containsInvalidChar(ActiveCell)
FALSE
Once you are rather sure that the function works as expected, remove the code from your nested loops and replace it with a simple call to the function:
(...)
For j = 1 To 3168
If containsInvalidChar(rangeArr(j, i)) then
tester2.Range("d4") = tester2.Range("d4") + 1
End If
Next j
By this, you separate the logic how to identify an invalid string from the logic of how to deal with that situation. You could easily change the function to use regular expressions instead of the like (which probably would increase execution speed) without touching the rest of the code, or you could reuse the function to mark invalid words with a different color (could even be used as function in conditional formatting).
Your current check, by the way, has a superfluent Len( in the check.
This is how it should work
Sub CountByError()
Dim rangeArr() As Variant
Dim tester2 As Worksheet
Dim i As Long, j As Long, k As Long
Set tester2 = Worksheets("tester2")
rangeArr = Worksheets("tester").Range("a2").Resize(3169, 30).Value2
tester2.Range("d4") = 0
For i = LBound(rangeArr, 1) To UBound(rangeArr, 1)
For j = LBound(rangeArr, 2) To UBound(rangeArr, 2)
For k = 1 To Len(rangeArr(i, j))
If Mid(rangeArr(i, j), k, 1) Like "[!a-zA-Z0-9-]" Then
tester2.Range("d4") = tester2.Range("d4") + 1
Exit For
End If
Next k
Next j
Next i
End Sub
The macro may be slow and some changes to optimize the code may be useful.
I have a dynamic population of tasks that I receive monthly, I want to assign these tasks to 6 groups for them to assess in an evenly distributed fashion. Each task has a ranking/priority, so if a group receives the number 1 task, I don't want to also give that same group the top 100 priorities. I want to apply a snaking/zigzag distribution.
This led me down a path to using the formula =MIN(MOD(ROW()-2,12),MOD(-ROW()+1,12)). I get the distribution I'm looking for, although at this stage I don't know how to account for any criteria that I need to add into my logic.
In the above image I am trying to take the groups in column F and apply them to Column D. Column E shows the example of the =MOD() formula and I could just use a lookup to replace the Mod values of 0-5 with my groups 1-6.
Where I hit a roadblock is in row 21, which is where I want to account for some criteria, or exceptions. I added a binary column A for visualization, but essentially, I want to say where column C (task location) = Loc4 to never assign a task to Group 4. In the instance where I don't want a task to be assigned to Group 4 at Loc 4, I then hope to skip Group 4 for a single assignment until it can be applied to the next possible ranked task. The simple solution is to remove all these occurrences at the end, but it really skews the even distribution I'm going for.
I tried to apply solver to this assignment, looking for the lowest std deviation, but I have too many data points.
This has led me to another post using some vba logic, which I really like the concept, but I cannot figure out how to modify it to account for some exceptions. enter link description here
Ideally I would love to use this concept of creating a single array of my groups, applying each group to a task as long as the criteria is met in this short list of task, write the groups into the list, reset and move down to the next subset of tasks. So, each time I select the next 6 tasks, they are assigned to one of my 6 groups, which will keep the distribution that I'm hoping for.
Here is the code from the user K.Davis post which I was trying to apply:
Sub assignEmployeeTasks()
Dim ws As Worksheet, i As Long
Set ws = ThisWorkbook.Worksheets(1)
Dim employeeList() As Variant
With ws
For i = 2 To lastRow(ws, 2)
If (Not employeeList) = -1 Then
'rebuild employeelist / array uninitialized
employeeList = buildOneDimArr(ws, "F", 2, lastRow(ws, "F"))
End If
.Cells(i, 4) = randomEmployee(employeeList)
Next
End With
End Sub
These are the "support" functions that allow your program to do it's job:
Function randomEmployee(ByRef employeeList As Variant) As String
'Random # that will determine the employee chosen
Dim Lotto As Long
Lotto = randomNumber(LBound(employeeList), UBound(employeeList))
randomEmployee = employeeList(Lotto)
'Remove the employee from the original array before returning it to the sub
Dim retArr() As Variant, i&, x&, numRem&
numRem = UBound(employeeList) - 1
If numRem = -1 Then 'array is empty
Erase employeeList
Exit Function
End If
ReDim retArr(numRem)
For i = 0 To UBound(employeeList)
If i <> Lotto Then
retArr(x) = employeeList(i)
x = x + 1
End If
Next i
Erase employeeList
employeeList = retArr
End Function
' This will take your column of employees and place them in a 1-D array
Function buildOneDimArr(ByVal ws As Worksheet, ByVal Col As Variant, _
ByVal rowStart As Long, ByVal rowEnd As Long) As Variant()
Dim numElements As Long, i As Long, x As Long, retArr()
numElements = rowEnd - rowStart
ReDim retArr(numElements)
For i = rowStart To rowEnd
retArr(x) = ws.Cells(i, Col)
x = x + 1
Next i
buildOneDimArr = retArr
End Function
' This outputs a random number so you can randomly assign your employee
Function randomNumber(ByVal lngMin&, ByVal lngMax&) As Long
'Courtesy of https://stackoverflow.com/a/22628599/5781745
Randomize
randomNumber = Int((lngMax - lngMin + 1) * Rnd + lngMin)
End Function
' This gets the last row of any column you specify in the arguments
Function lastRow(ws As Worksheet, Col As Variant) As Long
lastRow = ws.Cells(ws.Rows.Count, Col).End(xlUp).Row
End Function
Any help would be much appreciated! I'm up for any path that gets closer to my desired solution, formulas or vba. Please let me know if you have any questions.
Thanks!
This program works on a block of tasks at a time where the block size is a multiple of the group count. I have SIZE=2 to give a block size of 12 as this gives more opportunity to resolve conflicts than 6 would. It works by initially assigning the tasks to a zigzap pattern and then validates this against the rules you define. These are in the module validLocn(). If the validation is OK the process moves down the sheet to the next block. If the validation fails the plan is shuffled by swapping 2 element randomly chosen and validation retried. This continues up the maximum number set by MAXTRY. If still not resolved the user can chose to retry again, ignore and move on or abort the process. I have tested it with 150,000 records and it took less than a minute, but my test data may not represent your real data. The results in a pivot table on cols B,C,D show even distribution and no tasks for Grp4 at Locn4.
Count of Task
Row Loc1 Loc2 Loc3 Loc4 Loc5 Loc6 Gand Total
Gp1 4013 3975 3926 5082 3986 4018 25000
Gp2 4021 3992 4077 4928 3975 4007 25000
Gp3 3976 3952 4027 5023 4049 3973 25000
Gp4 5050 4915 4936 5035 5064 25000
Gp5 4072 3996 4034 4890 3969 4039 25000
Gp6 3964 4087 3986 5018 3996 3949 25000
Grand 25096 24917 24986 24941 25010 25050 150000
Hope that helps.
Option Explicit
Sub assignEmployeeTasks()
Dim ws As Worksheet, t0 As Single, t1 As Single
Set ws = ThisWorkbook.Sheets("Sheet1")
t0 = Timer
Const COL_GROUP = "F"
Const COL_LOCN = "C"
Const SIZE As Integer = 2 ' plan size = 2 * group count
Const MAXTRY = 50 ' no of tries to validate
Dim bOK As Boolean
Dim grp As Variant, iBlockStart As Long, i As Integer, r As Integer, step As Integer
'initialize grps and location
Dim countGrp As Integer, lastLocn As Long
lastLocn = ws.Range(COL_LOCN & Rows.Count).End(xlUp).Row
countGrp = ws.Range(COL_GROUP & Rows.Count).End(xlUp).Row - 1
grp = ws.Range(COL_GROUP & "2").Resize(countGrp, 1).Value
Dim plan() As String
ReDim plan(countGrp * SIZE, 2)
Dim itry As Integer, res
iBlockStart = 1
Do While iBlockStart < lastLocn
' initialize plan
Call zigzag(plan, grp)
For i = 1 To UBound(plan)
plan(i, 1) = ws.Range("C" & iBlockStart + i).Value
Next
' save 1st attempt
For i = 1 To UBound(plan)
ws.Range("D" & iBlockStart + i).Value = plan(i, 2)
Next
' validate
bOK = validLocn(plan, 0)
retry:
' retry to validate
itry = 0
While bOK = False And itry < MAXTRY
Call shuffle(plan, 1)
bOK = validLocn(plan, itry)
itry = itry + 1
Wend
' write new plan to sheet
For i = 1 To UBound(plan)
ws.Range("D" & iBlockStart + i).Value = plan(i, 2)
Next
' check rule again
If itry = MAXTRY Then
ws.Range(COL_LOCN & iBlockStart).Select
res = MsgBox("Failed to vaidate after " & MAXTRY & " attempts", vbAbortRetryIgnore, iBlockStart)
If res = vbRetry Then GoTo retry
If res = vbAbort Then Exit Sub
End If
iBlockStart = iBlockStart + UBound(plan)
Loop
t1 = Timer
MsgBox "Assigned " & lastLocn - 1 & " tasks in " & Int(t1 - t0) & " secs"
End Sub
' valid plan against rules
Function validLocn(plan As Variant, itry) As Boolean
Dim sLocn As String, sGrp As String, i As Integer
validLocn = True
For i = 1 To UBound(plan)
sLocn = plan(i, 1)
sGrp = plan(i, 2)
' rule 1
If sGrp = "Gp4" And sLocn = "Loc4" Then
validLocn = False
'Debug.Print itry, i, "Fail Rule 1", sGrp, sLocn
Else
'Debug.Print itry, i, "Pass Rule 1", sGrp, sLocn
End If
Next
End Function
' populate plan groups
Sub zigzag(plan As Variant, grp As Variant)
Dim i As Integer, r As Integer, step As Integer
r = 1: step = 1
For i = 1 To UBound(plan)
plan(i, 2) = grp(r, 1)
r = r + step
If r > UBound(grp) Then
r = UBound(grp)
step = -1
ElseIf r < 1 Then
r = 1
step = 1
End If
Next
End Sub
' shuffle plan
Sub shuffle(plan As Variant, i As Integer)
Dim tmp As String, n As Integer, j As Integer, k As Integer
For n = 1 To i
' random choose elements to shuffle
retry:
k = Int(1 + Rnd() * UBound(plan))
j = Int(1 + Rnd() * UBound(plan))
If k = j Then GoTo retry
tmp = plan(k, 2)
plan(k, 2) = plan(j, 2)
plan(j, 2) = tmp
Next
End Sub
' generate test data
Sub testdata()
Dim ws As Worksheet, i As Long
Set ws = ThisWorkbook.Sheets("Sheet1")
For i = 2 To 150001
ws.Cells(i, 2) = i - 1
ws.Cells(i, 3) = "Loc" & 1 + Int(Rnd() * 6)
Next
End Sub
My code loops through rows with data on one master-sheet and updates different sheets based on the category of the data on each row. When I run the macro, I can see the information temporarily flash where it should be pasted on the worksheet before disappearing. This does not happen where I have used the same copy/paste command before.
The beggining two loops with WOB and ROP will paste correctly while the custom loop does not. I have also tried making the Select Case into several elseif statements which has the same non-working result.
Sub SortData()
Dim Datasheet As Worksheet
Dim ROPsheet As Worksheet 'Rate of Penetration
Dim Customsheet As Worksheet
Dim WOBsheet As Worksheet 'Weight on Bit
Dim i As Long 'Used as counter to loop through compiled data sheet
Dim j As Long 'Used as counter for each Limiter tested
Dim LastRowCount As Long 'Finds number of rows for ending loop
Dim Limiter As String 'These are WOB, ROP, Custom ect.
Dim DepthCheck As Double 'Checks depth on individual limiter sheet with depth on data sheet
Dim DatetCheck As String 'Checks date on individual limiter sheet with depth on data sheet
Dim Depth As Double 'depth from data sheet
Dim Datet As String 'date from limiter sheet
Dim y As Double 'Used to progress through rows
Set Datasheet = Worksheets("Data")
Set ROPsheet = Worksheets("ROP")
Set Customsheet = Worksheets("Custom")
Set WOBsheet = Worksheets("WOB")
y = 1
i = 1
'_____________________________________Working_Code_Below__________________________________________________________
'Arbitrary Count for testing
For i = 1 To 100
y = y + 1
Limiter = Worksheets("Data").Cells(y, 2).Value
Depth = Worksheets("Data").Cells(y, 5).Value
Datet = Worksheets("Data").Cells(y, 6).Value
'WOB
If Limiter = "WOB" Then
j = 1
LastRowCount = WOBsheet.Cells.Find("*", searchorder:=xlByRows, searchdirection:=xlPrevious).Count
For j = 1 To LastRowCount
DepthCheck = Worksheets("WOB").Cells(j + 1, 5).Value
DatetCheck = Worksheets("WOB").Cells(j + 1, 6).Value
If DepthCheck <> Depth Or DatetCheck <> Datet Then
If j = LastRowCount Then
Datasheet.Range(Datasheet.Cells(y, 2), Datasheet.Cells(y, 13)).Copy Sheets("WOB").Cells(j + 1, 2)
GoTo EndLast
End If
Else
GoTo EndLast
End If
Next j
Else
GoTo ROPStart
End If
ROPStart:
If Limiter = "ROP" Then
j = 1
LastRowCount = ROPsheet.Cells.Find("*", searchorder:=xlByRows, searchdirection:=xlPrevious).Count
For j = 1 To LastRowCount
DepthCheck = Worksheets("ROP").Cells(j + 1, 5).Value
DatetCheck = Worksheets("ROP").Cells(j + 1, 6).Value
If DepthCheck <> Depth Or DatetCheck <> Datet Then
If j = LastRowCount Then
Datasheet.Range(Datasheet.Cells(y, 2), Datasheet.Cells(y, 13)).Copy Sheets("ROP").Cells(j + 1, 2)
GoTo EndLast
End If
Else
GoTo EndLast
End If
Next j
Else
GoTo CustomStart
End If
CustomStart:
j = 1
LastRowCount = Customsheet.Cells.Find("*", searchorder:=xlByRows, searchdirection:=xlPrevious).Count
Select Case Limiter
Case "WOB", "Balling", "RPM", "Vibrations", "Torque", "Buckling", "Differential Pressure", "Flow Rate", "Pump Pressure", "Well Control", "Directional", "Logging", "ROP"
GoTo EndLast
Case Else
For j = 1 To LastRowCount
DepthCheck = Worksheets("Custom").Cells(j + 1, D).Value
DatetCheck = Worksheets("Custom").Cells(j + 1, dt).Value
If DepthCheck <> Depth Or DatetCheck <> Datet Then
If j = LastRowCount Then
Datasheet.Range(Datasheet.Cells(y, 2), Datasheet.Cells(y, 13)).Copy Sheets("Custom").Cells(j + 1, 2)
GoTo EndLast
End If
Else
GoTo EndLast
End If
Next j
End Select
EndLast:
Next i
End Sub
No error messages appear.
PS. This is my first post so sorry if formatting is weird.
Welcome to SO and congratulations on your first post. One of these days I'll be there with you, I'm just looking for the perfect question that's all. Lack of courage has nothing to do with it, really, scout's honor. Pinky promise!
I've tried following your code and struggle quite a bit because of the nonlinear flow. The problem you describe sounds like the data is written and then overwritten. This would typically be caused by a superfluous loop, in your case it may be induced by GoTo.
Touching on the comments about finding the row count; this is a surprisingly nuanced subject with many different answers and the correct one dependent on your circumstances and needs. Most of the time I can use UsedRange, as in Sheet1.UsedRange.Rows.Count; but I predominately work on spreadsheets I maintain and keep things as tight as my knowledge allows at the time. I don't remember how long ago I bookmarked this website but I swear I used it daily for a couple months straight: OZGrid Excel Ranges And of course Chip Pearson is worth a call out CPearson Last Used Cell
Please take this last bit as constructive criticism and have a good laugh. When you try to follow this code and get lost, take a step back, look at your code, and find the same pattern - and stop doing it. Break the habit and break the habit hard. Some people, myself included have a near visceral reaction when trying to debug spaghetti code. Try to write linearly top down. You'll find that you understand your own code better, it's easier to keep track of your thoughts, and transfer those thoughts into code. It's a win, win, win situation. GoTo's are almost entirely unnecessary and really impede the progress of others trying to help; using one here or there can be a handy little shortcut in a 5 line function but are best avoided when your code requires scrolling.
Sub aProcedure()
GoTo T
V:
j = vbCancel
b = "point"
GoTo K
X2:
j = x
b = "before"
GoTo K
A1:
For i = VbMethod To vbCancel
b = DoThingWith(DoThingWith(b, 44), b)
Next
j = j * 3
a = DoThingWith(a, b)
GoTo Z
Z:
b = "times"
GoTo K
U2:
j = j + 1 - x
b = "has"
GoTo K
A2:
MsgBox DoThingWith(a)
Exit Sub
X1:
j = j + 1
b = "made"
GoTo K
T:
a = "this"
GoTo U1
K:
a = DoThingWith(a, b)
DoEvents
Select Case j
Case 0
GoTo A2
Case 1
GoTo U1
Case 2
GoTo U2
Case 3
GoTo W
Case 4
GoTo X1
Case 5
GoTo Y
Case Else
GoTo X2
End Select
W:
j = 2 * (j - 1)
b = "been"
GoTo K
Y:
b = "many"
GoTo A1
U1:
a = Replace(a, Left(a, 1), UCase(Left(a, 1)))
GoTo V
End Sub
Private Function DoThingWith(a, Optional b = 46, Optional c = 32)
If IsNumeric(b) Then
b = CInt(b)
c = CInt(c)
Select Case Asc(Right(a, 1))
Case b
DoThingWith = a & Chr(b - c - 1)
Case Else
DoThingWith = a & Chr(b)
End Select
ElseIf IsNumeric(c) Then
c = CInt(c)
DoThingWith = a & Chr(c) & b
Else
DoThingWith = a & b & c
End If
End Function
The output:
First time poster and new to programming in general. I have a project in which i have to build a financial model to mine for data in excel. I have succeeded in building said model on VBA. I have ran tests on 3,000 line dataset and it was successful. I will briefly explain what it does.
I track a given stock on a given day on multiple exchanges. I download the data (roughly 935,000 lines) The first step is copy all the data for a given exchange (roughly 290,000) onto a new sheet (this takes roughly 8min), then I create a new column to log the bid ask spread (12secs), the next step is what Im having trouble with, I basically rank each line of data twice, one column for Bid size and one column for Ask size. I created a function which uses excel Percentile function and ranks based on where the given bid and ask size lands. As of right now, I have been running the Macro for the last 35min and has yet to execute. I cant attempt the other macros since each macro depends on the previous one.
So my basic issue is that since my data set is large, my model keeps crashing.The code seems to be fine when working with the test data, and it doesn't throw any errors when I run the program, but with the larger data set it just crashes. Does anyone have any suggestions? Is this normal with such large amounts of data?
Thanks in advance.
Sham
Here is the sub and function thats giving me the trouble, the sub takes in the required inputs to run the function and then pops into the assigned cell. The code is suppose to repeat the process for three separate sheets. For now, Id like it to work on one sheet, hence used the comments to not include the loop
Sub Bucketting()
Dim firstRow As Long
Dim lastRow As Long
Dim counter As Long
Dim bidRange As Range
Dim offerRange As Range
Dim bidScroll As Range
Dim offerScroll As Range
Dim Ex As String
Dim i As Integer
'For i = 1 To 1 Step 1 'Sheet Selection Process
' If i = 1 Then
' Ex = "Z"
' ElseIf i = 2 Then
' Ex = "P"
' Else
' Ex = "T"
' End If
Sheets("Z").Select 'Sheet selected
With ActiveSheet
firstRow = .UsedRange.Cells(1).Row + 1
lastRow = .UsedRange.Rows.Count
Set bidRange = .Range("F2:F" & lastRow)
Set offerRange = .Range("G2:G" & lastRow)
For counter = lastRow To firstRow Step -1
Set bidScroll = .Range("F" & counter)
Set offerScroll = .Range("G" & counter)
With .Cells(counter, "J")
.Value = DECILE_RANK(bidRange, bidScroll)
End With
With .Cells(counter, "K")
.Value = DECILE_RANK(offerRange, offerScroll)
End With
Next counter
End With
Range("J1").Select
ActiveCell = "Bid Rank"
ActiveCell.Offset(0, 1) = "Offer Rank"
'Next i
End Sub
Function DECILE_RANK(DataRange, RefCell)
'Credit: BJRaid
'DECILE_RANK(The Range of data)
'Declares the function that can be called in the spreadsheet cell - enter '=DECILE_RANK(A5:A50,A5)
'Using the percentile worksheet function calculate where the 10th, 20th etc percentile of the reference range are
DEC1 = Application.WorksheetFunction.Percentile(DataRange, 0.1)
DEC2 = Application.WorksheetFunction.Percentile(DataRange, 0.2)
DEC3 = Application.WorksheetFunction.Percentile(DataRange, 0.3)
DEC4 = Application.WorksheetFunction.Percentile(DataRange, 0.4)
DEC5 = Application.WorksheetFunction.Percentile(DataRange, 0.5)
DEC6 = Application.WorksheetFunction.Percentile(DataRange, 0.6)
DEC7 = Application.WorksheetFunction.Percentile(DataRange, 0.7)
DEC8 = Application.WorksheetFunction.Percentile(DataRange, 0.8)
DEC9 = Application.WorksheetFunction.Percentile(DataRange, 0.9)
' Calculate the Decile rank that the reference cell value sits within
If (RefCell <= DEC1) Then DECILE_RANK = 1
If (RefCell > DEC1) And (RefCell <= DEC2) Then DECILE_RANK = 2
If (RefCell > DEC2) And (RefCell <= DEC3) Then DECILE_RANK = 3
If (RefCell > DEC3) And (RefCell <= DEC4) Then DECILE_RANK = 4
If (RefCell > DEC4) And (RefCell <= DEC5) Then DECILE_RANK = 5
If (RefCell > DEC5) And (RefCell <= DEC6) Then DECILE_RANK = 6
If (RefCell > DEC6) And (RefCell <= DEC7) Then DECILE_RANK = 7
If (RefCell > DEC7) And (RefCell <= DEC8) Then DECILE_RANK = 8
If (RefCell > DEC8) And (RefCell <= DEC9) Then DECILE_RANK = 9
If (RefCell > DEC9) Then DECILE_RANK = 10
End Function
935,000 lines is a lot for excel. Like, really a lot. Barring saying using a real database, If your application is literally putting a =Percentile(...) in each cell, I would recommend Trying to use another tool for that. Perhaps something within VBA itself. More generally, use something outside of a cell - then store the result value in the cell. There is a lot of overhead in maintaining those formulas that are interdependent on 935k rows of data.
The problem is that your looping through each row individually, the Excel way is to try and work with whole ranges at once whenever possible. I would load the ranges into arrays, then modify your DECILE_RANK code to work with the items in the array.
Note that variant arrays that read ranges in are 2-D.
Here is the fully functioning code including my custom VBA array slicer. Note that it was only tested on a small dataset:
Sub Bucketting()
Dim lastRow As Long
Dim bidArray As Variant
Dim offerArray As Variant
Sheets("Sheet1").Select 'Sheet selected
With ActiveSheet
lastRow = .UsedRange.Rows.Count + 1
bidArray = .Range("F2:F" & lastRow)
offerArray = .Range("G2:G" & lastRow)
Range("J2:J" & lastRow).Value = GetArraySlice2D(DECILE_RANK(bidArray), "column", 1, 1, 0)
Range("K2:K" & lastRow).Value = GetArraySlice2D(DECILE_RANK(offerArray), "column", 1, 1, 0)
End With
Range("J1").Select
ActiveCell = "Bid Rank"
ActiveCell.Offset(0, 1) = "Offer Rank"
End Sub
Function DECILE_RANK(DataRange As Variant) As Variant
' Credit: BJRaid
' DECILE_RANK(The Range of data)
' Declares the function that can be called in the spreadsheet cell - enter '=DECILE_RANK(A5:A50,A5)
Dim DEC(0 To 10) As Variant
Dim i As Integer, j As Integer
'Using the percentile worksheet function calculate where the 10th, 20th etc percentile of the reference range are
DEC(0) = 0
For i = 1 To 9
DEC(i) = Application.WorksheetFunction.Percentile(DataRange, 0.1 * i)
Next i
DEC(10) = Application.WorksheetFunction.Max(DataRange)
' Calculate the Decile rank that the reference cell value sits within
For i = 1 To UBound(DataRange, 1)
For j = 1 To 10
If ((DataRange(i, 1) > DEC(j - 1)) And (DataRange(i, 1) <= DEC(j))) Then
DataRange(i, 1) = j
Exit For
End If
Next j
Next i
DECILE_RANK = DataRange
End Function
Public Function GetArraySlice2D(Sarray As Variant, Stype As String, Sindex As Integer, Sstart As Integer, Sfinish As Integer) As Variant
' this function returns a slice of an array, Stype is either row or column
' Sstart is beginning of slice, Sfinish is end of slice (Sfinish = 0 means entire
' row or column is taken), Sindex is the row or column to be sliced (NOTE:
' 1 is always the first row or first column)
' an Sindex value of 0 means that the array is one dimensional 3/20/09 Lance Roberts
Dim vtemp() As Variant
Dim i As Integer
On Err GoTo ErrHandler
Select Case Sindex
Case 0
If Sfinish - Sstart = UBound(Sarray) - LBound(Sarray) Then
vtemp = Sarray
Else
ReDim vtemp(1 To Sfinish - Sstart + 1)
For i = 1 To Sfinish - Sstart + 1
vtemp(i) = Sarray(i + Sstart - 1)
Next i
End If
Case Else
Select Case Stype
Case "row"
If Sfinish = 0 Or (Sstart = LBound(Sarray, 2) And Sfinish = UBound(Sarray, 2)) Then
vtemp = Application.WorksheetFunction.Index(Sarray, Sindex, 0)
Else
ReDim vtemp(1 To Sfinish - Sstart + 1)
For i = 1 To Sfinish - Sstart + 1
vtemp(i) = Sarray(Sindex, i + Sstart - 1)
Next i
End If
Case "column"
If Sfinish = 0 Or (Sstart = LBound(Sarray, 1) And Sfinish = UBound(Sarray, 1)) Then
vtemp = Application.WorksheetFunction.Index(Sarray, 0, Sindex)
Else
ReDim vtemp(1 To Sfinish - Sstart + 1)
For i = 1 To Sfinish - Sstart + 1
vtemp(i) = Sarray(i + Sstart - 1, Sindex)
Next i
End If
End Select
End Select
GetArraySlice2D = vtemp
Exit Function
ErrHandler:
Dim M As Integer
M = MsgBox("Bad Array Input", vbOKOnly, "GetArraySlice2D")
End Function
I'm not sure if this will directly address your problem, but have you considered using Application.ScreenUpdating = False? Don't forget to set it back to true once your data has processed.