Excel VBA - Nested loop to format excel table columns - vba

I have a macro that so far, adds 4 new table columns to an existing table ("Table1"). Now, I would like the macro to format the 3rd and 4th row as percentage. I would like to include this in the loop already listed in my code. I have tried several different ways to do this. I don't think I quite understand how the UBound function works, but hopefully you can understand what I am trying to do.
I also am unsure if I am allowed to continue to utilize the WITH statement in my nested For loop in regards to me 'lst' variable.
#Jeeped - I'm looking at you for this one again...thanks for basically walking me through this whole project lol
Sub attStatPivInsertTableColumns_2()
Dim lst As ListObject
Dim currentSht As Worksheet
Dim colNames As Variant, r1c1s As Variant
Dim h As Integer, i As Integer
Set currentSht = ActiveWorkbook.Sheets("Sheet1")
Set lst = ActiveSheet.ListObjects("Table1")
colNames = Array("AHT", "Target AHT", "Transfers", "Target Transfers")
r1c1s = Array("=([#[Inbound Talk Time (Seconds)]]+[#[Inbound Hold Time (Seconds)]]+[#[Inbound Wrap Time (Seconds)]])/[#[Calls Handled]]", "=350", "=[#[Call Transfers and/or Conferences]]/[#[Calls Handled]]", "=0.15")
With lst
For h = LBound(colNames) To UBound(r1c1s)
.ListColumns.Add
.ListColumns(.ListColumns.Count).Name = colNames(h)
.ListColumns(.ListColumns.Count).DataBodyRange.FormulaR1C1 = r1c1s(h)
If UBound(colNames(h)) = 2 or UBound(colNames(h)) = 3 Then
For i = UBound(colNames(h), 2) To UBound(colNames(h), 3)
.ListColumns(.ListColumns.Count).NumberFormat = "0%"
End if
Next i
Next h
End With
End Sub

You don't need to nest a second for loop. If you want to set the 3rd and 4th columns to a percentage, you only need to set that when the iteration of the loop (h) is 2 or 3 (remembering that arrays index from 0). You also shouldn't cross arrays for the main loop, and since LBound is in most cases 0 you might as well just use that anyway. Try this:
With lst
For h = 0 To UBound(r1c1s)
.ListColumns.Add
.ListColumns(.ListColumns.Count).Name = colNames(h)
.ListColumns(.ListColumns.Count).DataBodyRange.FormulaR1C1 = r1c1s(h)
If h = 2 or h = 3 Then
.ListColumns(.ListColumns.Count).NumberFormat = "0%"
End if
Next h
End With
To answer the other point in your question, UBound(array) just gives the index of the largest element (the Upper BOUNDary) in the given array. So where you have 50 elements in such an array, UBound(array) will return 49 (zero based as mentioned before). LBound just gives the other end of the array (the Lower BOUNDary), which is generally zero.

Related

Efficient Data Transfer from Excel VBA to Web-Service

I have a large worksheet (~250K rows, 22 columns, ~40MB plain data) which has to transfer its content to an intranet API. Format does not matter. The problem is: When accessing the data like
Const ROWS = 250000
Const COLS = 22
Dim x As Long, y As Long
Dim myRange As Variant
Dim dummyString As String
Dim sb As New cStringBuilder
myRange = Range(Cells(1, 1), Cells(ROWS, COLS)).Value2
For x = 1 To ROWS
For y = 1 To COLS
dummyString = myRange(x, y) 'Runtime with only this line: 1.8s
sb.Append dummyString 'Runtime with this additional line 163s
Next
Next
I get a wonderful 2D array, but I am not able to collect the data efficiently for HTTP export.
An X/Y loop over the array and access myRange[x, y] has runtimes >1min. I was not able to find an array method which helps to get the imploded/encoded content of the 2D array.
My current workaround is missusing the clipboard (Workaround for Memory Leak when using large string) which works fast, but is a dirty workaround in my eyes AND has one major problem: The values I get are formatted, “.Value” and not “.Value2”, so I have to convert the data on server site again before usage, e.g. unformat currency cells to floats.
What could be another idea to deal with the data array?
My thoughts are that you create two string arrays A and B. A can be of size 1 to ROWS, B can be of size of 1 to COLUMNS. As you loop over each row in your myRange array, fill each element in B with each column's value in that row. After the final column for that row and before you move to the next row, join array B and assign to the row in A. With a loop of this size, only put necessary stuff inside the loop itself. At the end you would join A. You might need to use cstr() when assigning items to B.
Matschek (OP) was able to write the code based on the above, but for anyone else's benefit, the code itself might be something like:
Option Explicit
Private Sub concatenateArrayValues()
Const TOTAL_ROWS As Long = 250000
Const TOTAL_COLUMNS As Long = 22
Dim inputValues As Variant
inputValues = ThisWorkbook.Worksheets("Sheet1").Range("A1").Resize(TOTAL_ROWS, TOTAL_COLUMNS).Value2
' These are static string arrays, as OP's use case involved constants.
Dim outputArray(1 To TOTAL_ROWS) As String ' <- in other words, array A
Dim interimArray(1 To TOTAL_COLUMNS) As String ' <- in other words, array B
Dim rowIndex As Long
Dim columnIndex As Long
' We use constants below when specifying the loop's limits instead of Lbound() and Ubound()
' as OP's use case involved constants.
' If we were using dynamic arrays, we could call Ubound(inputValues,2) once outside of the loop
' And assign the result to a Long type variable
' To avoid calling Ubound() 250k times within the loop itself.
For rowIndex = 1 To TOTAL_ROWS
For columnIndex = 1 To TOTAL_COLUMNS
interimArray(columnIndex) = inputValues(rowIndex, columnIndex)
Next columnIndex
outputArray(rowIndex) = VBA.Strings.Join(interimArray, ",")
Next rowIndex
Dim concatenatedOutput As String
concatenatedOutput = VBA.Strings.Join(outputArray, vbNewLine)
Debug.Print concatenatedOutput
' My current machine isn't particularly great
' but the code above ran and concatenated values in range A1:V250000
' (with each cell containing a random 3-character string) in under 4 seconds.
End Sub

VBA Finding Max value, without using MAX function and printing corresponding cell

I think I need to do a loop here but I'm not quite sure how exactly to write out the syntax as I'm used to just using the max function.
The function I need to create takes in two arrays; the first array has the numeric values while the second array has strings. The function is supposed to find the value in the first array that is the largest and return the corresponding string from the second array.
I'm not sure exactly how to construct my loop. I'm thinking I need to use some form of conditional statements.
Here's what I have so far:
Function FindMax(valueArray() As Integer, nameArray() As String) As String
Dim i As Long, y As Long
y = valueArray(0) 'change to 1 if using a different array structure
FindMax = nameArray(0) 'change to 1 if using a different array structure
For i = LBound(valueArray, 1) To UBound(valueArray, 1)
If valueArray(i) > y Then
y = valueArray(i)
FindMax = nameArray(i)
End If
Next i
Debug.Print ; y
Debug.Print ; FindMax
End Function
Here's a worksheet formula that gets the job done quick & easy:
=INDEX($C$3:$C$10,MATCH(MAX($B$3:$B$10),$B$3:$B$10))
If your:
Numbers of which to find the Maximum are in cells B3:B10, and,
Strings that you want to return are in cells C3:C10
...then the Maximum can be found with:
{MyMax} =MAX($B$3:$B$10)
...and the "Position #` of {MyMax} can be found with:
{Pos#} =MATCH( {MyMax} ,$B$3:$B$10)
...and the corresponding string can be found with:
=INDEX($C$3:$C$10, {Pos#} )
...so if we put it all together, we get:
=INDEX($C$3:$C$10,MATCH(MAX($B$3:$B$10),$B$3:$B$10))
Function FindMax(valueArray() As Integer, nameArray() As String) As String
dim i as long, y as long
y = valueArray(0) 'change to 1 if using a different array structure
FindMax = nameArray(0) 'change to 1 if using a different array structure
for i = lbound(valueArray,1) to ubound(valueArray,1)
if valueArray(i) > y then
y = valueArray(i)
FindMax = nameArray(i)
end if
next i
End Function
Pay attention to the bottom half of the code. See where is say :
this=FindMax(arr,arr2)
?
That is how you call a function. Obviously you'll need two arrays to pass to this function. I suggest googling "Functions vba" and do some light reading.

Pass user input from excel cells to an Array

I am very new to VBA, so I apologize if this is a very simple question. I am trying to pass user input data into an array. Actually, 4 different arrays. All 4 arrays can have up to 3 elements, but could only need one at any given time. They are then sorted a specific way via For Loops and then will output the sendkeys function to the active window (which will not be excel when it is running). I have the for loops figured out and it is sorting the way i need it to. I just need to be able to get the user input into those arrays and then output them to a phantom keyboard (i.e. sendkeys). I appreciate any help or advice!
FYI, I have declared the arrays as strings and the variables as long... the message boxes are there to just test the sort, they are not very important
For i = 0 To UBound(SheetPosition)
If j = UBound(Position) Then
j = 0
End If
For j = 0 To UBound(Position)
If k = UBound(Direction) Then
k = 0
End If
For k = 0 To UBound(Direction)
If l = UBound(Temper) Then
l = 0
End If
For l = 0 To UBound(Temper)
MsgBox(i)
MsgBox(SheetPosition(i))
MsgBox(j)
MsgBox(Position(j))
MsgBox(k)
MsgBox(Direction(k))
MsgBox(l)
MsgBox(Temper(l))
Next
Next
Next
Next
you could use Application.InputBox() method in two ways:
Dim myArray As Variant
myArray = Application.InputBox("List the values in the following format: " & vbCrLf & "{val1, val2, val3, ...}", Type:=64) '<--| this returns an array of 'Variant's
myArray = Split(Application.InputBox("List the values in the following format: " & vbCrLf & "val1, val2, val3, ...", Type:=2), ",") '<--| this returns an array of 'String's
Yes, you could get the input from the user using Input boxes:
myValue = InputBox("Give me some input")
Or forms, which is the preferred method. Unfortunately, forms take some time to develop and are best deployed through Excel add-ins, which also require time to learn how to setup.
Here is a good tutorial on using the SendKeys method:
http://www.contextures.com/excelvbasendkeys.html
The usual way of getting data from cells into an array would be:
Dim SheetPosition As Variant
SheetPosition = Range("A1:A3").Value
or perhaps
Dim SheetPosition As Variant
SheetPosition = Range("A1:A" & Cells(Rows.Count, "A").End(xlUp).Row).Value
A few things to note:
The array needs to be dimensioned as a Variant.
The dimension of the array will be rows x columns, so in the first example above SheetPosition will be dimensioned 1 To 3, 1 To 1, and in the second example it might be dimensioned 1 To 5721, 1 To 1 (if the last non-empty cell in column A was A5721)
If you need to find the dimensions of a multi-dimensioned array, you should use UBound(SheetPosition, 1) to find the upper bound of the first dimension and UBound(SheetPosition, 2) to find the upper bound of the second dimension.
Even if you include Option Base 0 at the start of your code module, the arrays will still be dimensioned with a lower bound of 1.
If you want a single dimensioned array and your user input is in a column, you can use Application.Transpose to achieve this:
Dim SheetPosition As Variant
SheetPosition = Application.Transpose(Range("A1:A3").Value)
In this case SheetPosition will be dimensioned 1 To 3.
If you want a single dimensioned array and your user input is in a row, you can still use Application.Transpose to achieve this, but you have to use it twice:
Dim SheetPosition As Variant
SheetPosition = Application.Transpose(Application.Transpose(Range("A1:C1").Value))
FWIW - Your If statements in the code in the question are not achieving anything - each of the variables that are being set to 0 are going to be set to 0 by the following For statements anyway. So your existing code could be:
For i = LBound(SheetPosition) To UBound(SheetPosition)
For j = LBound(Position) To UBound(Position)
For k = LBound(Direction) To UBound(Direction)
For l = LBound(Temper) To UBound(Temper)
MsgBox i
MsgBox SheetPosition(i)
MsgBox j
MsgBox Position(j)
MsgBox k
MsgBox Direction(k)
MsgBox l
MsgBox Temper(l)
Next
Next
Next
Next

LinEst function

I'm trying to teach myself some basic VBA in Excel 2010 and I've come across a problem I can't google myself out of. The objective is to create a button which when pressed, automatically does linest for me and writes the values into an array. So far, this is my code.
Private Sub CommandButton1_Click()
Dim linest As Variant
Dim linestArray(4,1) as Variant
Dim i As Integer, j as Integer
linest = Application.LinEst(Range("U49:U51"), Range("T49:T51"), True, True)
For i = 0 To 4
linestArray(i,0) = accessing values of linest variable fyrst column
Cells(68 + i, 21) = linestArray(i,0)
Next
For j = 0 To 4
linestArray(j,1) = accessing values of linest variable second column
Cells(68 + j, 22) = linestArray(j,0)
Next
End Sub
How do I access the values of variable linest so I can store them to an array and print them? Thank you.
EDIT: I figured it out. Variable linest is already an array! I feel pretty dumb. Sorry, this can be ignored.
New code:
Dim linestArray As Variant
linestArray = Application.LinEst(Range("U49:U51"), Range("T49:T51"), True, True)
For i = 0 To 4
For j = 0 To 1
Cells(68 + i, 21 + j) = linestArray(i + 1, j + 1)
Next
Next
The output of any such formula is a Variant array. So you've got that part right.
For a general approach to these Application. (use WorksheetFunction. instead, it's much faster) type functions is...
Type the function in Excel (as an array formula, Ctrl-Shift-Return, if need be)
The output is an N x M matrix of some sort (N =1 , M =1) for most cases
When you do Var = Application.Linest(xyx), the answer gets stored in Var
Get the size of the Var using Ubound(Var, 1), Ubound(Var, 2) to get number of rows and columns (note that these are base 0 type arrays)
Usually, the size will be one x one. In that case, your variable is stored in Var(0,0) i.e. a zero base multidimensional variant array, the top left element.
Hope this helps.

Identifying the iteration of a For Each loop in VBA?

If I have a loop that commences:
For each c in Range("A1:C8")
Is there a property of the placeholder c (c.count, c.value, c.something,...) that identifies the number of times the loop has iterated thus far? I would rather use something like this than including another variable.
Instead of using a "for each c in range" you can do something like this:
Dim c as Long 'presumably you did this as a Range, just change it to Long.
Dim myRange as Range 'Use a range variable which will be easier to call on later
Set myRange = Range("A1:C8")
For c = 1 to myRange.Cells.Count
'Do something to the cell, identify it as myRange.Cells(c), for example:
myRange.Cells(c).Font.Bold = True '<--- replace with your code that affects the cell
Next
This allows you to do the exact same For/Next loop, without including an unnecessary counter variable. In this case, c is a counter but also serves the purpose of identifying the cell being impacted by the code.
You need to count it yourself like this
Dim i as integer
i = 0
For each c in Range("A1:C8")
i = i + 1
Or
Dim i as integer
Dim c as Range
For i = 0 to Range("A1:C8").Count - 1
Set c = Range("A1:C8").Cells(i)
(Revised)
Using Column or Row properties, as appropriate to the direction you are iterating, you can compute an ordinal number on the fly. Thus
For Each c1 in myRange
myOrdinal = c1.row - myRange.row + 1 ' down contiguous cells in one column
myOrdinal = c1.Column - myRange.Column + 1 ' contiguous columns, L2R
Next