I've only been using VBA for a couple of weeks now so bear with me.
I'm trying to change a macro so that it reads from an array instead of a range. One of the sections I need to change uses .formulaR1C1 to run a vlookup, but im having a hard time trying to work out how this can be done with the array.
An example of this is the below line:
.Range("M2:L" & lastrow).FormulaR1C1 = "=VLOOKUP(RC[-1], Sheet2!R1C1:R4C10, 3, 0)"
I'm not too sure on whether I can set the value of an array to a formula, as I've done above, or if I have to maybe store the value as a String and then edit the value of the cell later when printing the column back on to the worksheet.
What I have so far is below:
For i = 2 To lastrow
arr(i, 13).FormulaR1C1 = "=VLOOKUP(RC[-1],Sheet2!R1C1:R4C10,3,0)"
Next
Thanks in advance!
Storing the value as a string would certainly be a valid option. Simply write the formula in your array, presuming it is of Variant / String type, and when you put the data back into a worksheet you can use .FormulaR1C1 on a Cell (Range) object to apply it as a formula.
arr(i, 13) = "=VLOOKUP(RC[-1],WMSPAM!R1C1:R4C10,3,0)"
...
Range("M2").FormulaR1C1 = Arr(1,13)
I believe this approach is likely the most effective and most easily maintained for you. As you are learning and appear to be curious of what is possible, here are a couple more examples of how you could approach this, with some further explanation.
.FormulaR1C1 is a Range object method and so the only way it could be called on an Array item would be if that item was a Range object.
Dim arr(0 To 10) As Range
Set arr(0) = Range("A1")
arr(0).FormulaR1C1 = "=2+2"
Note that as Ranges are an Object (of reference type), this operation will directly effect the Range specified in the array. In the above example, the formula "=2+2" will be placed into cell A1. You can learn more about the difference between Reference and Value types here.
If your array contains only values, the other way to achieve what you need is to use the WorksheetFunction object. With this object you can access the formula functions, that you would use in worksheet, within VBA.
WorksheetFunction.VLookup(Arg1, Arg2, Arg3, Arg4)
As with anything in writing code the WorksheetFunction methods take some trial and error to get them to work how you would expect, but in my experience these specifically can be a tricky to implement, there are though some cases where they can be very useful!
You can read more about the VLookup method here.
You may try to use .Offset():
Sub Test()
lastrow = 11
With Sheets(1)
Set Rng = .Range("M2:L" & lastrow)
End With
For i = 0 To Rng.Rows.Count - 1
Rng.Offset(i, 12).FormulaR1C1 = "=VLOOKUP(RC[-1],Sheet2!R1C1:R4C10,3,0)"
Next
End Sub
I'm not sure what you are asking. Your first code line writes a formula to a two-column range in the most efficient way there is already, your second snippet shows the less efficient way of doing it one cell at the time. If the goal is to use the vlookup to fill cells and then dispose of the formula, one efficient way is this:
.Range("M2:L" & lastrow).FormulaR1C1 = "=VLOOKUP(RC[-1], Sheet2!R1C1:R4C10, 3, 0)"
.Range("M2:L" & lastrow).Value2 = .Range("M2:L" & lastrow).Value2
Related
I'm trying to drop a range of cells straight into an array to run some calculations. The range will always be A8:E?, but I never know how many total rows there will be. One of the columns contains string values that I need for determining conditions. I set a variable to find the last row (and no, this data never has any blanks so the method to set it works just fine). This is what I have"
Sub use_arr()
Dim rw As Integer
rw = Worksheets("Sheet1").Range("A8").End(xlDown).Row
Dim myArr()
myArr = Worksheets("Sheet1").Range("A8:E" & rw)
End Sub
But it keeps throwing a "Run_time error '13'" at me.
Arrays are still new to me, someone suggested using them instead of looping through cells for faster execution. So I'm still figuring this out.
Either declare myArr just as a Variant, not an array, or specify the Value property of the range:
myArr = Worksheets("Sheet1").Range("A8:E" & rw).Value
I am aware that this;
Arr() =Range("E2:X2500")
...then doing stuff with the Arr, and then dumping back using:
Range("E2:X2500")=Arr()
is many folds more efficient (faster) that looping through and referencing cells directly.
It's light speed!
But, this range-to-array assignment only grabs cells' value.
Is there a way to assign an actual range (continuous or not) into an array (with the same light speed) in such a way that you could then treat array items the way you would refer to cells, like:
arr(23).row 'getting a row number
Or;
If Arr(23).Value ="Pending" then arr(23).font.bold=1 else arr(23).font.bold=0
I know i can dim a range-type array where each item can store an actual single cell range. But this array cannot be handled the same way - with one liner assignment:
Dim Arr () as Range
Set Arr = Range("E2:X2500") 'error
Instead, I would need to iterate each cell and assign it to the next item in the range-type array, which would allow me to treat items the way I'd refer to cells, but take substantially longer to load as I'm dealing with a Loop.
Also how would I dump a range-type array back into the sheet with the same ease and effectiveness of the one liner assignment? I think the only way would be to use a loop yet again, correct?
Side question :
Speedwise, is it any better to refer to cells via a range-type array over referring to cells directly via the sheet, or are both basically the same?
Thanks!
Well, the array use will save a lot of code running time. But, there are some issues which must be understood:
First thing when work in VBA and your project increases, is to properly declare your variables. Try making a reflex in putting Option Explicit on top of all your modules. In the array case, the thing, from this point of view stays like that:
Dim Arr() As variant, arr1 As Variant
Both declarations work in excel. But the second one is recommended (on mai taste), when you need an array from a range. When you want building a, let us say, result array, it will be zero based and you must take care of the range size where the values will be returned.
The array content cannot be retrieved exactly like you tried in your question in case of not fix/known number of elements. Look at the next test code:
Sub testArrays()
Dim sh As Worksheet, rng As Range, arrTest As Variant
Set sh = ActiveSheet
Set rng = sh.Range("A1:F4")
arrTest = rng.value
sh.Range("J1").Resize(UBound(arrTest, 1), UBound(arrTest, 2)).value = arrTest
End Sub
It is recommended to use arrTest = sh.Range("A1:F4").value. Using range Value. Excel is able to understand what you need according to your declaration, but it is good for you to differentiate somehow, from the way of the range definition.
Sometimes, you need to build an array during analyzing of the dynamic range. If you cannot know the new array dimensions and need to Redim (Preserve), only the second dimension of the array can be re-dimensioned and Transpose function must be use, in such a case. And finally the resulted array can be properly loaded in a range, only if you know the array number of rows and columns.
You can deduce the range row, from the array row, in the next way:
If we are referring to the above arrTest we know that its first row is first row of the sheet and it has 5 columns.
So, arrTest(3, 1) will be sh.Range("A3").Value and its row would be 3.
Then, arrTest(3, 4) will be sh.Range("D3").Value and its row would be also 3.
If your array comes from a range starting with the fifth row, you must add four in order to obtain the sheet row extracted from the array row...
So, your example can be transformed in:
If arrTest(3, 4) ="Pending" then sh.Cells(3, 4).Font.Bold=1 Else sh.Cells(3, 4).Font.Bold=0
Now if you need a ranges array, you cannot do it in the way you tried. You must use the ranges address and build the range at the end:
Sub testArraysBis()
Dim sh As Worksheet, rng As Range, rng1 As Range, lastCol As Long
Dim rng2, arrTest As Variant, arrT As Variant, arrF As Variant
Set sh = ActiveSheet
lastCol = sh.Cells(1, Cells.Columns.Count).End(xlToLeft).column
Set rng = sh.Range(sh.Cells(1, 1), sh.Cells(4, lastCol))
Set rng1 = sh.Range("A5:F6")
arrT = Array(rng.Address, rng1.Address)
arrTest = rng.value
Debug.Print UBound(arrTest), LBound(arrTest)
sh.Range("J1").Resize(UBound(arrTest, 1), UBound(arrTest, 2)).value = arrTest
Set rng2 = sh.Range(arrT(0))
Debug.Print rng2.Address
arrF = sh.Range(arrT(0)).value
Debug.Print UBound(arrF, 2)
End Sub
rng2 range will be built using the address string, extracted from arrT array. An array (arrF) can also be extracted from the arrT first element...
Epilog:
The best way, in terms of speed, is to load the range in arrays, make all processing using them (in memory and very fast due to this aspect), but the most important issue is to build another array (or even a range, using Union) and retrieve the data AT ONCE. Sending of each partial processing result to a cell/range consumes a lot of time and other resources, for a big range size...
I want to populate an array with the values of a range in other sheet different than the active one. I do need to load into arrays data ranges values from many different sheets and then made thousands of operations.
I cannot be thinking about activating sheets, using loops, or even worse accessing the data in the sheets by cells(). And I want to write a neat and clean code avoiding loops for inteligibility.
I started by:
dim claimsarray as variant
claimsArray = Range(Cells(1, 1), Cells(a, b)).Value
a and b are integers
It seems to work. No error. But the values of the array are empty because the array is populated with the values of the active sheet. I want to get the values froma sheet called "claims".
claimsArray = sheets(“claims”).Range(Cells(1, 1), Cells(a, b)).Value
that gives me an error 1004
looking for solutions in stackoverflow I tried the following modifications:
FIRST
worksheets() instead of sheets():
claimsArray = worksheets(“claims”).Range(Cells(1, 1), Cells(a, b)).Value
gives me an error 1004
SECOND
Changing the dimensioning of the array
dim claimsarray as variant
vs
dim claimsarray() as variant
all combinations give me error 1004
It seems to me that you can populate an array in this way only in the active sheet. So I modify:
sheets(”claims”).activate
claimsArray = worksheets(“claims”).Range(Cells(1, 1), Cells(a, b)).Value
it works.
THE QUESTION:
How to populate an array from a different sheet without using loop and without having to activate it.
Why cant I not refer to other sheet to populate the array? Where is the flaw? is it just that VBA is weak code?
NOTE1:
I read many websites about populating arrays with ranges:
http://www.cpearson.com/excel/ArraysAndRanges.aspx
https://excelmacromastery.com/excel-vba-array/
at non avail. They don't really deal with this particular problematic.
NOTE2:
I ended up using the typical loop_:
For i = 1 To a
For j = 1 To b
claimsArray(i, j) = Sheets("claims").Cells(i, j).Value
Next j
Next i
5 lines instead of one. Makes the code so much longer and conbersome...
thanks to #banana I understood where is "the flaw" of passing a range to an array.
claimsArray = sheets(“claims”).Range(Cells(1, 1), Cells(a, b)).Value
does not work properly when "claims" is not the active sheet because Excel gets confused as to which cell is actually cells(1,1)
therefore the elegant and efective solution is simply telling excel this way:
dim ST as worksheet
dim claimsArray as variant
set ST = thisworkbook.sheets("claims")
claimsArray = ST.Range(ST.Cells(1, 1), ST.Cells(a, b)).Value
It is also very important as #banana pointed out in the coments that ST is initialize as to refering to "thisworkbook" to avoid that several workbooks are open having a sheet called "claims".
This whole problematic might be the reason why then populating arrays with loops is at the very end the less problematic way.
In my case it worked when I added the Sheets reference also inside the Range inputs (in each cell reference):
claimsArray = Sheets(“claims”).Range(Sheets(“claims”).Cells(1, 1),Sheets(“claims”).Cells(a, b)).Value
I'm trying to get the values from an array formula into VBA as an array. Simple example: I have a cell (let's say D1) which has an array formula in it such as
=A1:A10*B1:B10
when I highlight this on the spreadsheet and press F9 I'll get an array of 10 numbers, say, ={5;12;15;24;25;24;42;40;54;70}
I want to be able to store these values in a VBA array: a(0)=5, a(1)=12, a(3)=15 etc; you get the idea.
Tried hunting for an answer on this one, but can't find anything relevant even on MSFT. Lots of answers about how to go the other way from VBA to the worksheet range (I know that one) but not this way. Looked at trying to do it via a ParamArray (the number of elements won't be fixed), by assigning directly to a undimensioned array and via EVALUATE(range) but I can't get any of these to work. I feel I must be missing something obvious.
Not sure what you have tried already. But Evaluate should work.
If I have the following:
The code:
Sub getArrayFormulaResult()
sFormula = Range("D1").FormulaArray
aResult = Evaluate(sFormula)
MsgBox Join(Application.Transpose(aResult), ",")
End Sub
will result in:
try this
Dim var as variant
var=Worksheets("MySheet").Evaluate(Worksheets("MySheet").Range("D1").formula)
Note you should use Worksheet.Evaluate to ensure this works when Mysheet is not the active sheet. The result will always be a 2_D array (with one column for your example array formula)
I am redesigning a part of a webpage to make it easier to update in the future. Currently, it is a series of tables, that are hard-coded. To redesign the table (for example, to alphabetize it like I want to), requires manually swapping around a lot of values in the html.
This is what I'd like to do:
Create a url_Link object with a title and link variable, to hold the display name and the url respectively.
Create an array of url_Link objects, and populate it at the top of the .asp file for the page.
Perform a for each loop on those arrays to build and populate the table
This itself isn't so bad, but I run into two problems.
First, I'd like to not have to define the array size, as this makes a second place that has to be changed when changes are made to the number of links.
There will be some logic to prevent certain url_Link objects from being displayed (for example, some users can't access certain pages, so they will not see links), and this would cause issues when sizing the arrays.
I know that I could just make the arrays of a large size, but this seems wasteful to me (and I don't know how for each functions and do not want a bunch of empty rows to show up).
What can I do to resolve these problems? I'm not very knowledgeable in vbscript, and most of the code that I have been working with does not take advantage of arrays or objects.
UPDATE:
I've tried using a redim PRESERVE to trim the excess fat of an oversized array. The problem is that in some cases, my array is populated by smaller amounts of objects than its max size because of if conditions. This is causing problems later when I use a for loop (tried to get a for each to work and that is not happening at the moment). I get the error "This array is fixed or temporarily locked" on the redim line
Code:
dim systemSettingsArray(1)
arrayCounter = 0
if ADMIN = "Y" then
set systemSettingsArray(arrayCounter) = (new url_Link).Init("Account Administration","Maintenance/Account_Admin.asp")
arrayCounter = arrayCounter + 1
end if
set systemSettingsArray(arrayCounter) = (new url_Link).Init("Time Approval","Maintenance/system_Time_Approval.asp")
redim Preserve systemSettingsArray(arrayCounter)
To show the correct way to use dynamic arrays in VBScript and to prove Matt's comment wrong:
Option Explicit
ReDim a(-1)
Dim b : b = Array()
Dim c()
Dim i
For i = 0 To 1
ReDim Preserve a(UBound(a) + 1) : a(UBound(a)) = i
ReDim Preserve b(UBound(b) + 1) : b(UBound(b)) = i
On Error Resume Next
ReDim Preserve c(UBound(c) + 1) : c(UBound(c)) = i
WScript.Echo Err.Description, "- caused by Dim c()"
On Error GoTo 0
Next
WScript.Echo "a:", Join(a)
WScript.Echo "b:", Join(b)
output:
Subscript out of range - caused by Dim c()
Subscript out of range - caused by Dim c()
a: 0 1
b: 0 1
Update wrt comment:
Both the a and the b way are correct - you get an one dimensional dynamic array to which UBound() can be applied from the start. Some people may prefer b, because they don't like ReDim v without a previous Dim v; other may feel that b is clumsy or errorprone.
If you look at this problem about a two-dimensional array, you may come to the conclusion, that the a way scales better.
Use redim preserve on the array. You can use UBound to find the current number of elements and do something like
ReDim Preserve myArrayName (UBound(myArrayName) + 1)
http://msdn.microsoft.com/en-us/library/c850dt17%28v=vs.84%29.aspx