VBA- Ammend code from Copy and paste to destination - vba

my code is running really slowly and I'm trying to fasten it. The only way I can think of is to do without the last bit of code which does copy, select,paste twice for two different target worksheets. Was wondering if I'm able to change it to something like Destination:= ____ & ____ instead of selecting and pasting twice?
Sub compare()
'compare if the values of two ranges are the same
'Select workbook to prevent mismatch error
Application.ScreenUpdating = False
Application.DisplayStatusBar = False
Workbooks("Compare.xlsm").Activate
Dim referencesheetname, newsheetname, outputsheetname As String
referencesheetname = "Reference"
newsheetname = "New"
Dim range1, range2 As Range
'define the variables
Dim referencesheetcols As Integer
Dim range1rows, range1cols, range2rows, range2cols, testrows, testcols, i, j, p, q As Long
Dim bMatches, rowmatched As Boolean
Dim product As String
'Define names for easy reference
product = "Ethylene"
'Set range you wish the macro to search up till
newsheetcols = 3000
referencesheetcols = 3000
'How many rows and columns should we compare?
'Set testcols to 150 to test whole range
testrows = 1
testcols = 200
'Set p for position to place data at (i.e. if p=1, data will be pasted)
p = Sheets(referencesheetname).UsedRange.Rows.Count
q = Sheets("Datasheet").UsedRange.Rows.Count
'Pasted table range data starts from row 7
For l = 1 To newsheetcols
'ActiveWorkbook.Worksheets(newsheetname).Select
'only test if correct product down column B
If CStr(Sheets(newsheetname).Rows(l).Cells(1, 2).Value) = product Then
rowmatched = False
For k = 5 To referencesheetcols
'bmatch = False
'Define range compare rows 6 onwards for both sheets
Set range1 = Sheets(referencesheetname).Rows(k)
Set range2 = Sheets(newsheetname).Rows(l)
' count the rows and columns in each of the ranges
range1rows = range1.Rows.Count
range1cols = range1.Columns.Count
range2rows = range2.Rows.Count
range2cols = range2.Columns.Count
'Check if ranges are the same dimension?
bMatches = (range1rows = range2rows And range1cols = range2cols)
'if same dimensions loop through the cells
If bMatches Then
For i = 1 To testrows
For j = 1 To testcols
If (range1.Cells(i, j).Value <> range2.Cells(i, j).Value) Then
'Conclude that range dimension is not the same
bMatches = False
i = testrows
j = testcols
'Exit loops
End If
Next
Next
End If
'If ranges of two comparison sheets are the same
If bMatches Then
rowmatched = True
k = referencesheetcols
End If
'Sheets(outputsheetname).Cells(1, 1).Value = rowmatched
'Set place to paste data
If (Not (rowmatched) And k = referencesheetcols) Then
'Copy and paste specified number of columns
range2.Resize(1, 300).Copy
Sheets(referencesheetname).Cells(p, 1).Offset(2, 0).Select
ActiveSheet.Paste
p = p + 1
Sheets("Datasheet").Activate
ActiveSheet.Cells(q, 1).Offset(2, 1).Select
ActiveSheet.Paste
q = q + 1
End If
Next
End If
Next
Application.ScreenUpdating = True
Application.DisplayStatusBar = True
End Sub

Something like below should be ok to change to copy - destination.
range2.Resize(1, 300).Copy Destination:=Sheets(referencesheetname).Cells(p, 1).Offset(2, 0)
Although if you really wanted to speed up your code I would say you would need to read the range into an array and then do your processing on the array. looking at the sheet is costly in terms of cpu time, selecting should be avoided where ever possible
You could also turn calculation off and just recalc when you need it too. You could also look up "WITH"'s as these can speed it up a bit too

Related

For loop to copy entire row when match found between two sheets

I am trying to get a For loop which copies an entire row from worksheet 1 to worksheet 3 if the cell in column C in ws1 and column AT in ws2 matches. I have two issues:
1. It seems to be stuck in the For i = xxxxx loop and does not move to the next k (only copies one line 25 times)
2. When I use it on a sheet that has 100,000 rows for worksheet 1 and 15,000 rows on worksheet 2, excel just crashes. Is there a way to manage this?
Sub CopyBetweenWorksheets()
Application.ScreenUpdating = False
Dim i As Long, k As Long, ws1 As Worksheet, ws2 As Worksheet, myVar As String, myVar2 As String
Set ws1 = Worksheets("BOM")
Set ws2 = Worksheets("APT")
Set ws3 = Worksheets("Combined")
'get the last row for w2 and w1
ii = ws1.Cells.SpecialCells(xlCellTypeLastCell).row
kk = ws2.Cells.SpecialCells(xlCellTypeLastCell).row
For k = 2 To kk
myVar = ws2.Cells(k, 46)
For i = 688 To ii '688 To ii
myVar2 = ws1.Cells(i, 3)
If myVar2 = myVar Then
ws3.Rows(k).EntireRow.Value = ws1.Rows(i).EntireRow.Value 'copy entire row
Exit For
End If
Next i
Next k
End Sub
Your code is fine (not mentioning the missing Application.ScreenUpdating = True), but it will hang on large number of rows and columns because of the amount of interations with the application (Excel in this case).
Each time you request a value from a single cell from Excel, your code will hang for about 4 secounds per 1 million requests. From an entire row it will hang for 4 secounds per 4000 requests. If you try writing a single cell, your code will hang for 4 secounds per 175000 requests, and writing an entire row will hang your code for 4 secounds per 300 requests.
This way, only if you try parsing 15.000 rows of data from one sheet to another, your code will hang for about 3,3 minutes.. not to mention all read requests..
So, always keep the amount of interactions with any application from vba to a minimum, even if you have to create a much bigger code.
Here is what your code should look like if you want to handle a lot of data:
Sub CopyBetweenWorksheets2()
Dim aAPT, aBOM, aCombined As Variant
Dim lLastRow As Long, lLastColumn As Long
Dim i As Long, j As Long
Const APTColRef = 3
Const BOMColRef = 46
Const MAXCol = 200
'Speed up VBA in Excel
Application.ScreenUpdating = False
Application.EnableEvents = False
Application.Calculation = xlCalculationManual
'Get the last row and column to use with the combined sheet
lLastRow = WorksheetFunction.Min(APT.Cells.SpecialCells(xlCellTypeLastCell).Row, BOM.Cells.SpecialCells(xlCellTypeLastCell).Row)
lLastColumn = WorksheetFunction.Min(MAXCol, WorksheetFunction.Max(APT.Cells.SpecialCells(xlCellTypeLastCell).Column, BOM.Cells.SpecialCells(xlCellTypeLastCell).Column))
'Parse all values to an array, reducing interactions with the application
aAPT = Range(APT.Cells(1), APT.Cells(lLastRow, lLastColumn))
aBOM = Range(BOM.Cells(1), BOM.Cells(lLastRow, lLastColumn))
'Creates a temporary array with the values to parse to the destination sheet
ReDim aCombined(1 To lLastRow, 1 To lLastColumn)
'Loop trough values and parse the row value if true
For i = 1 To lLastRow
If aAPT(i, APTColRef) = aBOM(i, BOMColRef) Then
For j = 1 To lLastColumn
aCombined(i, j) = aAPT(i, j)
Next
End If
Next
'Parse values from the destination array to the combined sheet
Combined.Range(Combined.Cells(1), Combined.Cells(lLastRow, lLastColumn)) = aCombined
'Disable tweaks
Application.ScreenUpdating = True
Application.EnableEvents = True
Application.Calculation = xlCalculationManual
End Sub
!! I named the sheets objects in the VBA itself, so you don't have to declare a new variable and you also won't have any problems if you rename them later. So, insted of sheets("APT"), I just used APT (you will have to rename it too if you want the code to work) !!
Plus, here is my speed code I wrote for speed testing my codes. I always keep it at hand, and use it in almost every function i write
Sub Speed()
Dim i As Long
Dim dSec As Double
Dim Timer0#
Dim TimerS#
Dim TimerA#
Dim TimerB#
dSec = 4 ''Target time in secounds''
i = 1
WP1:
Timer0 = Timer
For n = 1 To i
SpeedTestA
Next
TimerA = Timer
For n = 1 To i
SpeedTestB
Next
TimerB = Timer
If TimerB - Timer0 < dSec Then
If TimerB - Timer0 <> 0 Then
i = CLng(i * (dSec * 2 / (TimerB - Timer0)))
GoTo WP1
Else
i = i * 100
GoTo WP1
End If
End If
MsgBox "Código A: " & TimerA - Timer0 & vbNewLine & "Código B: " & TimerB - TimerA & vbNewLine & "Iterações: " & i
End Sub
Sub SpeedTestA() 'Fist Code
End Sub
Sub SpeedTestB() 'Secound Code
End Sub

Compare data in 2 excel workbooks (unsorted data)

i am comparing the data in 2 workbooks, the column headers are in the same order, they are: ID, DepartmentName, Name, SalesAmount, StartDate, End Date.
Currently i am comparing all the cells in sheet 1 to sheet 2 (for example: cell A1 in sheet 1 to cell A1 in sheet 2 ). However, now the data in sheet 2 is in a different order so my current method of comparing will not work.
If sheet 1 contains the correct data, i want to be able to match the correct rows to sheet 2 and check the data still matches. For the rows that are not present in sheet 2 display a table to notify me of which IDs are missing.
Code which compares cell to cell and identifies differences:
For Each mycell In ActiveWorkbook.Worksheets(shtSheet2).UsedRange
If Not mycell.Value = ActiveWorkbook.Worksheets(shtSheet1).Cells(mycell.Row, mycell.Column).Value Then
mycell.Interior.Color = vbRed
difference = difference + 1
End If
Next
Any advice or help will be greatly appreciated! thank you
You should read down the list of "good" IDs and for each one use the Range.Find method to look for the entry in shtSheet2. If not found, copy the "good" trade data to the output sheet. If found, then loop through the data items comparing them. Here's the code:
Dim sourceId As Range
Dim testIdData As Range
Dim outputRange As Range
Dim cellFound As Range
Dim columnNum As Integer
Dim copyTheData As Boolean
Dim difference As Integer
Const NUM_COLUMNS_DATA As Integer = 6 '
' Assumes that worksheet variables are already defined
Set sourceId = ActiveWorkbook.Worksheets(shtSheet1).Range("A1")
Set testIdData = ActiveWorkbook.Worksheets(shtSheet2).Range("A1")
Set outputRange = ActiveWorkbook.Worksheets(shtSheet3).Range("A1")
' Extend testIdData to cover all rows of data
Set testIdData = testIdData.Resize(testIdData.CurrentRegion.Rows.Count)
Do Until sourceId.Value = ""
copyTheData = False
' Look for ID in test data
Set cellFound = testIdData.Find(What:=sourceId.Value, LookIn:=xlValues, LookAt:=xlWhole, MatchCase:=False)
If cellFound Is Nothing Then
' This entry not found, so copy to output
copyTheData = True
outputRange.Resize(ColumnSize:=NUM_COLUMNS_DATA).Interior.Color = vbRed
Else
' Test that all the items match
' This assumes that columns are in same order
For columnNum = 2 To NUM_COLUMNS_DATA ' No need to test the ID column
If sourceId.Cells(ColumnIndex:=columnNum).Value <> cellFound.Cells(ColumnIndex:=columnNum).Value Then
outputRange.Cells(ColumnIndex:=columnNum).Interior.Color = vbRed
copyTheData = True
End If
Next columnNum
End If
If copyTheData Then
sourceId.Resize(ColumnSize:=NUM_COLUMNS_DATA).Copy
' Do PasteSpecial to avoid over-writing the ".Interior.Color = vbRed"
outputRange.PasteSpecial xlPasteValuesAndNumberFormats
Application.CutCopyMode = False
Set outputRange = outputRange.Offset(RowOffset:=1)
difference = difference + 1
End If
Set sourceId = sourceId.Offset(RowOffset:=1)
Loop
Remember to test it thoroughly before using it on real data.

Why do my VBA code sometimes work and most of the times it doesn't?

Sub UpdateCSAH()
Dim S As String
Dim R As Long
Dim RR As Long
Dim CC As Long
Dim i As Long
Dim j As Long
Dim csah() As String 'an array that stores the CSAH sites
ReDim csah(1 To 100, 1 To 7)
Dim Ran As Range
Dim Ran1 As Range
Set Ran = Worksheets("Current Sites").Range("A1").CurrentRegion 'Ran is the region that has values
RR = 1 'row number in csah
CC = 1 'column number in csah
'check each value in Ran to see if its Route section has "CSAH"
For Each cell In Ran
R = cell.row
S = CStr(Cells(R, 4).value)
If InStr(S, "CSAH") > 0 Then 'check if "CSAH" is in the Route section
If CC > 7 Then 'reset the column number and go to the next row when reach the end of the column
CC = 1
RR = RR + 1
End If
csah(RR, CC) = cell.value
CC = CC + 1
End If
Next cell
Worksheets("CSAH Sites").Select
Range("A2:G100").Select
Selection.ClearContents
'assign each array values to cells in sheet"CSAH Sites"
i = 1
j = 1
For i = 1 To UBound(csah, 1)
For j = 1 To UBound(csah, 2)
Cells(i + 1, j) = csah(i, j)
Next j
Next i
'format the CSAH Sites values
Set Ran1 = Worksheets("CSAH Sites").Range("A1").CurrentRegion
For Each cell In Ran1
If cell.row = 1 Then
With cell.Font
.Color = -11489280
End With
ElseIf cell.row Mod 2 = 0 Then
With cell.Interior
.Color = 10092441
End With
End If
Next cell
End Sub
I have an Excel worksheet named "Current Sites" that has some data. If the 4th column has the word "CSAH", I want to store the values of that row into an array and assign those values to cells in the worksheet named "CSAH Sites". My code sometimes works (the 1st time you click), and most of times it doesn't work or doesn't work properly.
Please help me out! Thanks A Bunch!!
It looks like you want to check every row of data in the "Current Sites" sheet and if column 4 includes the "CSAH" text, then write the first 7 columns of data for that entry to the "CSAH Sites" sheet and add some colour to the even-numbered rows.
To check every row of data, you can read down just one column and use either the Offset or the Cells method to see the values of neighbouring cells. In your code you were "touching" every cell and each time you were then looking at the value in column 4 and also checking to see if the code had gone past column 7. That slows things down a lot and makes the code hard to understand.
You can also assign the values from a range of cells directly to another range of cells without using variables or an array.
See if this does what you want:
Sub UpdateCSAH()
Dim currentSitesRange As Range
Dim thisSiteRange As Range
Dim outputCell As Range
Dim numRowsOfData As Long
Const NUM_COLUMNS_OF_DATA As Integer = 7
Set currentSitesRange = Worksheets("Current Sites").Range("A1")
numRowsOfData = currentSitesRange.CurrentRegion.Rows.Count
Set currentSitesRange = currentSitesRange.Resize(RowSize:=numRowsOfData) 'currentSitesRange is the region that has values
Worksheets("CSAH Sites").Range("A2:G100").ClearContents
Set outputCell = Worksheets("CSAH Sites").Range("A2")
For Each thisSiteRange In currentSitesRange.Cells
' Look for "CSAH" in the Route section (column D)
If InStr(1, thisSiteRange.Offset(ColumnOffset:=3).Value, "CSAH", vbTextCompare) > 0 Then
' Found "CSAH" so write NUM_COLUMNS_OF_DATA columns of data to CSAH Sites sheet
outputCell.Resize(ColumnSize:=NUM_COLUMNS_OF_DATA).Value = thisSiteRange.Resize(ColumnSize:=NUM_COLUMNS_OF_DATA).Value
' Format the even-numbered rows
If outputCell.Row Mod 2 = 0 Then
With outputCell.Resize(ColumnSize:=NUM_COLUMNS_OF_DATA).Interior
.Color = 10092441
End With
End If
Set outputCell = outputCell.Offset(RowOffset:=1)
End If
Next thisSiteRange
End Sub

Fastest way to check if two ranges are equal in excel vba [duplicate]

This question already has answers here:
How to compare two entire rows in a sheet
(11 answers)
Closed 8 years ago.
Imagine you have two sets of data and the number of rows and columns are the same. Now you want check if data in cells in one set is equal to data in cells with the same relative address in the other set. If thats true for all cells of a row, remove the row from both sets. I can code this very easily by comparing each cell and that's not good for large data sets. See code below for two columns where the two sets of data happen to be in the same sheet side by side with 300 in column offset between them.
Dim RngOb As Range
Dim c As Range
Range("A1", "B1").Select
set RngOb = Range(Selection, Selection.End(xlDown))
For Each c In RngOb.Rows
If c.Cells(1,1).Value = c.Offset(0, 300).Cells(1,1).Value Then
If c.Cells(1,2).Value = c.Offset(0, 300).Cells(1,2).Value Then
c.EntireRow.Delete
End If
End If
Next
My actual data has more than 100 columns and different number of columns from day to day. I'm looking for a smart, fast way to do this for large data sets. I highly appriciate answers, feedback and criticism. :D
Here is a simple way to compare two rows in isomorphic ranges.............in this example row #5 of each range:
Sub RowCompare()
Dim ary1() As Variant
Dim Range1 As Range, Range2 As Range, rr1 As Range, rr2 As Range
Set Range1 = Range("B9:F20")
Set Range2 = Range("I16:M27")
Set rr1 = Range1.Rows(5)
Set rr2 = Range2.Rows(5)
ary1 = Application.Transpose(Application.Transpose(rr1))
ary2 = Application.Transpose(Application.Transpose(rr2))
st1 = Join(ary1, ",")
st2 = Join(ary2, ",")
If st1 = st2 Then
MsgBox "the same"
Else
MsgBox "different"
End If
End Sub
If you have embedded commas in the cells, then choose another character in the JOIN
If I understand your problem correctly, the following code should allow you to do what you want. Within the code, you select the range you wish to process; the first column of each data set, and the number of columns within each data set.
It does assume only two data sets, as you wrote, although that could be expanded. And there are ways of automatically determining the dataset columns, if there is no other data in between.
Option Explicit
Option Base 0
Sub RemoveDups()
Dim I As Long, J As Long
Dim rRng As Range
Dim vRng As Variant, vRes() As Variant
Dim bRng() As Boolean
Dim aColumns, lColumns As Long
Dim colRowsDelete As Collection
'vRng to include from first to last column to be tested
Set rRng = Range("f1", Cells(Rows.Count, "F").End(xlUp)).Resize(columnsize:=100)
vRng = rRng
ReDim bRng(1 To UBound(vRng))
'columns to be tested
'Specify First column of each data set
aColumns = Array(1, 13)
'num columns in each data set
lColumns = 3
For I = 1 To UBound(vRng)
bRng(I) = vRng(I, aColumns(0)) = vRng(I, aColumns(1))
For J = 1 To lColumns - 1
bRng(I) = bRng(I) And (vRng(I, aColumns(0) + J) = vRng(I, aColumns(1) + J))
Next J
Next I
'Rows to Delete
Set colRowsDelete = New Collection
For I = 1 To UBound(bRng)
If bRng(I) = True Then colRowsDelete.Add Item:=I
Next I
'Delete the rows
If colRowsDelete.Count > 0 Then
Application.ScreenUpdating = False
For I = colRowsDelete.Count To 1 Step -1
rRng.Rows(colRowsDelete.Item(I)).EntireRow.Delete
Next I
End If
Application.ScreenUpdating = True
End Sub

Big loop crashes in VBA

Screenshot
I am updating a word list (2) with the frequency ranking of another list (1). The code is designed to for every entry in list 1 go through list 2 and add the frequency ranking to every identical entry in it. If I limit the list to a few entries in each, it works exactly as intended, but the lists are quite large. List 1 contains 55.000 words and list 2 contains 18.000 words. Is there a way to prevent the code from crashing, or alternatively rewrite it in a more efficient manner? I am sure it is far from optimal, as I am a complete neophyte in VBA. I’ll paste in the code below.
Thanks much
Option Explicit
Sub CorrectFrequencyData()
Dim Frequency As Double
Dim CurrentLocation As Range
Application.ScreenUpdating = False
Set CurrentLocation = Range("i5")
Do Until CurrentLocation.Value = ""
Frequency = CurrentLocation.Offset(0, -6).Value
Range("n4").Activate
Do Until ActiveCell.Value = ""
If ActiveCell.Value = CurrentLocation.Value Then ActiveCell.Offset(0, 1).Value = ActiveCell.Offset(0, 1).Value + Frequency
ActiveCell.Offset(1, 0).Activate
Loop
Set CurrentLocation = CurrentLocation.Offset(1, 0)
Loop
Application.ScreenUpdating = True
End Sub
It Looks like there may be a few ways to speed up your code. Firstly you could use a SUMIF as GavinP suggested like so in your second frequency column =SUMIF(I:I, N4, C:C) If you flow this down for your second frequency column what this is saying is check column I for the value in N + row and everywhere that you find that value at the frequency from column C to a Total.
Now options to speed up your code:
Option Explicit
Sub CorrectFrequencyData()
Application.ScreenUpdating = False
I'm not sure if you have formulas in your code but you can set them to manual instead of having them recalculate every time you change values on your sheet.
Application.Calculation = -4135 'xlCalculationManual
Instead of looping through your sheet you can assign your range to an array and loop through that which is faster. We can also eliminate the need to loop through the second list for every entry in the first list. We'll do this by storing the first list of words and their frequency in a dictionary
Dim ArrWords() as variant
Dim LastRow as long
LastRow = ActiveSheet.Cells(ActiveSheet.Rows.Count, 9).End(-4162).Row 'Version non-specific Endrow, xlUP
ArrWords = Range("C4:I" & LastRow)
Dim dicWordFrequency as Object
Set dicWordFrequency = CreateObject("Dictionary.Scripting")
Dim tempWord as String
Dim i as Long
For i = 1 to Ubound(ArrWords)
tempWord = arrWords(i,7)
If not dicWordFrequency.Exists(tempWord) then
DicWordFrequency.Add tempWord, arrWords(i,1)
Else
DicWordFrequency.Item(tempWord)= dicWordFrequency.Item(tempWord) + arrWords(i,1)
End If
Next
Now we can loop through your worksheet and update the frequencies for the words in the second list.
LastRow = ActiveSheet.Cells(ActiveSheet.Rows.Count, 14).End(-4162).Row 'Version non-specific Endrow, xlUP
ArrWords = Range("N4:O" & LastRow)
For i = 1 to Ubound(arrWords)
tempWord = arrwords(i,1)
If dicWordFrequency.Exists(tempWord) then
arrWords(i,2) = dicWordFrequency.Item(tempWord)
End If
Next
'Dump your new array with the totals to a range
Dim result as Range
Set Result = Range("N4")
Result.ReSize(UBound(arrWords,1), Ubound(ArrWords,2)).value = arrWords
Application.ScreenUpdating = True
Application.Calculation = -4105 'xlCalculationAutomatic
End Sub