Excel VBA Can't delete entire row when part of row is a table - excel

I'm trying to loop through my data and Union certain row numbers that I need to delete later on. The code below stores the correct rows, but I can't delete them. I believe it's because my data is arranged in a table, since I'm able to delete the desired rows if the data is not in a table. I get the error message 'run time error 1004 - delete method of range class failed' on the line Urng.delete.
Sub DeleteRows()
Dim ws4 As Worksheet: Set ws4 = Worksheets("Sheet1")
Dim LastRow As Long
Dim CurrentRow As Long
Dim GroupValue
Dim GroupTotal As Long
Dim x As Long
Dim Urng As Range
Application.ScreenUpdating = False
ws4.Activate
GroupValue = ws4.Range("B6").Value
CurrentRow = 6 LastRow = ws4.Cells(Rows.Count, "B").End(xlUp).Row
Set Urng = Rows(LastRow + 1)
For x = 1 To LastRow
GroupTotal = Application.WorksheetFunction.CountIf(Range("B6:B" & LastRow), GroupValue)
If GroupTotal = 1 Then
Set Urng = Union(Urng, Rows(CurrentRow))
End If
CurrentRow = CurrentRow + GroupTotal
GroupValue = Range("B" & CurrentRow).Value
If GroupValue = "" Then '
Exit For
End If
Next x
Urng.Delete
Application.ScreenUpdating = True
End Sub
I've tried using .EntireRow.Delete without luck.
There's no data outside the table, so deleting just the table rows could be a solution, however, I don't know how to build the loop that Unions the row numbers if I can't use the row number in Union(Urng, Rows(CurrentRow)).
Is there a VBA-solution to delete multiple entire rows, where part of the row is a table?

This is how to delete row number 5 from a table named TableName:
Sub TestMe()
Range("TableName[#All]").ListObject.ListRows(5).Delete
End Sub
Concerning your specific problem, the case is that in Urng you are having rows, which are both in and outside the table. Thus, they cannot be deleted with .Delete. Write this before Urng.Delete to see yourself:
Urng.Select
Stop
Unrg.Delete
At the sample you may see that the row 6 is in the table and row 18 is outside the table:
Concerning deletion of two rows, which are not close to each other in a table, I guess that the only way is to loop. It is a bit slower indeed, but it works:
Sub TestMe()
Dim cnt As Long
Dim arrRows As Variant: arrRows = Array(10, 12)
Dim table As ListObject: Set table = ActiveSheet.ListObjects("SomeTable")
For cnt = UBound(arrRows) To LBound(arrRows) Step -1
table.ListRows(arrRows(cnt)).Delete
Next cnt
'This works only when the rows are after each other, e.g. 2,3,4
table.Range.Rows("2:4").Select
Stop
table.Range.Rows("2:4").Delete
End Sub

Related

Removing blank entries from table with VBA

I have a table of inputs that I run calculations on. Having empty rows in that table affects the calculations. Right now I have a macro that sorts the table everytime there's any changes made, because of which all the blank rows collect at the bottom of the table. So I just have to resize the table manually before every calculation.
You can see an empty row at the bottom of the table here. I don't know how to go about removing it such that the table ends with last non empty entry
But I want to know if there's a way to snap the table grid back to the last non empty row using VBA and remove the empty rows from the table. Thanks!
Here is the VBA function that would delete the empty rows
Public Sub DeleteBlankRows()
Dim LastRowIndex As Integer
Dim RowIndex As Integer
Dim UsedRng As Range
Set UsedRng = ActiveSheet.UsedRange
LastRowIndex = UsedRng.Row - 1 + UsedRng.Rows.Count
Application.ScreenUpdating = False
For RowIndex = LastRowIndex To 1 Step -1
If Application.CountA(Rows(RowIndex)) = 0 Then
Rows(RowIndex).Delete
End If
Next RowIndex
Application.ScreenUpdating = True
End Sub
Here is sample
And this is how it looks after
Delete Blank Rows in an Excel Table
No need to sort the table if you don't want to.
Option Explicit
Sub DeleteTableBlankRows()
With Sheet1.ListObjects(1).DataBodyRange
Dim cCount As Long: cCount = .Columns.Count
Dim drg As Range ' Delete Range
Dim rrg As Range ' Row Range
For Each rrg In .Rows
If Application.CountBlank(rrg) = cCount Then
If drg Is Nothing Then
Set drg = rrg
Else
Set drg = Union(drg, rrg)
End If
End If
Next rrg
If Not drg Is Nothing Then
drg.Delete
End If
End With
End Sub
Please, use the next Sub. It will delete all table empty rows after filtering. I mean the last (empty) ones:
Private Sub deleteTableEmptyRows(Optional TblD As Range) 'calculate table last empty rows
Dim lastShER As Long, lastShTblR As Long, lastTblER As Long, lastTblR As Long
If TblD Is Nothing Then Set TblD = ActiveSheet.ListObjects(1).DataBodyRange
lastShER = TblD.cells(TblD.rows.count, 1).End(xlUp).row + 1 'last empty row on the sheet (no needed, only to demonstrate how to calculate)
lastTblER = lastShER - (TblD.row - 1) 'last empty row on the table DataBodyRange
lastShTblR = TblD.rows.count + TblD.row 'last table row on the sheet
lastTblR = lastShTblR - TblD.row 'last table row
If lastTblER > 1 Then TblD.rows(lastTblER & ":" & lastTblR).Select 'please, change Select with Delete, only after checking that selected range is as you need
End Sub
It can be called in this way:
Sub testDeleteTableEmptyRows()
Dim TblD As Range
Set TblD = ActiveSheet.ListObjects(1).DataBodyRange 'use here the table name (or index)
deleteTableEmptyRows TblD
End Sub

Copy rows to bottom of sheet based on cell value and sorted ascending wise

I have an Excel sheet which is pulled from JIRA. This sheet has variable rows each week. Once it is pulled, I have a macro which performs various actions. One of these is to move certain rows to bottom of the sheet based on a value present in Column 'F'. In this particular case, if the value 'RCR' is present in column 'F', then that particular row should cut and paste at the bottom.
For this I have written the below code. This code works well and does the job. But the issue is since it loops from bottom to top, the list of rows with 'RCR' values is in a descending manner. But I want the rows to be sorted in an ascending manner.
If I use "1 to lastRowOne" in the For loop, then what happens is the row gets deleted after the move has been done, due to this, if the next row also has the value of 'RCR', that particular row is skipped because it takes the place of the deleted row. So the macro moves to the row after that, which is causing the macro to miss certains rows having consecutive value = 'RCR'.
Dim wsOne As Worksheet
Dim lastRowOne As Long
Dim lastRowTwo As Long
Set wsOne = ActiveWorkbook.Sheets("Status")
lastRowOne = wsOne.Cells(wsOne.Rows.Count, 1).End(xlUp).Row
lastRowTwo = wsOne.Cells(wsOne.Rows.Count, 1).End(xlUp).Row + 1
For I = lastRowOne To 1 Step -1
If wsOne.Range("F" & I).Value = "RCR" Then
wsOne.Rows(lastRowTwo).Value = wsOne.Rows(I).Value
wsOne.Rows(I).EntireRow.Delete
End If
Next
Is there a way that this can be remedied?
Use Union() to make a non-contiguous range, copy that range and then delete afterwards.
Dim wsOne As Worksheet
Dim lastRowOne As Long
Dim i As Long
Dim rng As Range
Set wsOne = ActiveWorkbook.Sheets("Status")
lastRowOne = wsOne.Cells(wsOne.Rows.Count, 1).End(xlUp).Row
With wsOne
For i = 1 To lastRowOne
If wsOne.Range("F" & i).Value = "RCR" Then
If rng Is Nothing Then
Set rng = .Rows(i)
Else
Set rng = Union(rng, .Rows(i))
End If
End If
Next
rng.Copy .Range("A" & lastRowOne + 1)
rng.Delete
End With

How to make a loop that deletes rows that contain previous dates

I'm creating a loop that is supposed to find cells that have dates before 01/01/2019 and if they find they delete the entire row.
The problem is that if they delete per example row number 2, the code is passing to row 3, but since row 2 was deleted previously the original row 3 would be skipped.
I wrote the loop and tried to add "i = 1 - 1" but the excel never stops running the code and blocks the program
Dim ws As Worksheet
Dim Ldate As Date
Dim N As Long
Ldate = "01/01/2019"
N = ws.Cells(Rows.Count, 8).End(xlUp).Offset(0, 0).Row
For i = 1 To N
If ws.Cells(i, 8).Value < Ldate Then
ws.Rows(i).Delete
' i = 1 - 1
End If
Next i
Don't let Excel guess if this string "01/01/2019" is mm/dd/yyyy or dd/mm/yyyy Never use strings to write a date, instead always use real dates: Ldate = DateSerial(2019, 1, 1) to create dates. String-Dates are very evil! The only use case to cast a real date into a string is to print it on paper or screen. All other cases must use real dates (number formats) to be reliable.
.Offset(0, 0) is usless remove it. Moving 0 rows and 0 columns from current cell will not change current cell at all.
If you delete/add rows then your loops must be backwards in order to row count correctly: For iRow = LastRow To 1 Step - 1
So you end up with something like this
Dim ws As Worksheet
Set ws = ThisWorkbook.Worksheets("Sheet1")
Dim Ldate As Date
Ldate = DateSerial(2019, 1, 1)
Dim LastRow As Long
LastRow = ws.Cells(ws.Rows.Count, 8).End(xlUp).Row
Dim iRow As Long
For iRow = LastRow To 1 Step - 1
If ws.Cells(iRow , 8).Value < Ldate Then
ws.Rows(iRow).Delete
End If
Next iRow
Alternative if you use a forward loop, you need to collect the rows to delete in a Range variable and delete it at once after the loop is finished. This way deleting doesn't affect the row counting inside the loop (because we delete after the loop):
Dim ws As Worksheet
Set ws = ThisWorkbook.Worksheets("Sheet1")
Dim Ldate As Date
Ldate = DateSerial(2019, 1, 1)
Dim LastRow As Long
LastRow = ws.Cells(ws.Rows.Count, 8).End(xlUp).Row
Dim RowsToDelete As Range
Dim iRow As Long
For iRow = 1 To LastRow
If ws.Cells(iRow , 8).Value < Ldate Then
If RowsToDelete Is Nothing Then
Set RowsToDelete = ws.Rows(iRow)
Else
Set RowsToDelete = Union(RowsToDelete, ws.Rows(iRow))
End If
End If
Next iRow
If Not RowsToDelete Is Nothing Then
RowsToDelete.Delete
End If
Actually this approach should be faster because it only performs one delete action in the end (instead of one per deleted row).
Edit according comments:
Test if a worksheet exists before you use it.
Option Explicit
Function WorksheetExists(ByVal WorksheetName As String, Optional ByVal InWorkbook As Workbook) As Boolean
'default workbook is ThisWorkbook
If InWorkbook Is Nothing Then
Set InWorkbook = ThisWorkbook
End If
Dim ws As Worksheet
On Error Resume Next
Set ws = InWorkbook.Worksheets(WorksheetName)
On Error GoTo 0
WorksheetExists = Not ws Is Nothing
End Function
Then use it like
If WorksheetExists("Sheet1") Then …
'or to test for a worksheet in a different workbook
If WorksheetExists("Sheet1", Workbooks("OtherWorkbook.xlsx")) Then
The simplest way is to change
For i = 1 To N
to
For i = N To 1 step -1
This way when your row is deleted it will not change the number of the following rows as these will be lower numbered rows.

Running Macro Once Does Not Do Anything. Running the Macro Again Works

I'm having some trouble with a macro I've been working on. It's used to delete blanks (over a million blank rows) when another separate macro is run. If I get this one working, I would like to merge the two macros together.
Here is the macro:
Sub Test()
DeleteBlankTableRows ActiveSheet.ListObjects(1)
End Sub
Sub DeleteBlankTableRows(ByVal tbl As ListObject)
Dim rng As Range
Set rng = tbl.DataBodyRange ' Get table data rows range.
Dim DirArray As Variant
DirArray = rng.Value2 ' Save table values to array.
' LOOP THROUGH ARRAY OF TABLE VALUES
Dim rowTMP As Long
Dim colTMP As Long
Dim combinedTMP As String
Dim rangeToDelete As Range
' Loop through rows.
For rowTMP = LBound(DirArray) To UBound(DirArray)
combinedTMP = vbNullString ' Clear temp variable.
' Loop through each cell in the row and get all values combined.
For colTMP = 1 To tbl.DataBodyRange.Columns.Count
combinedTMP = combinedTMP & DirArray(rowTMP, colTMP)
Next colTMP
' Check if row is blank.
If combinedTMP = vbNullString Then
' Row is blank. Add this blank row to the range-to-delete.
If rangeToDelete Is Nothing Then
Set rangeToDelete = tbl.ListRows(rowTMP).Range
Else
Set rangeToDelete = Union(rangeToDelete, tbl.ListRows(rowTMP).Range)
End If
End If
Next rowTMP
' DELETE BLANK TABLE ROWS (if any)
If Not rangeToDelete Is Nothing Then rangeToDelete.Delete
End Sub
First time it is run, it loads and acts like it's going to work. Less than a minute after loading...nothing happens (at least, visually). I run it again and it loads quickly; this time, the blank rows are visually gone.
A similar idea using an explicit parent sheet reference and Index and Max to determine if a row is blank.
Option Explicit
Public Sub DeleteRowsIfBlank()
Dim ws As Worksheet, table As ListObject, arr(), i As Long, counter As Long, unionRng As Range
Set ws = ThisWorkbook.Worksheets("Sheet1")
Set table = ws.ListObjects(1)
arr = table.DataBodyRange.Value
counter = table.DataBodyRange.Cells(1, 1).Row
For i = LBound(arr, 1) To UBound(arr, 1)
If Application.Max(Application.Index(arr, i, 0)) = 0 Then
If Not unionRng Is Nothing Then
Set unionRng = Union(unionRng, table.Range.Rows(counter))
Else
Set unionRng = table.Range.Rows(counter)
End If
End If
counter = counter + 1
Next
If Not unionRng Is Nothing Then unionRng.Delete
End Sub

Excel - Move rows containing an empty cell to another sheet

This is my first attempt at VBA, so I apologize for my ignorance. The situation is as follows: I have a spreadsheet that consists of 4 columns and 629 rows. When I am trying to do is iterate through the 4 cells in each row and check for a blank cell. If there is a row that contains a blank cell, I want to cut it from Sheet1 and paste it into the first available row in Sheet2.
(Ideally the number of columns AND the number of rows is dynamic based on each spreadsheet, but I have no idea how to iterate through rows and columns dynamically)
Sub Macro1()
'
' Macro1 Macro
' Move lines containing empty cells to sheet 2
'
' Keyboard Shortcut: Ctrl+r
'
Dim Continue As Boolean
Dim FirstRow As Long
Dim CurrentRow As Long
Dim LastRow As Long
Dim EmptySheetCount As Long
Dim Counter As Integer
'Initialize Variables
LContinue = True
FirstRow = 2
CurrentRow = FirstRow
LastRow = 629
EmptySheetCount = 1
'Sheets(Sheet1).Select
'Iterate through cells in each row until an empty one is found
While (CurrentRow <= LastRow)
For Counter = 1 To 4
If Sheet1.Cells(CurrentRow, Counter).Value = "" Then
Sheet1.Cells(CurrentRow).EntireRow.Cut Sheet2.Cells(EmptySheetCount, "A")
EmptySheetCount = EmptySheetCount + 1
Counter = 1
CurrentRow = CurrentRow + 1
GoTo BREAK
Else
Counter = Counter + 1
End If
Counter = 1
BREAK:
Next
Wend
End Sub
When I run it, I typically get an error around the Sheet1.Cells(CurrentRow, Counter).Value = "" area, so I know I'm referencing sheets incorrectly. I've tried Sheets(Sheet1), Worksheets("Sheet1") and nothing seems to be working. When I do change to Worksheets("Sheet1"), however, it runs and just freezes Excel.
I know I'm doing multiple things wrong, I just know way too little to know what.
Thanks a lot in advance. And sorry for the crap formatting.
There are a few things wrong with your code so rather than go through them individually here is a basic looping version that does what you're after.
Sub moveData()
Dim wksData As Worksheet
Dim wksDestination As Worksheet
Dim lastColumn As Integer
Dim lastRow As Integer
Dim destinationRow As Integer
Set wksData = Worksheets("Sheet1")
Set wksDestination = Worksheets("Sheet2")
destinationRow = 1
lastColumn = wksData.Range("XFD1").End(xlToLeft).Column
lastRow = wksData.Range("A1048576").End(xlUp).Row
For i = lastRow To 1 Step -1 'go 'up' the worksheet to handle 'deletes'
For j = 1 To lastColumn
If wksData.Cells(i, j).Value = "" Then 'check for a blank cell in the current row
'if there is a blank, cut the row
wksData.Activate
wksData.Range(Cells(i, 1), Cells(i, lastColumn)).Cut
wksDestination.Activate
wksDestination.Range(Cells(destinationRow, 1), Cells(destinationRow, lastColumn)).Select
ActiveSheet.Paste
'If required this code will delete the 'cut' row
wksData.Rows(i).Delete shift:=xlUp
'increment the output row
destinationRow = destinationRow + 1
Exit For 'no need to carry on with this loop as a blank was already found
End If
Next j
Next i
set wksData = Nothing
set wksDestination = Nothing
End Sub
There are other ways that will achieve the same outcome but this should give you and idea of how to use loops, sheets, ranges, etc.
The lastColumn and lastRow variables will find the the last column/row of data in the given columns/rows (i.e, in my code it finds the last column of data in row 1, and the last row of data in column A).
Also, you should get into the habit of debugging and stepping through code to identify errors and see exactly what each line is doing (this will also help you learn too).
You might find this of use.
It uses an array variable to store the values of the cells in the row to be moved. It does not use cut and paste, so only transfer the data values, and the code does not require activation of the required sheets.
The destination rows are in the same order as the rows on the original sheet.
The method used to find the last cell used in the row and column is more elegant than other answers given.
Option Explicit
Public Sub test_moveData()
Dim wksData As Worksheet
Dim wksDestination As Worksheet
Set wksData = shtSheet1 ' Use the Codename "shtSheet1" for the worksheet. ie the value of the sheet property that is displayed as "(Name)"
Set wksDestination = shtSheet2
moveData wksData, wksDestination
End Sub
Public Sub moveData(wksData As Worksheet, wksDestination As Worksheet)
Dim ilastColumn As Integer
Dim ilastRow As Integer
Dim iRow As Long
Dim iColumn As Long
Dim iDestinationRowNumber As Integer
Dim MyArray() As Variant
Dim rngRowsToDelete As Range
iDestinationRowNumber = 1
ilastColumn = wksData.Cells(1, wksData.Columns.Count).End(xlToLeft).Column
ilastRow = wksData.Cells(wksData.Rows.Count, 1).End(xlUp).Row
ReDim MyArray(1, ilastColumn)
Set rngRowsToDelete = Nothing
For iRow = 1 To ilastRow Step 1 'No need to go 'up' the worksheet to handle 'deletes'
For iColumn = 1 To ilastColumn
If wksData.Cells(iRow, iColumn).Value = "" Then 'check for a blank cell in the current row
MyArray = wksData.Range(wksData.Cells(iRow, 1), wksData.Cells(iRow, ilastColumn)).Value
wksDestination.Range(wksDestination.Cells(iDestinationRowNumber, 1),
wksDestination.Cells(iDestinationRowNumber, ilastColumn) _
).Value = MyArray
'Store the rows to be deleted
If rngRowsToDelete Is Nothing Then
Set rngRowsToDelete = wksData.Rows(iRow)
Else
Set rngRowsToDelete = Union(rngRowsToDelete, wksData.Rows(iRow))
End If
'increment the output row
iDestinationRowNumber = iDestinationRowNumber + 1
Exit For 'no need to carry on with this loop as a blank was already found
End If
Next iColumn
Next iRow
If Not rngRowsToDelete Is Nothing Then
rngRowsToDelete.EntireRow.Delete shift:=xlUp
End If
Set rngRowsToDelete = Nothing
Set wksData = Nothing
Set wksDestination = Nothing
End Sub
' enjoy

Resources