What is the fastest way to TRIM 1 million rows in Excel using VBA? - excel

You may know that Excel has a physical limit of 1 million rows (well, its 1,048,576 rows). I'm trying to TRIM my data containing 1 million rows in the fastest possible way.
Right now I'm using:
Private Sub CommandButton1_Click()
Dim cell As Range
On Error GoTo errHandler
Application.ScreenUpdating = False
For Each cell In ActiveSheet.UsedRange.SpecialCells(xlCellTypeConstants)
errHandler:
If err.Number = 1004 Then
Exit Sub
End If
cell = WorksheetFunction.Trim(cell)
cell.NumberFormat = "#"
Next cell
Application.ScreenUpdating = True
End Sub

Looing each cell could be avoided. For example,
Sub test()
With Range("A1:A3")
.Value = Evaluate("Trim(" & Range("A1:A3").Address & ")")
End With
End Sub
And if the constant range is nonadjacent then try..
EDIT as per comments below
Private Sub CommandButton1_Click()
Dim rng As Range, ar As Range
Application.ScreenUpdating = False
Set rng = ActiveSheet.ListObjects("Table1") _
.Range.Offset(1).SpecialCells(xlCellTypeConstants)
For Each ar In rng.Areas
ar.Value = Application.Trim(ar)
Next ar
Application.ScreenUpdating = True
End Sub

Solution given by Naresh works well. But you need to change your range to capture complete 1 million+ cells
It took around 5 seconds for me to trim 1 million+ cells
Sub TestTrim()
StartTime = Timer
With Range("A1:A" & Rows.Count)
.Value = Evaluate("Trim(" & Range("A1:A" & Rows.Count).Address & ")")
End With
TotalTime = Timer - StartTime
MsgBox TotalTime & " seconds"
End Sub

Related

Excel VBA If range.value = something then fills Columns G

For example
if range A14:A200
if A14 = 1 so fill G14 Ok
if A14 = 1 so fill G14 Ok
and so on
For example
if range A14:A200
if A14 = 1 so fill G14 Ok
if A15 = 1 so fill G15 Ok
and so on
you could use the excel formulas:
Sub IFSomething()
With Range("A14:A200") reference the needed range
With .Offset(, 6) ' reference the cells 6 columns to the right of referenced range
.FormulaR1C1 = "=IF(RC[-6]=1,""OK"","""")" ' place a formula in referenced range
.Value = .Value ' leave only values
End With
End With
End Sub
So here is revise solution I hope this resolve your query.
Sub If_loop_test()
Dim x As Integer
For x = 1 To 200
If Range("A" & x).Value = 1 Then
Range("G" & x).Value = "ok"
End If
Next
End Sub
Here is a relatively clean and versatile version.
Remember if you're going to be applying this to large data sets this might be slow. you can fix this by importing the range into an array and iterating through that. your code will go from taking 10 seconds on very large data sets to under a second.
Option Explicit
Sub If_Offset_Value()
Dim WS As Worksheet
Dim RG As Range
Dim CL As Range
Dim CheckVal As Variant
' > Change this to whatever value you're checking for.
CheckVal = 1
Application.EnableEvents = False
Application.Calculation = xlCalculationManual
Application.ScreenUpdating = False
Set WS = ThisWorkbook.Worksheets("My WorkSheet Name")
Set RG = WS.Range("A14:A200")
For Each CL In RG.Cells
If CL.Value = CheckVal Then
' > Couple of options here depending on your needs:
' Both options give you the same result, but Offset
' moves left and right if you change RG column,
' whereas column letter referense will stay G
'1) Offset Method
CL.Offset(0, 6).Value = "OK"
'2) Reference Column Letter
WS.Range("G" & CL.Row).Value = "OK"
End If
Next CL
Application.EnableEvents = True
Application.Calculation = xlCalculationAutomatic
Application.ScreenUpdating = True
End Sub

Conditional insert of page breaks

I have a form that is changing all the time and I have boxes of text in column "C". Also some text in cells of column "C" is too long so I am wrapping it with my VBA. I want to make conditional page breaks that will read through my Print Area and insert page breaks after each empty row before heading. My VBA code below is working fine except for text being wrapped. So the problem is: If I set PgSize = 91 in Sub FitGroupsToPage() (that's an amount of rows could be fitted to each page) to 91 and don't wrap my text then everything works fine. However text must be wrapped to fit to my page. Then there is not 91 rows but less, according to the length of the text in wrapped cells. So number 91 is dynamic each time after hiding and wrapping Sub FitMyTextPlease() and Sub HideMyEmptyRows() and Sub SetPrintArea(). Number of rows can also be different on every page (depending of how much text there are wrapped on each page). Any ideas of how this issue can be fixed?
Sub FitMyTextPlease()
Application.ScreenUpdating = False
ThisWorkbook.Sheets("Print version").PageSetup.CenterHeader = "&""Times New Roman,Bold""&12 " & Range("Data!V28").Text & Chr(13) & Chr(13) & " " & "&""Times New Roman,Normal""&12 " & Range("Data!V30").Text
'ThisWorkbook.Sheets("Print version").PageSetup.CenterHeader = Range("Data!V28").Text
ThisWorkbook.Sheets("Print version").Select
With ActiveWorkbook.ActiveSheet
With .Cells.Rows
.WrapText = True
.VerticalAlignment = xlCenter
.EntireRow.AutoFit
End With '.Cells.Rows
.Columns.EntireColumn.AutoFit
End With 'sheet
Application.ScreenUpdating = True
End Sub
Sub HideMyEmptyRows()
Dim myRange As Range
Dim cell As Range
Application.ScreenUpdating = False
Set myRange = ThisWorkbook.Sheets("Print version").Range("Print_Area")
For Each cell In myRange
myRange.Interior.ColorIndex = 0
If cell.HasFormula = True And cell.value = "" And cell.EntireRow.Hidden = False Then Rows(cell.Row).EntireRow.Hidden = True
Next
Application.ScreenUpdating = True
End Sub
Sub SetPrintArea()
Dim ws As Worksheet
Dim lastrow As Long
Set ws = ThisWorkbook.Sheets("Print version")
' find the last row with formatting, to be included in print range
lastrow = ws.UsedRange.SpecialCells(xlCellTypeLastCell).Row
ws.PageSetup.PrintArea = ws.Range("A1:C" & lastrow).Address
End Sub
Sub Printed_Pages_Count()
Range("A1").value = (ActiveSheet.HPageBreaks.Count + 1) * (ActiveSheet.VPageBreaks.Count + 1)
End Sub
Sub FitGroupsToPage()
Dim rStart As Range, rEnd As Range, TestCell As Range
Dim lastrow As Long, PgSize As Integer
Dim n As Integer
PgSize = 91 ' Assumes 91 rows per page
Set rStart = Range("C1")
lastrow = Cells(Rows.Count, 1).End(xlUp).Row
Do
Set TestCell = rStart.Offset(PgSize, 0)
If Len(TestCell) = 0 Or Len(TestCell.Offset(-1, 0)) = 0 Then
Set rEnd = TestCell.End(xlUp)
Else
Set rEnd = TestCell.End(xlUp).End(xlUp)
End If
ActiveWindow.SelectedSheets.HPageBreaks.Add Before:=rEnd.Offset(1, 0)
Set rStart = rEnd.Offset(1, 0)
n = n + 1
If n > 1000 Then Exit Sub ' Escapes from an infinite loop if code fails
Loop Until rStart.Row > lastrow - 50
End Sub
Sub FitMyHeadings()
Call FitMyTextPlease
Call HideMyEmptyRows
Call SetPrintArea
Call FitGroupsToPage
Call Printed_Pages_Count
End Sub
If standard row height is 15, then for 91 rows the total row height would be 1365. When text wraps one line, the row height becomes 30. So what you might try doing is defining 1365 as the total row height per page before inserting a break instead of 91 as the total number of rows.
You can determine the row height with Range("A1").RowHeight

Loop through filter criteria

I've been trying to figure this out but no progress...
I have a filter (COLUMN D) and I'm trying to create a loop to each criteria (I got at least 1000 criterias) on my filter.
Ex: For each criteria on filter (column D), I'll run a range copy...
That code isnt working at all:
Sub WhatFilters()
Dim iFilt As Integer
iFilt = 4
Dim iFiltCrit As Integer
Dim numFilters As Integer
Dim crit1 As Variant
ActiveSheet.Range("$A$1:$AA$4635").AutoFilter Field:=16, Criteria1:= _
"Waiting"
numFilters = ActiveSheet.AutoFilter.Filters.Count
Debug.Print "Sheet(" & ActiveSheet.Name & ") has " & numFilters & " filters."
If ActiveSheet.AutoFilter.Filters.Item(iFilt).On Then
crit1 = ActiveSheet.AutoFilter.Filters.Item(iFilt).Criteria1
For iFiltCrit = 1 To UBound(crit1)
Debug.Print "crit1(" & iFiltCrit & ") is '" & crit1(iFiltCrit)
'Copy everything
Next iFiltCrit
End If
End Sub
My mistake seems to be identifying my filter column...
I realize this was asked a while ago but I havent seen anything that I consider copy-paste ready. here is what I came up with. It should work for unlimited criteria. It does create a single new sheet called "temp" that can be deleted once finished.
Dim currentCell As Long
Dim numOfValues As Long
Sub filterNextResult()
' copy and move the data from the data sheet, column A (can be changed if needed) to a new sheet called "temp"
' check to make sure there is at least 1 data point in column A on the temp sheet
If currentCell = 0 Then
Application.ScreenUpdating = False
Call createNewTemp
Application.ScreenUpdating = True
End If
' find the total number of unique data points we will be filtering by in column A of the temp sheet
If numOfAccounts = 0 Then
Application.ScreenUpdating = False
Call findNumOfValues
Application.ScreenUpdating = True
End If
With Sheet1.UsedRange
.AutoFilter 1, Worksheets("temp").Range("A" & currentCell).Value
currentCell = currentCell + 1
' check to make sure we havent reached the end of clumn A. if so exit the sub
If numOfValues + 1 = currentCell Then
MsgBox ("This was the last value to filter by")
Exit Sub
End If
End With
End Sub
'sub that will look for the number of values on the temp sheet column a
Private Sub findNumOfValues()
' count the number of non empty cells and assign that value (less 1 for the title in our case) to the numOfValues
numOfValues = Worksheets("temp").Range("A:A").Cells.SpecialCells(xlCellTypeConstants).Count
End Sub
Private Sub createNewTemp()
Sheet1.Range("A:A").Copy
ActiveWorkbook.Sheets.Add.Name = "temp"
' remove duplicates
Worksheets("temp").Range("A1").Select
With ActiveWorkbook.ActiveSheet
.Paste
.Range("A:A").RemoveDuplicates Columns:=Array(1), Header:=xlYes
End With
' check to make sure there are vlaues in the temp sheet
If Worksheets("temp").Range("A2").Value = "" Then
MsgBox "There are no filter values"
End
Else
currentCell = 2
End If
Sheet1.Activate
Sheet1.Range("A1").Select
Selection.AutoFilter
End Sub
This worked for me
Sub WhatFilters()
Dim iFilt As Integer
Dim i, j As Integer
Dim numFilters As Integer
Dim crit1 As Variant
If Not ActiveSheet.AutoFilterMode Then
Debug.Print "Please enable AutoFilter for the active worksheet"
Exit Sub
End If
numFilters = ActiveSheet.AutoFilter.Filters.Count
Debug.Print "Sheet(" & ActiveSheet.Name & ") has " & numFilters & " filters."
For i = 1 To numFilters
If ActiveSheet.AutoFilter.Filters.Item(i).On Then
crit1 = ActiveSheet.AutoFilter.Filters.Item(i).Criteria1
If IsArray(crit1) Then
'--- multiple criteria are selected in this column
For j = 1 To UBound(crit1)
Debug.Print "crit1(" & i & ") is '" & crit1(j) & "'"
Next j
Else
'--- only a single criteria is selected in this column
Debug.Print "crit1(" & i & ") is '" & crit1 & "'"
End If
End If
Next i
End Sub

Create comments to a range of cells ftom the values of another range of cells

I want to create comments to a range of cells. The comments should contain the values of another range of cells.
Here is what I have so far:
Private Sub Worksheet_Change(ByVal Target As Range)
Dim sResult As String
If Union(Target, Range("A18")).Address = Target.Address Then
Application.EnableEvents = False
Application.ScreenUpdating = False
sResult = "Maximal " & Target.Value
With Range("I6")
.ClearComments
.AddComment
.Comment.Text Text:=sResult
End With
Application.EnableEvents = True
Application.ScreenUpdating = True
End If
End Sub
This works for one cell. I need this for a range of cells. For example, let's say I need the values of cells A1:F20 in comments of cells A21:F40. I do not want to copy the same Sub as many times.
It should do you the job if you replace
With Range("I6")
.ClearComments
.AddComment
.Comment.Text Text:=sResult
End With
with
For Each cell In Range("A1", "F20").Cells
Dim V As Range
Set V = cell.Offset(20, 0)
With cell
.ClearComments
If Not IsEmpty(V) Then
.AddComment V.Value
End If
End With
Next
This will basically ignore all empty cells.
Output:
My code:
Sub TEST()
For Each cell In Range("A1", "F20").Cells
Dim V As Range
Set V = cell.Offset(20, 0)
With cell
.ClearComments
If Not IsEmpty(V) Then
.AddComment V.Value
End If
End With
Next
End Sub
I made some adaptions to your advices, thanks a lot, this solved my problem:
Private Sub Worksheet_Change(ByVal target As Range)
Dim src As Range: Set src = Worksheets("maxleft").Range("C2:K11")
Dim tar As Range: Set tar = Range("I6:Q15")
For i = 0 To tar.Rows.Count - 1
For j = 0 To tar.Columns.Count - 1
Dim sResult As String
sResult = "Maximal " & Worksheets("maxleft").Cells(src.Row + i, src.Column + j)
With Cells(tar.Row + i, tar.Column + j)
.ClearComments
.AddComment
.Comment.Text Text:=sResult
End With
Next j
Next i
End Sub
From your question I understand that you want to select a range of cells (e.g. "A1:A5"), then select another range of cells (e.g. "B6:B10") and the respective values of the first selected Range should be placed as comments in the secon selected Range. Is this correct?
The following code checks if 2 ranges with an equal length are selected and copies the values of the first selected range as comments to the second selected range:
Private Sub Worksheet_SelectionChange(ByVal target As Range)
If InStr(target.Address, ",") Then
Dim selected_range() As String
selected_range = Split(target.Address, ",")
If Range(selected_range(0)).Rows.Count = Range(selected_range(1)).Rows.Count Then
Dim src As Range: Set src = Range(selected_range(0))
Dim tar As Range: Set tar = Range(selected_range(1))
For i = 0 To src.Rows.Count - 1
Dim sResult As String
sResult = "Maximal " & Cells(src.Row + i, src.Column)
With Cells(tar.Row + i, tar.Column)
.ClearComments
.AddComment
.Comment.Text Text:=sResult
End With
Next i
End If
End If
End Sub

Defining a range from values in another range

I have an excel file of tasks which have either been completed or not, indicated by a Yes or No in a column. Ultimately I am interested in data in a different column but I want to set up the code so it ignores those rows where the task has been completed. So far I have defined the column range containing the yes/no's but I don't know which command to run on this range. I imagine I want to define a new range based on the value in column C.
Option Explicit
Sub Notify()
Dim Chk As Range
Dim ChkLRow As Long
Dim WS1 As Worksheet
On Error GoTo WhatWentWrong
Application.ScreenUpdating = False
'--> If the text in column C is Yes then Ignore (CountIF ?)
'--> Find last cell in the column, set column C range as "Chk"
Set WS1 = Sheets("2011")
With WS1
ChkLRow = .Range("C" & Rows.Count).End(xlUp).Row
Set Chk = .Range("C1:C" & ChkLRow)
End With
'--> Else Check date in column H
'--> Count days from that date until today
'--> Display list in Message Box
Reenter:
Application.ScreenUpdating = True
Application.DisplayAlerts = True
Exit Sub
WhatWentWrong:
MsgBox Err.Description
Resume Reenter
Application.ScreenUpdating = True
End Sub
Would it perhaps be easier to simply define one range based on the values in column C rather than first defining column C as the range and then redefining it?
Thanks
Yes Column H has the date the task 'arrived' and I want to display a count from then to the current date. The tasks are identified by a 4 digit code in Column A. I envisage the message box saying Task '1234' outstanding for xx days. – Alistair Weir 1 min ago
Is this what you are trying? Added Col I for visualization purpose. It holds no significance otherwise.
Option Explicit
Sub Notify()
Dim WS1 As Worksheet
Dim Chk As Range, FltrdRange As Range, aCell As Range
Dim ChkLRow As Long
Dim msg As String
On Error GoTo WhatWentWrong
Application.ScreenUpdating = False
Set WS1 = Sheets("2011")
With WS1
ChkLRow = .Range("C" & Rows.Count).End(xlUp).Row
'~~> Set your relevant range here
Set Chk = .Range("A1:H" & ChkLRow)
'~~> Remove any filters
ActiveSheet.AutoFilterMode = False
With Chk
'~~> Filter,
.AutoFilter Field:=3, Criteria1:="NO"
'~~> Offset(to exclude headers)
Set FltrdRange = .Offset(1, 0).SpecialCells(xlCellTypeVisible)
'~~> Remove any filters
ActiveSheet.AutoFilterMode = False
For Each aCell In FltrdRange
If aCell.Column = 8 And _
Len(Trim(.Range("A" & aCell.Row).Value)) <> 0 And _
Len(Trim(aCell.Value)) <> 0 Then
msg = msg & vbNewLine & _
"Task " & .Range("A" & aCell.Row).Value & _
" outstanding for " & _
DateDiff("d", aCell.Value, Date) & "days."
End If
Next
End With
End With
'~~> Show message
MsgBox msg
Reenter:
Application.ScreenUpdating = True
Exit Sub
WhatWentWrong:
MsgBox Err.Description
Resume Reenter
End Sub
SNAPSHOT
Why not brute force it.
Dim r_table as Range, i as Integer, N as Integer
' Start from the top
Set r_table = Sheets("2011").Range("C1")
' Find the last entry on column C and count the # of cells
N = Sheets("2011").Range(r_table, r_table.End(xlDown)).Rows.Count
Dim table_values() as Variant
' This will transfer all the values from the spreadsheet into an VBA array
' and it works super fast. Access values with A(row,col) notation.
table_values = r_table.Resize(N, 5).Value2 ' No. of columns is 5 ?
For i=1 to N
If table_values(i,1)="Yes" Then 'Check Column C
Else
... table_values(i,5) ' Column H
End if
Next i
MsgBox ....
This will be super fast, with no flicker on the screen.

Resources