Currently I have a couple of SUM functions setup in my worksheet.
Lets use H2:H34 for my example: If I add a row within that range, the sum function will automatically adjust. The problem is when I add new rows, it is below the range (below H:33). In this case, I would just have to highlight the column and expand/drag the range to where it needs to be. There are a total of 8 columns that are doing a sum function. Is this only possible by using a macro?
=SUBTOTAL(3,H2:H34)
With your formula being the extent of the range minus one row we can use this:
=SUBTOTAL(3,H2:INDEX(H:H,ROW()-1))
Now as rows are added or deleted the reference will remain intact.
If there is a chance that row 2 would be deleted you will get #Ref errors. To avoid that you can use this formula:
=SUBTOTAL(3,INDEX(H:H,2):INDEX(H:H,ROW()-1))
Now there are no specific cell references and adding or deleting will not effect the formula and the range will be dynamic.
you could use a named range like =sum(range1) and something like;
Select first empty cell in column F starting from row 1. (without using offset )
to find the first empty cell to change the reference for the named range.
Triggering it would be difficult... potentially could use on_save?
or option 2.... bit of a dodge, but can work....
you say your sum is in cell H35... maybe move it to h36, and create a dummy hidden row in 35 (0.1 row height). that way when you add a row, it is always above 35. Use sum H1:H35 and it will always auto update? its not a neat solution, but should work
Use the following code, you need to adjust column "H" to whatever column you need.
Dim sumtest As Variant
sumstest = Application.WorksheetFunction.Subtotal(9, Range("H2:H" & ActiveSheet.Cells(ActiveSheet.Rows.Count, "H").End(xlUp).Row))
Related
I have this formula:
IF(ROWS($Q$27:Q27)<=$P$25,INDEX(DataTable[[#All],[Time]],$P27),"")
and if I drag it to the right, it should automatically read each column respectively; example:
=IF(ROWS($Q$27:R27)<=$P$25,INDEX(DataTable[[#All],[Name]],$P27),"")
^Notice that the first Q27 is fixed, the second Q27 is variable.
I drag this formula to the right by 15 columns, and down to 50 rows. that's 750 formulas in total.
I want to do this in vba, but if I did this, it will be 750 lines of code for each cell representing each row/column.
example: .Range("G17").Formula=IF(ROWS($Q$27:R27)<=$P$25,INDEX(DataTable[[#All],[Name]],$P27),"""")
and if I drag it down, it will automatically pick up what I exactly want, example:
=IF(ROWS($Q$27:Q28)<=$P$25,INDEX(DataTable[[#All],[Time]],$P28),"")
so this formula should be written 750 times in total for the cell range [ A27:N76 ]
Any faster / more dynamic approach? and if possible, can I make it depend on more than 50 lines based on a cell value inside the sheet?
Example:
This should do it all in one line:
Range("A27:N76").FormulaR1C1 = "=IF(ROWS(R27C17:RC[16])<=R25C16,INDEX((DataTable[[#All],[Name]],RC16),"""")"
EDIT: Seems a more that one line of code required after all 😊
The code below will do what you want (this time fully tested)
Sub FillFormulas()
Dim inC%, rgHead As Range
''' Assumes the target sheet is Active.
''' o If that's not the case, change this With statement to reference the target sheet
With ActiveSheet
''' Set rgHead to the Table's header row
Set rgHead = .ListObjects("DataTable").Range.Rows(1)
''' Add the formulas to the target range, column by column updating the table header on the fly
With .Range("A27:N76")
For inC = 1 To .Columns.Count
.Columns(inC).FormulaR1C1 = _
"=IF(ROWS(R27C17:RC[16])<=R25C16,INDEX(DataTable[[#All],[" & rgHead.Cells(inC) & "]],RC16),"""")"
Next inC
End With
End With
End Sub
so this formula should be written 750 times in total for the cell range [A27:N76]
You don't need to do that. If you specify range.Formula, it will fill the proper formulas all the way across and down. Just give it the formula of the top/left most cell.
So, in your case
Range("A27:N76").Formula = "=IF(ROWS($Q$27:R27)<=$P$25 ... "
EDIT: This response had some obvious errors
This has an obvious error (as tested part and then merged to the full thing).
Range(A27:N76).FormulaR1C1 = "=IF(ROWS(R27C17:RC[16])<=R25C16,INDEX((DataTable[[#All],[Name]],$P27),"""")"
I'm still learning VBA. I'm having a requirement in which I need to work on rows which are not empty in a particular column. I tried For loop. But considering 10 thousand entries in the Excel, there were performance issues. Can you suggest a while loop which only jumps to the rows which has value and not through all the rows?
Basically I need the handle of all the rows which are not empty.
SpecialCells is your friend here.
If you want all Cells that have Constant Values in them from Column A, then you can use Columns(1).SpecialCells(xlCellTypeConstants)
If you want all Cells that have a Formula in them from Column A, then you can use Columns(1).SpecialCells(xlCellTypeFormulas)
If you want all Cells that are Blank from Column A, then you can use Columns(1).SpecialCells(xlCellTypeBlanks)
Together, these 3 will give you all cells in the column. This means that "Non Blank" will be xlCellTypeConstants combined with xlCellTypeFormulas. Another name for "combination" is Union, which lets us stick Ranges together
Dim rngNonBlank AS Range
Set rngNonBlank = Union(Columns(1).SpecialCells(xlCellTypeConstants), _
Columns(1).SpecialCells(xlCellTypeFormulas))
Dim rngLoopThrough AS Range
For Each rngLoopThrough In rngNonBlank
'You can use rngLoopThrough.EntireRow to get the entire Row
Next rngLoopThrough
I'd like to reference a single cell in a table, from outside the table, using square-bracket sheet-formula notation.
Something like:
[MyTable[MyField] 3]
or
[MyTable[MyField] 3:3]
-to reference the 3rd row of the MyField column, or:
[MyTable[MyField] MyRow]
-to reference the MyRow row (leftmost row-header) of the MyField column.
Needs to work from outside the table, ie can't use # or #ThisRow.
Not looking for methods involving MATCH, INDEX, OFFSET, etc. Not looking for VBA methods. Just straightforward table-notation. Not looking for manually creating named ranges.
Why? Because, Tables :)
Pre-2013 Excel.
(PS, didn't there used to be a way (pre-Tables) to reference cells by row and column headers? I think it was maybe called "auto-naming", or something like that.)
heh, well this works:
=Table1[Column2] 3:3
So that's progress :)
Just awesome would be a way to reference a row by the contents of left-most column.
Thx!
You can also use index() function as in:
index(MyTable[MyField], 3)
So you get row 3 from the column MyField in table MyTable.
Reference:
https://www.ozgrid.com/forum/forum/help-forums/excel-general/116365-reference-a-single-cell-in-a-table-using-structured-referencing
We can reuse the idea of the intersection operator (i.e. a space between two references) and improve it to have the relative row number of the targeted item in the table, referred to as row_nb here:
=tbl[col] OFFSET(tbl[[#Headers],[col]],row_nb,)
or just without intersection actually (cf. comment below):
=OFFSET(tbl[[#Headers],[col]],row_nb,)
E.g. =Table1[Column2] OFFSET(Table1[[#Headers],[Column2]],2,)
This way you do not depend on the position of the table in the worksheet. Well, it yields a more complicated formula where table name tbl and column name col appear twice. Here are some comments about it:
You can of course keep the OFFSET(...) part only. The downside is that the formula will never return any error if row_nb exceeds the actual number of line items in the table. It'll return e.g. just 0 if the cells below the table are empty.
Keeping a formula that throws an error when we refer to an off-table row, we can further improve it: make it "dynamic" by letting tbl, col and row_nb be parameters:
=INDIRECT(tbl&"["&col&"]") OFFSET(INDIRECT(tbl&"[[#Headers],["&col&"]]"),row_nb,)
Assuming that we've defined tbl, col and row_nb as named ranges. Or else just use cell addresses:
=INDIRECT(A1&"["&A2&"]") OFFSET(INDIRECT(A1&"[[#Headers],["&A2&"]]"),A3,)
I'm not a big fan of INDIRECT but in this case it really comes in handy to let the formula adapt to various tables, columns and line items.
To handle tables that don't start at the first row of a worksheet, we can use the ROW() function. We can determine the first row of the data in the table with:
=ROW(myTable)
Using this and the Indirect() Function we can identify the first cell in a named column with
= myTable[myField] INDIRECT(ROW(myTable) & ":" & ROW(myTable))
The 3rd cell in that column would be:
= myTable[myField] INDIRECT(ROW(myTable)+3 & ":" & ROW(myTable)+3)
This is probably an easy one but I just started using VBA recently. So I defined a single column range of about 10 rows and named it CB_CL_Values.
Lets say the range is {1,2,1,3,2,1,1,1,1,3} and right now it's set up to change every values of the range to 1.
Sub Unit_Options_Reset()
Range("CB_CL_Values") = 1
End Sub
Lets say I only want to change values 4 to 10 of that range and leave the first 3 as they are. How would I write this?
Range("CB_CL_Values").cells(4).Resize(7,1).Value=1
Range("CB_CL_Values").cells(4) is the 4th cell in your defined range. By default Cells(r) (with one parameter) takes the r'th cell counting across and then down in your range, so if your range was more than one column wide you should use Siddharth's notation (eg) .Cells(r,1) (r'th row, 1st column).
Note that if you try to address a cewll which technically isn't in your range e.g.:
Range("A1:A10").Cells(20)
Excel will not complain that code will reference A20.
.Resize(7,1) takes that 4th cell and expands the range to 7 rows in height and one column wide (Resize always expands down and to the right from the starting point)
You can use .Cells property of the range to address an individual cell. For multiple cells, you can use a loop.
Range("CB_CL_Values").Cells(r, 1).Value = "Something"
Where r is say 3 for third row.
The data in column A looks like this.
RowHeaderThatIsText
RowHeaderThatIsNumber
empty
empty
empty
empty
empty
14.00
-3.00
-4.00
The project goal is to to calculate summary statistics for the series of numbers and update the summary each month.
My goal is to allow a new number to be added to the series and have the summary update automatically.
Here's what I've done so far.
Define a range named LastCell with the formula
=INDEX($A:$A, MAX(($A:$A <> "") * (ROW($A:$A))))
This returns the last non-empty cell in the column. The data to summarize is always the last block of numbers.
Define a named range called HeaderOffset with the formula
=3
Used in the step 3.
Define a range named FirstCell with the formula
=OFFSET(LastCell, HeaderOffset - COUNTA($A:$A), 0)
This returns the first cell of the last block of numbers if, as is the case with the data I'll be summarizing, the cells between the first and last blocks are empty.
Define a range named DataBlock with the formula
=FirstCell:LastCell
So far so good. This allows one to enter =SUM(DataBlock) into any cell and get the expected result of 7.00. And, if I add another value, say 3.00, to the list the SUM result will update to 10.
The part I don't like is HeaderOffset. If another row is added to the header, HeaderOffset needs to be updated from =3 to =4. What if I forget to update HeaderOffset? This lead me to the problem I can't currently solve...
Is there an Excel formula that, given the ending cell, returns the starting cell of a block of data? Basically I'm looking for a FirstCell formula that removes the need for HeaderOffset.
As a bonus I was trying to do this whole thing without using volatile Excel functions. I failed by using OFFSET. Solving this is great. Solving it without volatile functions is ideal.
Is FirstCell always the first number? If so try this to define FirstCell
=INDEX($A:$A,MATCH(TRUE,ISNUMBER($A:$A),0))
That's an "array formula" if entered on the worksheet but doesn't need any special treatment if used in the "refers to:" box to define a named range.
Note: if your final aim is the sum of the numbers then could you just use =SUM(A:A) [I assume that's oversimplifying the issue?]
Revised given comment below:
Try this for LastCell
=INDEX($A:$A,MATCH(9.99E+307,$A:$A))
and this one for FirstCell
=INDEX($A:$A,MATCH(2,1/($A$1:LastCell=""))+1)
assumes LastCell is numeric (although that can be changed if required)