Make combo boxes exclusive/precise in generating data - excel

I have a userform with 7 combo boxes that are used to search data from the worksheet. I intend to present column 6(mass) and 8(index) as ranges i.e. for mass: 0.007-0.1; 0.11-2.5; 0.251-0.5 etc. The other 5 combo boxes are just absolute values (not range).I'm attempting to loop through the cells in the data sheet(shD) and whenever the row matches matches all selections made on the userform; then the entire row is copied to the results sheet(shR). The user may leave some of the combo boxes blank, but they should still be able to get results. What the code is doing now is may be say in the time combobox(cbInj) I selected say 15 seconds; the code will include 20 seconds which does not match the 15sec on the combo box. Here is my code;
'combo boxes variable definition, in order to compact and make the code easy to be understood:
Set cbPr = User_search.Cbx_Project_code
Set cbTr = User_search.Cbx_TrueNOC
Set cbDn = User_search.Cbx_DNAmass
Set cbK = User_search.Cbx_Kit
Set cbQ = User_search.Cbx_QIndex
Set cbInj = User_search.Cbx_Injection_time
Set cbInstr = User_search.Cbx_Instrument
'Check selection for mass and present it as a range
If Len(cbDn.Value) > 0 Then
arrDn = Split(cbDn.Value, "-")
mnDn = CDbl(arrDn(0))
mxDn = CDbl(arrDn(1))
End If
'checkfor Index if selected and present it as a range
If Len(cbQ.Value) > 0 Then
arrQ = Split(cbQ.Value, "-")
mnQ = CVar(arrQ(0))
mxQ = CVar(arrQ(1))
End If
'count the total rows on Data
totD = shD.Range("B" & Rows.Count).End(xlUp).Row 'last row of "Data" sheet
For i = 5 To totD
vDn = shD.Cells(i, 6).Value
vQ = shD.Cells(i, 8).Value
If (Trim(shD.Cells(i, 2)) = Trim(cbPr.Value) Or cbPr.Value = "") And _
(Trim(shD.Cells(i, 5)) = Trim(cbTr.Value) Or cbTr.Value = "") And _
vDn > mnDn And vDn <= mxDn Or cbDn.Value = "" And _
(Trim(shD.Cells(i, 7)) = Trim(cbK.Value) Or cbK.Value = "") And _
vQ > mnQ And vQ <= mxQ Or cbQ.Value = "" And _
(Trim(shD.Cells(i, 9)) = Trim(cbInj.Value) Or cbInj.Value = "") And _
(Trim(shD.Cells(i, 10)) = Trim(cbInstr.Value) Or cbInstr.Value = "") Then
totR = shR.Cells(Rows.Count, 1).End(xlUp).Row
shD.Rows(i).EntireRow.Copy Destination:=shR.Cells(totR + 1, 1)
End If
Next i

Slight logic problem in your "range" tests - eg:
vQ > mnQ And vQ <= mxQ Or cbQ.Value = "" And _
should be
((vQ > mnQ And vQ <= mxQ) Or cbQ.Value = "") And _
I would do something like this. Individual tests are faster, since there's no need to continue checking after any failed test
Sub Tester()
Dim cbPr, cbTr, cbDn, cbk, cbQ, cbInj, cbInstr 'all variants
Dim rw As Range, isMatch As Boolean, arrCrit
'get combo boxes values
cbPr = Trim(User_search.Cbx_Project_code.Value)
cbTr = Trim(User_search.Cbx_TrueNOC.Value)
cbDn = Trim(User_search.Cbx_DNAmass.Value)
If Len(cbDn) > 0 Then cbDn = Split(cbDn, "-") 'convert to array
cbk = Trim(User_search.Cbx_Kit.Value)
cbQ = Trim(User_search.Cbx_QIndex.Value)
If Len(cbQ) > 0 Then cbDn = Split(cbQ, "-") 'convert to array
cbInj = Trim(User_search.Cbx_Injection_time.Value)
cbInstr = Trim(User_search.Cbx_Instrument.Value)
arrCrit = Array(2, cbPr, 5, cbTr, 6, cbDn, 7, cbk, 8, cbQ, 9, cbInj, 10, cbInstr)
For i = 5 To shD.Range("B" & Rows.Count).End(xlUp).Row
Set rw = shD.Rows(i)
For n = LBound(arrCrit) To UBound(arrCrit) - 1 Step 2
isMatch = CellIsMatch(rw.Cells(arrCrit(n)), arrCrit(n + 1))
If Not isMatch Then Exit For
Next n
If isMatch Then rw.Copy shR.Cells(Rows.Count, 1).End(xlUp).Offset(1, 0)
Next i
End Sub
'Does a cell value match the supplied criteria?
' Criteria could be a range of two numeric values
Function CellIsMatch(cell As Range, crit) As Boolean
Dim v
v = cell.Value
If Len(v) > 0 Then
'is the criteria an array (range) ?
If TypeName(crit) Like "*()" Then
'assumes v is numeric
CellIsMatch = (v > CDbl(Trim(crit(0))) And _
v < CDbl(Trim(crit(1))))
Else
CellIsMatch = (Trim(v) = crit) Or Len(crit) = 0
End If
End If
End Function

Related

Add multiple checkbox caption in List box in User Form

enter image description here
Many thanks for your reply, Please find attached a picture of the user form I Got the data in the list box by some other ways no I am facing an issue to update and edit the data. I am trying to call the data from Listbox to textbox and checkboxes by below code for Editing.
Private Sub ListBox1_DblClick(ByVal Cancel As MSForms.ReturnBoolean) 'UPDATE LISBOX DATA
Dim p As Integer
Me.ComboBoxitem.Value = Me.ListBox1.List(Me.ListBox1.ListIndex, 1)
For p = 0 To Me.ListBox1.ListCount < 1
Me.CheckBoxSmall.Value = Me.ListBox1.List(p, 3)
Me.CheckBoxMedium.Value = Me.ListBox1.List(p, 3)
Me.CheckBoxLarge.Value = Me.ListBox1.List(p, 3)
Me.CheckBoXL.Value = Me.ListBox1.List(p, 3)
Me.CheckBoXXL.Value = Me.ListBox1.List(p, 3)
Me.CheckBoXXXL.Value = Me.ListBox1.List(p, 3)
Me.txtsmallqty.Value = Me.ListBox1.List(p, 4)
Me.TextBoxmedium.Value = Me.ListBox1.List(p, 4)
Me.TextBoxlarge.Value = Me.ListBox1.List(p, 4)
Me.TextBoXL.Value = Me.ListBox1.List(p, 4)
Me.TextBoxxL.Value = Me.ListBox1.List(p, 4)
Me.TextBoxxxL.Value = Me.ListBox1.List(p, 4)
Next
Me.TextBox1.Value = Me.ListBox1.List(Me.ListBox1.ListIndex, 0)
End Sub
and for update the data in excel sheet after editing , I am using below code :
Private Sub CommandButton1_Click() ' Update Data
Dim L As Long
Dim th As Worksheet
Set th = ThisWorkbook.Sheets("Data")
L = Application.WorksheetFunction.Match(CLng(Me.TextBox1.Value), th.Range("A1:A1000"), 0)
th.Range("B" & L) = Me.ComboBoxitem.Value
th.Range("D" & L) = Me.CheckBoxSmall.Value
th.Range("D" & L) = Me.CheckBoxMedium.Value
th.Range("D" & L).Value = Me.CheckBoxLarge.Value
th.Range("D" & L).Value = Me.CheckBoXL.Value
th.Range("D" & L).Value = Me.CheckBoXXL.Value
th.Range("D" & L).Value = Me.CheckBoXXXL.Value
th.Range("E" & L) = Me.txtsmallqty.Value
th.Range("E" & L) = Me.TextBoxmedium.Value
th.Range("E" & L) = Me.TextBoxlarge.Value
th.Range("E" & L) = Me.TextBoXL.Value
th.Range("E" & L) = Me.TextBoxxL.Value
th.Range("E" & L) = Me.TextBoxxxL.Value
Me.CheckBoxSmall.Value = False
Me.CheckBoxMedium.Value = False
Me.CheckBoxLarge.Value = False
Me.CheckBoXL.Value = False
Me.CheckBoXXL.Value = False
Me.CheckBoXXXL.Value = False
Me.txtsmallqty.Value = ""
Me.TextBoxmedium.Value = ""
Me.TextBoxlarge.Value = ""
Me.TextBoXL.Value = ""
Me.TextBoxxL.Value = ""
Me.TextBoxxxL.Value = ""
Me.TextBox1.Value = ""
End Sub
Addition due to comment:
"I am trying to pull Listbox data in 6 checkboxes and 6 text boxes from the first code mention above, the Issue I am facing from this code, shows only data from the first line of Listbox to all text boxes and checkboxes.
By the mean of the second code I have to update data in excel sheet."
But I am not able to get the perfect result, you are requested to please review the above Code and let me know where I am Mistaking.
Your Kind Response will be Highly Appreciated.
As you are displaying always six rows per chosen item (corresponding to six sizes of Small,Medium,...,XXXL) with item info only in the 1st row, a main issue is to get the correct .ListIndex by doubleclicking to any row within the listbox.
1. The start row index p (containing the serial# and product name) can be calculated from the currently double clicked .ListIndex using an int(eger) division multiplied by six rows to get to the first row (see section 1):
p = (Me.ListBox1.ListIndex \ 6) * 6
Example: a double click into .ListIndex of 0..5 results in the start row index p = 0, of 6..11 in 6, ... - i.e. always returning the first row of a bundle of six rows containing sizes.
2. To avoid endless assignments I defined two variant arrays (chkboxes and txtboxes) containing the checkbox and textbox names (see section 2). - Another frequently used method consists in enumerating the control names facilitating assignments in a later loop.
3. The 3rd step assigns the listbox'es main info (3a) and the size-related values (3b) to all single controls; the latter action is executed in a loop referring to the controls via Me.Controls(chkboxes(i)).Value and Me.Controls(txtboxes(i)).Value.
The following code example should give you a start and allow to finish the 2nd procedure by yourself (remind: don't overload a post by too many independant questions, focus to one issue :-;)
Private Sub ListBox1_DblClick(ByVal Cancel As MSForms.ReturnBoolean) 'UPDATE LISBOX DATA
'1. get the start row containing the serial code,
' (even if doubleclicked in one of the five following rows)
Dim p As Long ' instead of Integer
p = (Me.ListBox1.ListIndex \ 6) * 6 ' each item has 6 rows (sizes available)
'2. define arrays containing checkbox|textbox names
Dim chkboxes, txtboxes
chkboxes = Split("CheckBoxSmall,CheckBoxMedium,CheckBoxLarge,CheckBoXL,CheckBoXXL,CheckBoXXXL", ",")
txtboxes = Split("txtsmallqty,TextBoxmedium,TextBoxlarge,TextBoXL,TextBoXXL,TextBoxxxL", ",")
'3. a) write item name & Serial# to corresponding userform controls
Me.ComboBoxitem.Value = Me.ListBox1.List(p, 1) ' Item name
Me.TextBox1.Value = Me.ListBox1.List(p, 0) ' Serial number
'3. b) loop through all six rows representing sizes
Dim i As Long
For i = 0 To 5 ' listbox items and both ctrl arrays are 0-based!
Me.Controls(chkboxes(i)).Value = CBool(Me.ListBox1.List(p + i, 3)) ' 4th column has index 3!
Me.Controls(txtboxes(i)).Value = Me.ListBox1.List(p + i, 4) ' 5th column has index 3!
Next i
End Sub

Adding complexity to an if then else loop

I've got a macro that works perfectly but that I now need to customize it and add complexity.
The macro is basically the following code repeated numerous times for a variety of ranges.
For i = 2 To n
Range(Cells(13, i), Cells(19, i)).Value = Sheets(i).Range("J13:J19").Value
Next i
The logic/complexity that I need to add to this should go as follows:
if the sum of the range O13:O19 on sheet i is greater than zero, then the value of the range cells(13,i),cells 19,i) on this sheet are equal to the value of the range p13:p19 on sheet i.
If the value of the sum of range O13:O19 on sheet i is not greater than 0, then set the value of the target range equal to each cell in (range sheet(i).range("I13:I19")-sheet(i).range("K13:K19")*4).value
In simpler terms, if the sum of the range is 0, set the value of every cell in range A to the value of every cell in range b less the (value of every cell in range C * 4)...
Sub Op_ex_analysis_macro()
ActiveWorkbook.Sheets.Add Before:=Worksheets(1)
ActiveSheet.Name = "Control Panel"
Range("A:A").ColumnWidth = 36
Range("A12").Value = "Property Code"
Range("A13:A16") = Sheets(2).Range("A13:A16").Value
Range("A17") = Sheets(2).Range("B17").Value
Range("A18") = Sheets(2).Range("A18").Value
Range("A19") = Sheets(2).Range("B19").Value
Range("A20:A29") = Sheets(2).Range("A21:A30").Value
Range("A30") = Sheets(2).Range("B31").Value
Range("A31") = Sheets(2).Range("A33").Value
Range("A32:A36") = Sheets(2).Range("A35:A39").Value
Range("A37:A38") = Sheets(2).Range("A41:A42").Value
Range("A40").Value = "Analyst"
Range("A41").Value = "Number of Units"
Range("A42").Value = "Asset Manager"
Range("A43").Value = "Tenancy"
Range("A44").Value = "Year Built/Type"
Range("A45").Value = "Management Company"
Range("A46").Value = "End of Compliance Year"
Range("A47").Value = "Property Name"
Range("A48").Value = "Number of Properties"
Range("A49").Value = "City"
Range("A50").Value = "State"
'Consolidate Property Codes
n = ActiveWorkbook.Sheets.Count
For i = 2 To n
Z = Sheets(i).Range("P49").Value
Cells(12, i) = Z
Next i
'Consolidate rows 13-19
For i = 2 To n
Select Case Application.Sum(ThisWorkbook.Sheets(i).Range("O13:O33"))
Case Is > 0
Range(Cells(13, i), Cells(19, i)).Value = Sheets(i).Range("P13:P19").Value
Case Is = 0
Range(Cells(13, i), Cells(19, i)).Value = ThisWorkbook.Sheets(i).Range("I13:I19") - ThisWorkbook.Sheets(i).Range("K13:K19") * 4
End Select
Next i
In this case i think the best option is to use a Select case statement.
For i = 2 To n
Select Case Application.Sum(ThisWorkbook.Sheets(i).Range("O13:O33"))
Case Is > 0
Range(Cells(13, i), Cells(19, i)).Value = Sheets(i).Range("P13:P19").Value
Case Is < 0
Range(Cells(13, i), Cells(19, i)).Value = ThisWorkbook.Sheets(i).Range("I13:I19") - ThisWorkbook.Sheets(i).Range("K13:K19") * 4
End Select
Next i
Hope this helps :)
EDIT If ou want to account for whent it's "0" then just add a Case Is 0
After a lot of trial and error, I was able to solve the problem through via a different route.
As A.S.H correctly noted above, you can't do arithmetic on VBA arrays.
The first half of my code was basically moving an array, as Scott Craner noted on a different page, which is simple.
Directing VBA to perform calculations requires the coder to send the formula through a range cell by cell.
Ultimately, the code that performed as required was as follows:
Dim rng As Range
n = ActiveWorkbook.Sheets.Count
With ActiveSheet
For i = 2 To n
If Application.Sum(Sheets(i).Range("O13:O33")) > 0 Then
.Range(.Cells(13, i), .Cells(19, i)).Value = Sheets(i).Range("P13:P19").Value
Else
For Each rng In .Range(.Cells(13, i), .Cells(19, i))
rng.Value = Sheets(i).Cells(rng.Row, "I") - (4 * Sheets(i).Cells(rng.Row, "K"))
Next rng
End If
Next i
End With
If the condition of the first 1/2 of the if statement is met, then it's just set these values equal to those values. If the condition is not met, then the Else statement directs Excel to move through the range performing the calculation as it goes.

excel vba formula determining highest value based on text

Hi currently i'm having a problem regarding the displaying of the most significant text among 4 rows in one column . What I have here is remarks of clients which is excellent,good,fair and bad ..and i would like to display the word excellent on a cell if it is present in that column , otherwise if good is the highest value present then it should display it ,if fair then fair or and lastly if bad then display bad
enter image description here
Hope this is not too late to answer your question. Try the following formula:
=INDEX({"Bad","Fair","Good","Excellent"},MATCH(1,(MATCH({"Bad","Fair","Good","Excellent"},B2:E2,0)),0))
See the image for reference:
It's not a formula, but the main trouble, as I see, is not to grade four known values you listed above, but to exclude empty and unknown values. Moreover, when such happened, user must be informed about it and make the right decision...
'''''''
Private Sub sb_Test_fp_Grade3()
Debug.Print fp_Grade3(Selection, 1, True)
End Sub
Public Function fp_Grade3(pRng As Range, _
Optional pUnkMod& = 0, _
Optional pEmpDen As Boolean = False) As String
' pUnkMod - Mode of UnKnown grades handling
' 0-Ignore; 1-Info only; 2-Deny
' pEmpDen - Deny or not empty values. If Deny, then empty treated as Unknown
' according pUnkMod setting
Const S_BAD As String = "BAD"
Const S_FAI As String = "FAIR"
Const S_GOO As String = "GOOD"
Const S_EXC As String = "EXCELLENT"
Const S_UNK As String = "UNK" ' UNKNOWN
Dim rCell As Range
Dim lVal&, lMax&, lUnk&
Dim sGrades$(0 To 4), sRet$, sVal$
sGrades(0) = S_UNK
sGrades(1) = S_BAD
sGrades(2) = S_FAI
sGrades(3) = S_GOO
sGrades(4) = S_EXC
lMax = 0
lUnk = 0
sRet = vbNullString
For Each rCell In pRng
sVal = rCell.Value
If (LenB(sVal) > 0 Or pEmpDen) Then
Select Case UCase(rCell.Value)
Case S_BAD: lVal = 1
Case S_FAI: lVal = 2
Case S_GOO: lVal = 3
Case S_EXC: lVal = 4
Case Else: lVal = 0
End Select
Select Case (lVal > 0)
Case True ' Known values
If (lVal > lMax) Then
lMax = lVal
If (lMax = 4) Then
If (pUnkMod = 0) Then Exit For
End If
End If
Case False ' UnKnown values
Select Case pUnkMod
Case 0 ' ignore them
' do nothing
Case 1 ' info about them
lUnk = lUnk + 1
Case Else ' 2 & any others - stop
lMax = 0
Exit For
End Select
End Select
End If
Next
If (lUnk > 0) Then sRet = " & " & lUnk & "x" & S_UNK
sRet = sGrades(lMax) & sRet
fp_Grade3 = sRet
End Function
'''

VBA Excel Populating cells based on previous existence

I haven't seen this addressed yet, but I think that might be because I don't know how to phrase my problem concisely. Here's an example of what I'd like to try and do:
Given a column which holds state initials check output sheet if that state has been found before. If it hasn't then populate a new cell with that state's initials and initialize the count (number of times state has been found) to one. If the state's initials are found in a cell within the output sheet then increment the count by one.
With this, if we have a 50,000 (or however many) lined excel sheet that has states in random order (states may or may not be repeated) we will be able to create a clean table which outputs which states are in the raw data sheet and how many times they appeared. Another way to think about this is coding a pivot table, but with less information.
There's a couple of ways that I've thought about how to complete this, I personally think none of these are very good ideas but we'll see.
Algorithm 1, all 50 states:
Create 50 string variables for each state, create 50 long variables for the counts
Loop through raw data sheet, if specific state found then increment appropriate count (this would require 50 if-else statements)
Output results
Overall..... terrible idea
Algorithm 2, flip-flop:
Don't create any variables
If a state is found in raw data sheet , look in output sheet to check if state has been found before
If state has been found before, increment cell adjacent by one
If state has not been found before, change next available blank cell to state initials and initialize cell adjacent to one
Go back to raw data sheet
Overall..... this could work, but I feel as if it would take forever, even with raw data sheets that aren't very big but it has the benefit of not wasting memory like the 50 states algorithm and less lines of code
On a side note, is it possible to access a workbook's (or worksheet's) cells without activating that workbook? I ask because it would make the second algorithm run much quicker.
Thank you,
Jesse Smothermon
A couple of point that will speed up your code:
You don't need to active workbooks, worksheets or ranges to access them
eg
DIM wb as workbook
DIM ws as worksheet
DIM rng as range
Set wb = Workbooks.OpenText(Filename:=filePath, Tab:=True) ' or Workbooks("BookName")
Set ws = wb.Sheets("SheetName")
Set rng = ws.UsedRange ' or ws.[A1:B2], or many other ways of specifying a range
You can now refer to the workbook/sheet/range like
rng.copy
for each cl in rng.cells
etc
Looping through cells is very slow. Much faster to copy the data to a variant array first, then loop through the array. Also, when creating a large amount of data on a sheet, better to create it in a variant array first then copy it to the sheet in one go.
DIM v As Variant
v = rng
eg if rng refers to a range 10 rows by 5 columns, v becomes an array of dim 1 to 10, 1 to 5. The 5 minutes you mention would probably be reduced to seconds at most
Sub CountStates()
Dim shtRaw As Excel.Worksheet
Dim r As Long, nr As Long
Dim dict As Object
Dim vals, t, k
Set dict = CreateObject("scripting.dictionary")
Set shtRaw = ThisWorkbook.Sheets("Raw")
vals = Range(shtRaw.Range("C2"), _
shtRaw.Cells(shtRaw.Rows.Count, "C").End(xlUp)).Value
nr = UBound(vals, 1)
For r = 1 To nr
t = Trim(vals(r, 1))
If Len(t) = 0 Then t = "Empty"
dict(t) = dict(t) + 1
Next r
For Each k In dict.keys
Debug.Print k, dict(k)
Next k
End Sub
I implemented my second algorithm to see how it would work. The code is below, I did leave out little details in the actual problem to try and be more clear and get to the core problem, sorry about that. With the code below I've added the other "parts".
Code:
' this number refers to the raw data sheet that has just been activated
totalRow = ActiveSheet.Range("A1").End(xlDown).Row
For iRow = 2 To totalRow
' These are specific to the company needs, refers to addresses
If (ActiveSheet.Cells(iRow, 2) = "BA") Then
badAddress = badAddress + 1
ElseIf (ActiveSheet.Cells(iRow, 2) = "C") Then
coverageNoListing = coverageNoListing + 1
ElseIf (ActiveSheet.Cells(iRow, 2) = "L") Then
activeListing = activeListing + 1
ElseIf (ActiveSheet.Cells(iRow, 2) = "NC") Then
noCoverageNoListing = noCoverageNoListing + 1
ElseIf (ActiveSheet.Cells(iRow, 2) = "NL") Then
inactiveListing = inactiveListing + 1
ElseIf (ActiveSheet.Cells(iRow, 2) = "") Then
noHit = noHit + 1
End If
' Algorithm beginning
' If the current cell (in state column) has something in it
If (ActiveSheet.Cells(iRow, 10) <> "") Then
' Save value into a string variable
tempState = ActiveSheet.Cells(iRow, 10)
' If this is also in a billable address make variable true
If (ActiveSheet.Cells(iRow, 2) = "C") Or (ActiveSheet.Cells(iRow, 2) = "L") Or (ActiveSheet.Cells(iRow, 2) = "NL") Then
boolStateBillable = True
End If
' Output sheet
BillableWorkbook.Activate
For tRow = 2 To endOfState
' If the current cell is the state
If (ActiveSheet.Cells(tRow, 9) = tempState) Then
' Get the current hit count of that state
tempStateTotal = ActiveSheet.Cells(tRow, 12)
' Increment the hit count by one
ActiveSheet.Cells(tRow, 12) = tempStateTotal + 1
' If the address was billable then increment billable count
If (boolStateBillable = True) Then
tempStateBillable = ActiveSheet.Cells(tRow, 11)
ActiveSheet.Cells(tRow, 11) = tempStateBillable + 1
End If
Exit For
' If the tempState is unique to the column
ElseIf (tRow = endOfState) Then
' Set state, totalCount
ActiveSheet.Cells(tRow - 1, 9) = tempState
ActiveSheet.Cells(tRow - 1, 12) = 1
' Increment the ending point of the column
endOfState = endOfState + 1
' If it's billable, indicate with number
If (boolStateBillable = True) Then
tempStateBillable = ActiveSheet.Cells(tRow - 1, 11)
ActiveSheet.Cells(tRow - 1, 11) = tempStateBillable + 1
End If
End If
Next
' Activate raw data workbook
TextFileWorkbook.Activate
' reset boolean
boolStateBillable = False
Next
I ran it once and it seems to have worked. The problem is that it took roughly five minutes or so, the original code takes 0.2 (rough guess). I think the only way to make the code perform quicker is to somehow be able to not activate the two workbooks over and over. This means that the answer is not complete but I will edit if I figure out the rest.
Note I will revisit pivot tables to see if I can do everything that I need to in them, as of now it looks like there are a couple of things that I won't be able to change but I'll check
Thank you,
Jesse Smothermon
I kept with the second algorithm. There is the dictionary option that I forgot but I'm still not very comfortable with how it works and I generally don't understand it quite yet. I played with the code for a bit and changed some thing up, it now works faster.
Code:
' In output workbook (separate sheet)
Sheets.Add.Name = "Temp_Text_File"
' Opens up raw data workbook (originally text file
Application.DisplayAlerts = False
Workbooks.OpenText Filename:=filePath, Tab:=True
Application.DisplayAlerts = True
Set TextFileWorkbook = ActiveWorkbook
totalRow = ActiveSheet.Range("A1").End(xlDown).Row
' Copy all contents of raw data workbook
Cells.Select
Selection.Copy
BillableWorkbook.Activate
' Paste raw data into "Temp_Text_File" sheet
Range("A1").Select
ActiveSheet.Paste
ActiveWorkbook.Sheets("Billable_PDF").Select
' Populate long variables
For iRow = 2 To totalRow
If (ActiveWorkbook.Sheets("Temp_Text_File").Cells(iRow, 2) = "BA") Then
badAddress = badAddress + 1
ElseIf (ActiveWorkbook.Sheets("Temp_Text_File").Cells(iRow, 2) = "C") Then
coverageNoListing = coverageNoListing + 1
ElseIf (ActiveWorkbook.Sheets("Temp_Text_File").Cells(iRow, 2) = "L") Then
activeListing = activeListing + 1
ElseIf (ActiveWorkbook.Sheets("Temp_Text_File").Cells(iRow, 2) = "NC") Then
noCoverageNoListing = noCoverageNoListing + 1
ElseIf (ActiveWorkbook.Sheets("Temp_Text_File").Cells(iRow, 2) = "NL") Then
inactiveListing = inactiveListing + 1
ElseIf (ActiveWorkbook.Sheets("Temp_Text_File").Cells(iRow, 2) = "") Then
noHit = noHit + 1
End If
If (ActiveWorkbook.Sheets("Temp_Text_File").Cells(iRow, 10) <> "") Then
tempState = ActiveWorkbook.Sheets("Temp_Text_File").Cells(iRow, 10)
If (ActiveWorkbook.Sheets("Temp_Text_File").Cells(iRow, 2) = "C") Or (ActiveWorkbook.Sheets("Temp_Text_File").Cells(iRow, 2) = "L") Or (ActiveWorkbook.Sheets("Temp_Text_File").Cells(iRow, 2) = "NL") Then
boolStateBillable = True
End If
'BillableWorkbook.Activate
For tRow = 2 To endOfState
If (ActiveSheet.Cells(tRow, 9) = tempState) Then
tempStateTotal = ActiveSheet.Cells(tRow, 12)
ActiveSheet.Cells(tRow, 12) = tempStateTotal + 1
If (boolStateBillable = True) Then
tempStateBillable = ActiveSheet.Cells(tRow, 11)
ActiveSheet.Cells(tRow, 11) = tempStateBillable + 1
End If
Exit For
ElseIf (tRow = endOfState) Then
ActiveSheet.Cells(tRow, 9) = tempState
ActiveSheet.Cells(tRow, 12) = 1
endOfState = endOfState + 1
If (boolStateBillable = True) Then
tempStateBillable = ActiveSheet.Cells(tRow, 11)
ActiveSheet.Cells(tRow, 11) = tempStateBillable + 1
End If
End If
Next
'stateOneTotal = stateOneTotal + 1
'If (ActiveSheet.Cells(iRow, 2) = "C") Or (ActiveSheet.Cells(iRow, 2) = "L") Or (ActiveSheet.Cells(iRow, 2) = "NL") Then
' stateOneBillable = stateOneBillable + 1
'End If
'ElseIf (ActiveSheet.Cells(iRow, 10) = "FL") Then
'stateTwoTotal = stateTwoTotal + 1
'If (ActiveSheet.Cells(iRow, 2) = "C") Or (ActiveSheet.Cells(iRow, 2) = "L") Or (ActiveSheet.Cells(iRow, 2) = "NL") Then
' stateTwoBillable = stateTwoBillable + 1
'End If
End If
'TextFileWorkbook.Activate
If (ActiveWorkbook.Sheets("Temp_Text_File").Cells(iRow, 2) = "C") Or (ActiveWorkbook.Sheets("Temp_Text_File").Cells(iRow, 2) = "L") Or (ActiveWorkbook.Sheets("Temp_Text_File").Cells(iRow, 2) = "NL") Then
billableCount = billableCount + 1
End If
boolStateBillable = False
Next
' Close raw data workbook and raw data worksheet
Application.DisplayAlerts = False
TextFileWorkbook.Close
ActiveWorkbook.Sheets("Temp_Text_File").Delete
Application.DisplayAlerts = True
Thank you for the comments and suggestions. It is very much appreciated as always.
Jesse Smothermon

VBA-Excel and large data sets causes program to crash

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.

Resources