Delete blank rows other than first column - excel

I have written a macro to delete the row if it is a blank row or if in column B the cell contains the string XYZ. However, this macro can take a couple minutes to run if there is 200+ rows of data. Can anyone provide anything more efficient in VBA format?
Sub DeleteBlanks()
Dim lr As Long, r As Long
For r = Cells(Rows.Count, "B").End(xlUp).Row To 1 Step -1
Range("B" & r).Replace "*XYZ*", "", xlWhole
If Range("B" & r).Value = "" Then
Range("B" & r & ":Q" & r).Delete (xlShiftUp)
End If
Next r
Application.ScreenUpdating = False
End Sub

I would add the ScreenUpdating line to the top, and also turn calculation to manual:
Sub DeleteBlanks()
Dim lr As Long, r As Long
Application.ScreenUpdating = False
Application.Calculation = xlCalculationManual
For r = Cells(Rows.Count, "B").End(xlUp).Row To 1 Step -1
Range("B" & r).Replace "*XYZ*", "", xlWhole
If Range("B" & r).Value = "" Then
Range("B" & r & ":Q" & r).Delete (xlShiftUp)
End If
Next r
Application.ScreenUpdating = True
Application.Calculation = xlCalculationAutomatic
End Sub
As you have it, the entire macro runs, then the screenUpdating is turned off. You can speed it up by putting that up front, then turning it back on when the macro is finished.

In addition to what #BruceWayne said, I will shorten the code
Range("B" & r).Replace "*XYZ*", "", xlWhole
If Range("B" & r).Value = "" Then
With
If Range("B" & r).Value = "" Or InStr(1, Range("B" & r).Value, "XYZ") > 0 then
That will lower the actions that the code needs to make.

First of all, the screen updating should be disabled before the proccess, and re-enabled after that, so the screen will not flash, and load of resources will not be high.
Other than that, text replacement is completely unneeded in your case.
By reading your current code, I assume you consider a blank row if it's empty on column B.
Try this:
Sub DeleteBlanks()
Application.ScreenUpdating = False
Dim lr As Long, r As Long
For r = Cells(Rows.Count, "B").End(xlUp).Row To 1 Step -1
If Range("B" & r).Value = "" Or Range("B" & r).Value Like "*XYZ*" Then
Range("B" & r & ":Q" & r).Delete (xlShiftUp)
End If
Next r
Application.ScreenUpdating = True
End Sub

This solution should be virtually instantaneous:
Public Sub Colin_H()
Dim v, rCrit As Range, rData As Range
With [a1]
Set rData = .Resize(.Item(.Parent.Rows.Count).End(xlUp).Row, .Item(, .Parent.Columns.Count).End(xlToLeft).Column)
End With
Set rCrit = rData.Resize(2, 2).Offset(, rData.Columns.Count + 1)
rCrit.Resize(1) = rData(1, 2): rCrit(2, 1) = "*": rCrit(2, 2) = "<>*xyz*"
rData.AdvancedFilter xlFilterCopy, rCrit, rCrit.Resize(1, 1).Offset(, 2)
With rCrit.Resize(1, 1).Offset(, 2).Resize(rData.Rows.Count, rData.Columns.Count)
v = .Value2
rData = v
.ClearContents
rCrit.ClearContents
End With
End Sub
Notice that there is no looping, no row shifting, and no iterated range construction.
This uses the advanced filter of the range object to filter your records in one quick blast to a range adjacent to your source data. The result is then copied over the source without using the clipboard. There is no quicker or more efficient way to achieve your objective.

Related

If condition is true filter the value and put "yes" next to it else"no"

Hello i m very new at VBA as i m suffering with an issue here,in filtered cell if the condition is correct then put "yes" else put "NO"
but when i run the code in for LOOP it put the yes data in all even if the condition is not true
VBA
Sub check()
Dim j As Long
Dim dsheet As Worksheet
Dim lastrow As Long
Dim fr As Range
Dim psheet As Worksheet
Dim c As Range
Set dsheet = Worksheets("Workings")
Set psheet = Worksheets("sheet1")
lastrow = dsheet.Cells(Rows.Count, 1).End(xlUp).row
For j = 1 To lastrow
psheet.Range("M2").Value = dsheet.Range("A2" & j)
psheet.Range("N2").Value = dsheet.Range("B2" & j)
psheet.Range("A1").AutoFilter Field:=1, Criteria1:=psheet.Range("M2")
psheet.Range("B1").AutoFilter Field:=2, Criteria1:=psheet.Range("N2")
psheet.Range("A2:I" & psheet.Cells(Rows.Count, 1).End(xlUp).row).SpecialCells (xlCellTypeVisible)
dsheet.Range("M2").Value = dsheet.Range("A" & j)
dsheet.Range("N2").Value = dsheet.Range("B" & j)
dsheet.Range("A1").AutoFilter Field:=1, Criteria1:=dsheet.Range("M2")
dsheet.Range("B1").AutoFilter Field:=2, Criteria1:=dsheet.Range("N2")
Set fr = psheet.Range("C2:C50").Find(what:="12345", MatchCase:=True)
For Each c In dsheet.Range("E2:E2000" & Range("A" & Rows.Count).End(xlUp).row).SpecialCells(xlCellTypeVisible)
If fr Is Nothing Then
dsheet.Range("A2" & Range("A" & Rows.Count).End(xlUp).row).SpecialCells (xlCellTypeVisible)
If c.Offset(, -1).Value = vbNullString Then Exit For
c.Value = "NO"
Else
dsheet.Range("A2" & Range("A" Rows.Count).End(xlUp).row).SpecialCells (xlCellTypeVisible)
If c.Offset(, -1). Value =vbNullString Then Exit For
c.Value = "Yes"
End If
Next c
Next j
dsheet. AutoFilterMode = False
psheet. AutoFilterMode = False
End Sub
so, i want the the code to put the "yes" or "NO" according to the condition,It will be great help if anyone help me in this issue
You need an End If for each If statement. It should be like this: Also, proper indenting helps make your code more readable.
If fr Is Nothing Then
dsheet.Range("A2" & Range("A" & Rows.Count).End(xlUp).row).SpecialCells (xlCellTypeVisible)
If c.Offset(, -1).Value = vbNullString Then Exit For
End If
c.Value = "NO"
Else
dsheet.Range("A2" & Range("A" Rows.Count).End(xlUp).row).SpecialCells
(xlCellTypeVisible)
If c.Offset(, -1). Value =vbNullString Then Exit For
End If
c.Value = "Yes"
End If

For Loop leaves unwanted rows behind

...because the row is only evaluated once and the next row is called for evaluation. But the next row is now the previous row. How do I account for this?
For i = 5 To Range("A" & "65536").End(xlUp).Row Step 1
If Application.WorksheetFunction.CountIf(Range("A" & i), "#N/A") = 1 Then
Range("A" & i).EntireRow.Delete
End If
Next i
You can delete your rows all at once, using Union. Like this:
Sub test()
Dim i As Long
Dim deleteRange As Range
For i = 5 To Range("A" & "65536").End(xlUp).Row Step 1
If Application.WorksheetFunction.CountIf(Range("A" & i), "#N/A") = 1 Then
If deleteRange Is Nothing Then
Set deleteRange = Range("A" & i).EntireRow
Else: Set deleteRange = Union(deleteRange, Range("A" & i).EntireRow)
End If
End If
Next i
deleteRange.Delete
End Sub
Loop backwards (and use Rows.Count rather than hard-coding 65536) as new versions of Excel have a capacity of more than a million rows.
For i = Range("A" & Rows.Count).End(xlUp).Row To 5 Step -1
If Application.WorksheetFunction.CountIf(Range("A" & i), "#N/A") = 1 Then
Range("A" & i).EntireRow.Delete
End If
Next i

Merge rows, sum one column of values, and keep earliest start time and latest end time

I have been able to find code that will merge rows and delete the duplicate rows that are not needed any more and sum one of the columns. However, those codes are based on ActiveCells, which will not work for me. I need this to work on a large range of data. As in the example below, there will be rows of 2, 3, or more rows that need to be merged. But I also have an additional requirement that I just cannot find a solution for. Below is a small set of data that we can use as an example. There are 4 columns here (there are 5 more columns in the actual data set, but they are all duplicate data and not needed for this example) that represents the challenge. I would need to merge these three rows into one, add the values in column B (continued below)
The final result would be this where the earliest Start date & time is kept and the latest start date & time are also kept:
The data will be in columns A through Z (row 1 is a header column), and data is added hourly. For all my other code, I typically limit the number of rows to 2000. We have not exceeded that yet. I have a custom menu that I will use to trigger the code as the purpose is to have as little user input as possible (automation is key). Is there a way to do this with VBA?
If column A is sorted then try this code:
Sub Test()
Dim Rng As Range, dRng As Range
Dim i As Long, LR As Long 'lastrow
With Application
.ScreenUpdating = False
.EnableEvents = False
.Calculation = xlCalculationManual
End With
LR = Range("A" & Rows.Count).End(xlUp).Row
Set Rng = Range("A2:D2")
For i = 3 To LR
If Rng(1) = Cells(i, 1) Then
Set Rng = Range(Rng(1), Cells(i, 4))
Else
If Rng.Rows.Count > 1 Then GoSub mSub
Set Rng = Range(Cells(i, 1), Cells(i, 4))
End If
Next
If Rng.Rows.Count > 1 Then GoSub mSub
If Not dRng Is Nothing Then dRng.EntireRow.Delete
With Application
.ScreenUpdating = True
.EnableEvents = True
.Calculation = xlCalculationAutomatic
End With
Exit Sub
mSub:
With WorksheetFunction
Rng(2) = .Sum(Rng.Columns(2))
Rng(3) = .Min(Rng.Columns(3))
Rng(4) = .Max(Rng.Columns(4))
End With
If dRng Is Nothing Then
Set dRng = Range(Rng(2, 1), Rng(Rng.Count))
Else
Set dRng = Union(dRng, Range(Rng(2, 1), Rng(Rng.Count)))
End If
Return
End Sub
Here is a quick bit of code I put together for you. It will do what you are asking for, but I believe there are better ways of doing it with more information on what you are looking for.
Sub combineLikes()
endRng = Range("D92000").End(xlUp).Row
i = 2
Do Until i > endRng
If Range("A" & i).Value = Range("A" & i).Offset(1, 0).Value Then
Range("B" & i).Value = Range("B" & i).Value + Range("B" & i).Offset(1, 0).Value
If Range("C" & i).Value > Range("C" & i).Offset(1, 0).Value Then Range("C" & i).Value = Range("C" & i).Offset(1, 0).Value
If Range("D" & i).Value < Range("D" & i).Offset(1, 0).Value Then Range("D" & i).Value = Range("D" & i).Offset(1, 0).Value
Rows(i + 1).EntireRow.Delete
endRng = endRng - 1
Else
i = i + 1
End If
Loop
End Sub

Compare values in Excel VBA

I am trying to compare cell A1 with B1 and if it is true populate cell F1 with the A1 value. But irrespective of my input values the if condition becomes true.
Sub Macro3()
Dim i As Integer
i = 1
For i = 1 To 10
If (Range("A" & i).Select = Range("B" & i).Select) Then
Range("A" & i).Select
Selection.Copy
Range("F" & i).Select
ActiveSheet.Paste
End If
Next i
End Sub
Instead of selecting, copying, and pasting, you can compare the Value property of the cells, then set the F column Value accordingly:
Dim i As Integer
For i = 1 To 10
If Range("A" & i).Value = Range("B" & i).Value Then
Range("F" & i).Value = Range("A" & i).Value
End If
Next
Consider this a compliment to Nick's answer (accept his if you find it to work, which you should). I wanted to help explain some of the things that are wrong in your code.
Before FIX:
Sub Macro3()
Dim i As Integer
i = 1
For i = 1 To 10
If (Range("A" & i).Select = Range("B" & i).Select) Then
Range("A" & i).Select
Selection.Copy
Range("F" & i).Select
ActiveSheet.Paste
End If
Next i
End Sub
AFTER FIX
Sub Macro4()
Dim i As Long
For i = 1 To 10
If Range("A" & i).Value = Range("B" & i).Value Then
Range("F" & i).Value = Range("A" & i).Value
End If
Next
End Sub
POINTS:
Use Long instead of Integer (small optimization since VBA will convert the int to a long anyway)
No need to declare i = 1 twice in a row
You should be comparing values, not simply selecting cells. There is rarely, if ever, a need to use the .Select keyword. You can access all object's properties directly.
Copy and paste is a heavy operation. Since you are in VBA, may as well just assign the value that is in A to the cell in column B. It's faster, and more effecient.
I hope this helps. BTW, you can simple enter:
=IF(A1=B1,A1,"")
in F1 and drag the formula down to get a similar result.
You can use a variant array to address your performance issue that you raise above. This code will run the same as Nicks except it will skip blanks cell, ie it will
update the F value if A and B are the same
skip updates if the A cell is blank
leave the existing F values in place if A<>B
It wasn't clear to me how you are comparing rows accross two sheets, can you expand on this?
Sub MyArray()
Dim X As Variant
Dim Y As Variant
Dim lngrow As Long
X = Range([a1], Cells(Rows.Count, "B").End(xlUp))
Y = Range([f1], [f1].Offset(UBound(X, 1) - 1, 0))
For lngrow = 1 To UBound(X, 1)
If Len(X(lngrow, 1)) > 0 Then
If X(lngrow, 1) = X(lngrow, 2) Then Y(lngrow, 1) = X(lngrow, 1)
End If
Next
Range([f1], [f1].Offset(UBound(X, 1) - 1, 0)) = Y
End Sub

Cut and Paste Rows, Why So Slow, Trying To Speed Up Performance

I have a small estimating worksheet I have made where you type in line items with a description, quantity and price. As it happens you often want to organize your data. In this case I would like to have the ability to move a range of cells up or down the list. (No data is being deleted, just shifting the range up or down) I am simply trying to cut and paste the data up or down one row at a time. I have an Up Arrow and Down Arrow for users to click on to activate the macro, one for up and one for down.
I have successfully written the macro code to make this happen, however it uses the select command and is very slow. I have used the same code before on another project and it was much faster, almost instant, the only difference that I can think of is that I was selecting the the ENTIRE row. In this particular instance I am only wanting to move the data in Columns B thru N. Columns O thru Y have fixed inputs and cannot be moved.
Below is the working code to move the range up.
Sub MoveUp()
Application.ScreenUpdating = False
ActiveSheet.Unprotect 'Unprotects The Sheet
If Not Intersect(ActiveCell, Range("B12:F98")) Is Nothing Then 'Makes sure you are within the correct range, cannot move top row up
Range("B" & ActiveCell.Row & ":" & "N" & ActiveCell.Row).Select 'Selects only area you want to move
Selection.Rows(Selection.Rows.Count + 1).Insert Shift:=xlDown
Selection.Rows(1).Offset(-1).Cut Selection.Rows(Selection.Rows.Count + 1)
Selection.Rows(1).Offset(-1).Delete Shift:=xlUp
Selection.Offset(-1).Select 'This keeps the cell you moved selected so you can keep moving it without having to reselect
Else
MsgBox "You Can't Move That Row Up"
End If
Call ResetRanges 'Resets the named ranges
Call ProtectWorkSheet 'Protects The Sheet
Application.ScreenUpdating = True
End Sub
I wrote an alternative macro without using select, trying to use defined ranges and use offsets to make the shift happen, but it was still just as slow as the above code. Am I missing something?
Sub MoveUpRangeMethod()
Application.ScreenUpdating = False
activerow = ActiveCell.Row
endon = activerow - 1
Set rng = Range("B" & activerow & ":" & "N" & activerow)
Set rng2 = Range("B" & activerow - 1 & ":" & "N" & activerow - 1)
rng3 = ("F" & endon)
rng.Cut
rng2.Insert Shift:=xlDown
Range(rng3).Activate
Application.ScreenUpdating = True
End Sub
Update:
I wrote the macro out another way and it still performs slowly. I think that there is something else behind the scenes causing the slowdown. It could quite possibly be from some bad code I have elsewhere. Below is the new code I tried, again, it works fast in a new sheet, but struggles in the workbook where I need it.
Sub ShiftCellsDown()
Dim ws As Worksheet: Set ws = ThisWorkbook.ActiveSheet
Application.ScreenUpdating = False
Application.Calculation = xlManual
Application.DisplayAlerts = False
ws.Range("B" & ActiveCell.Row - 1 & ":" & "N" & ActiveCell.Row - 1).Insert Shift:=xlDown, CopyOrigin:=xlFormatFromLeftOrAbove
ws.Range("B" & ActiveCell.Row - 1 & ":" & "N" & ActiveCell.Row - 1).Value = ws.Range("B" & ActiveCell.Row + 1 & ":" & "N" & ActiveCell.Row + 1).Value
ws.Range("B" & ActiveCell.Row + 1 & ":" & "N" & ActiveCell.Row + 1).Delete Shift:=xlUp
ws.Range(("F" & ActiveCell.Row - 1)).Activate
Application.DisplayAlerts = True
Application.Calculation = xlCalculationAutomatic
Application.ScreenUpdating = True
End Sub
How about the following, I've just simplified your code a little and added a couple of lines for better performance:
Sub MoveUpRangeMethod()
Dim ws As Worksheet: Set ws = ThisWorkbook.ActiveSheet
Application.ScreenUpdating = False
Application.Calculation = xlManual
Application.DisplayAlerts = False
ws.Range("B" & ActiveCell.Row & ":" & "N" & ActiveCell.Row).Cut
ws.Range("B" & ActiveCell.Row - 1 & ":" & "N" & ActiveCell.Row - 1).Insert Shift:=xlDown
ws.Range("F" & ActiveCell.Row - 1).Activate
Application.DisplayAlerts = True
Application.Calculation = xlCalculationAutomatic
Application.ScreenUpdating = True
End Sub
UPDATE:
The following update might help, instead of Cut & Insert Ranges, it assigns the values into Arrays and then passes them values into the desired cells, thus hopefully speeding up the process:
Sub MoveUpRangeMethod()
Dim ws As Worksheet: Set ws = ThisWorkbook.ActiveSheet
Dim Arr() As Variant, Arr2() As Variant ' declare two unallocated arrays.
Application.ScreenUpdating = False
Application.Calculation = xlManual
Application.DisplayAlerts = False
If ActiveCell.Row > 2 Then
Arr = ws.Range("B" & ActiveCell.Row & ":" & "N" & ActiveCell.Row).Value ' Arr is now an allocated array
Arr2 = ws.Range("B" & ActiveCell.Row - 1 & ":" & "N" & ActiveCell.Row - 1).Value ' Arrw is now an allocated array
ws.Range("B" & ActiveCell.Row).Resize(UBound(Arr2, 1), UBound(Arr2, 2)).Value = Arr2
ws.Range("B" & ActiveCell.Row - 1).Resize(UBound(Arr, 1), UBound(Arr, 2)).Value = Arr
ws.Range("F" & ActiveCell.Row - 1).Activate
End If
Application.DisplayAlerts = True
Application.Calculation = xlCalculationAutomatic
Application.ScreenUpdating = True
End Sub

Resources