Speed up loop in excel - vba
I had some great help to get this search tool working in excel but I was wondering if there is room for speed improvement. I did some research and with what little I understand about VB for i = LBOUND(array) To UBOUND(array) seems most optimal. Would 'For Each' be faster? I am wondering if there is a way to isolate the records currently in the worksheet, or if it is already doing this with L/UBOUND? If it is, is there a way to do 'ignore special characters' similar to SQL? After adding screenupdating and calculation, I was able to shave about 10 seconds off of the total run time. And further I was using FormulaR1C1 for my search before this new loop and it would limit the amount of columns to search while being super fast.
Range("W2:W" & LastRow).FormulaR1C1 = _
"=IF(ISERR(SEARCH(R1C23,RC[-22]&RC[-21]&RC[-20]&RC[-19]&RC[-18]&RC[-17]&RC[-16]&RC[-15]&RC[-15]&RC[-14]&RC[-13]&RC[-12]&RC[-11]&RC[-10]&RC[-9]&RC[-8]&RC[-7]&RC[-6]&RC[-5]&RC[-4]&RC[-3]&RC[-2]&RC[-1])),0,1)"
If WorksheetFunction.CountIf(Columns(23), 1) = 0 Then
Columns(23).Delete
Any help or recommendations are greatly appreciated.
Sub FindFeature()
Application.ScreenUpdating = False
Application.Calculation = xlCalculationManual
Dim shResults As Worksheet
Dim vaData As Variant
Dim i As Long, j As Long
Dim sSearchTerm As String
Dim sData As String
Dim rNext As Range
Dim v As Variant
Dim vaDataCopy As Variant
Dim uRange As Range
Dim findRange As Range
Dim nxtRange As Range
Dim ws As Range
'Put all the data into an array
vaData = ActiveSheet.UsedRange.Value
'Get the search term
sSearchTerm = Application.InputBox("What are you looking for?")
'Define and clear the results sheet
Set shResults = ActiveWorkbook.Worksheets("Results")
shResults.Range("A3").Resize(shResults.UsedRange.Rows.Count, 1).EntireRow.Delete
Set uRange = ActiveSheet.UsedRange
vaData = uRange.Value
vaDataCopy = vaData
For Each v In vaDataCopy
v = Anglicize(v)
Next
Application.WorksheetFunction.Transpose (vaDataCopy)
ActiveSheet.UsedRange.Value = vaDataCopy
'Loop through the data
Set ws = Cells.Find(What:=uRange, After:="ActiveCell", LookIn:=xlValues, LookAt:=xlPart, SearchOrder:=xlByRows, SearchDirection:=xlNext, MatchCase:=False, SearchFormat:=False)
If Not ws Is Nothing Then
Set findRange = ws
Do
Set nxtRange = Cells.FindNext(After:=ws)
Set findRange = nxtRange
Loop Until ws.Address = findRange.Address
ActiveSheet.UsedRange.Value = vaData
'Write the row to the next available row on Results
Set rNext = shResults.Cells(shResults.Rows.Count, 1).End(xlUp).Offset(1, 0)
rNext.Resize(1, uRange(vaData, 2)).Value = Application.Index(vaData, i, 0)
'Stop looking in that row after one match
End If
Application.Calculation = xlCalculationAutomatic
Application.ScreenUpdating = True
End Sub
Ultimately, the execution speed here is severely hampered by the apparent requirement to operate on every cell in the range, and because you're asking about performance, I suspect this range may contain many thousands of cells. There are two things I can think of:
1. Save your results in an array and write to the Results worksheet in one statement
Try replacing this:
'Write the row to the next available row on Results
Set rNext = shResults.Cells(shResults.Rows.Count, 1).End(xlUp).Offset(1, 0)
rNext.Resize(1, UBound(vaData, 2)).Value = Application.Index(vaData, i, 0)
'Stop looking in that row after one match
Exit For
With a statement that assigns the value Application.Index(vaData, i, 0) to an array variable, and then when you're completed the For i loop, you can write the results in one pass to the results worksheet.
NOTE This may be noticeably faster if and only if there are many thousands of results. If there are only a few results expected, then exeuction speed is primarily affected by the need to iterate over every cell, not the operation of writing the results to another sheet.
2. Use another method than cell iteration
If you can implement this method, I would use it in conjunction with the above.
Ordinarily I would recommend using the .Find and .FindNext methods as considerably more efficient than using the i,j iteration. But since you need to use the Anglicize UDF on every cell in the range, you would need to make some restructure your code to accommodate. Might require multiple loops, for example, first Anglicize the vaData and preserve a copy of the non-Anglicized data, like:
Dim r as Long, c as Long
Dim vaDataCopy as Variant
Dim uRange as Range
Set uRange = ActiveSheet.UsedRange
vaData = uRange.Value
vaDataCopy = vaData
For r = 1 to Ubound(varDataCopy,1)
For c = 1 to Ubound(varDataCopy,2)
varDataCopy(r,c) = Anglicize(varDataCopy(r,c))
Next
Next
Then, put the Anglicize version on to the worksheet.
ActiveSheet.UsedRange.Value = vaDataCopy
Then, instead of the For i =... For j =... loop, use the .Find and .FindNext method on the uRange object.
Here is an example of how I implement Find/FindNext.
Finally, put the non-Anglicized version back on the worksheet, again with the caveat that it might require use of Transpose function:
ActiveSheet.UsedRange.Value = vaData
Whil this still iterates over every value to perform the Anglicize function, it does not operate on every value a second time (Instr function). So, you're essentially operating on the values only once, rather than twice. I suspect this should be much faster, especially if you combine it with the #1 above.
UPDATE BASED ON OP REVISION EFFORTS
After some comment discussion & emails back and forth, we arrive at this solution:
Option Explicit
Sub FindFeature()
Application.ScreenUpdating = False
Application.Calculation = xlCalculationManual
Dim shSearch As Worksheet:
Dim shResults As Worksheet
Dim vaData As Variant
Dim i As Long, j As Long, r As Long, c As Long
Dim sSearchTerm As String
Dim sData As String
Dim rNext As Range
Dim v As Variant
Dim vaDataCopy As Variant
Dim uRange As Range
Dim findRange As Range
Dim nxtRange As Range
Dim rng As Range
Dim foundRows As Object
Dim k As Variant
Set shSearch = Sheets("City")
shSearch.Activate
'Define and clear the results sheet
Set shResults = ActiveWorkbook.Worksheets("Results")
shResults.Range("A3").Resize(shResults.UsedRange.Rows.Count, 1).EntireRow.Delete
'# Create a dictionary to store our result rows
Set foundRows = CreateObject("Scripting.Dictionary")
'Get the search term
sSearchTerm = Application.InputBox("What are you looking for?")
'# set and fill our range/array variables
Set uRange = shSearch.UsedRange
vaData = uRange.Value
vaDataCopy = Application.Transpose(vaData)
For r = 1 To UBound(vaDataCopy, 1)
For c = 1 To UBound(vaDataCopy, 2)
'MsgBox uRange.Address
vaDataCopy(r, c) = Anglicize(vaDataCopy(r, c))
Next
Next
'# Temporarily put the anglicized text on the worksheet
uRange.Value = Application.Transpose(vaDataCopy)
'# Loop through the data, finding instances of the sSearchTerm
With uRange
.Cells(1, 1).Activate
Set rng = .Cells.Find(What:=sSearchTerm, After:=ActiveCell, _
LookIn:=xlFormulas, LookAt:=xlPart, SearchOrder:=xlByRows, _
SearchDirection:=xlNext, MatchCase:=False, SearchFormat:=False)
If Not rng Is Nothing Then
Set findRange = rng
Do
Set nxtRange = .Cells.FindNext(After:=findRange)
Debug.Print sSearchTerm & " found at " & nxtRange.Address
If Not foundRows.Exists(nxtRange.Row) Then
'# Make sure we're not storing the same row# multiple times.
'# store the row# in a Dictionary
foundRows.Add nxtRange.Row, nxtRange.Column
End If
Set findRange = nxtRange
'# iterate over all matches, but stop when the FindNext brings us back to the first match
Loop Until findRange.Address = rng.Address
'# Iterate over the keys in the Dictionary. This contains the ROW# where a match was found
For Each k In foundRows.Keys
'# Find the next empty row on results page:
With shResults
Set rNext = .Cells(.Rows.Count, 1).End(xlUp).Offset(1, 0). _
Resize(1, UBound(Application.Transpose(vaData), 1))
End With
'# Write the row to the next available row on Results
rNext.Value = Application.Index(vaData, k, 0)
Next
Else:
MsgBox sSearchTerm & " was not found"
End If
End With
'# Put the non-Anglicized values back on the sheet
uRange.Value = vaData
'# Restore application properties
Application.Calculation = xlCalculationAutomatic
Application.ScreenUpdating = True
'# Display the results
shResults.Activate
End Sub
Public Function Anglicize(ByVal sInput As String) As String
Dim vaGood As Variant
Dim vaBad As Variant
Dim i As Long
Dim sReturn As String
Dim c As Range
'Replace any 'bad' characters with 'good' characters
vaGood = Split("S,Z,s,z,Y,A,A,A,A,A,A,C,E,E,E,E,I,I,I,I,D,N,O,O,O,O,O,U,U,U,U,Y,a,a,a,a,a,a,c,e,e,e,e,i,i,i,i,d,n,o,o,o,o,o,u,u,u,u,y,y", ",")
vaBad = Split("Š,Ž,š,ž,Ÿ,À,Á,Â,Ã,Ä,Å,Ç,È,É,Ê,Ë,Ì,Í,Î,Ï,Ð,Ñ,Ò,Ó,Ô,Õ,Ö,Ù,Ú,Û,Ü,Ý,à,á,â,ã,ä,å,ç,è,é,ê,ë,ì,í,î,ï,ð,ñ,ò,ó,ô,õ,ö,ù,ú,û,ü,ý,ÿ", ",")
sReturn = sInput
Set c = Range("D1:G1")
For i = LBound(vaBad) To UBound(vaBad)
sReturn = Replace$(sReturn, vaBad(i), vaGood(i))
Next i
Anglicize = sReturn
'Sheets("Results").Activate
End Function
Related
Excel VBA array of Selected Range
I know how to make two functions on each column (in this case TRIM and STRCONV to ProperCase Dim arrData() As Variant Dim arrReturnData() As Variant Dim rng As Excel.Range Dim lRows As Long Dim lCols As Long Dim i As Long, j As Long Range("H2", Range("H2").End(xlDown)).Select lRows = Selection.Rows.Count lCols = Selection.Columns.Count ReDim arrData(1 To lRows, 1 To lCols) ReDim arrReturnData(1 To lRows, 1 To lCols) Set rng = Selection arrData = rng.Value For j = 1 To lCols For i = 1 To lRow arrReturnData(i, j) = StrConv(Trim(arrData(i, j)), vbProperCase) Next i Next j rng.Value = arrReturnData Set rng = Nothing Currently I'm trying to figure out how to add one more FOR which where I could gather more than one selection ranges for example: Set myAnotherArray(0) = Range("H2", Range("H2").End(xlDown)).Select Set myAnotherArray(1) = Range("J2", Range("J2").End(xlDown)).Select For k = 1 To myAnotherArray.lenght Because I'm copying and pasting whole script to make aciton on three columns. Tried already: Dim Rng As Range Dim Area As Range Set Rng = Range("Range("H2", Range("H2").End(xlDown)).Select,Range("J2", Range("J2").End(xlDown)).Select") For Each Area In Rng.Areas Area.Font.Bold = True Next Area Even tried to Union range but I failed. Any sugesstions? And as always... Thank you for your time!
I found a way you could use to perform work on those ranges, refer to the code below: Sub DoSomethingWithRanges() Dim m_Worksheet As Excel.Worksheet Dim m_Columns() As Variant Set m_Worksheet = ActiveSheet ' fill all your columns in here m_Columns = Array(2, 3, 4) For Each m_Column In m_Columns ' the area being used ranges from the second until the last row of your column With m_Worksheet.Range(m_Worksheet.Cells(2, m_Column), m_Worksheet.Cells(m_Worksheet.UsedRange.Rows.Count, m_Column)) ' do things with range .Font.Bold = True End With Next m_Column End Sub In the variant array m_Columns you can add all the columns you want. Only downside is that in my example you have to use numbers to specify columns instead of "H". However, you don't have to worry about the row-indexes, since the area automatically ranges from the second to the last used row.
Create various ranges if cell is found or not found in another workbook
I have been struggling for a day and a half with my code. I have a spreadsheet with over 50 columns 18000 rows. I have been able to identify a smaller range of cells in column A defined by "AllEntRg" based on blank cells in column H(OpsCol). I'm stuck with my loops towards the bottom. For EntityRg, I am looping through each cell in "AllEntRg" and if it is Not found in Range CCRg which was defined in BudWb Wk4 Then I want to create a range of all of those cells. The next option, CostCRg, I want to define a range for all cells that ARE FOUND in CCrg. I have tested this by selecting individual cells and it provides the results I'm looking for but when I have this in the loops I'm getting the following two results: For EntityRg, the range.address defined is the same as AllEntRg (this shouldn't be the case). For CostCRg, I'm getting an error. I'm not sure what I'm not defining correctly. I've been stuck here for quite a while and I have tried using Match Function as well. Again, individually it works but in the loop I'm getting these results which are not expected. I'm interested on the feedback I may receive. Thanks. Application.Calculation = xlCalculationManual Application.ScreenUpdating = False Dim wb As Workbook Dim BudWkb As Workbook Dim Wk2 As Worksheet Dim PNLWkb As Workbook Dim fpath As String Dim fname As String Set BudWkb = Workbooks("SubModel Forecast_Other Admin v4.xlsm") Set Wk2 = BudWkb.Sheets("By PM") fname = "Feb15 PNL" 'fname = InputBox("Enter PNL File Name") Dim Wk4 As Worksheet Set Wk4 = BudWkb.Sheets("Validation") With Wk4 Dim CCCol As Long Dim fRowCC As Long Dim lRowCC As Long CCCol = Wk4.Cells.Find("Cost Center", lookat:=xlWhole).Column fRowCC = Wk4.Cells.Find("Cost Center", lookat:=xlWhole).Offset(1, 0).row lRowCC = Wk4.Cells.Find("Cost Center", lookat:=xlWhole).End(xlDown).row Dim CCRg As Range Set CCRg = Wk4.Range(Wk4.Cells(fRowCC, CCCol), Wk4.Cells(lRowCC, CCCol)) 'MsgBox (CCRg.Address) End With Set PNLWkb = Workbooks("Feb15 PNL.xlsx") Dim Wk1 As Worksheet Set Wk1 = PNLWkb.Sheets("det") With Wk1 If Left(Wk2.Name, 5) = "By PM" Then Dim OpsCol As Long OpsCol = Wk1.Cells.Find("Property Manager", lookat:=xlWhole).Column Else OpsCol = Wk1.Cells.Find("Submarket", lookat:=xlWhole).Column End If Dim FRow As Long Dim lRow As Long Dim ExpCol As Long Dim PropCodeCol As Long Dim Expense As String Expense = InputBox("Enter Expense GL") 'to locate begining and ending row of data on PNL report 'Identifies the column where the SubMarket names are located for lookup purposes 'Defines the expense GL column to lookup based on the inputbox above FRow = Wk1.Cells.Find("66990000", lookat:=xlPart).Offset(2, 0).row lRow = Wk1.Cells.Find("66990000", lookat:=xlPart).End(xlDown).Offset(-1, 0).row ExpCol = Wk1.Cells.Find(Expense, lookat:=xlPart).Column PropCodeCol = Wk1.Cells.Find("Property Code", lookat:=xlWhole).Column 'Defines the Range of the PM or Sub-Market Names Dim OpsRg As Range Set OpsRg = Wk1.Range(Wk1.Cells(FRow, OpsCol), Wk1.Cells(lRow, OpsCol)) 'Defines the Range of the Property Codes Dim PropCodeRg As Range Set PropCodeRg = Wk1.Range(Wk1.Cells(FRow, PropCodeCol), Wk1.Cells(lRow, PropCodeCol)) 'Defines the exact range of the expense column being analyzed Dim ExpRg As Range Set ExpRg = Wk1.Range(Wk1.Cells(FRow, ExpCol), Wk1.Cells(lRow, ExpCol)) End With Dim AllEntRg As Range For Each Cell In OpsRg If Cell = "" Then If AllEntRg Is Nothing Then Set AllEntRg = Cells(Cell.row, PropCodeCol) Else Set AllEntRg = Union(AllEntRg, Cells(Cell.row, PropCodeCol)) End If 'End If End If Next MsgBox (AllEntRg.Address) 'MsgBox (Application.Match(Wk1.Cells(59, 1), CCRg, 0)) 'Dim y 'y = Application.Match(Wk1.Cells(10, 1), CCRg, 0) 'If IsError(y) Then 'MsgBox ("pooopy error") 'End If Dim EntityRg As Range 'Dim c As Range For Each c In AllEntRg 'Dim z 'z = Application.Match(c, CCRg, 0) If CCRg.Find(c.Value, lookat:=xlPart) Is Nothing Then If EntityRg Is Nothing Then Set EntityRg = c Else Set EntityRg = Union(EntityRg, c) End If End If Next MsgBox (EntityRg.Address) Dim CostCRg As Range Dim r As Range For Each r In AllEntRg If Not CCRg.Find(r.Value, lookat:=xlPart) Is Nothing Then If CostCRg Is Nothing Then Set CostCRg = r Else Set CostCRg = Union(CostCRg, r) End If End If Next MsgBox (CostCRg.Address) Dim v As Double v = Application.WorksheetFunction.Sum(EntityRg) 'SendKeys "{F9}" MsgBox (v) Application.Calculation = xlCalculationAutomatic Application.ScreenUpdating = True
I have no means of running your code but I have reviewed it and have noticed some possible problems. lRowCC = Wk4.Cells.Find("Cost Center", lookat:=xlWhole).End(xlDown).row `.End(xlDown) is not a reliable method of finding the last row of a column. Read this answer of mine for an explanation: Excel vba – xlDown You say: “For EntityRg, the range.address defined is the same as AllEntRg (this shouldn't be the case).” Do you believe they are the same because EntityRg.Address = AllEntRg.Address? EntityRg .Address will be a string of absolute cell and range addresses separated by commas. You may not be aware that this string has a maximum length of about 255. I cannot find any documentation but from my own experimentation, EntityRg .Address will be truncated to less than 256 such that there is no partial cell or range address. Are you being fooled by the first 255 characters of these addresses matching? Another possibility is that every use of CCRg.Find(c.Value, lookat:=xlPart) returns Nothing so EntityRgand AllEntRg are equal. You say CostCRg gives an error; is this because it is Nothing? You have two loops searching CCRg for values in AllEntRg. One loop records the successes and one records the failures. Why not combine the loops into something like: If CCRg.Find(c.Value, lookat:=xlPart) Is Nothing Then If EntityRg Is Nothing Then Set EntityRg = c Else Set EntityRg = Union(EntityRg, c) End If Else If CostCRg Is Nothing Then Set CostCRg = r Else Set CostCRg = Union(CostCRg, r) End If End If I am concerned that For Each c In AllEntRg is not giving you what you expect. If you combine ranges with Union, it will tidy them up. So Union(Range("A2"), Range("A3", Range("A5"), Range("A6"), Range("A7")).Address is "$A$2:$A$3,$A$5:$A$7" not "$A$2,$A$3,$A$5,$A$6,$A$7". My recollection is that For Each c In AllEntRg would not split "$A$2:$A$3" into separate cells. Please use F8 to step through this loop to check that it is performing as you expect. Hope this helps Answer to problem described in comment Your problem is you are not being consistent in you use of Withs and, in particular, you are not identifying which workbook you want to operate on. Wk4 is explicitly specified to be within workbook BufdWkb and Wk1 is specified to be within PNLWkb. However, in Set AllEntRg = Cells(Cell.row, PropCodeCol) you do not specify a worksheet or workbook for Cells. This is the equivalent of Set AllEntRg = ActiveWorkbook.ActiveSheet.Cells(Cell.row, PropCodeCol)` You need to write Set AllEntRg = .Cells(Cell.row, PropCodeCol) (note period before Cells) and include this code within the With Wk1 Block.
VBA check for value in a range
I am trying to loop through a column and if cells = "what i'm lookng for" then do something. I have this so far, where I'm off is in the if statement where I check for the "name": Option Explicit Sub test() Dim wksDest As Worksheet Dim wksSource As Worksheet Dim rngSource As Range Dim name As String Dim LastRow As Long Dim LastCol As Long Dim c As Long Application.ScreenUpdating = False Set wksSource = Worksheets("Sheet1") With wksSource LastCol = .Cells(1, .Columns.Count).End(xlToLeft).Column For c = 16 To 20 LastRow = .Cells(.Rows.Count, c).End(xlUp).Row Set rngSource = .Range(.Cells(5, 16), .Cells(LastRow, 16)) name = rngSource.Value If name = "mark" do something End If Next c End With Application.ScreenUpdating = True 'MsgBox "Done!", vbExclamation End Sub
OK Chris Maybe a bit of simplification is required but also a few assumptions. It doesn't seem like LastCol is being used for anything - so let's assume this is the Column you want to loop through. Your loop has fixed start and end values yet you are determining the LastRow - so let's assume you want to start from row 5 (in your code) and loop to the LastRow in the LastCol. In order to determine LastCol you must have data in the row you are using to do this - so let's assume that there are values in row 1 in all columns up to column you want to loop say 16 (in your code). If you want to (IF) test for a single (string) value in this case then you must arrange for your rngSource to be a single cell value. You also don't need to assign this to a variable unless you need to use it again. Finally, if you want to check for other values you may want to consider using a SELECT CASE structure in place of your IF THEN structure. Have a look at the following and change my assumptions to meet your requirement - good luck. Sub test() Dim wksDest As Worksheet Dim wksSource As Worksheet Dim rngSource As Range Dim name As String Dim LastRow As Long Dim LastCol As Long Dim c As Long Application.ScreenUpdating = False Set wksSource = Worksheets("Sheet1") With wksSource LastCol = .Cells(1, .Columns.Count).End(xlToLeft).Column LastRow = .Cells(Rows.Count, LastCol).End(xlUp).Row FirstRow = 5 For c = FirstRow To LastRow If .Range(.Cells(c, LastCol), .Cells(c, LastCol)).Value = "Mark" Then MsgBox ("do something") End If Next c End With End Sub
You can just do that with one line. If Not IsError(Application.Match(ValueToSearchFor, RangeToSearchIn, 0)) Then 'The value found in the given range End If Example: Search for "Canada" in column C of sheet named "Country" If Not IsError(Application.Match("Canada", Sheets("Country").Range("C:C"), 0)) Then 'The value found in the given range End If
Pass value to find and Column where value need to be checked. It will return row num if its found else return 0. Function checkForValue(FindString As String,ColumnToCheck as String) As Long SheetLastRow = Sheets("Sheet1").Cells.Find(What:="*", SearchOrder:=xlRows, SearchDirection:=xlPrevious, LookIn:=xlValues).row With Sheets("Sheet1").Range("$" & ColumnToCheck & "$1:$" & ColumnToCheck & "$" & CStr(SheetLastRow) ) Set rng = .Find(What:=FindString, _ After:=.Cells(.Cells.Count), _ LookIn:=xlValues, _ lookat:=xlWhole, _ SearchOrder:=xlByRows, _ SearchDirection:=xlNext, _ MatchCase:=False) If Not rng Is Nothing Then checkForValue = rng.row 'return row its found 'write code you want. Else checkForValue = 0 End If End With End Function
I tried Hari's suggestion, but Application.Match works weird on range names (not recognizing them...) Changed to: WorksheetFunction.Match(... It works, but when value is not present A runtime ERROR jumps before IsError(...) is evaluated. So I had to write a simple -no looping- solution: dim Index as Long Index = -1 On Error Resume Next Index = WorksheetFunction.Match(Target,Range("Edificios"), 0) 'look for Target value in range named: Edificios On Error GoTo 0 If Index > 0 Then ' code for existing value found in Range # Index row End If Remeber Excel functions first index = 1 (no zero based) Hope this helps.
I'm guessing what you really want to do is loop through your range rngSource. So try Set rngSource = .Range(.Cells(5, 16), .Cells(LastRow, 16)) for myCell in rngSource if myCell.Value = "mark" then do something end if next myCell
Copying visible/filtered rows efficiently in excel
I am working with some very large datasets (various sheets with 65K+ rows and many columns each). I am trying to write some code to copy filtered data from one sheet to a new empty sheet as fast as possible, but have not had much success so far. I can include the rest of the code by request, but all it does is calculates the source and destination ranges (srcRange and destRange). The time taken to calculate these is negligible. The vast majority of the time is being spent on this line (4 minutes 50 seconds to be precise): srcRange.Rows.SpecialCells(xlCellTypeVisible).Copy Destination:=destRange Additionally I've tried this: destRange.Value = srcRange.Rows.SpecialCells(xlCellTypeVisible).Value But it doesn't work properly when there's a filter. Function FastCopy(srcSheet As String, srcCol As String, destSheet As String, destCol As String) Application.ScreenUpdating = False Application.Calculation = xlCalculationManual Dim srcRange As Range Dim destRange As Range Set srcRange = GetColumnRangeByHeaderName(srcSheet, srcCol, -1) Set destRange = GetColumnRangeByHeaderName(destSheet, destCol, srcRange.Rows.Count) 'destRange.Value = srcRange.Rows.SpecialCells(xlCellTypeVisible).Value srcRange.Rows.SpecialCells(xlCellTypeVisible).Copy Destination:=destRange Application.ScreenUpdating = True Application.Calculation = xlCalculationManual End Function This is a slow, dual core machine with 2GB of RAM running excel 2010. Results will obviously vary on a faster machine.
Try something like this to work with filtered ranges. You're on the right track, the .Copy method is expensive and simply writing values from range to range should be much faster, however as you observe, this doesn't work when a range is filtered. When the range is filtered, you need to iterate the .Areas in the range's .SpecialCells: Sub Test() Dim rng As Range Dim subRng As Range Dim destRng As Range Set destRng = Range("A10") Set rng = Range("A1:B8").SpecialCells(xlCellTypeVisible) For Each subRng In rng.Areas Set destRng = destRng.Resize(subRng.Rows.Count, subRng.Columns.Count) destRng.Value = subRng.Value Set destRng = destRng.Cells(destRng.Rows.Count, 1).Resize(1, 1).Offset(1, 0) Next End Sub Modified for your purposes, but untested: Function FastCopy(srcSheet As String, srcCol As String, destSheet As String, destCol As String) Application.ScreenUpdating = False Application.Calculation = xlCalculationManual Dim srcRange As Range Dim destRange As Range Dim subRng As Range Set srcRange = GetColumnRangeByHeaderName(srcSheet, srcCol, -1) Set destRange = GetColumnRangeByHeaderName(destSheet, destCol, srcRange.Rows.Count) For Each subRng In srcRange.Areas Set destRng = destRng.Resize(subRng.Rows.Count, subRng.Columns.Count) destRng.Value = subRng.Value Set destRng = destRng.Cells(destRng.Rows.Count, 1).Resize(1, 1).Offset(1, 0) Next Application.ScreenUpdating = True Application.Calculation = xlCalculationManual End Function
Simplest copying (no filter) Range("F1:F53639").Value = Range("A1:A53639").Value To expand on my comment Sub Main() Application.ScreenUpdating = False ' paste the Range into an array Dim arr arr = Range("$A$1:$A$53639").Value ' fill the range based on the array Range("$F$1").Resize(UBound(arr, 1), UBound(arr, 2)) = arr ' apply the same filter to your copied range as the original range '+ i don't know how you have applied your filter but just re-apply it to column F ' and delete the invisible cells ' unfortunately there is no xlCellTypeHidden or xlCelltypeInvisible hehe so you have to iterate Dim i As Long For i = Range("F" & Rows.Count).End(xlUp).Row To 1 Step -1 If (Range("F" & i).EntireRow.Hidden) Then Range("F" & i).Delete ' or Range("F" & i).EntireRow.Delete Next i Application.ScreenUpdating = True End Sub If you could provide the time it took you to run it that would be great I am very curious I just ran this code on 53639 rows and it took less than 1 second Sub Main() Application.ScreenUpdating = False Dim tNow As Date tNow = Now ' paste the Range into an array Dim arr arr = Range("$A$1:$A$53639").Value ' fill the range based on the array Range("$F$1").Resize(UBound(arr, 1), UBound(arr, 2)) = arr ' apply the same filter to your copied range as the original range ActiveSheet.Range("$F$1:$F$53640").AutoFilter Field:=1, Criteria1:="a" ' and delete the invisible cells ' unfortunately there is no xlCellTypeHidden or xlCelltypeInvisible hehe so you have to iterate Dim i As Long For i = Range("F" & Rows.Count).End(xlUp).Row To 1 Step -1 If (Range("F" & i).EntireRow.Hidden = True) Then Range("F" & i).Delete End If Next i Debug.Print DateDiff("s", tNow, Now) Application.ScreenUpdating = True End Sub
Read a value from spreadsheet X, compare adjacent values between spreadsheets X and Y
I have an Macro Based Excel file that generates a list of items received and their status (i.e. received, repaired, etc). This program runs daily, and right now I have it capture the previous day's list and place it in a spreadsheet called PreviousData before updating with the current day's list, which is placed in a spreadsheet called Data; this is used to compare what we believe we fixed/changed status on the previous day. I'm basically self taught in VBA, so I'm not super efficient or experienced. What I want to do is the following: On the Data Spreadsheet, grab the order number starting in J2 Switch to the PreviousData Spreadsheet, and search for the order number from step 1 Scenario A: If the order number is found on PreviousData, compare the status values next to the order number on both sheets; if they differ, run some code otherwise do nothing Scenario B: If the order number is not found on PreviousData, do nothing Repeat until 1st blank cell encountered in Data Spreadsheet I did some searching around the interwebs and found something (it might have been from this forum, actually) that would go row by row and compare cell values, but if scenario B came up the function would fail with "out of range." Here is the code I tried and have modified to try to get to work: Sub output() Dim varSheetA As Variant Dim varSheetB As Variant Dim varSheetRMA As Variant Dim strRangeToCheck As String Dim strRangeRMA As String Dim Variable As String Dim iRow As Long Dim iCol As Long Dim Count As Integer strRangeToCheck = "K2:L1000" strRangeRMA = "J2:J1000" ' If you know the data will only be in a smaller range, reduce the size of the ranges above. Debug.Print Now varSheetA = Worksheets("PreviousData").Range(strRangeToCheck) varSheetB = Worksheets("Data").Range(strRangeToCheck) ' or whatever your other sheet is. varSheetRMA = Worksheets("Data").Range(strRangeRMA) Debug.Print Now Sheets("Data").Select Range("J2").Select Selection.Copy Sheets("PreviousData").Select Cells.Find(What:=Variable, After:=ActiveCell, LookIn:=xlFormulas, _ LookAt:=xlPart, SearchOrder:=xlByRows, SearchDirection:=xlNext, _ MatchCase:=False, SearchFormat:=False).Activate For iRow = LBound(varSheetA, 1) To UBound(varSheetA, 1) For iCol = LBound(varSheetA, 2) To UBound(varSheetA, 2) If varSheetA(iRow, iCol) = varSheetB(iRow, iCol) Then ' Cells are identical. ' Do nothing. Else ' Cells are different. ' Code goes here for whatever it is you want to do. End If Next iCol Next iRow End Sub Please help :)
This code should be easier to understand + it does the job. Option Explicit Sub CompareStatuses() Dim ws1 As Worksheet, ws2 As Worksheet, rng1 As Range, rng2 As Range Dim lr1&, lr2&, i&, j& Set ws1 = ThisWorkbook.Sheets("Data") Set ws2 = ThisWorkbook.Sheets("PreviousData") lr1 = ws1.Range("J" & Rows.Count).End(xlUp).Row lr2 = ws2.Range("J" & Rows.Count).End(xlUp).Row For i = 2 To lr1 For j = 2 To lr2 Set rng1 = ws1.Range("J" & i) Set rng2 = ws2.Range("J" & j) If StrComp(CStr(rng1.Value), CStr(rng2.Value), vbTextCompare) = 0 And _ StrComp(CStr(rng1.Offset(0, 1).Value), CStr(rng2.Offset(0, 1).Value) _ ,vbTextCompare) <> 0 Then ' found a matching Order + both statuses are different ' this is where you wanted to run some code End If Set rng1 = Nothing Set rng2 = Nothing Next j Next i End Sub