I've got two Worksheets in Excel. I've written the following code to copy some data from Worksheet 1 to Worksheet 2, based on some values that the user inserts in Worksheet 2.
The macro works fine, and does what I need it to do, but after writing it down I've come to realize two things:
It takes quite some time for a small set of records(260 or so), as it goes one row at a time.
I read that using .select is not good practice, and I modified the code so that I would not use it, but I'm left wondering if I could improve the code to work faster if I did use it.
So, my main questions are:
How can I improve the speed of the code, so that it will be able to read copy rows faster.
Would it be better in this case to use .select in my case, so that it would work faster.
My code is the following:
Private Sub FillUp()
Dim DateVal, EquivalentDate As Date
Dim CrncyVal
Dim CountrVal
Dim DataRng As Range
Dim endrow As Long, startrow As Long
Dim ws1 As Worksheet
Dim ws2 As Worksheet
'Selecting the worksheets
Set ws1 = Worksheets("Sheet1")
Set ws2 = Worksheets("Sheet2")
''''declaring date, country and currency variables''''
DateVal = ws2.Range("E3").Value
CountryVal = UCase(ws2.Range("H3").Value)
CurrencyVal = UCase(ws2.Range("H4").Value)
EquivalentDateVal = DateAdd("yyyy", -1, DateVal)
'declaring other useful variables
startrow = 3
pasterow = 6
endrow = ws1.Cells(Rows.Count, 1).End(xlUp).Row
'delete the range we will be working with
ws2.Range("A6:F265").Clear
'start the ifs, to see what info the user wants to get
If ws2.Range("E3").Value = "" Then
'If the country cell is empty, we do nothing. We need at least this info
MsgBox prompt:="You didn't choose a month!!", Title:="Error: Please Choose a Month"
Exit Sub
ElseIf ws2.Range("H3").Value = "" Then
For i = 3 To endrow
If ws1.Cells(i, 3).Value <> "TOT" Then
With ws1
Set Rng = .Range(.Cells(i, 1), .Cells(i, 5))
End With
Rng.Copy
ws2.Cells(pasterow, 1).PasteSpecial
ws2.Cells(pasterow, 6) = DateVal
pasterow = pasterow + 1
End If
Next i
Exit Sub
ElseIf ws2.Range("H4").Value = "" Then
For i = 3 To endrow
If ws1.Cells(i, 3).Value <> "TOT" Then
If ws1.Cells(i, 1).Value = CountryVal Then
With ws1
Set Rng = .Range(.Cells(i, 1), .Cells(i, 5))
End With
Rng.Copy
ws2.Cells(pasterow, 1).PasteSpecial
ws2.Cells(pasterow, 6) = DateVal
pasterow = pasterow + 1
End If
End If
Next i
Exit Sub
Else
For i = 3 To endrow
If ws1.Cells(i, 3).Value <> "TOT" Then
If ws1.Cells(i, 1).Value = CountryVal Then
If ws1.Cells(i, 2).Value = CurrencyVal Then
With ws1
Set Rng = .Range(.Cells(i, 1), .Cells(i, 5))
End With
Rng.Copy
ws2.Cells(pasterow, 1).PasteSpecial
ws2.Cells(pasterow, 6) = DateVal
pasterow = pasterow + 1
End If
End If
End If
Next i
Exit Sub
End If
End Sub
Any help or opinion on how I can get the code to be faster or better in any way is welcome, as I am quite new to the whole Excel/VBA world.
Thanks!!
Well, after some time and using DhirendraKumar 's idea to use Autofilter I've managed to get the code to work much faster. Thanks again!!
I'm answering the question so that anyone that might come searching for an answer can see this example and maybe apply it to their problem.
Answers
I've answered my first question with the code below. The speed has been improved by using Autofilter, it works faster because it doesn't go row by row.
I didn't use Select in my code, and I don't use Activate anymore, so I guess I did not need to use neither.
Sub FillUp()
Dim DateVal
Dim CountryVal
Dim CurrencyVal
Dim endrow As Long, lastrow As Long, pasterow As Long
Dim ws1 As Worksheet
Dim ws2 As Worksheet
'Selecting the worksheets
Set ws1 = Worksheets("Cost Evolution 2")
Set ws2 = Worksheets("Sheet1")
''''declaring date, country and currency variables''''
DateVal = ws2.Range("E3").Value
CountryVal = UCase(ws2.Range("H3").Value)
CurrencyVal = UCase(ws2.Range("H4").Value)
'declaring other useful variables
pasterow = 6
lastrow = ws1.Cells(Rows.Count, 1).End(xlUp).Row
'delete the range we will be working with
ws2.Range("A6:F265").Clear
'start the ifs, to see what info the user wants to get
If DateVal = "" Then
'If the country cell is empty, we do nothing. We need at least this info
MsgBox prompt:="You didn't choose a month!!", Title:="Error: Please Choose a Month"
Exit Sub
ElseIf CountryVal = "" Then
With ws1.Range("A2:E2")
.AutoFilter Field:=3, Criteria1:="<>TOT"
End With
' make sure results were returned from the filter
If (ws1.Cells(ws1.Rows.Count, 1).End(xlUp).Address <> "$A$1") Then
ws1.Range("A3:E" & lastrow).Copy ws2.Range("A" & CStr(pasterow))
endrow = ws2.Cells(Rows.Count, 2).End(xlUp).Row
ws2.Range("F" & pasterow & ":F" & endrow).Formula = DateVal
pasterow = endrow + 1
End If
ws1.AutoFilterMode = False
MsgBox prompt:="Inserted complete month"
Exit Sub
ElseIf CurrencyVal = "" Then
With ws1.Range("A2:E2")
.AutoFilter Field:=3, Criteria1:="<>TOT"
.AutoFilter Field:=1, Criteria1:=CountryVal
End With
' make sure results were returned from the filter
If (ws1.Cells(ws1.Rows.Count, 1).End(xlUp).Address <> "$A$1") Then
ws1.Range("A3:E" & lastrow).Copy ws2.Range("A" & CStr(pasterow))
endrow = ws2.Cells(Rows.Count, 2).End(xlUp).Row
ws2.Range("F" & pasterow & ":F" & endrow).Formula = DateVal
pasterow = endrow + 1
End If
ws1.AutoFilterMode = False
MsgBox prompt:="Inserted complete month for the chosen country"
Exit Sub
Else
With ws1.Range("A2:E2")
.AutoFilter Field:=1, Criteria1:=CountryVal
.AutoFilter Field:=2, Criteria1:=CurrencyVal
.AutoFilter Field:=3, Criteria1:="<>TOT"
End With
' make sure results were returned from the filter
If (ws1.Cells(ws1.Rows.Count, 1).End(xlUp).Address <> "$A$1") Then
ws1.Range("A3:E" & lastrow).Copy ws2.Range("A" & CStr(pasterow))
endrow = ws2.Cells(Rows.Count, 2).End(xlUp).Row
ws2.Range("F" & pasterow & ":F" & endrow).Formula = DateVal
pasterow = endrow + 1
End If
ws1.AutoFilterMode = False
MsgBox prompt:="Inserted complete month for the chosen country and currency"
Exit Sub
End If
End Sub
Related
I'm really new to Excel VBA but recently need to come up with a solution to have excel iterate through a list and print the output.
Here on tab "Sheet2" is the item master. Each of the items is designated an Item Code.
On "Sheet1" I have a formula that finds the Unit Price and Starting Level and calculates the Total On Hand Liabilities.
I'd like to have Excel populate in cell Sheet1!A2 with each of the values in range Sheet2!A1:A, do the calculations, and paste all each of the outputs in a new sheet, as shown below.
Thank you.
I made a basic macro to do this, maybe you could tweak it to suit your needs.
Option Explicit
Sub Test()
Dim rng As Range
Dim switch As Boolean
switch = False
For Each rng In Worksheets("Sheet2").Range("A2", Worksheets("Sheet2").Range("A" & Rows.Count).End(xlUp))
Worksheets("Sheet1").Select
Range("A" & Rows.Count).End(xlUp).Select
'so that for the first iteration it will not offset, assuming you start off with blank wksht
If switch = True Then
ActiveCell.Offset(2, 0).Select
End If
ActiveCell.Value = "Item Number"
ActiveCell.Offset(0, 1).Value = "Description"
ActiveCell.Offset(0, 2).Value = "On Hand Liability"
ActiveCell.Offset(1, 0).Value = rng.Value
ActiveCell.Offset(1, 1).Value = rng.Offset(0, 1).Value
ActiveCell.Offset(1, 2).Value = rng.Offset(0, 2) * rng.Offset(0, 3)
switch = True
Next rng
End Sub
Suggest you use the Microsoft VBA language reference to look up loops. E.g. https://learn.microsoft.com/en-us/office/vba/language/reference/user-interface-help/for-eachnext-statement
Here is an example which will produce your output:
Option Explicit
Public Sub PopulateSheet1()
Dim SourceSheet As Worksheet
Dim SourceRow As Range
Dim SourceRows As Long
Dim TargetSheet As Worksheet
Dim TargetRow As Long
Set SourceSheet = ActiveWorkbook.Sheets("Sheet2")
Set TargetSheet = ActiveWorkbook.Sheets("Sheet1")
SourceRows = SourceSheet.Cells(Rows.Count, 1).End(xlUp).Row
TargetRow = 1
For Each SourceRow In SourceSheet.Range("A2:A" & SourceRows)
TargetSheet.Cells(TargetRow, 1) = Array("Item Number", "Description", "On Hand Liability")
TargetRow = TargetRow + 1
SourceRow.Cells(1, 1).Copy TargetSheet.Cells(TargetRow, 1)
TargetSheet.Cells(TargetRow, 2) = "=VLOOKUP(Sheet1!A" & TargetRow & ",Sheet2!A:B,2,FALSE)"
TargetSheet.Cells(TargetRow, 3) = "=VLOOKUP(Sheet1!A" & TargetRow & ",Sheet2!A:D,3,FALSE) * VLOOKUP(Sheet1!A" & TargetRow & ",Sheet2!A:D,4,FALSE)"
TargetSheet.Cells(TargetRow, 3).NumberFormat = "$#,##0.00"
TargetRow = TargetRow + 2
Next
End Sub
I copy data from a list into another list in different sheet, whenever I copy data it copy the cells which are in the second list already. I need to delete any duplicates in the sheet number 2, though I always end up deleting everything including rows, gridlines and mostly the data on the right of columns. I only use cells from "A13", "B13" and "C13" down. There are data on the right, specifically formulas which are rather important. How can I only apply cleansing of duplicates on that range?
Sub test()
Dim LastRow As Long, i As Long
Dim rng As Range
Set rng = Worksheets("ABCX Acrylics").Range("A13").CurrentRegion
With Worksheets("Sheet1")
LastRow = .Range("A" & .Rows.Count).End(xlUp).Row
For i = 1 To LastRow
If .Cells(i, 6) = "Acrylics" Then
With Worksheets("ABCX Acrylics")
.Cells(.Rows.Count, "A").End(xlUp).Offset(1, 0) = _
Worksheets("Sheet1").Cells(i, 1).Value
.Cells(.Rows.Count, "A").End(xlUp).Offset(0, 1) = _
Worksheets("Sheet1").Cells(i, 8).Value
.Cells(.Rows.Count, "A").End(xlUp).Offset(0, 2) = _
Worksheets("Sheet1").Cells(i, 9).Value
End With
End If
Next i
rng.RemoveDuplicates Columns:=1, Header:=xlYes
End With
End Sub
This sets your rng as the first three columns and also the RemoveDuplicates is an array of the first three columns. If you only include column 1, it removes all duplicates that have the first column match only. Also got rid of your nested End With statements to make it easier to follow.
Sub test()
Dim i As Long
Dim rng As Range
Dim ws1LR As Long
Dim ws2LR As Long
Dim ws1 As Worksheet
Dim ws2 As Worksheet
Set ws1 = ThisWorkbook.Worksheets("ABCX Acrylics")
Set ws2 = ThisWorkbook.Worksheets("Sheet1")
ws1LR = ws1.Cells(Rows.Count, 1).End(xlUp).Row
Set rng = ws1.Range(ws1.Cells(13, 1), ws1.Cells(ws1LR, 3))
ws2LR = ws2.Cells(Rows.Count, 1).End(xlUp).Row
For i = 1 To ws2LR
If ws2.Cells(i, 6) = "Acrylics" Then
ws1.Cells(Rows.Count, "A").End(xlUp).Offset(1, 0) = _
ws2.Cells(i, 1).Value
ws1.Cells(Rows.Count, "A").End(xlUp).Offset(0, 1) = _
ws2.Cells(i, 8).Value
ws1.Cells(Rows.Count, "A").End(xlUp).Offset(0, 2) = _
ws2.Cells(i, 9).Value
End If
Next i
rng.RemoveDuplicates Columns:=Array(1, 2, 3), Header:=xlYes
End With
End Sub
Here we go, this code works for me. However, there is a problem with my gridlines as they are erased. I need to have the same format as it was in "A13" before I applied my code. I think of Scripting.Dictionary to store the format. Any idea? Better approach?
Option Explicit
Sub test()
Dim LastRow As Long, i As Long
Dim rng8 As Range
Set rng8 = Worksheets("ABCX Acrylics").Range("A13:C1370")
With Worksheets("Sheet1")
LastRow = .Range("A" & .Rows.Count).End(xlUp).Row
For i = 1 To LastRow
If .Cells(i, 6) = "Acrylics" Then
With Worksheets("ABCX Acrylics")
.Cells(.Rows.Count, "A").End(xlUp).Offset(1, 0) = _
Worksheets("Sheet1").Cells(i, 1).Value
.Cells(.Rows.Count, "A").End(xlUp).Offset(0, 1) = _
Worksheets("Sheet1").Cells(i, 8).Value
.Cells(.Rows.Count, "A").End(xlUp).Offset(0, 2) = _
Worksheets("Sheet1").Cells(i, 9).Value
rng8.RemoveDuplicates Columns:=Array(1, 2, 3)
End With
End If
Next i
End With
End Sub
I have been trying to come up with/find a VBA code that copies blocks of data under my first block. Each block is 19 columns followed by a blank. The number of rows per block can vary.
See my screenshot below:
Therefore, I would like all my data continuous in the first columns A:S. Any help is highly appreciated.
I found the following code online, but this only pastes everything into the first column
Sub Column()
Dim iLastcol As Long
Dim iLastRow As Long
Dim jLastrow As Long
Dim ColNdx As Long
Dim ws As Worksheet
Dim myRng As Range
Dim ExcludeBlanks As Boolean
Dim mycell As Range
ExcludeBlanks = (MsgBox("Exclude Blanks", vbYesNo) = vbYes)
Set ws = ActiveSheet
iLastcol = ws.Cells(1, ws.Columns.Count).End(xlToLeft).Column
On Error Resume Next
Application.DisplayAlerts = False
Worksheets("Alldata").Delete
Application.DisplayAlerts = True
Sheets.Add.Name = "Alldata"
For ColNdx = 1 To iLastcol
iLastRow = ws.Cells(ws.Rows.Count, ColNdx).End(xlUp).Row
Set myRng = ws.Range(ws.Cells(1, ColNdx), _
ws.Cells(iLastRow, ColNdx))
If ExcludeBlanks Then
For Each mycell In myRng
If mycell.Value <> "" Then
jLastrow = Sheets("Alldata").Cells(Rows.Count, 1) _
.End(xlUp).Row
mycell.Copy
Sheets("Alldata").Cells(jLastrow + 1, 1) _
.PasteSpecial xlPasteValues
End If
Next mycell
Else
myRng.Copy
jLastrow = Sheets("Alldata").Cells(Rows.Count, 1) _
.End(xlUp).Row
mycell.Copy
Sheets("Alldata").Cells(jLastrow + 1, 1) _
.PasteSpecial xlPasteValues
End If
Next
Sheets("Alldata").Rows("1:1").EntireRow.Delete
ws.Activate
End Sub
Basic approach:
Sub Tester()
Dim c As Range, addr
Set c = ActiveSheet.Range("T1")
Do
Set c = c.End(xlToRight)
If c.Column = Columns.Count Then Exit Do
addr = c.Address 'strire the address since Cut will move c
c.CurrentRegion.Cut c.Parent.Cells(Rows.Count, 1).End(xlUp).Offset(1, 0)
Set c = ActiveSheet.Range(addr) '<< reset c
Loop
End Sub
This is a little more basic than #TimWilliams
With ThisWorkbook.Sheets("Alldata")
Dim lRow As Long, lCol As Long, cpyrng As Range
lCol = Cells(1, Columns.Count).End(xlToLeft).Column
For i = 21 To lCol Step 20
If .Cells(1, i).Value <> "" And .Cells(1, i).Offset(, -1).Value = "" Then
lRow = .Cells(.Rows.Count, 1).End(xlUp).Row
Set cpyrng = .Cells(1, i).CurrentRegion
cpyrng.Cut
Sheets("Sheet2").Cells(lRow, 1).Offset(2).Insert Shift:=xlDown
End If
Next i
End With
I'm attempting to match values between two sheets and if found and the conditions are met, perform the action of changing the cell colour.
PROBLEM:
I'm getting an error with my For...Next loop, even though I thought I have a NEXT for each FOR statement. Not sure what I've done wrong.
Also, I'm not sure my counters are setup correctly to accurately scan through each sheet/column needed. Any help would be appreciated.
Sub ReadData()
Dim wb As Workbook
Dim ws As Worksheet
Dim lastrow As Long
Dim i As Long
Set wb = ActiveWorkbook
Set ws = wb.Sheets("Ref1")
Set ws2 = wb.Sheets("TRA")
lastrow = Sheets("Ref1").Cells(Rows.Count, "A").End(xlUp).Row
lastrow2 = Sheets("TRA").Cells(Rows.Count, "A").End(xlUp).Row
Sheets("Ref1").Activate
i = 2
k = 2
For i = 2 To lastrow
For k = 2 To lastrow2
If Cells(i, 4).Value = "Active" Then
If ws.Cells(i, 18).Value = ws2.Cells(i, 1).Value And (ws2.Cells(i, 23).Value <> "Cancelled" Or ws2.Cells(i, 23).Value <> "Completed") Then
Cells(i, 20).Interior.ColorIndex = 9
End If
Next
Next
End Sub
Quick Repair
To better understand the code, it is often preferable to use letters,
instead of numbers, for columns.
The Code
Sub ReadData()
Dim wb As Workbook
Dim ws As Worksheet
Dim ws2 As Worksheet
Dim lastrow As Long
Dim lastrow2 As Long
Dim i As Long
Dim k As Long
' Use ThisWorkbook instead of ActiveWorkbook, if the code is
' in the same workbook where these sheets are.
With ActiveWorkbook
Set ws = .Worksheets("Ref1")
Set ws2 = .Worksheets("TRA")
End With
lastrow = ws.Cells(ws.Rows.Count, "A").End(xlUp).Row
lastrow2 = ws2.Cells(ws2.Rows.Count, "A").End(xlUp).Row
For i = 2 To lastrow
If ws.Cells(i, "D").Value = "Active" Then
For k = 2 To lastrow2
If ws.Cells(i, "R").Value = ws2.Cells(k, "A").Value _
And ws2.Cells(k, "W").Value <> "Cancelled" _
And ws2.Cells(k, "W").Value <> "Completed" Then
ws.Cells(i, "T").Interior.ColorIndex = 9 ' Brown
Exit For
End If
Next
End If
Next
End Sub
I'm new here and to the world of VBA.
I've been asked to create a spreadsheet which a user can enter a task number of a job they have been given, from this auto populate other columns with data from a master list, and then when they change the status of the job the row changes colour.
I know that this is easy to do using the vlookup function as a formula in each cell and conditional formatting.
I have searched the web and have found differnt macros that can do the job, but some of them take far to long to find a match, the master task list can contain upto 3000 items.
So I've very crudely cobbled together something that works and quickly.
I was wondering how I can clean these macros up as I'm sure the way I've done it will cause problems in the future or if there is a better way to do the job.
Also as a secoundry question is there any way to get a message box to pop up if the number the user enters doesn't match anything on the master list.
Private Sub Worksheet_Change(ByVal Target As Range)
Dim KeyCells As Range
Dim Status As Range
Set KeyCells = Range("B1:B700") 'Range Looking for Change
Set Status = Range("K1:K700")
Application.ScreenUpdating = False
If Not Application.Intersect(KeyCells, Range(Target.Address)) _
Is Nothing Then
Dim SourceLastRow As Long
Dim OutputLastRow As Long
Dim sourceSheet As Worksheet
Dim outputSheet As Worksheet
Set sourceSheet = Worksheets("Task List")
Set outputSheet = Worksheets("2016")
'Determine last row of Task List
With sourceSheet
SourceLastRow = .Cells(.Rows.Count, "A").End(xlUp).Row
End With
With outputSheet
'Determine last row in col B
OutputLastRow = .Cells(.Rows.Count, "B").End(xlUp).Row
'Put formula in relevant cell
.Range("F2:F" & OutputLastRow).Formula = _
"=VLOOKUP(B2,'Task List'!$A$2:$D$" & SourceLastRow & ",2,0)"
End With
With outputSheet
OutputLastRow = .Cells(.Rows.Count, "B").End(xlUp).Row
.Range("H2:H" & OutputLastRow).Formula = _
"=VLOOKUP(B2,'Task List'!$A$2:$D$" & SourceLastRow & ",3,0)"
End With
With outputSheet
OutputLastRow = .Cells(.Rows.Count, "B").End(xlUp).Row
.Range("G2:G" & OutputLastRow).Formula = _
"=VLOOKUP(B2,'Task List'!$A$2:$D$" & SourceLastRow & ",4,0)"
End With
End If
If Not Application.Intersect(Status, Range(Target.Address)) _
Is Nothing Then
Dim n As Long
iLastRow = Cells(Rows.Count, "B").End(xlUp).Row
For n = iLastRow To 1 Step -1
If Cells(n, "K").Value = "Closed" Then
Rows(n).Interior.Color = RGB(57, 255, 20)
End If
If Cells(n, "K").Value = "Bond" Then
Rows(n).Interior.Color = RGB(249, 255, 1)
End If
If Cells(n, "K").Value = "Eng Bond" Then
Rows(n).Interior.Color = RGB(255, 102, 0)
End If
If Cells(n, "K").Value = "Fail" Then
Rows(n).Interior.Color = RGB(255, 1, 1)
End If
If Cells(n, "K").Value = "" Then
Rows(n).Interior.ColorIndex = 2
End If
Next n
End If
Application.ScreenUpdating = True
End Sub