I have only one column of data. I need to write a macro that would go through all the values and delete all rows that contain the word "paper".
A B
1 678
2 paper
3 3
4 09
5 89
6 paper
The problem is that the number of rows is not fixed. Sheets may have different number of rows.
Here is another simple macro that will remove all rows with non-numeric values in column A (besides row 1).
Sub DeleteRowsWithStringsInColumnA()
Dim i As Long
With ActiveSheet '<~~ Or whatever sheet you may want to use the code for
For i = .Cells(.Cells(.Rows.Count, 1).End(xlUp).Row, 1).Row To 2 Step -1 '<~~ To row 2 keeps the header
If IsNumeric(.Cells(i, 1).Value) = False Then .Cells(i, 1).EntireRow.Delete
Next i
End With
End Sub
If you're confident that the rows in question would always contain "paper" specifically and never any other string, you should match based on the value paper rather than it being a string. This is because, particularly in Excel, sometimes you may have numbers stored as strings without realizing it--and you don't want to delete those rows.
Sub DeleteRowsWithPaper()
Dim a As Integer
a = 1
Do While Cells(a, 1) <> ""
If Cells(a, 1) = "paper" Then
Rows(a).Delete Shift:=xlUp
'Row counter should not be incremented if row was just deleted
Else
'Increment a for next row only if row not deleted
a = a + 1
End If
Loop
End Sub
The following is a flexible macro that allows you to input a string or number to find and delete its respective row. It is able to process 1.04 million rows of simple strings and numbers in 2.7 seconds.
Sub DeleteRows()
Dim Wsht As Worksheet
Dim LRow, Iter As Long
Dim Var As Variant
Var = InputBox("Please specify value to find and delete.")
Set Wsht = ThisWorkbook.ActiveSheet
LRow = Wsht.Cells(Rows.Count, 1).End(xlUp).Row
StartTime = Timer
Application.ScreenUpdating = False
With Wsht
For Iter = LRow To 1 Step -1
If InStr(.Cells(Iter, 1), Var) > 0 Then
.Cells(Iter, 1).EntireRow.Delete
End If
Next Iter
End With
Application.ScreenUpdating = True
Debug.Print Timer - StartTime
End Sub
Related
I have a file with more then 1 sheet, where in the Reports Sheet I want to filter by ASBN products and then delete them, because I already processed it in another sheet, so I need to delete the initial ones in order to paste back the processed one.
Idea is that this deleting code which is working, but is taking for at least 20 minutes, because I want to delete 123 572 rows, do you have any idea how could I make this work faster?
I also tried to clear contents first and then to delete empty rows, but it's the same.
Here you find the code:
Public Sub Remove_ABSN()
Dim area As String
Dim start As Long
area = "ABSN"
Application.ScreenUpdating = False
Application.Calculation = xlCalculationManual
Application.DisplayAlerts = False
Application.EnableEvents = False
start = Worksheets("Reports").Cells(Cells.Rows.Count, 1).End(xlUp).Row
Worksheets("Reports").Range("$A$2:$AN" & start).AutoFilter Field:=8, Criteria1:=area, Operator:=xlFilterValues
Worksheets("Reports").Range("$A$2:$AN$" & start).SpecialCells(xlCellTypeBlanks).EntireRow.Delete
Sheets("Reports").ShowAllData
Application.ScreenUpdating = True
Application.Calculation = xlCalculationAutomatic
Application.DisplayAlerts = True
Application.EnableEvents = True
End Sub
I think AutoFilter will be the fastest way to do it. Here are two sample scripts to try. You can see for yourself which one is faster.
Public Sub UnionDeleteRowsFast()
' Careful...delete runs on Sheet1
Dim sh2 As Worksheet
Set sh2 = Sheets("Sheet1")
Dim lastrow As Long
Dim Rng As Range
lastrow = Cells(Rows.Count, "B").End(xlUp).Row
For i = lastrow To 2 Step -1
If Cells(i, 2).Value = "Delete" Then
If Rng Is Nothing Then
Set Rng = Range("B" & i)
Else
Set Rng = Union(Rng, Range("B" & i))
End If
End If
Next
If Not Rng Is Nothing Then Rng.EntireRow.Delete
End Sub
Sub AutoFilterDeleteRowsFast()
' Careful...delete runs on ActiveSheet
With ActiveSheet
.AutoFilterMode = False
With Range("B4", Range("B" & Rows.Count).End(xlUp))
.AutoFilter 1, "*Delete*"
On Error Resume Next
.Offset(1).SpecialCells(12).EntireRow.Delete
End With
.AutoFilterMode = False
End With
End Sub
There is a way that is much faster.
Suppose a table of 100,000 lines (A1:B100001) with headers in line 1. Then delete condition refers to just 1 column (B).
One needs a auxiliar column (A) just to count the lines in the original order. Here I use autofill function.
So one can sort the table and after restore the original order.
Below there is a complete example, that generates randomly numbers from 1 to 10 (it's slow!), and after quickly delete all lines with values 3
Sub EraseValue()
Application.ScreenUpdating = False
Dim i As Long
Dim T1 As Single ' milisecs after booting (Start)
Dim T2 As Single ' milisecs after booting (End)
Dim LIni As Variant ' Initial line to delete
Dim LEnd As Variant ' Final line to delete
Const Fin = 100000 ' Lines in the table
Const FinStr = "100001" ' Last line (string)
Randomize (GetTickCount()) ' Seed of random generation
For i = 1 To Fin
Cells(i + 1, "B") = Int(Rnd() * 10 + 1) ' Generates from 1 to 10
If i Mod 100 = 0 Then Application.StatusBar = i
DoEvents
Next i
Application.StatusBar = False
Dim Table As Range
Dim Colu As Range
T1 = GetTickCount() ' Initial time
Cells(2, "A") = 1 ' Starting value
Cells(3, "A") = 2 ' Step
' Fill from 1 to 100,000 step 1
Range("A2:A3").AutoFill Destination:=Range("A2:A" & FinStr)
' Order by condition column
Table.Sort Key1:=Cells(1, "B"), Header:=xlYes
'One needs delete lines with column B = 3
'LIni: Search key that not exceed value 2 in the column
' (2 is immediately previous value)
'LEnd: Search key that not exceed value 3 in the column
'LIni and LFim is relative to 2 so add 1 for skip the header
'Add more 1 to Lini in order to get the first value in the column >= key
'
LIni = Application.Match(2, Colu, 1) + 2
LEnd = Application.Match(3, Colu, 1) + 1
If IsError(LIni) Or IsError(LEnd) Or LEnd < LEnd Then
MsgBox ("There is no lines to delete")
End
End If
Range(Rows(LIni), Rows(LEnd)).Delete (xlUp) ' Delete lines
Table.Sort Key1:=Cells(1, "A"), Header:=xlYes ' Restore initial order
T2 = GetTickCount() ' Get the final time
MsgBox ("Elapsed milisecs: " + Format((T2 - T1), "0"))
End Sub
In my old computer, it take a little bit more that 0.5 secs with 100,000 lines.
If one has a condition that involves 2 columns or more, one need to create an another auxiliary column with a formula that concatenate these columns related do desired condition and run the match in this column. The formula needs to usage relative references. For instance (assuming that the data of column C are string and is already filled with a header).
Cells(1,4) = "NewCol" ' New column D
Dim NewCol As Range
Set NewCol = Range("D2:D" & FinStr)
' Two previous columns concatenated. In line 2
' the formula would be "=Format(B2,"0")+C2" (B2 is a number)
NewCol.FormulaR1C1 = "=Format(RC[-2],"0") & RC[-1]"
NewCol.Copy
NewCol.PasteSpecial(XlValues) ' Convert all formulas to values
Application.CutCopyMode=false
So one usages the column D instead column B
Have a few questions about the following code that compressed and reformats a list.
I set variable endIndicator as a temporary marker for the end of the list. Would it be better to just continually check for the current end of the list via my ActiveSheet.UsedRange.Rows.Count + ActiveSheet.UsedRange.Rows(1).Row - 1?
I tried a With ActiveCell.Offset(rowOffset) statement in the delete loop which gives me a
run time error 424 Object Required
on the second iteration of the loop. I gather this has to do with the destruction of the previous row. Is there an appropriate With statement to use for this loop?
Similarly, I tried a With ActiveCell.EntireColumn for the last three statements and get the same result. I assume for the same reason. Is there an appropriate solution?
Here's the code
Option Explicit
Sub Condense1()
'Purpose : Condense list by removing unwanted rows
'Requires: Column B row verbiage
' Column A row blank for unwanted row
'Returns : Single compressed column of values wanted
Dim endIndicator As String
Dim rowOffset As Long
Worksheets(1).Activate 'Select Sheet
Range("A1").Select 'Set offset base
endIndicator = "zzzendozx" 'Assign unique value unlikely to be duplicated
'Find last used row
rowOffset = ActiveSheet.UsedRange.Rows.Count _
+ ActiveSheet.UsedRange.Rows(1).Row - 1
'Temporarily mark next row as loop terminator
ActiveCell.Offset(rowOffset, 0).Value = endIndicator
rowOffset = 0 'Reset offset pointer
'For each row from top to loop terminator
Do While ActiveCell.Offset(rowOffset).Value <> endIndicator
' Delete rows whose column "A" is empty
If Len(ActiveCell.Offset(rowOffset).Value) < 1 Then
ActiveCell.Offset(rowOffset).EntireRow.Delete
Else
rowOffset = rowOffset + 1 'Otherwise prepare to look at next row
End If
Loop
ActiveCell.Offset(rowOffset).EntireRow.Delete 'Remove loop terminator row
ActiveCell.EntireColumn.Delete 'Remove Column A
ActiveCell.EntireColumn.Font.Size = 14 'Set Font
ActiveCell.EntireColumn.AutoFit 'Set optimum column width
End Sub
This may be of some help:
Sub Kompressor()
Dim nLastRow As Long, nFirstRow As Long, i As Long
Set r = ActiveSheet.UsedRange
nLastRow = r.Rows.Count + r.Row - 1
nFirstRow = r.Row
For i = nLastRow To nFirstRow Step -1
With Cells(i, 1)
If .Value = "" Then .EntireRow.Delete
End With
Next i
End Sub
Note we run the loop backwards.The code will run slightly faster if we delete only one time.
So I have an excel sheet that can have anywhere from 5-1500 lines. Most lines have: 1) Title Row, 2) patient information, 3) blank row. Then it repeats. Some lines have 1) Title Row, 2) patient info, 3) additional patient info, 4)blank row. I need to insert a line between Rows 2&3 if there is info in row 3. Does this make sense?
Example:
--------A---------------------b-----------------c-------------------d--------
1-----acct #--------patient name------dr name------ date of service
2------123456-------Mickey Mouse-----Donald Duck--------1/4/19
3----------((((((((((((((all of this row is blank)))))))))))))))))))))----------
Or it could be this:
--------A---------------------b--------------------c-------------------d------
1-----acct #--------patient name--------dr name------ date of service
2------123456-------Mickey Mouse-----Donald Duck--------1/4/19
3------123456-------Mickey Mouse-----Donald Duck--------1/4/19
4----------((((((((((((((all of this row is blank)))))))))))))))))))))----------
Then this same format repeats throughout the sheet with different info of course. What I need is if row 3 has any info then insert a row between tows 2 & 3, but if row 3 is blank then skip to the next set.
This is the code I have so far but it is adding rows every other row no matter what.
Sub Macro()
Dim lastRow As Integer
lastRow = ActiveSheet.UsedRange.Rows(ActiveSheet.UsedRange.Rows.count).Row
Dim I As Long
For I = 6 To lastRow
If Cells(I + 2, 9).Text <> "" Then
Rows(I + 1).EntireRow.Insert Shift:=xlDown
lastRow=lastRow+1
End If
Next I
End Sub
As #BruceWayne stated in the comments, When inserting or deleting rows, columns or cells, it's helpful to iterate backwards. The Step parameter of a For-Next loop allows you to define how you would like to iterate. It defaults to Step 1. So instead of iterating from I = 6 to lastRow try
Dim lastRow As Long
Dim i As Long
lastRow = ActiveSheet.UsedRange.Rows(ActiveSheet.UsedRange.Rows.Count).Row
For i = lastRow To 6 Step -1
If Cells(i - 1, 9).Text <> "" And Cells(i, 9).Text <> "" Then
Rows(i).EntireRow.Insert Shift:=xlDown
End If
Next i
This would insert a row at your current iteration if both the current cell and the cell above it had data in them.
It's worth noting that if you were to iterate to row 1, the If statement above would raise an error, but you'd never need to.
EDIT:
If what you need is to only add a row between patient info and additional patient info, you'd need to find a consistently identifiable piece of data to add as a condition to the If statement.
Give this a try.
Customize the variables to fit your needs
Sub InsertRows()
' Define object variables
Dim rangeEval As Range
Dim currentCell As Range
' Define other variables
Dim sheetName As String
Dim rowCounter As Integer
' >>>> Customize this
sheetName = "Sheet1"
' Initialize the used range in column A ' Change the number in .Columns(1) to use another column
Set rangeEval = ThisWorkbook.Worksheets(sheetName).UsedRange.Columns(1)
' Loop through each cell in range
For Each currentCell In rangeEval.Cells
' We use this counter to check if we are every third row
rowCounter = rowCounter + 1
' If this is the third row and there is something in the cell, insert one row
If rowCounter Mod 3 = 0 And currentCell.Value <> vbNullString Then
currentCell.EntireRow.Insert
' Reset the counter if there is nothing in the cell
ElseIf currentCell.Value = vbNullString Then
rowCounter = 0
End If
Next currentCell
End Sub
I have tried my best to search for the answer but can't get what I'm looking for. I'm very new to VBA so may be going wrong in several places here . . .
I'm creating a data formatter that processes data with a different numbers of records (rows) each time it is used. Data will be on non-active sheet. First row has headings. I've successfully used similar code to the code below to identify rows with certain data on it and clear the contents of other cells on that row.
The reason I refer to column E is because it is the only column that has data in every record. I then have to find the rows that have a value in column BU, then multiply that value by 20 and insert the result in column BX of the same row.
I keep getting Run-time Error 13 but don't understand as it's simply a number with 2 decimal places in cell BU, and currently there is nothing in BX.
Sub CalcTotalLTA()
Dim i As Variant
'counts the no. of rows in E and loops through all
For i = 1 To Sheets("Input").Range("E2", Sheets("Input").Range("E2").End(xlDown)).Rows.Count
'Identifies rows where columns BU has a value
If Sheets("Input").Cells(i, 73).Value <> "" Then
'calculate Total LTA
Sheets("Input").Cells(i, 76).Value = Sheets("Input").Cells(i, 73).Value * 20
End If
Next i
End Sub
You're most likely having an issue because Application.Sheets holds both sheet types, which are Charts and Worksheets. Application.Sheets does not have a .Range() property.
Replace all instances of Sheets() with Worksheets().
Worksheets("Input").Cells(i, 76).Value = Worksheets("Input").Cells(i, 73).Value * 20
Even better:
Dim ws as Worksheet
Set ws = Worksheets("Input")
..
ws.Cells(i,76).Value = ws.Cells(i,73).Value * 20
Exclude Header Row From Range
Public Function rngExcludeHeaders(rng As Range) As Range
Set rng = rng.Offset(1, 0).Resize(rng.rows.count - 1, rng.Columns.count)
Set rngExcludeHeaders = rng
End Function
usage:
Dim MyRange as Range
Set MyRange = rngExcludeHeaders(ws.UsedRange)
Thanks to input from #Adam Vincent and #Vityata, and some other research (the reason why I'm solving this myself, hope that's not bad etiquette) I've found the solution. Starting the index 'i' at 2 and adding 1 at the end avoids the header row text and includes the last row too:
Option Explicit
Sub CalcTotalLTA()
Dim i As Variant
Dim ws As Worksheet
Set ws = Worksheets("Input")
'counts the no. of rows in E and loops through all
For i = 2 To ws.Range("E2", ws.Range("E2").End(xlDown)).Rows.Count + 1
'Identifies rows where columns BU has a value
If ws.Cells(i, 73).Value <> "" Then
'calculate Total LTA
ws.Cells(i, 76).NumberFormat = "0.00"
ws.Cells(i, 76).Value = ws.Cells(i, 73).Value * 20
End If
Next i
End Sub
Try it like this:
Option Explicit
Sub CalcTotalLTA()
Dim i As Long
With Worksheets("Input")
For i = 1 To .Range("E2", .Range("E2").End(xlDown)).Row
If .Cells(i, 3) <> "" Then
.Cells(i, 6) = .Cells(i, 3) * 20
End If
Next i
End With
End Sub
This is what I have changed:
Adding Option Explicit on top
I have used With Worksheets("Input") to make your code more understandable.
Furthermore, I suppose you do not need Rows.Count but .Row
I have changed 76 and 73 to 3 and 6 to avoid some scrolling to the right, thus be careful when you use it over your workbook.
Removed .Value as far as it is the default one.
I'm currently looking for a code to improve my Dashboard. Actually, I need to know how to use a loop in a column X who will affect a column Y (cell on the same line).
To give you an example:
Column A: I have all Production Order (no empty cell)
Column B: Cost of goods Sold (Sometimes blank but doesn't matter)
I actually pull information from SAP so my Column B is not in "Currency".
The action should be:
If A+i is not empty, then value of B+i becomes "Currency".
It's also for me to get a "generic" code that I could use with other things.
This is my current code...
Sub LoopTest()
' Select cell A2, *first line of data*.
Range("A2").Select
' Set Do loop to stop when an empty cell is reached.
Do Until IsEmpty(ActiveCell)
ActiveCell.Offset(0, 1).Style = "Currency"
ActiveCell.Offset(1, 0).Select
Loop
End Sub
Another example, getting Last Row, in case your data contains any blank rows.
Sub UpdateColumns()
Dim wks As Worksheet
Dim lastRow As Long
Dim r As Long
Set wks = ActiveSheet
lastRow = ActiveSheet.Cells.SpecialCells(xlLastCell).Row
For r = 2 To lastRow
If wks.Cells(r, 1) <> "" Then
wks.Cells(r, 2).NumberFormat = "$#,##0.00"
End If
Next r
End Sub
I can see I was a little slower than the others, but if you want some more inspiration, heer is a super simple solution (as in easy to understand as well)
Sub FormatAsCurrency()
'Dim and set row counter
Dim r As Long
r = 1
'Loop all rows, until "A" is blank
Do While (Cells(r, "A").Value <> "")
'Format as currency, if not blank'
If (Cells(r, "B").Value <> "") Then
Cells(r, "B").Style = "Currency"
End If
'Increment row
r = r + 1
Loop
End Sub
Try the following:
Sub calcColumnB()
Dim strLength As Integer
Dim i As Long
For i = 1 To Rows.Count
columnAContents = Cells(i, 1).Value
strLength = Len(columnAContents)
If strLength > 0 Then
Cells(i, 2).NumberFormat = "$#,##0.00"
End If
Next i
End Sub
Explanation--
What the above code does is for each cell in Column B, so long as content in column A is not empty, it sets the format to a currency with 2 decimal places
EDIT:
Did not need to loop
Here's a really simply one, that I tried to comment - but the formatting got messed up. It simply reads column 1 (A) for content. If column 1 (A) is not empty it updates column 2 (B) as a currency. Changing active cells makes VBA more complicated than it needs to be (in my opinion)
Sub LoopTest()
Dim row As Integer
row = 1
While Not IsEmpty(Cells(row, 1))
Cells(row, 2).Style = "Currency"
row = row + 1
Wend
End Sub