I'm trying to move all non blank rows to the top of my worksheet (actually to the top of an Autofilter).
I have realised that simply deleting the blank rows is quite slow, and that a faster alternative is assigning the range to a variant.
I have come up with this code, however for some reason it's losing some of the rows:
Public Sub CompactRows(ByRef ws As Worksheet)
Dim a As Variant
With ws
a = .AutoFilter.Range.Offset(1, 0).Columns(1).SpecialCells(xlCellTypeConstants).EntireRow
.AutoFilter.Range.Offset(1, 0).Clear
.AutoFilter.Range.Cells(2, 1).Resize(UBound(a), .UsedRange.Columns.Count) = a
End With
End Sub
So if I have 1000 rows, separated by blank rows, creating 100 sub-ranges, after the function ends I only have 100 rows (the other 900 are lost).
I noticed that in this case Ubound(a) also returns 100. My theory is that it copies only the first row in each sub-range but I'm not sure. Any solution to this, or another faster alternative to achieve the same result quickly will be greatly appreciated.
Sorting it to find the blanks shouldn't affect the rest of the rows - they should stay in the correct order. However, if you end up sorting on more than one column because what constitutes a blank row is more complicated, then it could happen.
This procedure takes a range with no headers. It adds a column with the original sort, sorts the range, deletes the blanks, then resorts the range back to the original way.
Public Sub CompactRows(ByRef rng As Range)
Dim rNew As Range
'Put the row in a column so you can sort it
'back to the original way later
With rng.Offset(, rng.Columns.Count).Resize(, 1)
.Formula = "=ROW()"
.Copy
.PasteSpecial xlPasteValues
End With
'Make a range that includes the sort column
Set rNew = rng.Resize(, rng.Columns.Count + 1)
'Sort on the first column to get all the blanks together
rNew.Sort rNew.Cells(1), xlAscending, , , , , , xlNo
'Assume a blank in column 1 is a blank row - delete them all in one shot
rNew.Columns(1).SpecialCells(xlCellTypeBlanks).EntireRow.Delete
'Resort the range on the sort column
rNew.Sort rNew.Cells(rNew.Columns.Count), xlAscending, , , , , , xlNo
'Delete the sort column
rNew.Cells(rNew.Columns.Count).EntireColumn.Delete
End Sub
As ExcelHero said, you can sort the rows from Excel. But this will change the order of your non-empty rows. IF you want to remove blank rows but keep the order of your data, you can apply this macro:
Sub removeEmptyRows()
Dim r As Range
For Each r In UsedRange.Rows
If Application.CountA(r) = 0 Then r.Delete
Next
End Sub
Related
situation is following:
I have 32 columns with data (various number of rows in columns) and need to delete cells with .value "downloaded" (always last cell in a column).
I have a code looping from column 32 to 1 and searching last_row for "downloaded" value. For 30 columns code seems to be working flawlessly but 2 columns return last_row value 1 even though there are multiple values (in fact hundreds of them) but they are non existent for VBA code.
Code:
Last_Col = ws.Cells(1, ws.Columns.Count).End(xlToLeft).Column
last_row = ws.Cells(Rows.Count & Last_Col).End(xlUp).Row
For R = Last_Col To 1 Step -1
With ws
Last_Col = R
last_row = ws.Cells(.Rows.Count & Last_Col).End(xlUp).Row
If Cells(last_row, Last_Col).Value Like "*Downloaded*" Then
Cells(last_row, Last_Col).ClearContents
End If
End With
Next R
Data is being drained from another worksheets. For 2 columns where I experience an error, I manually deleted values and inserted another, random batch of values and code worked as intended.
Checked columns formatting, worksheets from which data is taken but I struggle to find a solution.
Thank you for your help.
Clear Last Cell If Criteria Is Met
The main mistake was using Cells(.Rows.Count & Last_Col), where .Rows.Count & Last_Col would have resulted in a 8 or 9-digit string, while it should have been ws.Cells(ws.Rows.Count, Last_Col).End(xlUp).Row which was pointed out by chris neilsen in the comments.
Another important issue is using ws. in front of .cells, .rows, .columns, .range, aka qualifying objects. If you don't do it and e.g. the wrong worksheet is active, you may get unexpected results.
There is no need for looping backwards unless you are deleting.
Although it allows wild characters (*, ?), the Like operator is case-sensitive (a<>A) unless you use Option Compare Text.
The first solution, using the End property, will fail if a number of last columns is hidden or if you insert a new first row e.g. for a title.
The second solution, using the Find method (and the first solution), may fail if the data is filtered.
The Code
Option Explicit
Sub clearLastEnd()
Dim ws As Worksheet
Set ws = ThisWorkbook.Worksheets("Sheet1")
Dim LastCol As Long
LastCol = ws.Cells(1, ws.Columns.Count).End(xlToLeft).Column
Dim LastRow As Long
Dim c As Long
For c = 1 To LastCol
LastRow = ws.Cells(ws.Rows.Count, c).End(xlUp).Row
With ws.Cells(LastRow, c)
If InStr(1, .Value, "Downloaded", vbTextCompare) > 0 Then
.ClearContents
End If
End With
Next c
End Sub
Sub clearLastFind()
Dim ws As Worksheet
Set ws = ThisWorkbook.Worksheets("Sheet1")
Dim cel As Range
Set cel = ws.Cells.Find(What:="*", _
LookIn:=xlFormulas, _
SearchOrder:=xlByColumns, _
SearchDirection:=xlPrevious)
If Not cel Is Nothing Then
Dim c As Long
For c = 1 To cel.Column
Set cel = Nothing
Set cel = ws.Columns(c).Find(What:="*", _
SearchDirection:=xlPrevious)
If Not cel Is Nothing Then
If InStr(1, cel.Value, "Downloaded", vbTextCompare) > 0 Then
cel.ClearContents
Else
' The current last non-empty cell does not contain criteria.
End If
Else
' Column is empty.
End If
Next c
Else
' Worksheet is empty.
End If
End Sub
EDIT:
So you are curious why it worked at all. The following should shed a light on it:
Sub test()
Dim i As Long
Debug.Print "Right", "Wrong", "Rows.Count & i"
For i = 1 To 32
Debug.Print Cells(Rows.Count, i).Address, _
Cells(Rows.Count & i).Address, Rows.Count & i
Next i
End Sub
In a nutshell, Cells can have 1 or 2 arguments. When 1 argument is used, it refers to the n-th cell of a range, and it 'counts' by row. The more common usage is with 2 arguments: rows, columns. For example:
Cells(5, 10) ' refers to cell `J5`.
Using one argument is inconvenient here:
Cells(16384 * (5-1) + 10)
i.e.
Cells(65546)
It may be convenient when processing a one-column or a one-row range.
Well , let me see if i understand you have a table in worksheet table have 32 columns and X rows (because you only put WS and i can know if is WS=worksheet or WS= Table-range)
for this i am going to say is selection (if you put worksheet only hace to change for it)
in your code put:
Last_Col = ws.Cells(1, ws.Columns.Count).End(xlToLeft).Column
but in this you always wil obtein 1st cell so i dont understand why exist?
WS.columns.count
return number of columns you selection have
.End(xlToLeft)
return last cell if star to move to left (like Ctrl - left key)
so
Last_Col ---first go to cells (1,ws.Columns.Count) then go to left (End(xlToLeft)) and the end return number of column where finish (.Column) in this case you always get cell (1,"first column of your table")
NOTE: because you wrote that you have allways value in your cells (I have 32 columns with data (various number of rows in columns)
And for Row you have same question
Then you Wrote you want "Delete" but in your code you put Erase value (.ClearContents) so what do you want? because both are no equal
BUT if you have a table and want to search in any cells that have "Download" and only want to "clear content" you just may to use ".find" instead; or if you want to do all at same time you can use .replace (need to check before if .find return "nothing" or no , because if return nothing you get error)
If you have a table with 32 columns and each row have one cell where you put "Donloaded" and want to "delete" all row your code only need select column where appear "downloaded" (example Column "status").
If you have a table where any cell can take value "downloaded" and want to "delete" that cell you need to take care to resize your table and "move to" (when you delete cells you need to say where you want to move yor data remain "letf, "rigth", "up", down).
However if you say that "Downloaded" always appear in last row you can use For to change for all columns and use .end(xlDown)
For i=1 to 32
if cells(1,i).end(xlDown).value="downloaded" then cells(1,i).end(xlDown).ClearContents
next
BUT you need put more information because if you cant garantize that all cells have values and exist cells with "nothing" you will need
Is there anyway to get the range of the total sum column from a pivot table using excel vba. The range I am referring to is in the screenshot below, highlighted yellow.
I tried using the macro recorder in excel to see if it could help answer my question. This results in me getting this.
ActiveSheet.PivotTables("PivotTable1").PivotSelect _
"'Sum of Unit Cost' 'Row Grand Total'", xlDataAndLabel, True
however this selects a range which is more than needed as seen in the screenshot.
I could do something like offsetting what is selected by 2 rows and then maybe resizing it to fit the intended range but I was wondering if there was a more straight forward way of doing this.
You can use this code to select GrandTotals. It works for rows and columns in case you also need it. The final part is to remove last row (or column).
Sub SelectGrandTotal()
Dim pt As PivotTable
Dim rColumnTotal As Range, rRowTotal As Range
Dim numrows As Long, numcolumns As Integer
Set pt = ActiveSheet.PivotTables(1)
With pt
'The conditions below are checking if the GrandTotals are activated. Not really necessary in some cases.
'Uncomment this block to work with Columns
'If .ColumnGrand Then
' With .DataBodyRange
' Set rColumnTotal = .Rows(.Rows.Count)
' rColumnTotal.Select
' End With
'End If
If .RowGrand Then
With .DataBodyRange
Set rRowTotal = .Columns(.Columns.Count)
rRowTotal.Select
End With
End If
End With
'Resizes selection and removes last Row (you can do the same for columns if necessary)
numrows = Selection.Rows.Count
numcolumns = Selection.Columns.Count
Selection.Resize(numrows - 1, numcolumns).Select
End Sub
Try changing xlDataAndLabel to xlDataOnly; see the XlPTSelectionMode enum on docs.microsoft or find it in the Object Browser (F2) for a list of all the available members.
I am attempting to delete a specific table row based on values in two columns. I attempted to apply filters to the table columns to narrow my criteria, but once I click delete, the ENTIRE ROW is deleted causing values outside of the table to be deleted. Also, the macro recorder isn't as dynamic as I'd like it to be, since it ONLY selects the cell I clicked while recording.
Sub Macro2()
'
' Macro2 Macro
'
'
ActiveSheet.ListObjects("Table1").Range.AutoFilter Field:=1, Criteria1:= _
"Apple" \\Narrowing criteria in Column 1 of the table
Range("A4").Select \\This only applies to a specific cell, and the value can shift
Selection.EntireRow.Delete \\This will delete the entire sheet row, I'd like for only the table row to be deleted
Range("A5").Select
Selection.EntireRow.Delete
Selection.EntireRow.Delete
End Sub
Is there a way to find the desired string in a column and delete only the rows in the table once the criteria is met? I attempted to only delete the ListObject.ListRows, but it only references the row I've selected, and not the one based off criteria.
You could use .DataBodyRange and .SpecialCells(xlCellTypeVisible) to set a range variable equal to the filtered ranges, then unfilter and delete:
Dim dRng As Range
With ActiveSheet.ListObjects("Table1")
.Range.AutoFilter Field:=1, Criteria1:="Apple"
If WorksheetFunction.Subtotal(2, .DataBodyRange) > 0 Then
Set dRng = .DataBodyRange.SpecialCells(xlCellTypeVisible)
.Range.AutoFilter
dRng.Delete xlUp
End If
End With
You will have to indicate which cells/range you want to delete. You can find the relevant row by using the find function. Since your table is static I would propose the following macro.
A for loop checking each row is also possible, but not so efficient for a very large table. It can be useful to prepare your dataset by adding a flag to column c (e.g. a 1 if to be deleted).
EDIT suggestion by Tate also looks pretty clean
Sub tabledelete()
Dim ws As Worksheet
Dim rangecheck As Range
Dim rcheck As Integer
Set ws = Sheets("Sheet1") 'fill in name of relevant sheet
Set rangecheck = Range("A1") ' dummy to get the do function started
Do While Not rangecheck Is Nothing
With ws
With .Range("C2:C30") ' fill in relevant range of table
Set rangecheck = .Find(what:=1, LookAt:=xlWhole)
End With
If Not rangecheck Is Nothing Then 'only do something if a 1 is found
rcheck = rangecheck.Row
.Range(.Cells(rcheck, 1), .Cells(rcheck, 3)).Delete Shift:=xlUp 'delete 3 columns in row found
End If
End With
Loop
End Sub
Currently have a macro which counts the number of rows to use as a variable. Due to new data source which has blank rows this no longer functions.
I need it to continue counting until it hits two blanks which is the end of the data source but also include the blank rows in the count.
I have a macro that counts the number of rows to provide a variable for a separate macro which uses that number for a loop function. Everything was working fine except the new data to count has blank row in between data (which must remain and included in the total row count).
I can figure out how to count non-blanks and full cells separately but can't figure out how to do it together. Any suggestions?
Sub num_rows(nrows As Variant)
Dim numrows
Dim ra As Range
Dim i As Integer
'get number of rows between blank cells
Sheets("4 Gantt Overview").Activate
Set ra = Range("b7")
numrows = Range(ra.Address,Range(ra.Address).End(xlDown)).rows.Count
Range(ra.Address).Select
'establish counting loop
For i = 1 To numrows
ActiveCell.Offset(1, 0).Select
Next
nrows = numrows
Range("b7").Select
End Sub
For a data set of 130 rows and 2 blanks its counting only to 30 rows (the first blank position).
Imagine the following data:
If you want to find the first 2 blanks, you can use .SpecialCells(xlCellTypeBlanks) to fund all blanks in your range (here column A). It will turn something like the selected cells in the image. There are 6 selected areas that you can access with .SpecialCells(xlCellTypeBlanks).Areas.
So if we loop through all these areas For Each Area In .Areas and check their row count If Area.Rows.Count >= 2, we can easily find the area with 2 rows (or at least 2 rows).
The amount of rows (empty or not) is then Area.Row - AnalyzeRange.Row
So we end up with:
Option Explicit
Sub TestCount()
MsgBox CountRowsUntilTwoBlanks(Worksheets("Sheet1").Range("A:A"))
End Sub
Function CountRowsUntilTwoBlanks(AnalyzeRange As Range) As Long
Dim Area As Range
For Each Area In AnalyzeRange.SpecialCells(xlCellTypeBlanks).Areas
If Area.Rows.Count >= 2 Then 'if 2 or more then use >=2, if exactly 2 use =2
CountRowsUntilTwoBlanks = Area.Row - AnalyzeRange.Row
Exit For
End If
Next Area
End Function
So for this example it will return 16 rows.
Note that if your goal is to find the last used row, which in this example would be row 20 then you could just use …
Dim LastRow As Long
With Worksheets("Sheet1")
LastRow = .Cells(.Rows.Count, "A").End(xlUp).Row
End With
… to find the last used row in column A. Here LastRow returns 20.
This this macro. It will find first cell that is blank with a following cell blank as well.
Sub stopAtDoubleBlank()
Dim i As Long
i = 2
Do While Range("A" & i).Value <> "" Or Range("A" & i + 1) <> ""
i = i + 1
Loop
MsgBox i
End Sub
You can try something like this too if you want:
Sub lastrow()
Dim lr As Long
lr = ActiveSheet.Rows.Count
Cells(1, lr).Select
Selection.End(xlUp).Select
lr = ActiveCell.Row
End Sub
(go down to the very bottom and jump up to the last not empty row in A cloumn(that can be changed) also you can add something like +1 if you want an empty row at the end)
I have the example where I want to write a VBA statement which will select all data in a single column, there are no blanks in the column data. The column position will never change e.g. column A, and the data starts in row 3. However the total number of rows in the column will change regularly.
I want the system to dynamically select all the cells in column and then I can run a method against these selected pieces of data.
As an example of performing an action on your range without selecting it:
Public Sub Test()
Dim rColA As Range
With ThisWorkbook.Worksheets("Sheet1")
Set rColA = .Range(.Cells(3, 1), .Cells(.Rows.Count, 1).End(xlUp))
MsgBox "Column A range is " & rColA.Address 'Delete if you want.
rColA.Interior.Color = RGB(255, 0, 0) 'Turn the back colour red.
rColA.Cells(2, 1).Insert Shift:=xlDown 'Insert a blank row at second cell in range
'So will insert at A4.
'If the first cell in your range is a number then double it.
If IsNumeric(rColA.Cells(1, 1)) Then
rColA.Cells(1, 1) = rColA.Cells(1, 1) * 2
End If
End With
End Sub
Try
Dim LastRow as Long, sht as worksheet
Set sht = ThisWorkbook.Worksheets("My Sheet Name")
LastRow = sht.Cells(sht.Rows.Count, 1).End(xlUp).Row
sht.Range("A3:A" & LastRow).Select
Like Darren Bartrup-Cook says, you may not need to select the data, you can almost always perform actions directly which is much faster.
If your column is "isolated" meaning no other nonblank cells touch your data you can use:
Range("firstCellInYourColumn").CurrentRegion.Select
(this works the same way as Ctrl+* from keyboard)
otherwise use:
Range(Range("firstCellInYourColumn"), Range("firstCellInYourColumn").End(xlDown)).Select
both will work if there are really no blanks within your data.
You should also prepend all Range with worksheet expression, I omitted this.