Remove rows in excel - vba

Here is a tricky question.
I have an Excel file containing roughly 4000 articles in each of the 10 sheets within the workbook. I would like to keep only about 400 articles and the remaining 3600 articles should be removed.
All sheets within the workbook has the article ID in column A.
All sheets has headers on row 1-5.
The article ID can exist more than once in some of the sheets.
I want to list the 400 article ID's in the Visual Basic Script itself so that i don't have to create a separate sheet or column that contains the information.
Can someone please help me? I have tried so many scripts, but nothing seems to work...
In the example below I want to keep Article ID's 5 and 1 (and of course the headers). The remaining 5 rows should be removed
This is what i have tried:
Sub Delete()
Dim Firstrow As Long
Dim Lastrow As Long
Dim Lrow As Long
Dim CalcMode As Long
Dim ViewMode As Long
With Application
CalcMode = .Calculation
.Calculation = xlCalculationManual
.ScreenUpdating = False
End With
With ActiveSheet
.Select
ViewMode = ActiveWindow.View
ActiveWindow.View = xlNormalView
.DisplayPageBreaks = False
Firstrow = 6
Lastrow = .Cells(.Rows.Count, "A").End(xlUp).Row
For Lrow = Lastrow To Firstrow Step -1
With .Cells(Lrow, "Y")
If Not IsError(.Value) Then
If InStr(.Value, 1) = 0 Or InStr(.Value, 5) = 0 Then .EntireRow.Delete
End If
End With
Next Lrow
End With
End Sub
But, I get two issues:
All rows are removed including the rows that I want to remove (1 and 5).
It only works on the open sheet and not the whole workbook.
Kind regards,
Peter

Try this code. At the start it will ask what IDs you want to keep in all worksheets. There you enter numbers separated by comma (,), no spaces or character other than comma and digits aren't allowed.
Sub DeleteArticles()
Dim i As Long
Dim strIDToKeep As String
Dim arrIDToKeep() As String
Dim ws As Worksheet
Dim lastRow As Long
strIDToKeep = InputBox("What IDs to keep?")
arrIDToKeep = Split(strIDToKeep, ",")
For Each ws In Worksheets
lastRow = ws.Cells(ws.Rows.Count, 1).End(xlUp).Row
For i = lastRow To 6 Step -1
'if ID isn't present in array of IDs to keep, then we delete entire row
If UBound(Filter(arrIDToKeep, ws.Cells(i, 1).Value)) = -1 Then
ws.Rows(i).EntireRow.Delete
End If
Next
Next
End Sub

Try this code
Sub Test()
Dim ws As Worksheet
For Each ws In ThisWorkbook.Worksheets
Dim i As Long
For i = 10 To 2 Step -1
Select Case ws.Cells(i, 1).Value
'add your ids for which you don't want to delete the rows
Case 1, 5
'do nothing
Case Else
ws.Cells(i, 1).EntireRow.Delete
End Select
Next i
Next ws
End Sub

Related

Ideas to make this code more efficient

I have a worksheet that lists a persons name (column A) with associated data (columns B through G). I have code below that takes this list of a ~ 1000 rows that
A.) First copies and pastes each row three times (to create four identical rows for each entry) then
B.) Loops through the now ~4000 rows and creates a new worksheet for each person.
As there are many duplicate names in column A this only creates ~ ten new worksheets
The thing is, it runs but runs quite slowly (and I receive the Excel not responding warning at times). Is there anything to clean this up to make it more efficient? And after this I run another macro to save the new worksheets to a new workbook. Would it be faster to do that with code here?
Sub Split_Data()
'This will split the data in column A out by unique values
Const NameCol = "A"
Const HeaderRow = 1
Const FirstRow = 2
Dim SrcSheet As Worksheet
Dim TrgSheet As Worksheet
Dim SrcRow As Long
Dim LastRow As Long
Dim TrgRow As Long
Dim person As String
Dim lRow As Long
Dim RepeatFactor As Variant
'Optimize Macro Speed
Application.ScreenUpdating = False
Application.EnableEvents = False
Application.Calculation = xlCalculationManual
'Add four rows
lRow = 2
Do While (Cells(lRow, "B") <> "")
RepeatFactor = 4
Range(Cells(lRow, "A"), Cells(lRow, "G")).Copy
Range(Cells(lRow + 1, "A"), Cells(lRow + RepeatFactor - 1, "G")).Select
Selection.Insert Shift:=xlDown
lRow = lRow + RepeatFactor - 1
lRow = lRow + 1
Loop
Set SrcSheet = ActiveSheet
LastRow = SrcSheet.Cells(SrcSheet.Rows.Count, NameCol).End(xlUp).Row
For SrcRow = FirstRow To LastRow
person = SrcSheet.Cells(SrcRow, NameCol).Value
Set TrgSheet = Nothing
On Error Resume Next
Set TrgSheet = Worksheets(person)
On Error GoTo 0
If TrgSheet Is Nothing Then
Set TrgSheet = Worksheets.Add(After:=Worksheets(Worksheets.Count))
TrgSheet.Name = person
SrcSheet.Rows(HeaderRow).Copy Destination:=TrgSheet.Rows(HeaderRow)
End If
TrgRow = TrgSheet.Cells(TrgSheet.Rows.Count, NameCol).End(xlUp).Row + 1
SrcSheet.Rows(SrcRow).Copy Destination:=TrgSheet.Rows(TrgRow)
Next SrcRow
ResetSettings:
'Reset Macro Optimization Settings
Application.EnableEvents = True
Application.Calculation = xlCalculationAutomatic
Application.ScreenUpdating = True
End Sub
first you read the column of names in one pass and put it in an VBA array:
Dim DATA()
with SrcSheet
DATA= .range(.cells(FirstRow, NameCol), .cells(lastRow, namecol)).value2
end with
this gives you a 2D array.
then you create a new scripiting.dictionary , wich fills on a for loop with DATA, and each time a name doesn't exist, you add it to the dictionary.
Dim Dict as new scripting.dictionary 'needs a reference in VBE to : Microsoft Scripting Runtime
dim i& 'long
dim h$ 'string
for i=1 to lastrow-firstrow+1
h=DATA(i,1)
if not dict.exists(h) then
dict(h)=i 'creaates an entry with key=h, item=whatever , here i
end if
next i
You can either create the new worksheets on the fly while adding entries to Dict, or loop later for i=1 to dict.count ...
at the end , you reset all : erase DATA : set Dict=nothing.
Note that this code does not need error handling.
Plz comment on how much time this version needs to do the same task now.
EDIT : your do while looks slow (copy select, insert). If possible B.value2=A.value2 from a range perspective.

Handling Merged Cells when Deleting Rows

I'm working on writing a module to remove unwanted text from a number of worksheets within a single workbook. I've pieced together enough to remove rows that have a specific font type, and rows that are empty; however, I've hit a snag.
The worksheets have a number of merged cells. I want to delete specific rows based on key phrases. Example, if "Comments" is found anywhere in Column A delete the row. However, if comments is merged between A2:A4, text in B3:B4 remains, leaving junk in the sheets I don't want.
Is there a way to delete the merged cell, and all rows to the right of that cell, if in the value in Column A is any number of keywords I'm looking for?
Here's what I have so far...
Sub Delete_Rows_Courier()
Dim ws As Excel.Worksheet
Dim LastRow As Long
Dim i As Integer
For Each ws In Application.ThisWorkbook.Worksheets
LastRow = ws.Cells(Rows.Count, 1).End(xlUp).Row
i = 1
Do While i <= LastRow
If ws.Range("A" & i).Font.Name = "Courier New" Then
ws.Rows(i).Delete
i = i - 1
LastRow = LastRow - 1
End If
i = i + 1
Loop
Next
End Sub
Sub Delete_Empty_Rows()
Dim ws As Worksheet
Dim wb As Workbook
Dim i As Long
For Each ws In Application.ThisWorkbook.Worksheets
'Deletes the entire row within the selection if the ENTIRE row contains no data.
'We use Long in case they have over 32,767 rows selected.
'We turn off calculation and screenupdating to speed up the macro.
With Application
.Calculation = xlCalculationManual
.ScreenUpdating = False
'We work backwards because we are deleting rows.
For i = ws.UsedRange.Rows.Count To 1 Step -1
If WorksheetFunction.CountA(ws.Rows(i)) = 0 Then
ws.Rows(i).EntireRow.Delete
End If
Next i
.Calculation = xlCalculationAutomatic
.ScreenUpdating = True
End With
Next ws
End Sub
Sub RunMacros()
Delete_Empty_Rows
Delete_Rows_Courier
End Sub
With .Range("A" & i).Mergearea
x = .Rows.Count 'if you need to know how many rows were deleted
.EntireRow.Delete 'delete merged rows
End With

How to select and delete every 3rd column

I have a set of data where every third column is the same. I want to leave only the first column and other which are the same must be deleted.
At first I tried this code but it deleted wrong columns because in every loop other columns positions were altered.
Sub DeleteMultipleColumns()
Dim i As Integer
Dim LastColumn As Long
Dim ws As Worksheet
Set ws = Sheets("Arkusz2")
LastColumn = ws.Cells(1, Columns.Count).End(xlToLeft).Column
ws.Activate
For i = 4 To (LastColumn - 2)
ws.Columns(i).Select
Selection.Delete Shift:=xlToLeft
i = i + 3
Next i
End Sub
After this I tried another one using Union. It doesn't work as well:
Sub DeleteMultipleColumns()
Dim i As Integer
Dim LastColumn As Long
Dim ws As Worksheet
Set ws = Sheets("Arkusz2")
LastColumn = ws.Cells(1, Columns.Count).End(xlToLeft).Column
ws.Activate
For i = 4 To (LastColumn - 2)
Application.Union.Columns(i).Select
i = i + 3
Next i
Selection.Delete Shift:=xlToLeft
End Sub
So how to do it?
My new idea is to try with an array. Do I have other options?
This is the code that I've implemented after your very good answers (thanks: sam092, meohow, mattboy):
Sub DeleteMultipleColumns()
Dim i As Integer
Dim LastColumn As Long
Dim ws As Worksheet
Application.ScreenUpdating = False
Set ws = Sheets("Arkusz2")
LastColumn = ws.Cells(1, Columns.Count).End(xlToLeft).Column - 2
For i = LastColumn To 4 Step -3
ws.Columns(i).Delete Shift:=xlToLeft
Next i
Application.ScreenUpdating = True
End Sub
Reverse the direction. Start deleting from the right. I think you know how to modify your code
You can go backwards like this. Also, you don't need to select the column before deleting, you can simply delete it right away.
For i = ((LastColumn \ 4) * 4) To 4 Step -4
ws.Columns(i).Delete Shift:=xlToLeft
Next i
I upvoted Mattboy's code as the cleanest
It is possible to avoid the range loop and use an array as you suggest, although I post this more for kicks as the array generation is tricky
Uses Is it possible to fill an array with row numbers which match a certain criteria without looping?
Sub OneinThree()
Dim ws As Worksheet
Dim rng1 As Range
Dim x As String
Set ws = ActiveSheet
Set rng1 = Cells(1, ws.Cells(1, Columns.Count).End(xlToLeft).Column - 2)
x = Join(Filter(Application.Evaluate("=IF(MOD(column(A1:" & rng1.Address & "),3)=0,address(1,column(a1:" & rng1.Address & ")),""x"")"), "x", False), ",")
If Len(x) > 0 Then ws.Range(x).EntireColumn.Delete
End Sub

Efficient way to delete entire row if cell doesn't contain '#' [duplicate]

This question already has answers here:
Delete Row based on Search Key VBA
(3 answers)
Closed 8 years ago.
I'm creating a fast sub to do a validity check for emails. I want to delete entire rows of contact data that do not contain a '#' in the 'E' Column. I used the below macro, but it operates too slowly because Excel moves all the rows after deleting.
I've tried another technique like this: set rng = union(rng,c.EntireRow), and afterwards deleting the entire range, but I couldn't prevent error messages.
I've also experimented with just adding each row to a selection, and after everything was selected (as in ctrl+select), subsequently deleting it, but I could not find the appropriate syntax for that.
Any ideas?
Sub Deleteit()
Application.ScreenUpdating = False
Dim pos As Integer
Dim c As Range
For Each c In Range("E:E")
pos = InStr(c.Value, "#")
If pos = 0 Then
c.EntireRow.Delete
End If
Next
Application.ScreenUpdating = True
End Sub
You don't need a loop to do this. An autofilter is much more efficient. (similar to cursor vs. where clause in SQL)
Autofilter all rows that don't contain "#" and then delete them like this:
Sub KeepOnlyAtSymbolRows()
Dim ws As Worksheet
Dim rng As Range
Dim lastRow As Long
Set ws = ActiveWorkbook.Sheets("Sheet1")
lastRow = ws.Range("E" & ws.Rows.Count).End(xlUp).Row
Set rng = ws.Range("E1:E" & lastRow)
' filter and delete all but header row
With rng
.AutoFilter Field:=1, Criteria1:="<>*#*"
.Offset(1, 0).SpecialCells(xlCellTypeVisible).EntireRow.Delete
End With
' turn off the filters
ws.AutoFilterMode = False
End Sub
NOTES:
.Offset(1,0) prevents us from deleting the title row
.SpecialCells(xlCellTypeVisible) specifies the rows that remain after the autofilter has been applied
.EntireRow.Delete deletes all visible rows except for the title row
Step through the code and you can see what each line does. Use F8 in the VBA Editor.
Have you tried a simple auto filter using "#" as the criteria then use
specialcells(xlcelltypevisible).entirerow.delete
note: there are asterisks before and after the # but I don't know how to stop them being parsed out!
Using an example provided by user shahkalpesh, I created the following macro successfully. I'm still curious to learn other techniques (like the one referenced by Fnostro in which you clear content, sort, and then delete). I'm new to VBA so any examples would be very helpful.
Sub Delete_It()
Dim Firstrow As Long
Dim Lastrow As Long
Dim Lrow As Long
Dim CalcMode As Long
Dim ViewMode As Long
With Application
CalcMode = .Calculation
.Calculation = xlCalculationManual
.ScreenUpdating = False
End With
With ActiveSheet
.Select
ViewMode = ActiveWindow.View
ActiveWindow.View = xlNormalView
.DisplayPageBreaks = False
'Firstrow = .UsedRange.Cells(1).Row
Firstrow = 2
Lastrow = .Cells(.Rows.Count, "E").End(xlUp).Row
For Lrow = Lastrow To Firstrow Step -1
With .Cells(Lrow, "E")
If Not IsError(.Value) Then
If InStr(.Value, "#") = 0 Then .EntireRow.Delete
End If
End With
Next Lrow
End With
ActiveWindow.View = ViewMode
With Application
.ScreenUpdating = True
.Calculation = CalcMode
End With
End Sub
When you are working with many rows and many conditions, you better off using this method of row deletion
Option Explicit
Sub DeleteEmptyRows()
Application.ScreenUpdating = False
Dim ws As Worksheet
Dim i&, lr&, rowsToDelete$, lookFor$
'*!!!* set the condition for row deletion
lookFor = "#"
Set ws = ThisWorkbook.Sheets("Sheet1")
lr = ws.Range("E" & Rows.Count).End(xlUp).Row
ReDim arr(0)
For i = 1 To lr
If StrComp(CStr(ws.Range("E" & i).Text), lookFor, vbTextCompare) = 0 then
' nothing
Else
ReDim Preserve arr(UBound(arr) + 1)
arr(UBound(arr) - 1) = i
End If
Next i
If UBound(arr) > 0 Then
ReDim Preserve arr(UBound(arr) - 1)
For i = LBound(arr) To UBound(arr)
rowsToDelete = rowsToDelete & arr(i) & ":" & arr(i) & ","
Next i
ws.Range(Left(rowsToDelete, Len(rowsToDelete) - 1)).Delete Shift:=xlUp
Else
Application.ScreenUpdating = True
MsgBox "No more rows contain: " & lookFor & "or" & lookFor2 & ", therefore exiting"
Exit Sub
End If
If Not Application.ScreenUpdating Then Application.ScreenUpdating = True
Set ws = Nothing
End Sub
Instead of looping and referencing each cell 1 by 1, grab everything and put it into a variant array; Then loop the variant array.
Starter:
Sub Sample()
' Look in Column D, starting at row 2
DeleteRowsWithValue "#", 4, 2
End Sub
The Real worker:
Sub DeleteRowsWithValue(Value As String, Column As Long, StartingRow As Long, Optional Sheet)
Dim i As Long, LastRow As Long
Dim vData() As Variant
Dim DeleteAddress As String
' Sheet is a Variant, so we test if it was passed or not.
If IsMissing(Sheet) Then Set Sheet = ActiveSheet
' Get the last row
LastRow = Sheet.Cells(Sheet.Rows.Count, Column).End(xlUp).Row
' Make sure that there is work to be done
If LastRow < StartingRow Then Exit Sub
' The Key to speeding up the function is only reading the cells once
' and dumping the values to a variant array, vData
vData = Sheet.Cells(StartingRow, Column) _
.Resize(LastRow - StartingRow + 1, 1).Value
' vData will look like vData(1 to nRows, 1 to 1)
For i = LBound(vData) To UBound(vData)
' Find the value inside of the cell
If InStr(vData(i, 1), Value) > 0 Then
' Adding the StartingRow so that everything lines up properly
DeleteAddress = DeleteAddress & ",A" & (StartingRow + i - 1)
End If
Next
If DeleteAddress <> vbNullString Then
' remove the first ","
DeleteAddress = Mid(DeleteAddress, 2)
' Delete all the Rows
Sheet.Range(DeleteAddress).EntireRow.Delete
End If
End Sub

Run equation in every cell in a column

I am trying to run an equation in my spread sheet that will go through column "I" and delete every row that does not have an expiration date within 90 days from now... In other words i am trying to format my spread sheet to just give me a list of everything that is expiring in the next 90 days. The row where I put the stars is where I am having difficulty inserting the equation. I am not sure how to insert the equation but if it was ran in cell by itself it would look like this =IF(AND(I11-900),1,0)=1. What would I change Q11 to be so that way when the equation is run it will apply to every cell in the I column instead of just I 11
Sub DeleteNow()
Dim Firstrow As Long
Dim Lastrow As Long
Dim Lrow As Long
Dim CalcMode As Long
Dim ViewMode As Long
With Application
CalcMode = .Calculation
.Calculation = xlCalculationManual
.ScreenUpdating = False
End With
With Sheets("Copy")
.Select
ViewMode = ActiveWindow.View
ActiveWindow.View = xlNormalView
.DisplayPageBreaks = False
Firstrow = .UsedRange.Cells(1).Row
Lastrow = .UsedRange.Rows(.UsedRange.Rows.Count).Row
For Lrow = Lastrow To Firstrow Step -1
With .Cells(Lrow, "I")
If Not IsError(.Value) Then
If ******************** Then .EntireRow.Delete
End If
End With
Next Lrow
End With
ActiveWindow.View = ViewMode
With Application
.ScreenUpdating = True
.Calculation = CalcMode
End With
End Sub
I don't have XL on me at the moment, so there may be some syntax errors, but this should be a lot easier on you and very simple to understand and update. Notice I just built the core of the code, I left all your Application level stuff out.
With Sheets("Copy")
'.Select -> no need to select anything, you can work right with the object
ViewMode = ActiveWindow.View
ActiveWindow.View = xlNormalView
.DisplayPageBreaks = False
Dim myCol as Integer
myCol = 9
'the below assumes your data sets starts in column A and you want to filter on column I
.UsedRange.AutoFilter myCol, xlLast90Days 'this "xlLast90Days" is most likely not right, but if you do it manually while recording a macro, you will get the correct syntax
Dim rngDelete as Range
On Error Resume Next 'in case there are no visible cells
Set rngDelete = Intersect(.UsedRange, .UsedRange.Offset(1), .Columns(myCol)).SpecialCells(xlCellTypeVisible) 'assumes first row of usedrange is header row
'if there are values over 90 delete them
If not rngDelete is Nothing Then rngDelete.EntireRow.Delete
End With
Sub deleteRowsWithDateNotIn90Days()
Dim lastRow As Integer
Dim firstRow As Integer
Dim ctr As Integer
Dim currentCell As Range
Dim valueOfIColumn
Dim isWithin90Days As Boolean
lastRow = 17
firstRow = 1
Application.ScreenUpdating = False
With Sheets("Copy")
For ctr = lastRow To firstRow Step -1
Set currentCell = .Cells(ctr, 9)
valueOfIColumn = currentCell.Value
isWithin90Days = valueOfIColumn >= Date And valueOfIColumn <= (Date + 90)
If Not isWithin90Days Then
Debug.Print "deleting row of cell " + currentCell.Address
currentCell.EntireRow.Delete
End If
Next
End With
Application.ScreenUpdating = True
End Sub
EDIT: Use this as a basis to get started.
You can remove unnecessary code generated by the macro recorder.
I think what you are going to want to do is change your For Next loop into a For Each loop. That way you can just pull every element out of the array and modify it, then put it back. Like so:
'Psuedo code for learing, won't work if used.
Dim gRange as Range 'Generic
Dim testRange as Range
Set testRange = Worksheets("This").Range("Test Column")
For Each gRange in testRange
If(moreThan90Days)
gRange.EntireRow.Delete
End If
Next gRange
If you need more instruction a quick google search on For Next loops will probably turn up what you're looking for.