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
Related
I need to fill down a value in Sheet 1 Cell A2 with =Sheet 2 Cell A2 until the linked value is blank. I dont really know what to do.
I got so far so that I only need zeros in the fields:
Sub Test1()
Dim x As Integer
Dim i As Integer
Dim wsh As Worksheet
Set wsh = Worksheets("List with Weights")
Application.ScreenUpdating = False
i = 2
While (wsh.Cells(i, 1)) <> ""
wsh.Cells(i, 1).FormulaR1C1 = "='Sample Weight'!RC[0]"
wsh.Cells(i, 2).FormulaR1C1 = "='Sample Weight'!RC[0]"
wsh.Cells(i, 3).FormulaR1C1 = "='IS Weight'!RC[-1]"
i = i + 1
Wend
' Selects cell down 1 row from active cell.
ActiveCell.Offset(1, 0).Select
Columns("A:C").Select
Columns("A:C").EntireColumn.AutoFit
Application.ScreenUpdating = True
Range("A1").Select
End Sub
If I have understood you correctly, there is no need for a loop. Is this what you are trying?
Option Explicit
Sub Sample()
Dim wsThis As Worksheet
Dim wsThat As Worksheet
Dim wsOther As Worksheet
'~~> Set your relevant worksheets
Set wsThis = ThisWorkbook.Sheets("List with Weights")
Set wsThat = ThisWorkbook.Sheets("Sample Weight")
Set wsOther = ThisWorkbook.Sheets("IS Weight")
'~~> Find the last row in Col A of Sample Weight worksheet
Dim wsThatLRow As Long
wsThatLRow = wsThat.Range("A" & wsThat.Rows.Count).End(xlUp).Row
'~~> Insert the formula in 1 go in the relevant range
With wsThis
.Range("A2:A" & wsThatLRow).Formula = "='" & wsThat.Name & "'!A2"
.Range("B2:B" & wsThatLRow).Formula = "='" & wsThat.Name & "'!B2"
.Range("C2:C" & wsThatLRow).Formula = "='" & wsOther.Name & "'!B2"
End With
End Sub
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
I'm writing a code to look for a specific keyword ("Team") and when found I want to paste the team name in a specific column ("D") for all rows above. If the keyword is not found I want to copy the entire row. This all pasted into a new sheet.
What I have:
x-------------x------------x
x-------------x------------x
Team A----x------------x
x-------------x-------------x
x-------------x-------------x
Team B----x-------------x
What I want:
x----x----x----A
x----x----x----A
x----x----x----B
x----x----x----B
Here's what I have so far:
Sub fun()
Dim j as Integer
Dim lastrow as Integer
Dim team as String
Dim sh As Worksheet
sh = Sheets("Sheet 1")
lastrow = ActiveSheet.Cells(Rows.Count, 1).End(xlup).Row
Range("A" & lastrow).Select
for j = 1 to lastrow
If Instr(Cells(j,1).Value, "Team") Then
Cells(j,1).Value = Replace(Cells(j,1).Value, "Team ", "")
Cells(j,1).Value = team
Else
Range(Cells(j,1), Cells(j,3). Select
Selection.Copy
Windows("sheet.xlsm").Activate
ActiveSheet.Cells(1,1).Select
Selection.PasteSpecial Paste:=xlPasteValues, Transpose:=False
End If
next j
End Sub
I'm able to meet the second condition and paste entire rows but I'm unsure how to copy the team names and post them in column D in the new sheet.
Something like this:
Sub fun()
Dim j As Long, destRow As Long
Dim team As String, v, rngTeam As Range
Dim sh As Worksheet, shDest As Worksheet
Set sh = Sheets("Sheet1")
Set shDest = Sheets("Sheet2") 'for example
destRow = shDest.Cells(Rows.Count, 1).End(xlUp).Row + 1
For j = 1 To sh.Cells(Rows.Count, 1).End(xlUp).Row
v = sh.Cells(j, 1).Value
If InStr(v, "Team") > 0 Then
If Not rngTeam Is Nothing Then rngTeam.Value = Replace(v, "Team ", "") '<< set for already-copied rows
Set rngTeam = Nothing 'reset the range
Else
shDest.Cells(destRow, 1).Resize(1, 3).Value = sh.Cells(j, 1).Resize(1, 3).Value
'add to the range to populate next time we hit a "Team"
If rngTeam Is Nothing Then
Set rngTeam = shDest.Cells(destRow, 4)
Else
Set rngTeam = Application.Union(shDest.Cells(destRow, 4), rngTeam)
End If
destRow = destRow + 1
End If
Next j
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
I have tried to make a button which searches through a selection of data on one sheet for a ID number then returns the corresponding data in the row after onto a different sheet. I thought i had it sorted but this just wont work and have run out ideas.
Any help would be appreciated.
see code below:
Private Sub CommandButton2_Click()
Dim Userentry As String
Dim DataRange As Range
Dim i As Long
Dim location As Integer
Dim ws, ws1 As Worksheet
Set ws = Sheets("Sheet1")
Set ws1 = Sheets("Sheet4")
With TextBox2
Userentry = .Value
End With
Range("A36").Value = Userentry
For i = 2 To ws.Range("A" & Rows.Count).End(xlUp).Row
If LCase(Cells(i, 1).Value) = Userentry Then ws1.Range("F" & Rows.Count).End(xlUp).Offset(1, 0).Value = Cells(i, 2).Value
Next i
For i = 2 To ws.Range("A" & Rows.Count).End(xlUp).Row
If LCase(Cells(i, 1).Value) = Userentry Then ws1.Range("G" & Rows.Count).End(xlUp).Offset(1, 0).Value = Cells(i, 3).Value
Next i
For i = 2 To ws.Range("A" & Rows.Count).End(xlUp).Row
If LCase(Cells(i, 1).Value) = Userentry Then ws1.Range("H" & Rows.Count).End(xlUp).Offset(1, 0).Value = Cells(i, 4).Value
Next i
For i = 2 To ws.Range("A" & Rows.Count).End(xlUp).Row
If LCase(Cells(i, 1).Value) = Userentry Then ws1.Range("I" & Rows.Count).End(xlUp).Offset(1, 0).Value = Cells(i, 5).Value
Next i
End Sub
I'd throw in two possible solutions, with the goal to minimize execution time (should it be an issue)
solution 1
here you're still actually looping through column A cells but:
only if there's at least one matching value
considering non empty cells with text values only
`
Option Explicit
Private Sub CommandButton2_Click()
Dim Userentry As String, firstAddr As String
Dim ws1 As Worksheet
Dim f As Range
Set ws1 = Sheets("Sheet4")
Userentry = TextBox2.Value
ws1.Range("A36").Value = Userentry
With Sheets("Sheet1")
With .Range("A1:A" & .Cells(.Rows.Count, 1).End(xlUp).Row).SpecialCells(xlCellTypeConstants, xlTextValues)'<~~ consider column "A" cells with text values down to the LAST non empty one
Set f = .Find(What:=Userentry, LookAt:=xlWhole, LookIn:=xlValues)
If Not f Is Nothing Then '<~~ loop only if there's at least one matching value
firstAddr = f.Address
Do
ws1.Range("F" & Rows.Count).End(xlUp).Offset(1, 0).Resize(1, 4).Value = _
f.Offset(, 1).Resize(1, 4).Value
Set f = .FindNext(f)
Loop While f.Address <> firstAddr
End If
End With
End With
End Sub
`
solution 2
this avoids looping at all, but at the "expense" of sorting rows
Option Explicit
Private Sub CommandButton2_Click()
Dim Userentry As String
Dim countVal As Long
Dim ws1 As Worksheet
Set ws1 = Sheets("Sheet4")
Userentry = TextBox2.Value
ws1.Range("A36").Value = Userentry
With Sheets("Sheet1")
With .Range("A1:A" & .Cells(.Rows.Count, 1).End(xlUp).Row) '<~~ consider column "A" cells down to the LAST non empty one
countVal = Application.WorksheetFunction.CountIf(.Cells, Userentry) '<~~ count matching values
If countVal > 0 Then '<~~ if any then ...
.Resize(, 5).Sort key1:=.Range("A1"), order1:=xlDescending, Header:=xlNo '<~~ ... sort columns "A" to "E" by column "A" values...
ws1.Range("F" & Rows.Count).End(xlUp).Offset(1, 0).Resize(countVal, 4).Value = .Find(What:=Userentry, LookAt:=xlWhole, LookIn:=xlValues).Offset(, 1).Resize(countVal, 4).Value '<~~ ... and copy/paste values
End If
End With
End With
End Sub
if you should ever mind having Sheet1 rows sorted, then here's the "patch"
Private Sub CommandButton2_Click()
Dim Userentry As String
Dim countVal As Long
Dim ws1 As Worksheet
Dim helperCol As Range
Set ws1 = Sheets("Sheet4")
Userentry = TextBox2.Value
ws1.Range("A36").Value = Userentry
With Sheets("Sheet1")
Set helperCol = .UsedRange.Columns(.UsedRange.Columns.Count + 1) '<~~ set a helper column "out of the town" not to interfere with data already there
With .Range("A1:A" & .Cells(.Rows.Count, 1).End(xlUp).Row) '<~~ consider column "A" cells down to the LAST non empty one
countVal = Application.WorksheetFunction.CountIf(.Cells, Userentry) '<~~ count matching values
If countVal > 0 Then '<~~ if any then ...
With Intersect(.Rows.EntireRow, helperCol) '<~~ consider helper column rows corresponding to your data ones
.Formula = "=ROW()" '<~~ place an ascending index to every row
.Value = .Value '<~~ get rid of formulas, otherwise subsequent sorting would have no effect on their result
End With
.Resize(, helperCol.Column).Sort key1:=.Range("A1"), order1:=xlDescending, Header:=xlNo '<~~ ... sort columns "A" to "helpercol" rows by column "A" values...
ws1.Range("F" & Rows.Count).End(xlUp).Offset(1, 0).Resize(countVal, 4).Value = .Find(What:=Userentry, LookAt:=xlWhole, LookIn:=xlValues).Offset(, 1).Resize(countVal, 4).Value '<~~ ... copy/paste values ...
.Resize(, helperCol.Column).Sort key1:=helperCol, order1:=xlAscending, Header:=xlNo '<~~ ... and sort columns "A" to "helpercol" rows back by "helpercol" values
helperCol.Clear '<~~ finally clear "helpercol" column
End If
End With
End With
End Sub
Private Sub CommandButton2_Click()
Dim Userentry As String
Dim i As Long
Dim ws, ws1 As Worksheet
Set ws = Sheets("Sheet1")
Set ws1 = Sheets("Sheet4")
Userentry = TextBox2.Value
ws1.Range("A36").Value = Userentry
For i = 2 To ws.Range("A" & Rows.Count).End(xlUp).Row
If LCase(ws.Cells(i, 1).Value) = Userentry Then
ws1.Range("F" & Rows.Count).End(xlUp).Offset(1, 0).Resize(1, 4).Value = _
ws.Cells(i, 2).resize(1, 4).Value
End If
Next i
End Sub