I have a filter macro that will filter a table for items and delete rows with that item. This is done by looping through an array which referenced a finite range on reference worksheet.
I am trying to change this array to be dynamic so that I can add or remove items to be deleted without having to open the code.
Before:
Dim ArrCategory As Variant
ArrCategory = Worksheets("Sheet1").Range("B8:B12")
For i = 2 To LastRowA
For Each item In ArrCategory
If Range("E" & i).Value = item Then
lo1710.Range.autofilter Field:=5, Criteria1:=item
Application.DisplayAlerts = False
lo1710.DataBodyRange.SpecialCells(xlCellTypeVisible).Delete
Application.DisplayAlerts = True
lo1710.autofilter.ShowAllData
Else
End If
Next item
Next i
After:
Dim ArrCategory As Variant
ArrCategory = Worksheets("Sheet1").Range("B8:B" & Cells(Rows.Count, "B").End(xlUp).row)
For i = 2 To LastRowA
For Each item In ArrCategory
If Range("E" & i).Value = item Then
lo1710.Range.autofilter Field:=5, Criteria1:=item
Application.DisplayAlerts = False
lo1710.DataBodyRange.SpecialCells(xlCellTypeVisible).Delete
Application.DisplayAlerts = True
lo1710.autofilter.ShowAllData
Else
End If
Next item
Next i
After making this change I started getting the "No cells were found" error. When I look in the locals window to see what is in that array, I see the values that are supposed to be in there, but then also hundreds of "empties".
The code does work to eliminate the rows containing the items in the array.
Avoid implicit ActiveSheet references. Your Rows and Cells calls implicitly reference the active sheet, which is not guaranteed to be Worksheets("Sheet1").
Change
ArrCategory = Worksheets("Sheet1").Range("B8:B" & Cells(Rows.Count, "B").End(xlUp).row)
to
With Worksheets("Sheet1")
ArrCategory = .Range("B8:B" & .Cells(.Rows.Count, "B").End(xlUp).Row)
End With
and note the . in front of Range, Cells, and Rows.
Reference Non-Blanks
If there are blank but not empty cells below your data, you could use the following function to reference the correct range.
It uses the Find method and will fail
if the worksheet is filtered,
or if there are hidden rows.
The Function
Function SetNonBlankColumn( _
FirstCell As Range) _
As Range
Dim rg As Range
With FirstCell.Cells(1)
Dim lCell As Range
Set lCell = .Resize(.Worksheet.Rows.Count - .Row + 1) _
.Find("*", , xlValues, , , xlPrevious)
If lCell Is Nothing Then Exit Function
Set rg = .Resize(lCell.Row - .Row + 1)
End With
Set SetNonBlankColumn = rg
End Function
Usage
It is assumed that the column range has at least two rows.
In your code, you could utilize the function in the following way:
Dim rg As Range: Set rg = SetNonBlankColumn(Worksheets("Sheet1").Range("B8"))
Dim ArrCategory As Variant: ArrCategory = rg.Value
Also, your worksheet is not qualified, and 'the code will look for it' in the active workbook which may be the wrong one (fix with e.g. ThisWorkbook.Worksheets...).
Of course, if the blank but not empty cells are there by accident, select the cell below the last value and hold Ctrl + Shift and press Down as many times as needed to hit the bottom row, release, and press Del to clear the range and continue using your code with the corrections suggested by BigBen.
Related
VBA newb here.
Essentially, I'm collecting weekly compliance records for week over week data.
My main issue is that I have a queried table that is dynamic and on a good week it's empty.
I would like to be able to pull the contents of this table and paste them to the first empty row below a static table that contains year to date data.
This step is an easy one to accomplish manually, but I'm looking to automate for the sake of handing this report off to my less-than-tech-savvy team members.
This question: How to copy and paste two separate tables to the end of another table in VBA? has given me most of what I'm using so far. I've swapped a few of their values and declarations to be relevant to my sheet and ranges, but for the most part it's copy/paste with the listed solution for "Destination: ="
For the most part, this block does the exact thing I'm after:
(I've commented out GCC's second range, but intend to utilize it once this one's settled.)
Sub Inv_Copy_Paste()
Dim TC As Worksheet
'Dim Chart As Worksheet
Dim lr2 As Long
Set TC = Worksheets("TC Data Dump")
'Set Chart = Worksheets("Inventory for Charts")
lr2 = TC.Cells(Rows.Count, 1).End(xlUp).Row
With TC
.Range("O2", ("W2" & .Range("O" & Rows.Count).End(xlUp).Row)).Copy Destination:=TC.Cells(Rows.Count, 1).End(xlUp).Offset(1)
'.Range("K2", ("S2" & .Range("K" & Rows.Count).End(xlUp).Row)).Copy Destination:=Chart.Range("A" & lr2 + 1)
End With
End Sub
The one exception that I'm running into is that once the code copies populated data over, it adds a handful of blank lines below the data:
20 Blank Rows
Is this something I'm overlooking in the code that's already here?
I'll grant that I barely understand what the code is doing in the With TC portion, so any additional context would be greatly appreciated.
Bonus question: Will I need a separate Sub/Worksheet when I attempt to copy another dynamic query table to a second static table?
Dealing With Blanks
If your data is in Excel tables, you should use their methods and properties.
If you don't wanna, you'll need to write special, often complicated codes.
End(xlUp) will only go up to the last row (cell) in the table. If there are empty or blank rows at the bottom, they will also be copied.
The Find method with xlFormulas will go up to the last non-empty row while with xlValues, it will go up (further) to the last non-blank row.
Initial
Result
Main
Sub InvCopyPaste()
Dim wb As Workbook: Set wb = ThisWorkbook ' workbook containing this code
Dim wsTC As Worksheet: Set wsTC = wb.Sheets("TC Data Dump")
Dim wsInv As Worksheet: Set wsInv = wb.Sheets("Inventory for Charts")
Dim srg As Range, drg As Range
' Source: 'wsTC' to Destination: 'wsTC'
Set srg = RefNonBlankRange(wsTC.Range("O2:W2"))
If Not srg Is Nothing Then
Set drg = RefFirstNonBlankRowRange(wsTC.Range("A2") _
.Resize(, srg.Columns.Count)).Resize(srg.Rows.Count)
drg.Value = srg.Value ' for only values (most efficient)
'srg.Copy drg ' instead: for values, formulas and formats
Debug.Print "Copied from " & srg.Address & " to " & drg.Address & "."
End If
' Source: 'wsTC' to Destination: 'wsInv'
Set srg = RefNonBlankRange(wsTC.Range("K2:S2"))
If Not srg Is Nothing Then
Set drg = RefFirstNonBlankRowRange(wsInv.Range("A2") _
.Resize(, srg.Columns.Count)).Resize(srg.Rows.Count)
drg.Value = srg.Value ' for only values (most efficient)
'srg.Copy drg ' instead: for values, formulas and formats
Debug.Print "Copied from " & srg.Address & " to " & drg.Address & "."
End If
End Sub
The Help
Function RefNonBlankRange( _
ByVal FirstRowRange As Range) _
As Range
With FirstRowRange
Dim cel As Range: Set cel = .Resize(.Worksheet.Rows.Count - .Row + 1) _
.Find("*", , xlValues, , xlByRows, xlPrevious)
If Not cel Is Nothing _
Then Set RefNonBlankRange = .Resize(cel.Row - .Row + 1)
End With
End Function
Function RefFirstNonBlankRowRange( _
ByVal FirstRowRange As Range) _
As Range
Dim rg As Range: Set rg = FirstRowRange.Rows(1)
With rg
Dim cel As Range: Set cel = .Resize(.Worksheet.Rows.Count - .Row + 1) _
.Find("*", , xlValues, , xlByRows, xlPrevious)
If Not cel Is Nothing Then Set rg = .Offset(cel.Row - .Row + 1)
End With
Set RefFirstNonBlankRowRange = rg
End Function
Debug.Print Results in the Immediate window (Ctrl+G)
Copied from $O$2:$W$6 to $A$4:$I$8.
Copied from $K$2:$S$6 to $A$6:$I$10.
Firstly, the row count is counting the number of lines in the first column.
-lr2 = TC.Cells(Rows.Count, 1).End(xlUp).Row
Here.
Rather than counting the number of rows in the tablese you're trying to copy.
If you change the number 1 in this line to the column you are copying. I think its "O" which would be 15.
Then I'm afraid you'd have to redefine the lr2 for the second table or make another variable for it.
lr3 = TC.Cells(Rows.Count, 11).End(xlUp).Row '11 for the k column
Please let me know if this helps.
Sub oddzac()
Dim RowCount As Integer
ActiveSheet.Range("O2", Cells(Range("W" & Rows.Count).End(xlUp).Row, "W")).Copy Cells(Range("A" & Rows.Count).End(xlUp).Row, 1)
ActiveSheet.Range("K2", Cells(Range("S" & Rows.Count).End(xlUp).Row, "S")).Copy Cells(Range("A" & Rows.Count).End(xlUp).Row + 1, 1)
End Sub
This more what you're looking for?
Another forum responded with this solution:
Sub TC_Copy_Paste()
Dim TC As Worksheet, RowNum As Long
'
Set TC = Worksheets("TC Data Dump")
On Error Resume Next
With TC.Range("P3").ListObject
RowNum = Application.WorksheetFunction.CountA(.ListColumns(1).DataBodyRange)
.DataBodyRange.Cells(1, 1).Resize(RowNum, 9).Copy Destination:=TC.Cells(Rows.Count, 5).End(xlUp).Offset(1)
End With
With TC.Range("AJ3").ListObject
RowNum = Application.WorksheetFunction.CountA(.ListColumns(1).DataBodyRange)
.DataBodyRange.Cells(1, 1).Resize(RowNum, 9).Copy Destination:=TC.Cells(Rows.Count, 26).End(xlUp).Offset(1)
End With
End Sub
Again, I'm not sure why this works and the other doesn't but I wanted to share the end result.
I have tried to just set the range to ("A:A") but that makes the table too large and my computer freezes up, I have also tried to input a line like Range("A" & Rows.Count).End(xlUp).Offset(1) but that is not recognized by VBA.
Any help would be appreciated!
You need to first define your last row by referencing the last cell in the column then use .End(xlUp).row to find the last row number. You can then use that row number to build cell references, or even save the range as a range variable like I did:
Sub Last_Row_Example()
Dim LastRow As Long 'Last Row as a long integer
Dim RG As Range 'A range we can reference again and again very easily
'Consider renaming it to something more descriptive.
'for your particular situation
LastRow = Range("A" & Rows.Count).End(xlUp).Row ' Here we store the "LastRow" Number
Set RG = Range("A1:A" & LastRow) ' Here we build a range using the LastRow variable.
RG.Select
Application.CutCopyMode = False
ActiveSheet.ListObjects.Add(xlSrcRange, RG, , xlYes).Name = _
"Table3"
Range("Table3[[#All],[Ticker Name]]").Select
Selection.ConvertToLinkedDataType ServiceID:=268435456, LanguageCulture:= _
"en-US"
End Sub
I'm creating a macro that is formatting a collection of files and a step in this process is to delete columns that aren't required, keeping a specific set of columns.
I know I can delete columns based on their location and I have this approach implemented already ie 1,3,7 etc or A, C, G etc. But I'm conscious that the report being used might change layout or add extra columns over time and I want to ensure the required columns are kept.
Ideally this code would cycle through each column header starting at A1 until the last column and delete an entire column if the header value isn't found in a list. This list will be an array captured from a range in one of the sheets in the workbook.
List = {Blue, Green, Orange}
Original Table
Blue
Red
Green
Orange
Black
row
row
row
row
row
Formatted Table
Blue
Green
Orange
row
row
row
Does anyone have any suggestions on the approach I could take to get this working or if it's even possible? Any help would be greatly appreciated
You might profit from the following approach by reordering a datafield array via Application.Index which allows even to move the existing columns to any new position.
Note: this flexible solution can be time consuming for greater data sets,
where I would prefer other ways you can find in a lot of answers at SO.
Sub ReorderColumns()
Const headerList As String = "Blue,green,Orange"
'a) define source range
Dim src As Range
Set src = Tabelle3.Range("A1:E100")
'b) define headers
Dim allHeaders: allHeaders = src.Resize(1).Value2
Dim newHeaders: newHeaders = Split(headerList, ",")
'c) get column positions in old headers
Dim cols
cols = getCols(newHeaders, allHeaders)
'd) define data
Dim data As Variant
data = src.Value2
'e) reorder data based on found column positions
data = Application.Index(data, Evaluate("row(1:" & UBound(data) & ")"), cols)
'f) overwrite source data
src = vbNullString ' clear
src.Resize(UBound(data), UBound(data, 2)) = data
End Sub
Help function getCols()
Function getCols(individualHeaders, allHeaders)
'Purp: get 1-based column numbers of found headers via Match
getCols = Application.Match(individualHeaders, allHeaders, 0) ' 1-based
End Function
Please, test the next code. It is compact and fast enough. It will build the columns to be deleted range using Application.Match for the two involved arrays (the existing headers one and the ones to be kept). This code assumes that the headers exist in the first row of the processed sheets, starting from A:A column (If starting from a different column, the code can be adapted:
Sub DeleteColunsNotInArrayDel()
Dim sh As Worksheet, arrStay, lastCol As Long, arrH, arrCols, rngDel As Range
Set sh = ActiveSheet 'use here the sheet you need to process
lastCol = sh.cells(1, sh.Columns.count).End(xlToLeft).Column 'last column on the first row
arrStay = Split("Blue,Green,Orange", ",") 'the headers to not be deleted array
arrH = Application.Transpose(Application.Transpose(sh.Range(sh.cells(1, 1), sh.cells(1, lastCol)).Value2)) 'existing headers array
arrCols = Application.IfError(Application.match(arrH, arrStay, 0), "xx") 'match the two arrays and place "xx" where no match has been found
makeColsRng(arrCols).Delete 'delete the columns range, at once
End Sub
Function makeColsRng(arr) As Range
Dim i As Long, colL As String, strAddr As String
For i = LBound(arr) To UBound(arr) 'iterate between the matched arrays array
If arr(i) = "xx" Then 'for the not matching case:
colL = Split(cells(1, i).Address, "$")(1) 'extract the letter of the respective column
strAddr = strAddr & colL & "1," 'build the string of the columns to be deleted range
End If
Next i
Set makeColsRng = Range(left(strAddr, Len(strAddr) - 1)).EntireColumn 'return the necessary range
End Function
In case of headers not starting from the first sheet column, the function can easily be adapted by adding a new parameter (the first column number) to be added when the range to be deleted is built.
The above suggested solution is a fancy one, just for the sake of showing the respective approach, which is not too often used. It may have a limitation of the range building, in case of a string bigger than 254 digits, No error handling for the case of everything matching (even, easy to be added). The next version is standard VBA, compact, more reliable, faster and easier to be understood:
Sub DeleteColunsRangeNotInArray()
Dim sh As Worksheet, arrStay, lastCol As Long, rngH As Range, rngDel As Range, i As Long
Set sh = ActiveSheet
lastCol = sh.cells(1, sh.Columns.count).End(xlToLeft).Column'last column on the first row
arrStay = Split("Blue,Green,Orange", ",") 'the headers to not be deleted array
Set rngH = sh.Range(sh.cells(1, 1), sh.cells(1, lastCol)) 'existing headers range
For i = 1 To rngH.Columns.count
If IsError(Application.match(rngH(i).Value, arrStay, 0)) Then 'if not a match in arrStay:
addToRange rngDel, rngH(i) 'build a Union range
End If
Next i
'delete the not necessary columns at once:
If Not rngDel Is Nothing Then rngDel.EntireColumn.Delete
End Sub
Private Sub addToRange(rngU As Range, rng As Range)
If rngU Is Nothing Then
Set rngU = rng
Else
Set rngU = Union(rngU, rng)
End If
End Sub
Dynamic Named Range
I think a dynamic named range is an excellent choice for storing and retrieving your required columns. Please see the link I provided from https://exceljet.net/ to setup your dynamic named range.
Generic formula =$A$2:INDEX($A:$A,COUNTA($A:$A))
Regular Expression Approach
After reading in your named range, one approach for testing your columns is using regular expressions. To use this you will need to set a library reference to Microsoft VBScript Regular Expressions 5.5. The pipe character | represents an or statement, so we can join our array using that delimiter.
Deleting Ranges in loops
When deleting columns or rows within a loop, the best approach I have found is to union the ranges together in a variable and execute the deletion in one go. This helps performance and it prevents errors from deleting ranges the loop is working on.
I do this so often that I created a custom function for this UnionRange
' Helper function that allows
' concatinating ranges together
Public Function UnionRange( _
ByRef accumulator As Range, _
ByRef nextRange As Range _
)
If accumulator Is Nothing Then
Set UnionRange = nextRange
Else
Set UnionRange = Union(accumulator, nextRange)
End If
End Function
Putting it all together
Below is my implementation of what your code could look like, just make sure to first:
Create a Dynamic Named Range and populate with your required headers
Add Microsoft VBScript Regular Expressions 5.5 reference
Update Sheet1 to whatever sheet your table exists (possibly change logic for finding header row based on your needs)
' Need Regular Expressions Referenced in order to work!
' #libraryReference {Microsoft VBScript Regular Expressions 5.5}
Public Sub DemoDeletingNonRequiredColumns()
' Make sure to create a named range
' otherwise this section will fail. In this
' example the named range is `RequiredColumns`
Dim requiredColumns() As Variant
requiredColumns = Application.WorksheetFunction.Transpose( _
Range("RequiredColumns").Value2 _
)
' To test if the column is in the required
' columns this method uses regular expressions.
With New RegExp
.IgnoreCase = True
' The pipe charactor is `or` in testing.
.Pattern = Join(requiredColumns, "|")
Dim headerRow As Range
' This example uses `Sheet1`, but update to
' the actual sheet you are using.
With Sheet1
Set headerRow = .Range("A1", .Cells(1, Columns.Count).End(xlToLeft))
End With
Dim column As Range
For Each column In headerRow
' If the column name doesn't match the
' pattern, then concatenate it to the
' toDelete range.
If Not .Test(column.Value2) Then
Dim toDelete As Range
Set toDelete = UnionRange(toDelete, column.EntireColumn)
End If
Next
End With
' toDelete is used as it provides better performance
' and it also prevents errors when deleting columns
' while looping.
If Not toDelete Is Nothing Then
toDelete.Delete
Set toDelete = Nothing
End If
End Sub
Delete Columns Not In a List
Option Explicit
Sub DeleteIrrelevantColumns()
' Source - the worksheet containing the list of headers.
Const sName As String = "Sheet2"
Const sFirstCellAddress As String = "A2"
' Destination - the worksheet to be processed.
Const dName As String = "Sheet1"
Const dFirstCellAddress As String = "A1"
Dim wb As Workbook: Set wb = ThisWorkbook ' workbook containing this code
Dim sws As Worksheet: Set sws = wb.Worksheets(sName)
Dim sfCell As Range: Set sfCell = sws.Range(sFirstCellAddress)
Dim sData() As Variant
With sfCell
Dim slCell As Range: Set slCell = .Resize(sws.Rows.Count - .Row + 1) _
.Find("*", , xlFormulas, , , xlPrevious)
sData = .Resize(slCell.Row - .Row + 1).Value
End With
Dim sDict As Object: Set sDict = CreateObject("Scripting.Dictionary")
sDict.CompareMode = vbTextCompare
Dim sValue As Variant
Dim sr As Long
For sr = 1 To UBound(sData)
sValue = sData(sr, 1)
If Not IsError(sValue) Then ' exclude error values
If Len(sValue) > 0 Then ' exclude blanks
sDict(sValue) = Empty ' write
End If
End If
Next sr
Dim dws As Worksheet: Set dws = wb.Worksheets(dName)
Dim dfCell As Range: Set dfCell = dws.Range(dFirstCellAddress)
Dim drg As Range
With dfCell
Dim dlCell As Range: Set dlCell = _
.Resize(, dws.Columns.Count - .Column + 1) _
.Find("*", , xlFormulas, , , xlPrevious)
Set drg = .Resize(, dlCell.Column - .Column + 1)
End With
Dim dData() As Variant: dData = drg.Value
Dim dCells As Range
Dim dValue As Variant
Dim dc As Long
For dc = 1 To UBound(dData, 2)
dValue = dData(1, dc)
If sDict.Exists(dValue) Then
' If duplicate columns, keep only the left-most.
sDict.Remove dValue
Else
' Combine the irrelevant header cells into a range.
If dCells Is Nothing Then
Set dCells = drg.Cells(dc)
Else
Set dCells = Union(dCells, drg.Cells(dc))
End If
End If
Next dc
' Delete columns in one go.
If Not dCells Is Nothing Then
dCells.EntireColumn.Delete
End If
' Inform.
If sDict.Count = 0 Then
MsgBox "Irrelevant columns deleted.", vbInformation
Else
MsgBox "Irrelevant columns deleted." & vbLf & vbLf _
& "Columns not found:" & vbLf _
& Join(sDict.Keys, vbLf), vbCritical
End If
End Sub
I have a formula in Column A2.
I have a table similar to this:
Formula
Note
Datum
I am very happy because I am
Years
years old
=CONCATENATE(TEXT(C2;"dd-mm-yyyy");$D$1;E2;$F$1)
Any word, TEXT
01.04.2021
21
Autofill
Any word, TEXT 2
01.04.2021
25
I want to transfer it and use it automatically for the whole column. However, I tried possible and impossible ways to do it, but none of them worked. I also looked at forums such as here:
I don't have all the data filled in the table, so I want "excel" to look for the last row in which the record is and try to calculate the formula and return it to the last cell in column A.
Thank you in advance for all the help
(The formula joins the text together) =CONCATENATE(TEXT(C2;"dd-mm-yyyy");$D$1;E2;$F$1)
Sub AutofilCol()
' Apply to the entire column Autofill
Range("A1").Offset(1, 0).Activate
ActiveCell.FormulaR1C1 = _
"=CONCATENATE(TEXT(RC[2],""dd-mm-yyyy""),R1C4,RC[4],R1C6)"
' AutoFill
Selection.AutoFill Destination:=Range("A2:A").End(xlDown).Row
ActiveCell.EntireColumn.AutoFit
End Sub
It looks like this is what you want to do:-
Sub AutofillCol()
Dim Rl As Long ' last used row in column C
Dim Rng As Range
Rl = Cells(Rows.Count, "C").End(xlUp).Row
Set Rng = Range(Cells(2, "A"), Cells(Rl, "A"))
Rng.FormulaR1C1 = "=CONCATENATE(TEXT(RC[2],""dd-mm-yyyy""),R1C4,RC[4],R1C6)"
End Sub
Copy Formulas (Defining a Range)
In this case, there is no need to Activate (or Select) anything neither is the use of AutoFill (FillDown).
Let's say the first solution is the most flexible (reliable) but also the most complex. To better understand it, see the ranges at the various stages of the code printed in the Immediate window (CTRL+G). The flexibility is in the option to use any first cell address e.g. C5, D10, etc. and it will still work.
Depending on your data, you might easily get away with the remaining two solutions.
I didn't include any solution using End since you got that covered by another post.
Option Explicit
Sub copyFormulas()
Const First As String = "A1"
Dim ws As Worksheet: Set ws = ActiveSheet
Dim fCell As Range ' Last Cell in First Row Range
Dim frg As Range ' First Row Range of Table Range
With ws.Range(First)
Set fCell = .Resize(, .Worksheet.Columns.Count - .Column + 1) _
.Find("*", , xlFormulas, , , xlPrevious)
If fCell Is Nothing Then Exit Sub
Set frg = .Resize(, fCell.Column - .Column + 1)
Debug.Print "First", fCell.Address, frg.Address
End With
Dim tCell As Range ' Last Cell in Table Range
Dim trg As Range ' Table Range
With frg
Set tCell = .Resize(.Worksheet.Rows.Count - .Row + 1) _
.Find("*", , xlFormulas, , xlByRows, xlPrevious)
Set trg = .Resize(tCell.Row - .Row + 1)
End With
Debug.Print "Table", tCell.Address, trg.Address
Dim drg As Range ' Destination Range
Set drg = trg.Columns(1).Resize(trg.Rows.Count - 1).Offset(1)
Debug.Print "Destination", drg.Address
drg.FormulaR1C1 = "=CONCATENATE(TEXT(RC[2],""dd-mm-yyyy""),R1C4,RC[4],R1C6)"
' Or.
'drg.Formula = "=CONCATENATE(TEXT(C2,""dd-mm-yyyy""),$D$1,E2,$F$1)"
End Sub
Sub copyFormulasUsedRange()
With ActiveSheet.UsedRange.Columns(1)
.Resize(.Rows.Count - 1).Offset(1).FormulaR1C1 _
= "=CONCATENATE(TEXT(RC[2],""dd-mm-yyyy""),R1C4,RC[4],R1C6)"
End With
End Sub
Sub copyFormulasCurrentRegion()
With ActiveSheet.Range("A1").CurrentRegion.Columns(1)
.Resize(.Rows.Count - 1).Offset(1).FormulaR1C1 _
= "=CONCATENATE(TEXT(RC[2],""dd-mm-yyyy""),R1C4,RC[4],R1C6)"
End With
End Sub
I have this sub to delete a row when certain criteria is met. However, I find it taking way too much time to run. Is there any way I could make this run any faster?
'This sub deletes the row that has any of the following values
Dim ws As Worksheet, i&, lastrow&, value$
Set ws = ActiveWorkbook.Sheets("Product Qty")
lastrow = ws.Range("B" & ws.Rows.Count).End(xlUp).Row
Application.ScreenUpdating = False
For i = lastrow To 2 Step -1
value = ws.Cells(i, 2).value
' Check if it contains one of the keywords.
If (value Like "*VOI*" _
Or value Like "*SLOC*" _
Or value Like "*NCM*" _
Or value Like "*RTS*" _
Or value Like "*VND*" _
Or value Like "*DFFC*" _
Or value Like "*STOR*") _
Then
' Protected values found. Delete the row.
ws.Rows(i).delete
End If
Next
Application.ScreenUpdating = True
Two things that make your code faster:
Read your data into an array and loop through that array instead of a range. Looping through arrays is faster than looping through ranges.
Collect all the rows you want to delete in a range variable RowsToDelete using the Application.Union method and delete them all at once in the end.
Note that I recommend not to use Value as a variable name as this could easily confuse with the .Value property of a range.
Option Explicit
Sub DeleteRows()
Dim ws As Worksheet
Set ws = ActiveWorkbook.Sheets("Product Qty")
Dim LastRow As Long
LastRow = ws.Range("B" & ws.Rows.Count).End(xlUp).Row
'read data into array
DataArr() As Variant
DataArr = ws.Range("B1", "B" & LastRow).value
Dim ChkVal As String
'we collect all rows in a range using union
Dim RowsToDelete As Range
Dim iRow As Long
For iRow = 2 To UBound(DataArr, 1)
ChkVal = DataArr(iRow, 1)
' Check if it contains one of the keywords.
If (ChkVal Like "*VOI*" _
Or ChkVal Like "*SLOC*" _
Or ChkVal Like "*NCM*" _
Or ChkVal Like "*RTS*" _
Or ChkVal Like "*VND*" _
Or ChkVal Like "*DFFC*" _
Or ChkVal Like "*STOR*") Then
' Protected values found.
If RowsToDelete Is Nothing Then 'first row
Set RowsToDelete = ws.Rows(iRow)
Else 'all following rows
Set RowsToDelete = Union(RowsToDelete, ws.Rows(iRow))
End If
End If
Next
'delete all rows
If Not RowsToDelete Is Nothing Then RowsToDelete.Delete
End Sub
If you need multiple wildcard criteria, you can do it by an autofilter also:
put filter criteria in a range (on a separate sheet)
use the range for an autofilter
delete all rows
The criteria-rows are OR-combined and can be placed anywhere on a different worksheet:
By following, above critera defines all rows to be deleted:
Private Sub DeleteRowsFast()
Dim ws As Worksheet, fs As Worksheet
Set ws = ActiveSheet
Set fs = Sheets("FilterSheet")
ws.UsedRange.AdvancedFilter _
Action:=xlFilterInPlace, _
CriteriaRange:=fs.Range("Filter1"), _
Unique:=False
ws.Rows("2:1000000").Delete Shift:=xlUp ' delete visible rows
ws.ShowAllData
End Sub