Select & Copy Only Non Blank Cells in Excel VBA, Don't Overwrite - vba

I cant seem to find a solution for my application after endless searching. This is what I want to do:
I have cells in one excel sheet that can contain a mixture of dates and empty cells in one column. I want to then select the cells that have only dates and then copy them to a corresponding column in another sheet. They must be pasted in exactly the same order as in the first sheet because there are titles attached to each row. I do get it right with this code:
'Dim i As Long
'For i = 5 To 25
'If Not IsEmpty(Sheets("RMDA").Range("D" & i)) Then _
Sheets("Overview").Range("D" & i) = Sheets("RMDA").Range("D" & i)
'Next i
However, the dates in the first sheet are being updated on a daily basis and it can be that one title has not been updated (on another day) on the first sheet because the user has not checked it yet. If I leave it blank and If I follow the same procedure then it will "overwrite" the date in the second sheet and make the cell blank, which I do not want. I hope I was clear. Can someone please help me?
Regards

You can accomplish this very easily (and with little code) utilizing Excel's built-in AutoFilter and SpecialCells methods.
With Sheets("RMDA").Range("D4:D25")
.AutoFilter 1, "<>"
Dim cel as Range
For Each cel In .SpecialCells(xlCellTypeVisible)
Sheets("Overview").Range("D" & cel.Row).Value = cel.Value
Next
.AutoFilter
End With

you could try something like. This will give you the non blanks from the range, there may be an easier way... hope it helps
Sub x()
Dim rStart As Excel.Range
Dim rBlanks As Excel.Range
Set rStart = ActiveSheet.Range("d1:d30")
Set rBlanks = rStart.SpecialCells(xlCellTypeBlanks)
Dim rFind As Excel.Range
Dim i As Integer
Dim rNonBlanks As Excel.Range
For i = 1 To rStart.Cells.Count
Set rFind = Intersect(rStart.Cells(i), rBlanks)
If Not rFind Is Nothing Then
If rNonBlanks Is Nothing Then
Set rNonBlanks = rFind
Else
Set rNonBlanks = Union(rNonBlanks, rFind)
End If
End If
Next i
End Sub

Just because a cell is blank does not mean that it is actually empty.
Based on your description of the problem I would guess that the cells are not actually empty and that is why blank cells are being copied into the second sheet.
Rather than using the "IsEmpty" function I would count the length of the cell and only copy those which have a length greater than zero
Dim i As Long
For i = 5 To 25
If Len(Trim((Sheets("RMDA").Range("A" & i)))) > 0 Then _
Sheets("Overview").Range("D" & i) = Sheets("RMDA").Range("D" & i)
Next i
Trim removes all spaces from the cell and then Len counts the length of the string in the cell. If this value is greater than zero it is not a blank cell and therefore should be copied.

Related

Select cells between bold cells using a loop

I am working with data where the only consistency is the layout and the bold headings to distinguish between a new date.
I am trying to find the cells in between these cells in bold, find the value "Individual" (in column A) in the selected rows, then sum the values of the given rows in column D (as there can be more then 1 row with "Individual"), and copy this new value to a different cell.
Since the cells between the bold is one date, if the value is not there, the output cell needs to shift down one without filling in anything.
Here is what I have so far:
Sub SelectBetween()
Dim findrow As Long, findrow2 As Long
findrow = range("A:A").Find("test1", range("A1")).Row
findrow2 = range("A:A").Find("test2", range("A" & findrow)).Row
range("A" & findrow + 1 & ":A" & findrow2 - 1).Select
Selection.Find("Individual").Activate
range("D" & (ActiveCell.Row)).Select
Selection.copy
sheets("Mix of Business").Select
range("C4").Select
ActiveSheet.Paste
Exit Sub
errhandler:
MsgBox "No Cells containing specified text found"
End Sub
How can I loop through the data and each time it loops through a range, no matter if it finds the value (e.g. individual) or not, shifts down one row on the output cell? Also, how can I change the findrow to be a format (Bold) rather then a value?
Here is some data for reference:
This is what I am trying to get it to look like:
So you have a good start to trying to work through your data. I have a few tips to share that can hopefully help get you closer. (And please come back and ask more questions as you work through it!)
First and foremost, try to avoid using Select or Activate in your code. When you look at a recorded macro, I know that's all you see. BUT that is a recording of your keystrokes and mouseclicks (selecting and activating). You can access the data in a cell or a range without it (see my example below).
In order to approach your data, your first issue is to figure out where your data set starts (which row) and where it ends. Generally, your data is between cells with BOLD data. The exception is the last data set, which just has a many blank rows (until the end of the column). So I've created a function that starts at a given row and checks each row below it to find either a BOLD cell or the end of the data.
Private Function EndRowOfDataSet(ByRef ws As Worksheet, _
ByVal startRow As Long, _
Optional maxRowsInDataSet As Long = 50) As Long
'--- checks each row below the starting row for either a BOLD cell
' or, if no BOLD cells are detected, returns the last row of data
Dim checkCell As Range
Set checkCell = ws.Cells(startRow, 1) 'assumes column "A"
Dim i As Long
For i = startRow To maxRowsInDataSet
If ws.Cells(startRow, 1).Font.Bold Then
EndRowOfDataSet = i - 1
Exit Function
End If
Next i
'--- if we make it here, we haven't found a BOLD cell, so
' find the last row of data
EndRowOfDataSet = ws.Cells(ws.Rows.Count, "A").End(xlUp).Row
End Function
To show you how to use that with your specific data, I've created a test subroutine indicating how to loop through all the different data sets:
Option Explicit
Public Sub DataBetween()
Dim thisWB As Workbook
Dim dataWS As Worksheet
Set thisWB = ThisWorkbook
Set dataWS = thisWB.Sheets("YourNameOfSheetWithData")
'--- find the first bold cell...
'Dim nextBoldCell As Range
'Set nextBoldCell = FindNextBoldInColumn(dataWS.Range("A1"))
'--- now note the start of the data and find the next bold cell
Dim startOfDataRow As Long
Dim endOfDataRow As Long
Dim lastRowOfAllData As Long
startOfDataRow = 3
lastRowOfAllData = dataWS.Cells(ws.Rows.Count, "A").End(xlUp).Row
'--- this loop is for all the data sets...
Loop
endOfDataRow = EndRowOfDataSet(dataWS, startOfDataRow)
'--- this loop is to work through one data set
For i = startOfDataRow To endOfDataRow
'--- work through each of the data rows and copy your
' data over to the other sheet here
Next i
startOfDataRow = endOfDataRow + 1
Do While endOfDataRow < lastRowOfAllData
End Sub
Use both of those together and see if that can get you closer to a full solution.
EDIT: I should have deleted that section of code. It was from an earlier concept I had that didn't completely work. I commented out those lines (for the sake of later clarity in reading the comments). Below, I'll include the function and why it didn't completely work for this situation.
So here's the function in question:
Public Function FindNextBoldInColumn(ByRef startCell As Range, _
Optional columnNumber As Long = 1) As Range
'--- beginning at the startCell row, this function check each
' lower row in the same column and stops when it encounters
' a BOLD font setting
Dim checkCell As Range
Set checkCell = startCell
Do While Not checkCell.Font.Bold
Set checkCell = checkCell.Offset(1, 0)
If checkCell.Row = checkCell.Parent.Rows.Count Then
'--- we've reached the end of the column, so
' return nothing
Set FindNextBoldInColumn = Nothing
Exit Function
End If
Loop
Set FindNextBoldInColumn = checkCell
End Function
Now, while this function works perfectly well, the situation is DOES NOT account for is the end of the last data set. In other words, a situation like this:
The function FindNextBoldInColumn will return nothing in this case and not the end of the data. So I (should have completely) deleted that function and replaced it with EndRowOfDataSet which does exactly what you need. Sorry about that.

Find last column and last row in non-contiguous range [duplicate]

When I want to find the last used cell value, I use:
Dim LastRow As Long
LastRow = Range("E4:E48").End(xlDown).Row
Debug.Print LastRow
I'm getting the wrong output when I put a single element into a cell. But when I put more than one value into the cell, the output is correct.
What's the reason behind this?
NOTE: I intend to make this a "one stop post" where you can use the Correct way to find the last row. This will also cover the best practices to follow when finding the last row. And hence I will keep on updating it whenever I come across a new scenario/information.
Unreliable ways of finding the last row
Some of the most common ways of finding last row which are highly unreliable and hence should never be used.
UsedRange
xlDown
CountA
UsedRange should NEVER be used to find the last cell which has data. It is highly unreliable. Try this experiment.
Type something in cell A5. Now when you calculate the last row with any of the methods given below, it will give you 5. Now color the cell A10 red. If you now use the any of the below code, you will still get 5. If you use Usedrange.Rows.Count what do you get? It won't be 5.
Here is a scenario to show how UsedRange works.
xlDown is equally unreliable.
Consider this code
lastrow = Range("A1").End(xlDown).Row
What would happen if there was only one cell (A1) which had data? You will end up reaching the last row in the worksheet! It's like selecting cell A1 and then pressing End key and then pressing Down Arrow key. This will also give you unreliable results if there are blank cells in a range.
CountA is also unreliable because it will give you incorrect result if there are blank cells in between.
And hence one should avoid the use of UsedRange, xlDown and CountA to find the last cell.
Find Last Row in a Column
To find the last Row in Col E use this
With Sheets("Sheet1")
LastRow = .Range("E" & .Rows.Count).End(xlUp).Row
End With
If you notice that we have a . before Rows.Count. We often chose to ignore that. See THIS question on the possible error that you may get. I always advise using . before Rows.Count and Columns.Count. That question is a classic scenario where the code will fail because the Rows.Count returns 65536 for Excel 2003 and earlier and 1048576 for Excel 2007 and later. Similarly Columns.Count returns 256 and 16384, respectively.
The above fact that Excel 2007+ has 1048576 rows also emphasizes on the fact that we should always declare the variable which will hold the row value as Long instead of Integer else you will get an Overflow error.
Note that this approach will skip any hidden rows. Looking back at my screenshot above for column A, if row 8 were hidden, this approach would return 5 instead of 8.
Find Last Row in a Sheet
To find the Effective last row in the sheet, use this. Notice the use of Application.WorksheetFunction.CountA(.Cells). This is required because if there are no cells with data in the worksheet then .Find will give you Run Time Error 91: Object Variable or With block variable not set
With Sheets("Sheet1")
If Application.WorksheetFunction.CountA(.Cells) <> 0 Then
lastrow = .Cells.Find(What:="*", _
After:=.Range("A1"), _
Lookat:=xlPart, _
LookIn:=xlFormulas, _
SearchOrder:=xlByRows, _
SearchDirection:=xlPrevious, _
MatchCase:=False).Row
Else
lastrow = 1
End If
End With
Find Last Row in a Table (ListObject)
The same principles apply, for example to get the last row in the third column of a table:
Sub FindLastRowInExcelTableColAandB()
Dim lastRow As Long
Dim ws As Worksheet, tbl as ListObject
Set ws = Sheets("Sheet1") 'Modify as needed
'Assuming the name of the table is "Table1", modify as needed
Set tbl = ws.ListObjects("Table1")
With tbl.ListColumns(3).Range
lastrow = .Find(What:="*", _
After:=.Cells(1), _
Lookat:=xlPart, _
LookIn:=xlFormulas, _
SearchOrder:=xlByRows, _
SearchDirection:=xlPrevious, _
MatchCase:=False).Row
End With
End Sub
Note: this answer was motivated by this comment. The purpose of UsedRange is different from what is mentioned in the answer above.
As to the correct way of finding the last used cell, one has first to decide what is considered used, and then select a suitable method. I conceive at least three meanings:
Used = non-blank, i.e., having data.
Used = "... in use, meaning the section that contains data or formatting."
As per official documentation, this is the criterion used by Excel at the time of saving. See also this official documentation.
If one is not aware of this, the criterion may produce unexpected results, but it may also be intentionally exploited (less often, surely), e.g., to highlight or print specific regions, which may eventually have no data.
And, of course, it is desirable as a criterion for the range to use when saving a workbook, lest losing part of one's work.
Used = "... in use, meaning the section that contains data or formatting" or conditional formatting.
Same as 2., but also including cells that are the target for any Conditional Formatting rule.
How to find the last used cell depends on what you want (your criterion).
For criterion 1, I suggest reading this answer.
Note that UsedRange is cited as unreliable. I think that is misleading (i.e., "unfair" to UsedRange), as UsedRange is simply not meant to report the last cell containing data. So it should not be used in this case, as indicated in that answer. See also this comment.
For criterion 2, UsedRange is the most reliable option, as compared to other options also designed for this use. It even makes it unnecessary to save a workbook to make sure that the last cell is updated.
Ctrl+End will go to a wrong cell prior to saving
(“The last cell is not reset until you save the worksheet”, from
http://msdn.microsoft.com/en-us/library/aa139976%28v=office.10%29.aspx.
It is an old reference, but in this respect valid).
For criterion 3, I do not know any built-in method.
Criterion 2 does not account for Conditional Formatting. One may have formatted cells, based on formulas, which are not detected by UsedRange or Ctrl+End.
In the figure, the last cell is B3, since formatting was applied explicitly to it. Cells B6:D7 have a format derived from a Conditional Formatting rule, and this is not detected even by UsedRange.
Accounting for this would require some VBA programming.
As to your specific question:
What's the reason behind this?
Your code uses the first cell in your range E4:E48 as a trampoline, for jumping down with End(xlDown).
The "erroneous" output will obtain if there are no non-blank cells in your range other than perhaps the first. Then, you are leaping in the dark, i.e., down the worksheet
(you should note the difference between blank and empty string!).
Note that:
If your range contains non-contiguous non-blank cells, then it will also give a wrong result.
If there is only one non-blank cell, but it is not the first one, your code will still give you the correct result.
I created this one-stop function for determining the last row, column and cell, be it for data, formatted (grouped/commented/hidden) cells or conditional formatting.
Sub LastCellMsg()
Dim strResult As String
Dim lngDataRow As Long
Dim lngDataCol As Long
Dim strDataCell As String
Dim strDataFormatRow As String
Dim lngDataFormatCol As Long
Dim strDataFormatCell As String
Dim oFormatCond As FormatCondition
Dim lngTempRow As Long
Dim lngTempCol As Long
Dim lngCFRow As Long
Dim lngCFCol As Long
Dim strCFCell As String
Dim lngOverallRow As Long
Dim lngOverallCol As Long
Dim strOverallCell As String
With ActiveSheet
If .ListObjects.Count > 0 Then
MsgBox "Cannot return reliable results, as there is at least one table in the worksheet."
Exit Sub
End If
strResult = "Workbook name: " & .Parent.Name & vbCrLf
strResult = strResult & "Sheet name: " & .Name & vbCrLf
'DATA:
'last data row
If Application.WorksheetFunction.CountA(.Cells) <> 0 Then
lngDataRow = .Cells.Find(What:="*", _
After:=.Range("A1"), _
Lookat:=xlPart, _
LookIn:=xlFormulas, _
SearchOrder:=xlByRows, _
SearchDirection:=xlPrevious, _
MatchCase:=False).Row
Else
lngDataRow = 1
End If
'strResult = strResult & "Last data row: " & lngDataRow & vbCrLf
'last data column
If Application.WorksheetFunction.CountA(.Cells) <> 0 Then
lngDataCol = .Cells.Find(What:="*", _
After:=.Range("A1"), _
Lookat:=xlPart, _
LookIn:=xlFormulas, _
SearchOrder:=xlByColumns, _
SearchDirection:=xlPrevious, _
MatchCase:=False).Column
Else
lngDataCol = 1
End If
'strResult = strResult & "Last data column: " & lngDataCol & vbCrLf
'last data cell
strDataCell = Replace(Cells(lngDataRow, lngDataCol).Address, "$", vbNullString)
strResult = strResult & "Last data cell: " & strDataCell & vbCrLf
'FORMATS:
'last data/formatted/grouped/commented/hidden row
strDataFormatRow = StrReverse(Split(StrReverse(.UsedRange.Address), "$")(0))
'strResult = strResult & "Last data/formatted row: " & strDataFormatRow & vbCrLf
'last data/formatted/grouped/commented/hidden column
lngDataFormatCol = Range(StrReverse(Split(StrReverse(.UsedRange.Address), "$")(1)) & "1").Column
'strResult = strResult & "Last data/formatted column: " & lngDataFormatCol & vbCrLf
'last data/formatted/grouped/commented/hidden cell
strDataFormatCell = Replace(Cells(strDataFormatRow, lngDataFormatCol).Address, "$", vbNullString)
strResult = strResult & "Last data/formatted cell: " & strDataFormatCell & vbCrLf
'CONDITIONAL FORMATS:
For Each oFormatCond In .Cells.FormatConditions
'last conditionally-formatted row
lngTempRow = CLng(StrReverse(Split(StrReverse(oFormatCond.AppliesTo.Address), "$")(0)))
If lngTempRow > lngCFRow Then lngCFRow = lngTempRow
'last conditionally-formatted column
lngTempCol = Range(StrReverse(Split(StrReverse(oFormatCond.AppliesTo.Address), "$")(1)) & "1").Column
If lngTempCol > lngCFCol Then lngCFCol = lngTempCol
Next
'no results are returned for Conditional Format if there is no such
If lngCFRow <> 0 Then
'strResult = strResult & "Last cond-formatted row: " & lngCFRow & vbCrLf
'strResult = strResult & "Last cond-formatted column: " & lngCFCol & vbCrLf
'last conditionally-formatted cell
strCFCell = Replace(Cells(lngCFRow, lngCFCol).Address, "$", vbNullString)
strResult = strResult & "Last cond-formatted cell: " & strCFCell & vbCrLf
End If
'OVERALL:
lngOverallRow = Application.WorksheetFunction.Max(lngDataRow, strDataFormatRow, lngCFRow)
'strResult = strResult & "Last overall row: " & lngOverallRow & vbCrLf
lngOverallCol = Application.WorksheetFunction.Max(lngDataCol, lngDataFormatCol, lngCFCol)
'strResult = strResult & "Last overall column: " & lngOverallCol & vbCrLf
strOverallCell = Replace(.Cells(lngOverallRow, lngOverallCol).Address, "$", vbNullString)
strResult = strResult & "Last overall cell: " & strOverallCell & vbCrLf
MsgBox strResult
Debug.Print strResult
End With
End Sub
Results look like this:
For more detailed results, some lines in the code can be uncommented:
One limitation exists - if there are tables in the sheet, results can become unreliable, so I decided to avoid running the code in this case:
If .ListObjects.Count > 0 Then
MsgBox "Cannot return reliable results, as there is at least one table in the worksheet."
Exit Sub
End If
One important note to keep in mind when using the solution ...
LastRow = ws.Cells.Find(What:="*", After:=ws.range("a1"), SearchOrder:=xlByRows, SearchDirection:=xlPrevious).Row
... is to ensure that your LastRow variable is of Long type:
Dim LastRow as Long
Otherwise you will end up getting OVERFLOW errors in certain situations in .XLSX workbooks
This is my encapsulated function that I drop in to various code uses.
Private Function FindLastRow(ws As Worksheet) As Long
' --------------------------------------------------------------------------------
' Find the last used Row on a Worksheet
' --------------------------------------------------------------------------------
If WorksheetFunction.CountA(ws.Cells) > 0 Then
' Search for any entry, by searching backwards by Rows.
FindLastRow = ws.Cells.Find(What:="*", After:=ws.range("a1"), SearchOrder:=xlByRows, SearchDirection:=xlPrevious).Row
End If
End Function
Since the original question is about problems with finding the last cell, in this answer I will list the various ways you can get unexpected results; see my answer to "How can I find last row that contains data in the Excel sheet with a macro?" for my take on solving this.
I'll start by expanding on the answer by sancho.s and the comment by GlennFromIowa, adding even more detail:
[...] one has first to decide what is considered used. I see at least 6 meanings. Cell has:
1) data, i.e., a formula, possibly resulting in a blank value;
2) a value, i.e., a non-blank formula or constant;
3) formatting;
4) conditional formatting;
5) a shape (including Comment) overlapping the cell;
6) involvement in a Table (List Object).
Which combination do you want to test for? Some (such as Tables) may be more difficult to test for, and some may be rare (such as a shape outside of data range), but others may vary based on the situation (e.g., formulas with blank values).
Other things you might want to consider:
A) Can there be hidden rows (e.g. autofilter), blank cells or blank rows?
B) What kind of performance is acceptable?
C) Can the VBA macro affect the workbook or the application settings in any way?
With that in mind, let's see how the common ways of getting the "last cell" can produce unexpected results:
The .End(xlDown) code from the question will break most easily (e.g. with a single non-empty cell or when there are blank cells in between) for the reasons explained in the answer by Siddharth Rout here (search for "xlDown is equally unreliable.") 👎
Any solution based on Counting (CountA or Cells*.Count) or .CurrentRegion will also break in presence of blank cells or rows 👎
A solution involving .End(xlUp) to search backwards from the end of a column will, just as CTRL+UP, look for data (formulas producing a blank value are considered "data") in visible rows (so using it with autofilter enabled might produce incorrect results ⚠️). You have to take care to avoid the standard pitfalls (for details I'll again refer to the answer by Siddharth Rout here, look for the "Find Last Row in a Column" section), such as hard-coding the last row (Range("A65536").End(xlUp)) instead of relying on sht.Rows.Count.
.SpecialCells(xlLastCell) is equivalent to CTRL+END, returning the bottom-most and right-most cell of the "used range", so all caveats that apply to relying on the "used range", apply to this method as well. In addition, the "used range" is only reset when saving the workbook and when accessing worksheet.UsedRange, so xlLastCell might produce stale results⚠️ with unsaved modifications (e.g. after some rows were deleted). See the nearby answer by dotNET.
sht.UsedRange (described in detail in the answer by sancho.s here) considers both data and formatting (though not conditional formatting) and resets the "used range" of the worksheet, which may or may not be what you want.Note that a common mistake ️is to use .UsedRange.Rows.Count⚠️, which returns the number of rows in the used range, not the last row number (they will be different if the first few rows are blank), for details see newguy's answer to How can I find last row that contains data in the Excel sheet with a macro?
.Find allows you to find the last row with any data (including formulas) or a non-blank value in any column. You can choose whether you're interested in formulas or values, but the catch is that it resets the defaults in the Excel's Find dialog ️️⚠️, which can be highly confusing to your users. It also needs to be used carefully, see the answer by Siddharth Rout here (section "Find Last Row in a Sheet")
More explicit solutions that check individual Cells' in a loop are generally slower than re-using an Excel function (although can still be performant), but let you specify exactly what you want to find. See my solution based on UsedRange and VBA arrays to find the last cell with data in the given column -- it handles hidden rows, filters, blanks, does not modify the Find defaults and is quite performant.
Whatever solution you pick, be careful
to use Long instead of Integer to store the row numbers (to avoid getting Overflow with more than 65k rows) and
to always specify the worksheet you're working with (i.e. Dim ws As Worksheet ... ws.Range(...) instead of Range(...))
when using .Value (which is a Variant) avoid implicit casts like .Value <> "" as they will fail if the cell contains an error value.
I would add to the answer given by Siddarth Rout to say that the CountA call can be skipped by having Find return a Range object, instead of a row number, and then test the returned Range object to see if it is Nothing (blank worksheet).
Also, I would have my version of any LastRow procedure return a zero for a blank worksheet, then I can know it is blank.
I wonder that nobody has mentioned this, But the easiest way of getting the last used cell is:
Function GetLastCell(sh as Worksheet) As Range
GetLastCell = sh.Cells(1,1).SpecialCells(xlLastCell)
End Function
This essentially returns the same cell that you get by Ctrl + End after selecting Cell A1.
A word of caution: Excel keeps track of the most bottom-right cell that was ever used in a worksheet. So if for example you enter something in B3 and something else in H8 and then later on delete the contents of H8, pressing Ctrl + End will still take you to H8 cell. The above function will have the same behavior.
Updated at End of 2021
With Excel's new calculation engine and array functionality, and Filter Function, I believe this topic should now be far less contested and that the below options offer the best mix of speed, reliability, and simplicity (which has proven difficult to balance in the past as the numerous posts here illustrate).
Also, I'm defining last used as NOT blank as defined by the isBlank function.
Excel Formula
First, note that the Filter Function makes it much simpler to get a last cell using the below formulas for a specific row or column (in these case Column A or Row 1):
=MAX(FILTER(ROW(A:A),NOT(ISBLANK(A:A))))
=MAX(FILTER(COLUMN(1:1),NOT(ISBLANK(1:1))))
VBA Function For Last Row Specific Range
Using the above function we can convert it into a VBA function, yet make it even faster by limiting the range, while expanding its capabilities by doing multiple columns (thanks to Chris Neilsen for immediate feedback tweeking/suggestions). I also found massive speed improvement by scoping each column to only be a range with a row HIGHER than the previous last row.
Function FindLastRowInRange(someColumns As Range) As Long
Const zFx = "=MAX(FILTER(ROW(????),NOT(ISBLANK(????)),0))"
Dim tRng As Range, i As Long, tRow As Long, pRng As Range
With someColumns.Worksheet
Set tRng = Intersect(someColumns.EntireColumn, .UsedRange)
For i = 1 To tRng.Columns.Count
Set pRng = Intersect(tRng.Columns(i), _
Range(.Rows(FindLastRowInRange + 1), .Rows(.Rows.Count)))
If Not pRng Is Nothing Then
tRow = .Evaluate(Replace(zFx, "????", _
pRng.Address, 1, -1))
If tRow > FindLastRowInRange Then _
FindLastRowInRange = tRow
End If
Next i
End With
End Function
VBA Function For Last Row In Worksheet
To consider the entire worksheet (all columns), I would recommend using a different VBA formula that references the prior one, but is a Volatile Function. This ensures that the formula updates with any changes to a worksheet. Obviously, one could combine these two formulas, but I prefer to limit the usage of volatile functions.
Function FindLastRowInSheet(anywhereInSheet As Range) As Long
Application.Volatile
FindLastRowInSheet = FindLastRowInRange(anywhereInSheet.Worksheet.UsedRange)
End Function
Advantages Compared To Other Options
Allows for some or ALL rows/columns in worksheet without changing approach.
No possibility of missing hidden rows as is a risk with xlup
Ignores formatted/usedrange issues.
Does not interfere with user's Find settings.
Uses worksheet functionality which is faster than VBA calcs.
No counting cells (performance hog).
Hopefully this ends the debate, but if anyone finds weaknesses in this please share.
sub last_filled_cell()
msgbox range("A65536").end(xlup).row
end sub
Here, A65536 is the last cell in the Column A this code was tested on excel 2003.
However this question is seeking to find the last row using VBA, I think it would be good to include an array formula for worksheet function as this gets visited frequently:
{=ADDRESS(MATCH(INDEX(D:D,MAX(IF(D:D<>"",ROW(D:D)-ROW(D1)+1)),1),D:D,0),COLUMN(D:D))}
You need to enter the formula without brackets and then hit Shift + Ctrl + Enter to make it an array formula.
This will give you address of last used cell in the column D.
Thanks to pgsystemtester, this will give you the row number of last used cell:
{=MATCH(INDEX(D:D,MAX(IF(D:D<>"",ROW(D:D)-ROW(D1)+1)),1),D:D,0)}
I was looking for a way to mimic the CTRL+Shift+End, so dotNET solution is great, except with my Excel 2010 I need to add a set if I want to avoid an error:
Function GetLastCell(sh As Worksheet) As Range
Set GetLastCell = sh.Cells(1, 1).SpecialCells(xlLastCell)
End Function
and how to check this for yourself:
Sub test()
Dim ws As Worksheet, r As Range
Set ws = ActiveWorkbook.Sheets("Sheet1")
Set r = GetLastCell(ws)
MsgBox r.Column & "-" & r.Row
End Sub
Sub lastRow()
Dim i As Long
i = Cells(Rows.Count, 1).End(xlUp).Row
MsgBox i
End Sub
sub LastRow()
'Paste & for better understanding of the working use F8 Key to run the code .
dim WS as worksheet
dim i as long
set ws = thisworkbook("SheetName")
ws.activate
ws.range("a1").select
ws.range("a1048576").select
activecell.end(xlup).select
i= activecell.row
msgbox "My Last Row Is " & i
End sub
For the last 3+ years these are the functions that I am using for finding last row and last column per defined column(for row) and row(for column):
Last Column:
Function lastCol(Optional wsName As String, Optional rowToCheck As Long = 1) As Long
Dim ws As Worksheet
If wsName = vbNullString Then
Set ws = ActiveSheet
Else
Set ws = Worksheets(wsName)
End If
lastCol = ws.Cells(rowToCheck, ws.Columns.Count).End(xlToLeft).Column
End Function
Last Row:
Function lastRow(Optional wsName As String, Optional columnToCheck As Long = 1) As Long
Dim ws As Worksheet
If wsName = vbNullString Then
Set ws = ActiveSheet
Else
Set ws = Worksheets(wsName)
End If
lastRow = ws.Cells(ws.Rows.Count, columnToCheck).End(xlUp).Row
End Function
For the case of the OP, this is the way to get the last row in column E:
Debug.Print lastRow(columnToCheck:=Range("E4:E48").Column)
Last Row, counting empty rows with data:
Here we may use the well-known Excel formulas, which give us the last row of a worksheet in Excel, without involving VBA - =IFERROR(LOOKUP(2,1/(NOT(ISBLANK(A:A))),ROW(A:A)),0)
In order to put this in VBA and not to write anything in Excel, using the parameters for the latter functions, something like this could be in mind:
Public Function LastRowWithHidden(Optional wsName As String, Optional columnToCheck As Long = 1) As Long
Dim ws As Worksheet
If wsName = vbNullString Then
Set ws = ActiveSheet
Else
Set ws = Worksheets(wsName)
End If
Dim letters As String
letters = ColLettersGenerator(columnToCheck)
LastRowWithHidden = ws.Evaluate("=IFERROR(LOOKUP(2,1/(NOT(ISBLANK(" & letters & "))),ROW(" & letters & " )),0)")
End Function
Function ColLettersGenerator(col As Long) As String
Dim result As Variant
result = Split(Cells(1, col).Address(True, False), "$")
ColLettersGenerator = result(0) & ":" & result(0)
End Function
Last Row in a regular range or a Table (ListObject)
Finding the last row requires using different methods if the range is a regular range or table (List Object).
Finding the last row in tables requires specifying additional parameters (table name , the column relative position to the first tables column).
I created this universal function for last row, regardless of range type. Just give it any cell reference whatsoever and it will return the last row.
No hassle having to knew range characteristics, especially if your ranges are some times a regular range and sometimes a ListObject.
Using a regular range method on a table might return wrong results.
Sure you can plan ahead of time and use the right method each time, but why bother if you can utilizes a universal function ?
Sub RunMyLastRow()
Dim Result As Long
Result = MyLastRow(Worksheets(1).Range("A1"))
End Sub
Function MyLastRow(RefrenceRange As Range) As Long
Dim WS As Worksheet
Dim TableName As String
Dim ColNumber As Long
Dim LastRow As Long
Dim FirstColumnTable As Long
Dim ColNumberTable As Long
Set WS = RefrenceRange.Worksheet
TableName = GetTableName(RefrenceRange)
ColNumber = RefrenceRange.Column
''If the table (ListObject) does not start in column "A" we need to calculate the
''first Column table and how many Columns from its beginning the Column is located.
If TableName <> vbNullString Then
FirstColumnTable = WS.ListObjects(TableName).ListColumns(1).Range.Column
ColNumberTable = ColNumber - FirstColumnTable + 1
End If
If TableName = vbNullString Then
LastRow = WS.Cells(WS.Rows.Count, ColNumber).End(xlUp).Row
Else
LastRow = WS.ListObjects(TableName).ListColumns(ColNumberTable).Range.Find( _
What:="*", SearchOrder:=xlByRows, SearchDirection:=xlPrevious).Row
End If
MyLastRow = LastRow
End Function
''Get Table Name by Cell Range
Function GetTableName(RefrenceRange As Range) As String
If RefrenceRange.ListObject Is Nothing Then
GetTableName = vbNullString
Else
GetTableName = RefrenceRange.ListObject.Name
End If
End Function

Replace cell references with string from a corresponding cell

I have cells with calculations.
Here is one simple example, which is in row 11.
=$V11*$AB11*AF11
I'm trying to get this:
=[EAD: On Balance Sheet]*[PD Low]*[Collateral LGD High]
These 3 strings all come from row 10, in Column V, AB, and AF.
Here is another example:
Change this:
=$V11*VLOOKUP($AA11,Rates!AQ:AU,5,FALSE)*AE11
To this:
'[EAD: On Balance Sheet]*VLOOKUP([Proposed Risk Rating],Rates!AQ:AU,5,FALSE)*[Collateral LGD Low]
All formulas are on row 11, and I want to get the corresponding headers, which are all strings, from row 10.
I'm thinking that there must be a way to do this, since Excel knows all the relevant cell references, and keeps track of everything.
I can't figure out how to replace the reference with the string (in this case the corresponding header in row 10).
I'm pretty new to this so don't have enough 'reputation' to comment and clarify your question.
If the cells V11, AB11 and AF11 have the text "EAD: On Balance Sheet", "PD Low " and "Collateral LGD High" and you want this cell to show those words.
Then the following code could work:
sub combine_words()
dim i as string
dim j as string
dim k as string
i = range("V11").value
j = range("AB11").value
k = range("AF11").value
range("A11").value = "[" & i & "]*[" & j & "]*[" & k & "]"
end sub
replace the cell A11 with whichever cell you wanted the text inputed into.
Let me know if I understood your question incorrectly and I will change the code to match your needs if I can.
Perhaps a simple find and replace in the formula would work well enough. I'm sure there are lots of edge cases I'm not thinking about. Hopefully this steers the conversation in the right direction.
Sub SOExample()
Dim mySheet As Worksheet: Set mySheet = ThisWorkbook.Sheets("Sheet1")
Dim headerRng As Range: Set headerRng = mySheet.Range("A1:J1") 'Specify where to do replacements
Dim mycell As Range
Dim vkey As Variant
Dim myDict As Object: Set myDict = CreateObject("Scripting.Dictionary")
'Iterate each header row add the address as the key, and the NEXT row's Text as the value
For Each mycell In headerRng
If Not myDict.exists(mycell.Offset(1, 0).Address) Then
myDict.Add mycell.Offset(1, 0).Address, mycell.Text
End If
Next
'Iterate each cells formula and replace it
For Each mycell In headerRng
For Each vkey In myDict.keys
mycell.Offset(1, 0).Formula = Replace(mycell.Offset(1, 0).Formula, vkey, myDict(vkey), , , vbTextCompare)
Next
Next
End Sub

VBA search and copy

I'm automating an update I have to do and part of the macro I want to write needs specific text from what gets populated.
I have the following types of text in the same column for hundreds of rows:
ScreenRecording^naushi02^procr^10035
procr^10635^ScreenRecording^misby01
ScreenRecording^liw03^procr^10046
I've bold the text I need. I want to either replace the whole text with just what I need or place what I need in the next column, same row.
I had wrote something which worked for 60 or so lines before I realised that there are variations in the format. For the main, it's all the same which is why I didn't realise at first and I've spent a lot of wasted time writing something that is now useless... so I'm asking for expert help please.
Once I've got what I need from the first row, I need to move down until the last entry repeating.
I had some code which obviously didn't work fully.
I have thought about using the text 'ScreenRecording' in a search along with the special character which I can't find on my keyboard and then trying to copy all text from that point upto and including the 2nd numerical character. I don't know how to do this, if it would work or even if it's a good idea but because I've spent so much time trying to figure it out, I need some help please.
Thanks in advance
If you always want to return the value after the word 'ScreenRecording`, you can use the following function to do so.
Include it in a SubRoutine to replace in place if needed:
Function SplitScreenRecording(sInput As String) As String
Dim a As Variant
Const SDELIM As String = "^"
Const LOOKUP_VAL As String = "ScreenRecording"
a = Split(sInput, SDELIM)
If IsError(Application.Match(LOOKUP_VAL, a, 0)) Then
SplitScreenRecording = CVErr(2042)
Else
SplitScreenRecording = a(Application.Match(LOOKUP_VAL, a, 0))
End If
End Function
Sub ReplaceInPlace()
Dim rReplace As Range
Dim rng As Range
Set rReplace = Range("A1:A3")
For Each rng In rReplace
rng.Value = SplitScreenRecording(rng.Value)
Next rng
End Sub
if you want to replace:
Sub main2()
Dim key As String
Dim replacementStrng As String
key = "ScreenRecording"
replacementStrng = "AAA"
With Worksheets("mysheet01").columns("A") '<--| change "mysheet01" and "A" to your actual sheet name and column to filter
.Replace what:=key & "^*^", replacement:=key & "^" & replacementStrng & " ^ ", LookAt:=xlPart
.Replace what:="^" & key & "^*", replacement:="^" & key & "^" & replacementStrng, LookAt:=xlPart
End With
End Sub
while if you want to place what you need in the next column:
Sub main()
Dim myRng As Range
Set myRng = GetRange(Worksheets("mysheet01").columns("A"), "ScreenRecording^") '<--| change "mysheet01" and "A" to your actual sheet name and column to filter
myRng.Offset(, 1) = "value that I need to place in next row" '<--| change the right part of the assignment to what you need
End Sub
Function GetRange(rng As Range, key As String) As Range
With rng
.AutoFilter Field:=1, Criteria1:="*" & key & "*" '<--| apply current filtering
If Application.WorksheetFunction.Subtotal(103, .Cells) > 0 Then '<--| if there are visible cells other than the "header" one
With .SpecialCells(xlCellTypeConstants)
If InStr(.SpecialCells(xlCellTypeVisible).Cells(1, 1), key & "^") > 0 Then
Set GetRange = .SpecialCells(xlCellTypeVisible) '<--|select all visible cells
Else
Set GetRange = .Resize(.Parent.Cells(.Parent.Rows.Count, .Column).End(xlUp).row - 1).Offset(1).SpecialCells(xlCellTypeVisible) '<--|select visible rows other than the first ("headers") one
End If
End With
End If
.Parent.AutoFilterMode = False '<--| remove drop-down arrows
End With
End Function

How to determine first column used in each row after column A using VBA

I am trying to add code to one of my worksheets that determines what the first column is with a value in it for each row (excluding the first column A because it is all headings). I have searched but cannot find anything that works for the FIRST occurrence in a row. Thank you for the help.
The following code will return the cell address of the first occurrence of data in a row for a set of columns.
It's unclear to me from your question, whether you want to look for the first occurrence of data in a row for a given column (what I wrote below), or the first occurrence of data in a column for a given row (if you need this, change the word Columns to the word Rows in the below code).
Sub FindFirstRowWithData()
With Worksheets("Sheet1")
Dim lCol As Long
For lCol = 2 To 10 'loop through columns B to J
Dim rngFound As Range
Set rngFound = .Columns(lCol).Find(What:="*", SearchOrder:=xlByColumns, SearchDirection:=xlNext)
If Not rngFound is Nothing then
Debug.Print rngFound.Address
Else
Msgbox "No Data in column " & lCol.
End If
Next
End With
End Sub
try this
Option Explicit
Sub FindFirstColumn()
Dim rngToScan As Range
Dim value As Variant
value = "a" '<== set it as the value of which first occurrence you want to search for
Set rngToScan = ActiveSheet.Range("B2:F100") '<== set it as the actual range of which columns will be searched the first occurrence of variable 'value'
With rngToScan
.Offset(, .Columns.Count).Resize(, 1).FormulaR1C1 = "=match(""" & value & """,RC2:RC" & .Columns(.Columns.Count).Column & ",0)"
End With
End Sub