Move a Range with offset - vba

I'm having a trouble by moving a designated row with an offset up.
It throws me the Error 424 that needs an Object.
I set up the rng2 with the selected range, but when i try to move it up, the error get in.
I basically need that when the area in the 87 row finds an empty cell delete it so the label with the info can go up.
Just
to
Thanks for your time!
Sub RowOffset()
Application.ScreenUpdating = False
'Worksheets("Mine").Activate
Dim rng As Range
Dim rng2 As Range
Dim i As Long
Set rng = ThisWorkbook.ActiveSheet.Range("C87:C37")
Set rng2 = Range("C85:N85")
With rng
For i = .Rows.Count To 1 Step -1
If .Item(i) = "" Then
rng2.Select
rng2.Delete Shift:=xlUp
Set rng2 = rng2.Offset(-1, 0) 'THIS LINE HAS AN ERROR
End If
Next i
End With
Application.ScreenUpdating = True
End Sub

You can't .offset() a range that has been deleted before. Try this instead (not tested, I am answering on a smartphone):
With rng
For i = .Rows.Count To 1 Step -1
If .Item(i) = "" Then
set tmp=rng2
tmp.Delete Shift:=xlUp
End If
Set rng2 = rng2.Offset(-1)
Next i
End With
The rng2.offset(-1) (the second argument, 0, is optional) should not be inside the if-clause.
There is also no need to .select a range before deleting it. In fact, leaving this step out could improve the performance of your script.
Edit:
Well, as #Matto quite rightly remarked, my former solution still did not work. So I had another look at it and modified the whole sub as follows:
Sub rowOffset()
Dim rng As Range, row As Range, i%
Set rng = [c37:n85]
For i = rng.Rows.Count To 1 Step -1
Set row = rng.Rows.Item(i)
If row.Range("a1") = "" Then row.Delete shift:=xlUp
Next i
End Sub
I think this is shorter anyway ... and it works (tested).

The reason your code errors is because after rng2.Delete ... rng2 no longer points to a valid range (because you just deleted it).
That said a better approach would be to first identify the range to delete, then delete it in one go.
Something like this (assumes your first data is in column B)
Sub Demo()
Dim r1 As Range, r2 As Range
With ThisWorkbook.ActiveSheet
' Find Total row
' assumes there is no other data below the table in Column B
Set r2 = .Cells(.Rows.Count, 2).End(xlUp).Offset(-2, 0)
If IsEmpty(r2) Then
' Find the last row of data
Set r1 = r2.End(xlUp).Offset(1, 0)
.Range(r1, r2).EntireRow.Delete
End If
End With
End Sub

Range(“C35”).End(xlDown).Offset(1,0)
will give you the first cell in the first row to delete.
Then
Range(“C35”).End(xlToRight).End(xlDown).End(xlDown).Offset(-2,0)
Will give you the bottom right cell in the area that you want to delete.
So, set these two cells into variables (e.g. cell1 and cell2).
Then:
Range(cell1, cell2).Delete Shift:=xlUp
will land you at the result.

Related

VBA: Referring to active cells' row in a For/Each loop

the aim of my problem is to find a specific value (Text) and then refer to the entire row (or even better only the used range to the right of my active cell) in a For/Each loop.
The first part works fine of finding my value, however, the code for targeting the row of the active cell (so the cell found by the find function), does not work yet:
Sub Search()
Dim cell As Range
Dim Count As Long
Set cell = Cells.Find(what:="Planned Supply at BP|SL (EA)", LookIn:=xlValues, lookat:=xlWhole)
For Each cell In ActiveCell.EntireRow
If cell.Value = "0" Then
Count = Count + 1
End If
Next cell
Range("I1").Value = Count
End Sub
The following code will find the range to the right of your found cell and use your loop to do the comparision for each cell in the range. That part could probably be improved by using WorksheetFunction.CountIf.
Option Explicit
Sub Search()
Dim wks As Worksheet
Set wks = ActiveSheet
Dim cell As Range, sngCell As Range
Dim Count As Long
Set cell = wks.Cells.Find(what:="Planned Supply at BP|SL (EA)", LookIn:=xlValues, lookat:=xlWhole)
If cell Is Nothing Then Exit Sub ' just stop in case no hit
Dim rg As Range, lastColumn As Long
With wks
lastColumn = .Cells(cell.Row, .Columns.Count).End(xlToLeft).Column ' last used column in cell.row
Set rg = Range(cell, .Cells(cell.Row, lastColumn)) ' used rg right from found cell inlcuding found cell
End With
' loop from the original post
For Each sngCell In rg
If sngCell.Value = "0" Then
Count = Count + 1
End If
Next
Range("I1").Value = Count
End Sub

How to fix Compile Error: Sub or function not defined in VBA?

This is a code that goes through the cells in column B in sheet2. If it finds a value that is not a date in column B, then it copies it, pastes it another sheet called 'errors' and then deletes that row from Sheet2. Whenever I try to run this, however, I get a 'Compile Error: Sub or function not defined'. I saw some other posts on this, but nothing mentioned there seemed to work for me.
Sub removeerrors()
Dim i As Range
Dim x As Double
x = Worksheet("Errors").CountA("A1:A100")
For Each i In Worksheet("Sheet2").Range(Range("A2"), Range("A2").End(xlDown))
If IsDate(i.Offset(0, 1)) = False Then
Range(i, i.End(xlToRight)).Copy
Worksheet("Errors").Range("A1").Offset(x, 0).Paste
Range(i).EntireRow.Delete
End If
Next i
End Sub
There are a few other errors/changes that could be made within the script
Add s to Worksheet
Use Option Explicit at top of code
Application.WorksheetFunction.CountA
Add range as argument to Counta i.e. Application.WorksheetFunction.CountA(Worksheets("Errors").Range("A1:A100"))
Ensure correct ranges being worked with by wrapping in With Worksheets("Sheet2")
Determine last row by coming up from bottom of sheet with .Cells(.Rows.Count, "A").End(xlUp).Row, or you could end up looping to bottom of sheet
Correct syntax for delete line: i.EntireRow.Delete
You can put copy paste on one line: .Range(i, i.End(xlToRight)).Copy Worksheets("Errors").Range("A1").Offset(x, 0)
Be wary of using End(xlToRight) in cases of potentially ending up at far right of sheet.
Optimize code by switching some things off e.g. prevent repaint by switching off screen-updating during looping
Gather ranges to delete with Union and delete in 1 go or loop backwards to delete
VBA:
Option Explicit
Public Sub removeerrors()
Dim i As Range, x As Double, loopRange As Range, lastRow As Long, unionRng As Range
x = Application.WorksheetFunction.CountA(Worksheets("Errors").Range("A1:A100"))
Application.ScreenUpdating = False
With Worksheets("Sheet2")
lastRow = .Cells(.Rows.Count, "A").End(xlUp).Row
Set loopRange = .Range("A2:A" & lastRow)
If lastRow = 1 Then Exit Sub
For Each i In loopRange
If Not IsDate(i.Offset(0, 1)) Then
.Range(i, i.End(xlToRight)).Copy Worksheets("Errors").Range("A1").Offset(x, 0)
If Not unionRng Is Nothing Then
Set unionRng = Union(unionRng, i)
Else
Set unionRng = i
End If
End If
Next i
End With
If Not unionRng Is Nothing Then unionRng.EntireRow.Delete
Application.ScreenUpdating = True
End Sub
You just need to change Worksheet to Worksheets with 's' at the end.
Sub removeerrors()
Dim i As Range
Dim x As Double
x = Worksheets("Errors").CountA("A1:A100")
For Each i In Worksheets("Sheet2").Range(Range("A2"), Range("A2").End(xlDown))
If IsDate(i.Offset(0, 1)) = False Then
Range(i, i.End(xlToRight)).Copy
Worksheets("Errors").Range("A1").Offset(x, 0).Paste
Range(i).EntireRow.Delete
End If
Next i
End Sub
use fully qualified range references
loop backwards when deleting rows
update target sheet pasting row index
as follows
Option Explicit
Sub removeerrors()
Dim iRow As Long
Dim x As Double
x = Worksheets("Errors").CountA("A1:A100")
With Worksheets("Sheet2") ' referecne "Sheet2" sheet
With .Range(.Range("A2"), .Range("A2").End(xlDown)) ' reference referenced sheet range from cell A2 down to next not empty one
For iRow = .Rows.Count To 1 Step -1 ' loop reference range backwards from its last row up
If Not IsDate(.Cells(iRow, 2)) Then ' if referenced range cell in column B current row is not a date
.Range(.Cells(iRow, 1), .Cells(iRow, 1).End(xlToRight)).Copy Destination:=Worksheets("Errors").Range("A1").Offset(x, 0) ' copy referenced range current row spanning from column A to next not empty column and paste it to sheet "Errors" column A row x
x = x + 1 ' update offset
.Rows(1).EntireRow.Delete ' delete referenced range current row
End If
Next
End With
End With
End Sub

Looking for specific contents in each of the cells in the column and delete the row in some cases

I'm trying to take the output of our scheduling software for a TV station and get rid of anything for given times. Unfortunately the output of the scheduling software creates a text field for time, not a field that can be formatted to time. I haven't done any real programming in over a decade and this is frustrating me. Here's a sample of the first few rows of the sheet - every day of the month contains entries for each program from 6:00a to the next day at 5:30a.
The code I've got so far is:
Sub delete_extraneous()
Dim rng As Range
Dim j As Integer
Dim m As Integer
m = 1
j = 3
Goto ActiveSheet.Cells(j, m)
With ActiveSheet
lastRow = .Cells(.Rows.Count, "A").End(xlUp).Row
End With
For m = 1 To lastRow
If rng = "6:30a" Or "7:00a" Or "7:30a" Or "8:00a" Or "8:30a" Or "9:00a" Or "9:30a" Or "10:00a" Or "10:30a" Or "11:00a" Or "11:30a" Then
ActiveCell.EntireRow.Delete Shift:=xlShiftUp
End If
Next m
End Sub
Use an array of text-that-looks-like-time and match against it.
Sub delete_extraneous()
dim tms as variant, lastRow as long
tms = array("6:30a", "7:00a", "7:30a", "8:00a", "8:30a", "9:00a", "9:30a", _
"10:00a", "10:30a", "11:00a", "11:30a")
with activesheet
lastRow = .Cells(.Rows.Count, "C").End(xlUp).Row
For m = lastRow to 1 step-1
If not iserror(application.match(.Cells(m, "C").value, tms, 0)) Then
.rows(m).EntireRow.Delete Shift:=xlShiftUp
End If
Next m
.
end with
end sub
You could use Autofilter():
Sub test()
Dim hours As Variant
hours = Array("6:30a", "7:00a", "7:30a", "8:00a", "8:30a", "9:00a", "9:30a", "10:00a", "10:30a", "11:00a", "11:30a")
With Range("C1", Cells(Rows.Count, 3).End(xlUp))
.AutoFilter Field:=1, Criteria1:=hours, Operator:=xlFilterValues
.Offset(1, 0).SpecialCells(xlCellTypeVisible).EntireRow.Delete
If Not IsError(Application.Match(.Cells(1, 1).value, hours, 0)) Then .Rows(1).Delete
End With
ActiveSheet.AutoFilterMode = False
End Sub
You don't state what your specific issue is in the code, but I can tell you a few problems you have.
1) This is not valid syntax Goto ActiveSheet.Cells(j, m). There is a GoTo statement in VBA, but only use when absolutely necessary. (this case does not require it).
2) Don't rely on ActiveSheet. Instead reference the selected worksheet you desire to work with directly.
3) You never actually define rng so it's meaningless and your code will always bypass range. Using Option Explicit at the top of your modules can help avoid this issue.
4) Using active cell is also dangerous and may produce unintended consequences. In your case it will delete the same cell over and over and over again since you never activate any other cell. It's not needed.
See this code below. It also checks for row deletion and loads into a range for one delete statement later (which will be faster than deleting line by line, and doesn't require backwards looping).
Option Explicit
Sub delete_extraneous()
Dim mySheet As Worksheet
Set mySheet = Worksheets("mySheet") 'replace as needed
Dim lastRow As Long
lastRow = mySheet.Cells(mySheet.Rows.Count, 1).End(xlUp).Row
Dim m As Long
For m = 1 To lastRow
Select Case mySheet.Cells(m, 3).Value 'check each row against column C
Case Is = "6:30a", "7:00a", "7:30a", "8:00a", "8:30a", "9:00a", "9:30a", "10:00a", "10:30a", "11:00a", "11:30a"
Dim deleteRng As Range
If deleteRng Is Nothing Then
Set deleteRng = mySheet.Cells(m, 3)
Else
Set deleteRng = Union(deleteRng, mySheet.Cells(m, 3))
End If
End Select
Next
deleteRng.EntireRow.Delete
End Sub

Find to cells in range with specific colors and add comments

I am trying to create a macro that will search a column of text (A:A) for a specific interior color. In this case the interior color is 55. Normally I'd create a range of A1:A101 but the data that is added changes daily so there may be more or less.
Essentially once the macro identifies the cells with the colors I want the macro to add a comment to the cell. Something simple like "Hello World!".
So far this is what I have:
Sub AddCommentBasedOnColor()
Dim rng As Range, cell As Range
Set rng = Range("G:G")
Application.ScreenUpdating = False
Application.Calculation = xlManual
For Each cell In rng
If cell.Interior.ColorIndex = 55 Then
If rng.Comment Is Nothing Then rng.AddComment
rng.Comment.Text "Possible Aux Stacking"
End
End If
Next cell
Application.ScreenUpdating = True
Application.Calculation = xlAutomatic
End Sub
The problem that I am running into is that when I am running the code, the comment portion does not work at all. No comments are made and for some reason I get a debug code but did not have one before. Not sure what I did that changed it.
Additionally, when I remove the commenting section of this code it does take some time to run, any assistance with shortening that length of time would be appreciated as well.
Your code has logical problems.
With rng.AddComment you try setting a comment to the whole column G as rng is the whole column G. This is not possible.
And your inner If statement works as follows:
...
If rng.Comment Is Nothing Then rng.AddComment
rng.Comment.Text "Possible Aux Stacking"
End
...
If rng.Comment Is Nothing Then rng.AddComment. Here the If ends. The next program row is processing ever without additional conditions and the End then ends the Sub at this point.
To shortening the processing time you have not to run over all rows in column G. This is possible by calculation the last used row. How to do this differs on how you define the last used row. Since you are working with the cell's interior, I have defined the last used row as the last row having cells with not default content of empty cells.
Sub AddCommentBasedOnColor()
Dim rng As Range, cell As Range, lastUsedRow As Long
With ActiveSheet
lastUsedRow = .UsedRange.Rows(.UsedRange.Rows.Count).Row
Set rng = .Range("G1:G" & lastUsedRow)
For Each cell In rng
If cell.Interior.ColorIndex = 55 Then
If cell.Comment Is Nothing Then
cell.AddComment
cell.Comment.Text "Possible Aux Stacking"
End If
End If
Next cell
End With
End Sub
You can use Find rather than loop through each cell:
Sub AddCommentBasedOnColor()
Dim rng1 As Range
Dim rng2 As Range
Dim strFirst As String
Application.FindFormat.Interior.ColorIndex = 55
Set rng1 = Columns("G:G").Find(What:="", SearchDirection:=xlNext, SearchFormat:=True)
If Not rng1 Is Nothing Then
strFirst = rng1.Address
Set rng2 = rng1
Do
Set rng2 = Columns("G:G").Find(What:="", After:=rng2, SearchDirection:=xlNext, SearchFormat:=True)
If rng2.Comment Is Nothing Then
rng2.AddComment
rng2.Comment.Text "Possible Aux Stacking"
End If
Loop Until rng2.Address = strFirst
End If
End Sub

VBA what is more optimal: filtering hidden rows

I need to filter a excel sheet with more than 50000 rows 512 times. I am currently using the following code.
Do While Not IsEmpty(ActiveCell.value)
Worksheets("Sheet1").Activate
filtro = ActiveCell.value
Sheets("Sheet2").Select
Range("D1").Select
Selection.AutoFilter
ActiveSheet.Range("$A$1:$I$" & lastRow("D")).AutoFilter Field:=4, Criteria1:=filtro
Range("A1").Select
Do While Not IsEmpty(ActiveCell.value)
Do
ActiveCell.offset(1, 0).Select
oProgress.Increase 1
Loop While ActiveCell.EntireRow.Hidden = True
Call functio ' this function do something with the actual row
Loop
Loop
The problem is it's taking so much time to analyse 50000 rows X 512 times!, I was thinking maybe it is better to filter and then copy the rows into a temporary sheet, and check the values there?.
The line Active is processed by the function readValues
Like other posters to this question, I really am skeptical about filtering 512 times! However, since you say you absolutely need it, see this code below. I think it could be cleaned up still based on your needs, but its hard to say without seeing the bigger scope. It should however take you to a more efficient place than were you are now.
I tried to comment all my assumptions, but please reach out if you need more explanation.
Sub FilterAll512()
Dim wks1 As Worksheet
Set wks1 = Sheets(1)
Dim rng512 As Range, cel As Range
Set rng512 = wks1.Range("A1:A512") '-> say your filter values are in this range, adjust if needed
Dim wks2 As Worksheet
Set wks2 = Sheets(2)
For Each cel In rng512
With wks2
Dim rngFound As Range
Set rngFound = .Columns(4).Find(cel.Text, LookIn:=xlWhole) ' -> make sure value is there to be filtered on
If rngFound Then
.UsedRange.AutoFilter 4, cel.Text '-> assumes upper left most cell is A1
Dim rngSearch As Range, rngCel As Range
'-> assumes upper left most cell is A1 and row 1 has headers
Set rngSearch = Intersect(.UsedRange, .UsedRange.Offset(1), .Columns(4).EntireColumn).SpecialCells(xlCellTypeVisible)
For Each rngCell In rngSearch
oProgress.Increase 1 '-> i don't know what this does, but it doesn't look very efficient, see notes below
Call functio
Next
'-> perhaps above you are just counting rows?
'if so, you can use rngSearch.Rows.Count to get a total count
'or you can use rngCell.Row to get the current row
End If 'iF rngFound Then
End With ' With wks2
Next 'For Each cel In rng512
Set rngCell = Nothing
Set rngSearch = Nothing
Set rngFound = Nothing
Set cel = Nothing
Set rng512 = Nothing
Set wks2 = Nothing
Set wk1 = Nothing
End Sub
Excel has built in filtering tools(most people would describe it as an extremely basic function of excel). And even if you did have to loop through 500,000 rows, this is quite possibly one of the the worst(slowest) and most bizarre ways you could do it:
Do
ActiveCell.offset(1, 0).Select
Loop While ActiveCell.EntireRow.Hidden = True
You can look into Range().Sort and Range().AutoFilter which are the correct ways to do it. Good Luck.
An idea of what you can do is to calculate all row numbers or range of row numbers and then apply a code like this:
rowNumbers = "1:3,10:10,30:50"
Range(rowNumbers).EntireRow.Hidden = True