Variable number of terms in an Excel VBA Formula? - vba

Is it possible to write a formula in VBA for excel such that there are "n" terms in the formula, with the number of terms changing as the value of "n" does?
For instance, say you wanted to code cell a1 such that it was the sum of a2 and a3. Then you wanted b1 to be the sum of b2,b3,b4,b5 and so on such that each column 1 row 1 cell for a range of cells is the sum of "n" cells below it where "n" varies from column to column. Say that all cell addresses you wanted to use are known and stored in an array.
Here is some code to better explain what I'm asking:
For i = 0 to n
Range(arr1(i)).formula = "=" & range(arr2(i)).value & "-(" _
& Range(arrk(i)).value & "+" & Range(arrk+1(i)).value & "+" _
& Range(arrk+2(i)).value & "+" & ... & ")"
Next i
So what I'm looking for is one piece of VBA code that can make a cell formula contain a dynamic number of terms. The code above would make cell a1's value = a-(b+c+d+...) where the number of terms in the bracket is variable, depending on which cell the formula is applied to.
The image here shows an example of what I want to do. I'd like some code which could take "years income" and subtract a variable amount of "expenses" from it, where the number of expenses varies each year (but the number stays fixed for that year). The code needs to use a formula so that the expenses entries can be modified by the user.

Have you tried Array Formula ? :
Array Formula :
An Excel Array Formula performs multiple calculations on one or more sets of values (the 'array arguments') and returns one or more results.
details : http://www.excelfunctions.net/Excel-Array-Formulas.html

Thanks for the suggestions everyone, I found a solution (not a particularly efficient one, but a solution nonetheless) to the conundrum today.
First I created an array which used the "pattern" of the Junk cells to list every cell address which was to be included.
Taking this array, I used a for loop to create a series of temporary arrays with the application.index command. For each temporary array, I used the Join command to turn the list of cells into a single string which I then inputted into a cell formula. Thanks to #thepiyush13 whose array.formula approach inspired this.
Here's some example code to show what I did:
' hypothetical array containing two sets of cells to use
Dim array1(0 To 1, 0 To 1) As Variant
Dim vartemp As Variant
Dim vartemptransposed As Variant
' col 1 will be used to add I10 and I13, col2 I11 and I14
array1(0, 0) = "$I$10"
array1(1, 0) = "$I$13"
array1(0, 1) = "$I$11"
array1(1, 1) = "$I$14"
For i = 1 to 2
'application.index(arr,row#,col#) to create a new array
vartemp = Application.Index(array1, 0, i)
'error if not transposed
vartemptransposed = Application.Transpose(vartemp)
randomstring = Join(vartemptransposed, ",")
totalvalue = 100
'example formula: a1 = totalvalue - sum(I10,I13). a2 = totalvalue - sum(I11,I14)
Cells(1,i).formula = "=" & totalvalue & "-SUM(" & randomstring & ")"
Next i
I needed the code to run this many many times on large lists which are generated dynamically but always hold the same pattern of where the "junk cells" are. Not included in the code, but I also used another array for the cell addresses of where to place the formula.

Related

Concatenating data descriptions and aligning to data range

I have been trying to learn visual basic to write code to perform tasks when working with data in excel. I have been mostly copying snippets of code I find online and piecing them together. Currently, I have folders containing 10's of thousands of .csv files (data output from a CMM).
In each of these files column A consistently contains labels for the data and column B consistently contains the CMM data.
Currently, my program allows a user to select multiple .csv files and in a long roundabout way they all end up on one worksheet in excel with the data labels in the first column and the data in the next columns.
For example, if 10 CSV files are opened the data labels would be in the first column and the data would be in the next 10 columns.
The problem is that that the data labels are not aligned with the data and often each row of data has multiple labels.
I have been able to concatenate the data labels into one label for each row of data but cannot figure out how to align this label with the row of data.
At this point, I would be happy with a separate block of code that accomplishes this but... I suspect that my block of code that concatenates the labels could be easily modified to accomplish the task, I just haven't been able to figure it out.
So my code spits this out:
(Flatness) : Item (113)
Plane:RH_5_Mating_Surface
5.012 4.014 6.313 etc...
(Z) : Item (128) / (X) : Item (135)
Circle:Offset_Dowel_Hole
1.012 2.987 5.478 etc...
Circle:Cast_Hole_From_Offset_Dowel_Hole
2.147 7.895 4.258 etc...
Then this code concatenates the labels and spits them out in column B:
Dim rng1 As Range
Dim Lastrow As Long
Dim c As Range
Dim concat As String
Lastrow = .UsedRange.Rows(.UsedRange.Rows.Count).Row
Set rng1 = Range("A9:A" & Lastrow)
concat = ""
For Each c In rng1
If c > 0 Then
concat = concat & " " & c.Value
concat = Trim(concat)
Else
c.Offset(-1, 1).Value = concat
concat = ""
End If
Next c
The result is:
(Flatness) : Item (113) Plane:RH_5_Mating_Surface
5.012 4.014 6.313 etc...
(Z) : Item (128) / (X) : Item (135) Circle:Offset_Dowel_Hole
1.012 2.987 5.478 etc...
Circle:Cast_Hole_From_Offset_Dowel_Hole
2.147 7.895 4.258 etc...
What I need is:
I cant figure out how to show it here but...
I need the rows to match up, also note, here it shows that the data and labels are offset by the same amount but in reality they are not. So my thinking is that I need it to search for the next row containing data and put the label next to it.
I feel like I can just change this part...
Else
c.Offset(-1, 1).Value = concat
but I don't know how to do...
I tried nesting another "For Each" here instead similar to what its already doing but with a "For Each d In rng2" where "rng2" was the data column and it would look for the next row with data and place "concat" next to the data using "d.offset(-1, -1).Value = concat"
I couldn't figure out how to get it to work...
This is one possible way of doing it.
Delete the empty cells and shift up in the columns. If the data order is consistent, they should line up correctly.
For i=1 to Lastrow
If Range("A" & i).Value=""
Range("A" & i).Delete Shift:=xlUp
End If
If Range("B" & i).Value=""
Range("B" & i).Delete Shift:=xlUp
End If
Next
You can change the range according to your sheet. Also, modify the code to use another 'IF' condition to check blanks, if that works better for your case.

Excel - how to increment formula reference along a row

I have really long strings of text saved in roughly 1000 cells down Column A.
I have created the following formula (some VBA included for FindN), placed in cell B1:
=MID($A1,FindN("978",$A1,1),13)
I can copy this formula down Column B just fine. However, I also want to copy this formula across each row, so for example the formulas for the cells across the row should be as follows:
Cell C1: =MID($A1,FindN("978",$A1,2),13)
Cell D1: =MID($A1,FindN("978",$A1,3),13)
Cell E1: =MID($A1,FindN("978",$A1,4),13)
etc...
If I copy the formula in Cell B1 across the row, it will copy across =MID($A1,FindN("978",$A1,1),13) - but I need the "1" to increment by 1 each time.
I think I'd need to adjust the formula slightly, but a bit lost on how to do this...
Any help would be greatly appreciated. Please let me know if I should clarify further.
Use COLUMN() - it gives the column number of the current cell. You can offset this as required.
In this case for your incrementing number use COLUMN() - 1, so that in B you have 1, C; 2 etc.
You need use CELL formula to get current column number. Try something like this:
=MID($A1,FindN("978",$A1,CELL("column";A1)+1),13)
I dont have English Excel and im not sure first argument in CELL forumla is "column"
Try this :
Sub Fill()
With Sheets("Sheet1")
For i = 1 To .Cells(Rows.Count, "A").End(xlUp).Row
.Cells(i, 1).FormulaR1C1 = "=MID($A1,FindN(" & _
Chr(34) & "978" & Chr(34) & _
",$A1," & i - 1 & "),13)"
Next i
End With
End Sub

VBA CountIf using R1C1 and text

I'm trying to have a formula display the number or occurrences of the previous row of column "C" (RC3).
An ideal result would look like:
Count: 3
If there were 3 occurrences of the value in the previous row.
The following code returns "False".
y.FormulaR1C1 = "Count: " + "=Application.WorksheetFunction.CountIf(range(R3C3:R" & LR & "C3), " = "&R[-1]C3)"
y is a cell
LR is the last row
Thanks, and let me know if I can clarify further.
The CountIf Function is never going to count anything as long as it's inside a test string. And you don't need 'Application.WorksheetFunction' in a cell formula (only if you want to use that function entirely in VBA). Also you don't need a range function in an Excel formula. Try this:
y.FormulaR1C1 = "=""Count: ""&CountIf(R3C3:R" & LR & "C3,R[-1]C3)"
Though if there is nothing between your search range and your criteria cell, you could eliminate LR altogether with:
y.FormulaR1C1 = "=""Count: ""&CountIf(R3C3:R[-2]C3,R[-1]C3)"
If you are trying to make a formula that combines TEXT and builtin functions, use =CONCATENATE(). consider the example below:
If you are to use VBA to fill in formula with relative location (.FormulaR1C1), it would be:
=CONCATENATE("Count: ",COUNTIF(R[-5]C:R[-2]C,"=" &R[-1]C))

Looking through columns in Excel using VB.NET - Struggling to look past columns "AB" etc

Just had a question about my following code:
For Each c In "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
If Not String.IsNullOrWhiteSpace(exsheet.Range((c & "5")).Value.ToString) Then
pointer = pointer + 1
XRisks(pointer) = exsheet.Range(c & "6").Value.ToString
End If
Next
In essence, the code looks through the 5th row of each column in an excel spreadsheet(called exsheet). If there is something in that cell, it saves it into the array (XRisks).
However, my excel spreadsheet goes past Z and continues to AA AB AC AD etc in the column headings, which means, with the code I have at the moment, it stops reading columns after column Z.
I tried adding & "AA" & "AB" & "AC" etc to the end of my alphabet string but all this did was saved A again, then A again, then A again, then B again as it is just looking at each character in my string.
Hope I have described that enough. Thanks in advance!
Joe
You can simply use .Cells instead of .Range with an index rather than letters.
For i As Integer = 1 To 50
If Not String.IsNullOrWhiteSpace(exsheet.Cells(5, i).Value.ToString) Then
...
Next
To iterate over all columns, you can try usedRange. This may be insecure, however, since the usedRange does not necessarily start on the first column if that is empty (that said, with most table-like sheets it is safe enough).
For i As Integer = 1 To exsheet.UsedRange.Columns.Count
If Not String.IsNullOrWhiteSpace(exsheet.Cells(5, i).Value.ToString) Then
...
Next

Excel pulling data from certain cells

I have a file that I only want to extract cells B9, B19, B29, etc etc etc in a pattern throughout the entire file. I would preferably like it to be extracted to a different excel file or someway so that I can do stuff with only those cells in another excel worksheet.
Potentially, I may have several excel files that I may need to do this sort of thing so if there were a way where I had the same format throughout a lot of files that I could always extract cells B9, B19, B29 that would be great. any help appreciated
I looking for syntax if possible
EDIT
Was thinking if I could somehow make an excel IF statement saying if Row has a 9 in it and the row is B then print it somewhere but I want it printed in a column
EDIT 2
I just want column B not A like I mentioned before.
B9, B19,B29,B39 through the whole file
Just in case you want to do it with code:
Sub Test()
'Assumes Sheet1 has your values and Sheet2 will be the data extracted from every row ending in 9
Dim iCounter As Long
Dim newSheetRow As Long
Dim aValue As String
Dim bValue As String
newSheetRow = 1
'Start and nine and increment by 10 till you reach end of sheet
For iCounter = 9 To Sheet1.Rows.Count - 1 Step 10 'NOTE: You may not want to do it by RowCount, but just showing you could
aValue = Sheet1.Range("A" & iCounter)
bValue = Sheet1.Range("B" & iCounter)
Sheet2.Range("A" & newSheetRow).Value = "We were on row: " & iCounter
Sheet2.Range("B" & newSheetRow).Value = aValue
Sheet2.Range("C" & newSheetRow).Value = bValue
newSheetRow = newSheetRow + 1
Next iCounter
MsgBox "Done"
End Sub
You could use the INDIRECT function. It takes a cell reference as a text string and returns the value in that cell. So instead of using
=data!a9
to get the value in sheet "data" in cell a9, you use
=indirect("data!a9")
You can also use r1c1 notation, like this:
=indirect("data!r9c1",false)
From there you can use the ROW and COLUMN functions to go in steps of 10:
=INDIRECT("data!r"&-1+10*ROW()&"c"&COLUMN(),FALSE)
If you put this formula in A1 of your output sheet and then copy and paste it down and across, it will give you the values in data!A9, data!A19, data!A29,... in cells A1, A2, A3... Depending on how you want your output arranged, you might have to modify the cell reference string.
Depending on how often you want to do this depends on how you need to do it, if it's a one of them some simple excel commands might help.
e.g.
In Cell C1 put the following:
=MOD(ROW(),10)
then replicate this down to the bottom of your data. the command will return the numbers 1 through to 0. You can then filter the data on column C where value is 9 then select the visible rows and copy the data to a new sheet.
ROW() ' this returns the ROW number of cell the command is in.
MOD(number, divisor) ' this basically divides one number by the other and returns the remainder. so row 9 / 10 = 0 remainder of 9, row 19 / 10 = 1 remainder of 9.
Hope this helps.