I am creating a dynamic report summarizing data on multiple XLS sheets all within a single workbook. The sheets have names that tie to a specific date.
This is simplified example of what I am doing, which works fine - it gives the correct answer which is the value of the cell at reference BB38 on the sheet called "221122":
=LAMBDA(r,INDIRECT("'" & r & "'!BB38"))("221122")
Problem comes when I want to iterate this over an array of sheets using BYROW instead of just passing the sheet name to the lambda. Simple example to replicate the problem:
=BYROW({"221122"}, LAMBDA(r,INDIRECT("'" & r & "'!BB38")))
This gives a #VALUE! error, instead of the correct answer which is the reference to that same cell (as part of a one cell dynamic array result). The only way I can solve it is by adding a SUM around the INDIRECT:
=BYROW({"221122"}, LAMBDA(r,SUM(INDIRECT("'"&r&"'!BB38"))))
Apart from being ugly, what I REALLY want to do is get a group of cells (spilled) back, like this, but then I can't use the SUM trick:
=BYROW({"221120","221121","221122"}, LAMBDA(r,INDIRECT("'"&r&"'!BB38:BD38")))
So that I am aiming towards is a spilled range like this:
Column A
Column B
Column C
221120!BB38
221120!BC38
221120!BD38
221121!BB38
221121!BC38
221121!BD38
221122!BB38
221122!BC38
221122!BD38
I know that you can't pass a dynamic function to INDIRECT but that's not what I am doing here - I am passing a single row of the dynamic array, represented by r.
In comment, Harun24hr points out correctly that BYROW can't return a dynamic array - that's why SUM worked. My own 'hack' way around this was to get the individual 1xN ranges of cells representing BB38, BC38 and BD38 and then HSTACK them together, e.g.:
a, BYROW(sheets, LAMBDA(r, SUM(INDIRECT("'" & r & "'!AY38")))),
b, BYROW(sheets, LAMBDA(r, SUM(INDIRECT("'" & r & "'!AZ38")))),
c, BYROW(sheets, LAMBDA(r, SUM(INDIRECT("'" & r & "'!BA38")))),
d, BYROW(sheets, LAMBDA(r, SUM(INDIRECT("'" & r & "'!BB38")))),
HSTACK(a,b,c,d)
Real question is: is there a more elegant / scalable way than HSTACK 1xN columns together?
Any ideas please? Thank you.
How about this approach:
=LET(start,221120,
end,221122,
DROP(REDUCE(0,SEQUENCE(1+end-start,,start),LAMBDA(s,e,VSTACK(s,INDIRECT("'"&e&"'!BB38:BD38)))),1))
Or simple =VSTACK('221120:221122'!BB38:BD38) based on this answer by JvdV: https://stackoverflow.com/a/74077560/12634230
Related
I'm trying to do Vlookup for target data in different rows.
But unfortunately, I can't find the way to do it.
Vlookup, Hlookup can't get the target data correctly.
Can you advise?
Resource Data Sheet
Result Output (expectation)
Matching ID number consist of these target data.
Dim A As String
Dim B As String
Dim C As String
Dim D As String
Dim E As String
Dim F As String
A = Application.WorksheetFunction.Index(iSheet.Range("G1"), Application.WorksheetFunction.Match(iSheet.Range("A11"), iSheet.Range("A3:A5"), 0))
B = Application.WorksheetFunction.Index(iSheet.Range("I1"), Application.WorksheetFunction.Match(iSheet.Range("A11"), iSheet.Range("A3:A5"), 0))
C = Application.WorksheetFunction.Index(iSheet.Range("K1"), Application.WorksheetFunction.Match(iSheet.Range("A11"), iSheet.Range("A3:A5"), 0))
'*** Error Thrown On Next Line (unable to get the index property of the worksheetfunction class)
D = Application.WorksheetFunction.Index(iSheet.Range("G1"), Application.WorksheetFunction.Match(iSheet.Range("A12"), iSheet.Range("A3:A5"), 0))
E = Application.WorksheetFunction.Index(iSheet.Range("I1"), Application.WorksheetFunction.Match(iSheet.Range("A12"), iSheet.Range("A3:A5"), 0))
F = Application.WorksheetFunction.Index(iSheet.Range("K1"), Application.WorksheetFunction.Match(iSheet.Range("A12"), iSheet.Range("A3:A5"), 0))
iSheet.Range("C7").Value = A & ", " & B & ", " & C
iSheet.Range("C8").Value = D & ", " & E & ", " & F
You're on the wrong track with VLookup() and HLookup().
Try using this: (on multiple lines for readability purposes)
=TEXTJOIN(",",
TRUE,
IF(NOT(ISBLANK(I3)),"POWER", ""),
IF(NOT(ISBLANK(K3)),"MB", ""),
IF(NOT(ISBLANK(M3)),"ANT", "")
)
Let me explain:
TextJoin() concatenates information, based on:
"," : the delimiter to be used
TRUE: how to handle empty cells
the arguments to be concatenated
IF(NOT(ISBLANK(I3)),"POWER", ""): if I3 is filled in, you need to put the word "POWER". If not, put an empty string.
Are you looking for something like this?
Right... so, this will technically do what you have asked for. But I suspect that it may not be exactly what you actually want. I am afraid your original question was quite unclear and I have had to make some guesses/assumptions.
=CONCAT(
IF(INDEX($H$3:$H$5,MATCH($A11,$A$3:$A$5,0))<>"",$G$1 & ", ",""),
IF(INDEX($J$3:$J$5,MATCH($A11,$A$3:$A$5,0))<>"",$I$1 & ", ",""),
IF(INDEX($L$3:$L$5,MATCH($A11,$A$3:$A$5,0))<>"",$K$1 & ", ","")
)
The CONCAT function just adds strings together, one after the other.
Within it, we have one formula for each of your merged column headers.
Please note: If you have lots of column headers, this probably won't be a viable solution. However, making a formula to handle a dynamic number of headers would be far more complex and I would advise re-organising your data to allow use of a simpler formula instead.
So, the inner formula:
IF(INDEX($H$3:$H$5,MATCH($A11,$A$3:$A$5,0))<>"",$G$1 & ", ",""),
Similar to how VLOOKUP would, the INDEX/MATCH is returning the POWER reference number for the specific ID Num. If there is no Ref No., it returns an empty string. If there is a Ref No., this is returned along with a trailing comma and space.
This is repeated for each of your Ref columns, and then CONCAT sticks all the results together. This does leave a trailing comma, however, that is relatively simple to remove if required, and I will leave that for you to work out.
As for returning the Ref No.'s themselves this can be done in the same way, but instead of returning the header, you repeat the INDEX/MATCH formula:
=CONCAT(
IF(INDEX($H$3:$H$5,MATCH($A11,$A$3:$A$5,0))<>"",INDEX($H$3:$H$5,MATCH($A11,$A$3:$A$5,0)) & ", ",""),
IF(INDEX($J$3:$J$5,MATCH($A11,$A$3:$A$5,0))<>"",INDEX($J$3:$J$5,MATCH($A11,$A$3:$A$5,0)) & ", ",""),
IF(INDEX($L$3:$L$5,MATCH($A11,$A$3:$A$5,0))<>"",INDEX($L$3:$L$5,MATCH($A11,$A$3:$A$5,0)) & ", ","")
)
Why Not VLOOKUP?
You may wonder why I used an INDEX/MATCH formula, rather than a VLOOKUP formula.
VLOOKUP would work, however, INDEX/MATCH is better in literally every way. To my knowledge, there is NEVER a situation where VLOOKUP would be a better option.
INDEX/MATCH is faster. When you have hundreds or thousands of formulas, you can often speed up a workbook drastically simply by swapping out all of the VLOOKUPs.
INDEX/MATCH is more versatile. It can work on rows or columns and your lookup range can be anywhere. You can even lookup a value in a 2D table using INDEX/MATCH/MATCH.
INDEX/MATCH is more stable. Moving or re-ordering columns doesn't break your formulas.
INDEX/MATCH is easier to use. It may look confusing if you are used to VLOOKUP, but once you understand it, it is actually easier to use because you can directly reference the ranges, rather than counting the number of columns accross.
Cannot get SUM(SUMIF to work with VBA, yields “Run-time error 1004”. SUM(SUMIF is typically used in Excel to sum using multiple criteria.
This is my formula.
Worksheets("units").Cells(i, j).FormulaR1C1 = "=pws!R[0]C[0]+SUM(SUMIF(pws!R1C3:R1C" & (2 + numlxp) & ",central!C[1]R3:C[1]R" & (2 + numatt) & ",pws!R[0]C3:R[0]C" & (2 + numlxp) & "))"
I also tried a version using OFFSETS instead of R1C1. The OFFSETS versions works within Excel itself, but not in VBA.
I then tried simplifying to the below, to see if there was an issue with SUM(SUMIF specifically within VBA, but it returns a formula with # for implicit intersectionality and gives the wrong result ‘0’:
Worksheets("units").Cells(2, 2).Formula = "=SUM(SUMIF(pws!C1:L1,central!C3:C6,pws!C2:L2))"
Try using SUMPRODUCT combined with unary operator:
Yellow cells would be the criteria (in your case, central!C3:C6).
Formula in cell C6 is:
=SUMPRODUCT(--(COUNTIF($A$6:$A$7,$C$1:$L$1)>0)*$C$2:$L$2)
Formula in cell C7 is:
=SUMPRODUCT(--($C$1:$L$1=$A$6:$A$7)*$C$2:$L$2)
First formula ignore duplicates in criteria and second one does not. Second one is easier to implement but if your criteria has duplicate values, it will duplicate result, so watch out and choose wisely. Just as example, notice if both of my criterias are the same, each formula returns a different output:
Anyways, when you are done choosing, you should be able to implement this in VBA code like this (or kind of):
Worksheets("units").Cells(2, 2).Formula = "=SUMPRODUCT(--(COUNTIF(central!$C$3:$C$6,pws!$C$1:$L$1)>0)*pws!$C$2:$L$2)"
SUMPRODUCT and Double Unary
Operators
This is my first post and I can't find an exact answer anywhere. I have an Excel spreadsheet that is becoming too large to operate because I have long formulas in millions of cells. I need to know how to calculate the formula using VBA but have only the value appear in Excel. An example is I want to multiply column B by column C:
I have tried this code:
Range("D3:D6").Formula = Evaluate("=B3*C3")
It correctly calculates for the first cell, but for the other cells it still tries to calculate B3*C3 as a fixed reference rather than as a dynamic references that changes as the cell position changes.
How could I fix this? Thankyou.
Edit:
My actual spreadsheet looks like this:
This formula needs to be applied down 17520 rows and across 300 columns, but I only want the values to appear in Excel. The purpose of this is to reduce file size and reduce calculation time.
=IF(-SUMIF(AIG$4:AIG4,"<0")>0.9*SUMIF(AIG$4:AIG4,">0"),0,IF(-SUMIF(AIG$4:AIG4,"<0")-IF(WQ5<'Battery Specs'!$B$13,-'Battery Specs'!$B$15,IF(WQ5=ROUNDDOWN('Battery Specs'!$B$13,0)+1,-('Battery Specs'!$B$13-ROUNDDOWN('Battery Specs'!$B$13,0))*'Battery Specs'!$B$15,0))>0.9*SUMIF(AIG$4:AIG4,">0"),-(0.9*SUMIF(AIG$4:AIG4,">0")+SUMIF(AIG$4:AIG4,"<0")),IF(WQ5<'Battery Specs'!$B$13,-'Battery Specs'!$B$15,IF(WQ5=ROUNDDOWN('Battery Specs'!$B$13,0)+1,-('Battery Specs'!$B$13-ROUNDDOWN('Battery Specs'!$B$13,0))*'Battery Specs'!$B$15,IF((0.9*SUMIF(AIG$4:AIG4,">0")+SUMIF(AIG$4:AIG4,"<0"))>'Battery Specs'!$B$10,0,IF(((0.9*SUMIF(AIG$4:AIG4,">0")+SUMIF(AIG$4:AIG4,"<0"))+$AIF5*0.9)>'Battery Specs'!$B$10,'Battery Specs'!$B$10-(0.9*SUMIF(AIG$4:AIG4,">0")+SUMIF(AIG$4:AIG4,"<0")),$AIF5))))))
Try
Range("D3:D6").Formula = "=B3*C3"
Range("D3:D6").Value = Range("D3:D6").Value
or simply
Range("D3:D6").Formula = Evaluate("(B3:B6)*(C3:C6)")
Using Evaluate to calculate the formulas and then transfer the values to Excel will be slower than having Excel calculate the formulas directly.
Here are a couple of things you could try:
a) Formulas
You only have 5 million formulas in 5 million cells - this is not an overwhelmingly large number but your formulas are very long and each formula references a large number of cells.
Your formula contains many repeated expressions and calculations - move them out to helper cells and try to simplify/shorten your formula.
b) VBA - don't use Evaluate - just grab the range of 5 million cells into a single variant array and use VBA Loops, arithmetic and logic to accomplish the same task as your formulas and then put the array back.
I believe best option is to keep formulas instead of using VBA to produce same formulas. Instead, when entering new data in SpreadSheet, be sure to set calculations to manual (go to Formulas tab=>Calculation Options=>Manual. This way you wont trigger calculations when entering/pasting new values, meaning your Excel file will operate just as fast as if there was no formulas. After you entered/pasted all new values, set Calculations to Automatic.
You might want to try this:
Dim i as Integer
For i = 3 to 6
Range("D" & i).Value = Range("B" & i).Value * Range("C" & i).Value
Next i
The other alternative is to name your formula, for instance, instead of saying =B3*C3, you can give it a name say times value, and then say =timesvalue. Next, remove the formula and leave the value only. E.g. range("D3:D30").value=range("D3:D30").value
I would like to put the below coding into a vba like a function. There is a bunch of data created already by VBA, and when the VBA does its work, then the following function should be run, but i dont know how to add to my vba so that the function always runs as long as data contains. The macro i created already puts the datasheet together, now instead of creating the below with lenthy codings, i just want my macro to run the below, like a man who clicks on the below right hand corner of the cell which contains the below function.
It should be something: Activesheet.ForulaR1C1 = "=RIGHT(AY4,LEN(AY4)-FIND(".",AY4))" something. Can someone help me? Thanks
ORIGINAL FUNCTION TO BE RUN "=RIGHT(AY4,LEN(AY4)-FIND(".",AY4))"
This is where I am at now:
Sub Project_numbers()
Dim j As Integer
Zorro = Range("AY" & Rows.Count).End(xlUp).Row
o = 4
Worksheets("MJE").Range("AF" & o).FormulaR1C1 = "=RIGHT(AE4,LEN(AE4)-FIND(".",AE4))"
o = o + 1
End Sub
You have a couple of problems here. The biggest is that you've got quotation marks in your formula. VBA reads these as the end of the string, so it's interpreting your formula as two separate text strings: =Right(AE4,LEN(AE4)-FIND( and ,AE4)), separated by a .. This isn't a structure VBA can do anything with, so it's going to fail at that point.
When you're inserting a formula with VBA that contains quotation marks, you need to use two quotes together to indicate that it's a literal quote mark that's part of the string, rather than the end of the string:
"=RIGHT(AE4,LEN(AE4)-FIND(""."",AE4))"
The second problem is that you're using the FormulaR1C1 method, which expects cell references to be given in R1C1 (row#column#) notation, rather than A1 notation, but then passing it a formula that uses A1 notation. Again, this is going to confuse the issue and produce errors.
I'm guessing you used the macro recorder to get the syntax, then inserted your own formula? The macro recorder, for some weird reason, loves to use the R1C1 reference style, but we can use a different method for written code.
The full line you need is:
Worksheets("MJE").Range("AF" & o).Formula = "=RIGHT(AE4,LEN(AE4)-FIND(""."",AE4))"
EDITED TO ADD:
With further information, specifically that you need the range referenced to change as you loop, you have some options on how to do it.
1. Use the R1C1 reference style
This allows you to include relative references in formulae easily. You'll use R to designate the formula's row, and C to designate its column; so a cell that referred to itself would simply be =RC. You can follow the R and C with numbers to designate specific rows and columns, so cell B2 would be =R2C2 - row 2, column 2. More usefully, you can use =R[#]C[#] to offset your formula by a certain amount.
In your formula, assuming it's always going to be looking at column AE but whichever row the formula is entered into, your line would be:
Worksheets("MJE").Range("AF" & o).FormulaR1C1 = "=RIGHT(RC31,LEN(RC31)-Find(""."",RC31))"
2. Build your formula from variables.
You already have a variable you can use, o, so we can combine that with the rest of the string to get the appropriate references. It's harder to read, though...
Worksheets("MJE").Range("AF" & o).Formula = "=RIGHT(AE" & o & ",LEN(AE" & o & ") - FIND(""."",AE" & o & "))"
Personally, I find this method rather cumbersome to work with, but it's an option.
3. Assign the formula to your entire range as a single operation
Personally, I prefer this option; I find it to be the neatest one. I'm assuming, from your formula, that your data starts on row 4, and you want the formula to go into every cell between AE4 and the end of your data, which is stored in Zorro. You can use this line to add the formula in one go:
Worksheets("MJE").Range("AF4","AF" & Zorro).Formula = "=RIGHT(AE4,LEN(AE4)-FIND(""."",AE4))"
The cell references will update automatically for each row. There's no need for a loop with this method - of course, if you're looping anyway, that may be no great saving.
I'm using Excel VBA to insert an HLOOKUP function at the end of a given row. I've been attempting to use the R1C1 functionality in VBA for inserting the HLOOKUP function, which should appear as follows in Excel:
=HLOOKUP(D2,'DM NYASSOV'!3:34,32,0)
My issue is that I need the HLOOKUP to be dynamic enough that it can reference variables from the same row on which the HLOOKUP function is to be pasted.
Currently my VBA reads as follows:
ThisWorkbook.Sheets(selectTab).Cells(r, 21).FormulaR1C1 = "=HLOOKUP(RC[-17],'DM RC[-19]!'RC[2]:RC[3],RC[3]-RC[2]+1,0)"
RC[-17] references the variable I'm looking for;
RC[-19] is the underlying book/tab identifier;
RC[2] contains the initial row value, and
RC[3] contains the final row reference range.
My main issue is with correctly identifying the dynamic range selection:
'DM NYASSOV'!3:34 / 'DM RC[-19]!'RC[2]:RC[3]
Any pointers are greatly appreciated.
I think you have a few issues here. First is using VBA to dynamically build a formula that then calculates a third value that then gets used in the formula which finally calculates a result. This seems like an unnecessary amount of runaround for a value that can be done with either a formula built on the front end or calculated via VBA in the back end. The second is all the dynamic lookup of it all. It's not so much a problem, but rather a lot to keep track of as you jump through the four hoops. Just the same...
The first parameters of your HLOOKUP can either be a value like "SM1804" or a reference to a cell that contains "SM1804". You can either use VBA to bring this value directly into the formula and save Excel some processing by having to lookup that value at formula processing time:
ThisWorkbook.Sheets(selectTab).Cells(r, 21).FormulaR1C1 = "=HLOOKUP(" & Sheets(selectTab).Cells(r, 21).Offset(0, -17).value & ",'DM RC[-19]!'RC[2]:RC[3],RC[3]-RC[2]+1,0)"
Or you can stick the reference to the cell in there (which is what you are doing now):
ThisWorkbook.Sheets(selectTab).Cells(r, 21).FormulaR1C1 = "=HLOOKUP(RC[-17],'DM RC[-19]!'RC[2]:RC[3],RC[3]-RC[2]+1,0)"
On to the second parameter... I believe you have a sheet name in DM!RC[-19] This is totally OK, but you'll need to use 'Indirect' to get that sheet name into the HLOOKUP formula:
ThisWorkbook.Sheets(selectTab).Cells(r, 21).FormulaR1C1 = "=HLOOKUP(RC[-17],Indirect("'" & DM!RC[-19] & "'!RC[2]:RC[3]"),RC[3]-RC[2]+1,0)"
...this is where things get a little tricky. If, for instance, in DM!RC[-19] you have the sheetname "Sheet1" then indirect is going to return: 'Sheet1'!RC[2]:RC[3] and your HLOOKUP will use that range to do the lookup... Doesn't make a lot of sense to do a HLOOKUP on a range with two cells. So I assume that you have number values in RC[2] and RC[3] there that represent rows. So really the indirect would have to look like:
ThisWorkbook.Sheets(selectTab).Cells(r, 21).FormulaR1C1 = "=HLOOKUP(RC[-17],Indirect("'" & DM!RC[-19] & "'!R" & RC[2] & "C:R" & RC[3] & "C"),RC[3]-RC[2]+1,0)"
Now if your RC[2] has the value "4" and your RC[3] has the value "20" your indirect will return: 'Sheet1'!R4C:R20C and your HLOOKUP will use that value as the range in which it will look up.
You are also doing some math on those values in RC[2] and RC[3], so that's probably all good and doesn't need to be changed. You just need to keep in your head what VBA is going to return as a formula, and then what that formula is going to get from Indirect and then what the resulting HLOOKUP is going to find.
It's a lot to keep track of and may be simplified just by writing the formulas directly in the cell and copying down, or just doing the HLOOKUP functionality directly in VBA.