I always see on SO that people use this to find the last row in a column
lRow = .Range("A" & .Rows.Count).End(xlUp).Row
I have always used this function in my projects to find it as I want the last unused row but now that i always see people using End xlUp approach i am wondering why since it only gives that column which may not be correct.
Function GetLastRow(sh As Worksheet) As Long
Dim X As Long
X = sh.UsedRange.Rows.Count 'force excel to recalculate last row
GetLastRow = sh.Cells.SpecialCells(xlLastCell).Row
End Function
Is there any situation where this function fails to give the last row and why it happens?
Some times it happens that it give me a row that could be many lines after the last row of data I assume due to formatting(exported reports generally - I then work my way back up in a loop to remove these rows if fully blank)
What is UsedRange actually considering as used?
I really want a reliable function that I can depend on in the future
Excel tracks cells sparsely: think of it as tracking a row and column pair for each cell that has ANY information (actually its a bit more complex than that but its still a good way of thinking about it). The information can be formatting or data or formulas or ...
So the last used cell is the last entry in the cell tracking table. But the cell tracking table is not reset just by clearing information.
And since Excel 2007 sh.UsedRange.Rows.Count does not (unfortunately) always reset the cell table.
Mostly when you want to find the last used cell you want to find the last cell that actually contains data or formulas rather than the last cell in the cell table.
Using Range.End(xlUp) etc finds the last VISIBLE cell that contains data or formulas, so needs to be used with care if you hide rows or use filtering. And requires looping code when considering multiple columns. But its the fastest of the 3 main methods.
Using Find is usually the most reliable method (but it ignores shapes and comments, and has problems with merged cells and empty pivot tables):
jLastRow = oSht.Cells.Find(What:="*", LookIn:=xlFormulas, SearchOrder:=xlByRows, SearchDirection:=xlPrevious).Row
jLastCol = oSht.Range("A1:A" & CStr(jLastRow)).EntireRow.Cells.Find(What:="*", LookIn:=xlFormulas, SearchOrder:=xlByColumns, SearchDirection:=xlPrevious).Column
In Excel, if you enter data from A1 to Z100, then the used range goes from A1 to Z100.
If you delete rows 50 to 100, the used range is still A1 to Z100. Just clearing cells does not reduce the used range. Excel will mark the last row/last column where data was entered previously.
UsedRange can give you an unexpected result if you want to find the last populated cell in the spreadsheet, because it takes into account rows and columns that may have been in use previously.
If you want to find the last row in a column, the method with rows.count and end(xlUp) is more accurate, because it considers the current values. UsedRange may be bigger.
Related
I have a table in Excel like such, where the number of rows will vary each day:
Column A
Column B
Column C
Cell 1
Cell 2
Show
Cell 3
Cell 4
Show
Cell 5
Cell 6
Ignore
I am using vba to convert the range to a html table, and then email it.
I have a helper column (Column C), and I want to use a formula there to filter out certain rows.
However, that filter is not excluding hidden cells from being displayed in the html table.
I currently use this
Dim LastRow As Long LastRow = rInput.Cells.Find("*", SearchOrder:=xlByRows, SearchDirection:=xlPrevious).Row
to find the last row of my table. This works great in projects where you want all of the table included.
I tried to change it to Find("Ignore", which gets me Object variable or With block variable not set
I tried including 'SpecialCells(xlCellTypeVisible)' in my
ConvertRangeToHTMLTable(Sheet2.Range("$A:$J").Rows("5:" & LastRow), 5)
and using a filter to hide the 'Ignore' cells. But that did not stop them showing in the emailed html table.
You probably have some sort of loop which goes over the rows right? It will not automatically skip the hidden rows just because they are filtered out, you need to specifically tell it to skip them. You can do something like:
For Each r In myRange.Rows
If Not r.EntireRow.Hidden Then
doSomething
End If
Next r
Ended up adjusting the table (and thus the range I cared about) to start at row 1 rather than row 5, and using
strBody = dsaEmailHeader & ConvertRangeToHTMLTable(Sheet2.Range("$A:$H").Rows("1:" & LastRow).SpecialCells(xlCellTypeVisible))
worked, where it didn't previously.
I have inherited a poorly designed workbook, and I am trying to make it work a bit better without starting from scratch.
The last problem I have is that I have a formula in a column that I need to copy to the next column, but change the row number referenced in the formula. The easiest thing to do would be to change the format of the workbook but that will cause an uprising by the users.
=IF((CommaSeparatedListContains(RTM!$I$8,ROW()-2))=TRUE,"X","")
The code above is what I need to copy, but I need to change it so that it looks at I9 instead of I8. RTM is the name of the sheet that the cell is on, and CommaSeparatedListContains is a macro that will return true if the referenced cell has a value (ROW()-2) in the comma delimited list.
Basically I need a macro to add a new column to the worksheet that works like the others, so that the end users who don't know how to use Excel can just click a button and add a column.
For example, that code is in cell A1, and I need to move it to B1 keeping the I the same but increment the row number. If I remove Both $ signs it would change it to J8, if I have $I8 it stays I8, and if I have $I$8 it stays I8.
Thanks for re-affirming my understanding. I'll give this a shot with providing some code, based on your string:
Dim LC as Long, i as Integer
Columns(9).Insert Shift:=xlToRight, CopyOrigin:=xlFormatFromLeftOrAbove
Cells(1,10).Value="" 'Add your header
LC = Cells(1, Columns.Count).End(xlToLeft).Column
i = LC-1 'See description below code
Range("J2:J100").Formula="=IF((CommaSeparatedListContains(RTM!$I$" & i & ",ROW()-2))=TRUE,""X"","""")"
For i, you want to ensure that you substract the correct number... given the example of I8 being the cell you want to reference, and assuming that Column I is the last column of your sheet, then the 9th column, 8th row, is the cell you want to reference. So, the variable i = last column - 1, in this example.
In this case, the column is always added to the right of column I, the assumed last column in the sheet.
One other assumption is that you're using rows 2 to 100 for the range that you have the formula... So, Range("J2:J100").Formula will be affected by your actual range for the formula.
I have a row and I want to sum only visible cells, i know if it's a column I can use subtotal (109,range), but this one doesn't seem to work for cells in one row. Anyone knows how to sum only visible cells in a row?
Please click here for picture
If a VBA solution is okay, this will work:
Function sumVisible(rng As Range) As Double
Dim cel As Range
For Each cel In rng
If cel.EntireColumn.Hidden = False Then
sumVisible = sumVisible + cel.Value
End If
Next cel
End Function
Pretty straightforward - just checks if a cell in your range has a hidden column, if it's visible, sum it.
=sumVisible(D2:M2) is how you'd use it.
You can check the width of the cell.
=IF(CELL("width",A1)=0,"hidden","open")
you can then sum your cells as need it using IF and CELL
=IF(CELL("width",A1)=0,0,A1)
more info below:
Ignoring a hidden column in an excel sum formula
You can do this using a single worksheet formula alone, provided that none of the columns which remain unhidden will have a width of less than or equal to 0.5 (which, in practice, would be so narrow as to be virtually hidden in any case).
Assuming a range of A1:E1
=SUMPRODUCT(0+(CELL("width",OFFSET(A1,,N(INDEX(COLUMN(A1:E1)-MIN(COLUMN(A1:E1)),,))))>0),A1:E1)
Unfortunately this formula will not update automatically as changes regarding hiding/unhiding columns within the range are made. As such, it will be necessary to 'recommit' it each time you make changes in that respect; one way to do this is via going into the formula as if to edit it and then recommitting by pressing ENTER.
Perhaps of interest is this post.
Regards
I'm trying to find a way to copy all rows up to the first empty cell in a formula column, and also do it faster than using a .Select loop. Right now I'm trying this:
Sub CopyValues2()
FinalRow = Columns(9).Find("0").Row
Worksheets("Worksheet").Range("I3:K" & FinalRow).Copy
End Sub
I have column I = to column A. Right now it copies to the first 0 even if it's in a legitimate value so I tried lookAt:=xlWhole but that gave me an error. I also tried putting if statements in column I: =IF(A3<>"",A3,"null") and searching for "null" but then it only selects the first row. I assume that's because it is seeing the "null" in the if statement.
I also tried using Rows.count but that did not work due to the formulas in "empty" cells.
My ideal would be to have an IF(A3<>"",A3,"") and then find the first "" cell but I haven't gotten that to work either. Recommendations would be appreciated!
Try finding the last non-blank value, perhaps using the following command:
FinalRow = Columns(9).Find(What:="*", SearchDirection:=xlPrevious, LookIn:=xlValues).Row
I am very new to VBA, and most of the times I manage to fit my needs by simple jury-rigging someone else's code - sometimes I also find exactly what I was looking for in answers to other people's questions. But right now it seems I can't figure out how to solve this problem (I tried asking in other excel forums, but with no result).
I'm working with very large data output files, which present all the variables I need to analyze scattered in sheets, in multiple workbooks. In order to statistically analyze these data files we often have to rearrange the output data into a more statistically friendly format.
What I'm trying to code is a Summary new sheet for each of the workbooks I need analyzed, which will be populated with a dynamic range copied from each of the sheets and pasted one below the other.
The difficulty resides in the fact that the range is not fixed: has a variable starting point as row in column "A", and a variable length (and thus a variable ending point, but always in Column "F").
So I would need to adapt a simple copy/paste code like the one I've been using for ages
Sub SummurizeSheets()
Dim ws As Worksheet
Application.ScreenUpdating = False
Sheets("Summary").Activate
For Each ws In Worksheets
If ws.Name <> "Summary" Then
ws.Range("A1:A2").Copy
ActiveSheet.Range("A65536").End(xlUp).Offset(1, 0).PasteSpecial Transpose:=True
End If
Next ws
End Sub
with a dynamic range starting in:
Column "A", row =(MATCH(Summary!$M$1;[SheetName]!A:A;0)
and ending in the last non-blank cell starting from the range-start point (which is effectively the last populated cell within the range belonging to the variable described in the Summary Column M1)
this last part looks like frankenstein's monster as:
MATCH(TRUE;INDEX(ISBLANK(INDIRECT(CONCATENATE(CONCATENATE("A";MATCH(Summary!$M$1;[SheetName]!A:A;0));":";"A100")));0;0);0)+ROW(INDIRECT(CONCATENATE("A";MATCH(Summary!$M$1;[SheetName]!A:A;0))))-1
which basically looks for the first blank cell in an arbitrary custom range, from the above-mentioned starting point to A100, and, once found, offsets the row number by (-1).
Finally, the question: how can I translate those two cell references into the ws.Range("xx:xx") above to have my old pal copy/paste do its job with the range I require?
If the output is a row number, just use Cells() in a combination with Range():
Range(Cells(ROW NUMBER, COLUMN NUMBER), Cells(ROW NUMBER2, COLUMN NUMBER2))
where the ROW NUMBER is the output of your frankenstein and the COLUMN NUMBER is obviously the number of the column. ROW NUMBER2 is the row number of your last row