I've been asked to create a macro that compare two numbers in two cells and then it should write a third column that says for example: L6 is less than M6 (any image of a down arrow)
I tried to record this macro:
Sub Macro20()
Range("N2").Select
ActiveCell.FormulaR1C1 = _
"=IF(RC[-2]=RC[-1],""L and M are equal"",IF(RC[-2]>RC[-1],""L is greater than M (UP ARROW) "",""L is less than M (DOWN ARROW)""))"
Range("N2").Select
Selection.AutoFill Destination:=Range("N2:N" & Range("L" & Rows.Count).End(xlUp).Row)
Range(Selection, Selection.End(xlDown)).Select
End Sub
and this is the output:
This is just an example, the whole code should be used to a large amount of data soon, anyway there are some errors must be avoided.
The code into the cell must not be shown (see the blue arrow into the picture), it should display only the value.
How can I fetch an arrow image instead of the string: L is greater than M (UP ARROW)?
Can you help me in doing a better code than this?
Here is a simple solution which enters the formula in the entire range without looping.
Option Explicit
Sub Sample()
Dim ws As Worksheet
Dim lRow As Long
'~~> Change this to the relevant sheet
Set ws = Sheet1
With ws
'~~> Find last row in column L
lRow = .Range("L" & .Rows.Count).End(xlUp).Row
'~~> Insert the formula in Col N. Change as applicable
With .Range("N1:N" & lRow)
.Formula = "=IF(L1=M1,""L and M are equal"",IF(L1>M1,""L is greater than M " & _
ChrW(&H2191) & _
""", ""L is less than M " & _
ChrW(&H2193) & _
"""))"
'~~> Optional - Convert formula to values
.Value = .Value
End With
End With
End Sub
Screenshot
Note:
To insert Up arrow, you can use ChrW(&H2191) and for down arrow you can use ChrW(&H2193)
If you want to put the formula from the 2nd row then it will be
'~~> Insert the formula in Col N. Change as applicable
With .Range("N2:N" & lRow)
.Formula = "=IF(L2=M2,""L and M are equal"",IF(L2>M2,""L is greater than M " & _
ChrW(&H2191) & _
""", ""L is less than M " & _
ChrW(&H2193) & _
"""))"
'~~> Optional - Convert formula to values
.Value = .Value
End With
Similarly for a different row, you will have to adjust accordingly.
EDIT
do you think is possible to use a arrow text already formatted? For example a red one (or whatever color) with a specific size? And then put this inside your vba code? – Alex D. 4 hours ago
Yes it is possible. In this case you can use Worksheet_Change event to handle changes in column L and column M to populate column N
I have commented the code below. If you still have problems understanding it then feel free to ask. The below code goes in the sheet code area. You can change the symbol attributes (Style, Color and Size) right at the top of the code.
Code
Option Explicit
'~~> Change the symbol attributes here
Const Font_Style As String = "Bold"
Const Font_Size As Long = 15
Const Font_Color As Long = -16776961 '(Red)
Private Sub Worksheet_Change(ByVal Target As Range)
On Error GoTo Whoa
Application.EnableEvents = False
Dim r As Variant
'~~> Check if the change happened in the relevant column
If Not Intersect(Target, Me.Range("L:M")) Is Nothing Then
For Each r In Target.Rows
'~~> If even one cell is empty then clear out N cell
If Len(Trim(Range("L" & r.Row).Value2)) = 0 Or _
Len(Trim(Range("M" & r.Row).Value2)) = 0 Then
Range("N" & r.Row).ClearContents
'~~> Check if L = M
ElseIf Range("L" & r.Row) = Range("M" & r.Row) Then
Range("N" & r.Row).Value = "L and M are equal"
'~~> Check if L > M
ElseIf Range("L" & r.Row) > Range("M" & r.Row) Then
With Range("N" & r.Row)
.Value = "L is greater than M " & ChrW(&H2191)
'~~> Format the symbol which is at 21st position
With .Characters(Start:=21, Length:=1).Font
.FontStyle = Font_Style
.Size = Font_Size
.Color = Font_Color
End With
End With
'~~> L < M
Else
With Range("N" & r.Row)
.Value = "L is less than M " & ChrW(&H2193)
'~~> Format the symbol which is at 18th position
With .Characters(Start:=18, Length:=1).Font
.FontStyle = Font_Style
.Size = Font_Size
.Color = Font_Color
End With
End With
End If
Next r
End If
Letscontinue:
Application.EnableEvents = True
Exit Sub
Whoa:
MsgBox Err.Description
Resume Letscontinue
End Sub
In action
Here is an alternative:
Sub alex()
Dim i As Long, LastRow As Long
Dim L, M, txt As String
LastRow = Cells(Rows.Count, "L").End(xlUp).Row
For i = 2 To LastRow
L = Cells(i, "L").Value
M = Cells(i, "M").Value
If L = M Then
txt = "they are equal"
ElseIf L > M Then
txt = "L is greater"
Else
txt = "M is greater"
End If
Cells(i, "N") = txt
Next i
End Sub
You can speed this up a little by bring all the column L and M data into VBA arrays and doing the comparisons within VBA.
To get arrows rather than text, use:
Sub alex()
Dim i As Long, LastRow As Long
Dim L, M, txt As String
LastRow = Cells(Rows.Count, "L").End(xlUp).Row
For i = 2 To LastRow
L = Cells(i, "L").Value
M = Cells(i, "M").Value
If L = M Then
txt = "n"
ElseIf L > M Then
txt = "h"
Else
txt = "i"
End If
Cells(i, "N") = txt
Next i
End Sub
and format the results cells in column N to use the Wingdings 3 font
Related
Basically I just found this little code on the web, I thought it might help me because I want to improve it. But on the
Do Until Range("A" & amp, R) = ""
line I got the mentioned error in the title.
Here is the code:
Sub Use_Instr()
R = 1
'loop to the last row
Do Until Range("A" & amp, R) = ""
'check each cell if contains 'apple' then..
'..place 'Contains Apple' on column B
If Range("A" & amp, R) Like "*apple*" Then
Range("B" & amp, R) = "Contains Apple"
End If
R = R + 1
Loop
End Sub
It does search the "apple" term in the sentences in A column and write "contains apple" in the B
column if it contains "apple"
Try this instead:
R = 1
'loop to the last row
Do Until Range("A" & R).Value = ""
'check each cell if contains 'apple' then..
'..place 'Contains Apple' on column B
If Range("A" & R).Value Like "*apple*" Then
Range("B" & R).Value = "Contains Apple"
End If
R = R + 1
Loop
Range("A" & amp, R) is not the right way to address the range. When you copied from the website, it copied the html encoding as well. In Html & is encoded as &. Simply replace & amp, with & in your code. So your code becomes
R = 1
'loop to the last row
Do Until Range("A" & R) = ""
'check each cell if contains 'apple' then..
'..place 'Contains Apple' on column B
If Range("A" & R) Like "*apple*" Then
Range("B" & R) = "Contains Apple"
End If
R = R + 1
Loop
Also to make the code case insensitive, as suggested by #VBasic2008 in comments below, you may want to change Range("A" & R) Like "*apple*" to If LCase(Range("A" & R).Value2) Like "*apple*" Then.
Having said that, I would use a different approach than using a loop which is a slightly slower approach.
It does search the "apple" term in the sentences in A column and write "contains apple" in the B column if it contains "apple"
If you were to do this in Excel, then you would use the formula =IF(ISNUMBER(SEARCH("apple",A1)),"Contains Apple","")
So what we will do is find the last row in Column B and then add this formula in the entire range in one go! Finally we will convert the formula to values.
Option Explicit
Sub Sample()
Dim ws As Worksheet
Dim lRow As Long
Dim sFormula As String
'~~> Change this to the relevant sheet
Set ws = Sheet1
'~~> This is your formula
' =IF(ISNUMBER(SEARCH("apple",A1)),"Contains Apple","")
sFormula = "=IF(ISNUMBER(SEARCH(""apple"",A1)),""Contains Apple"","""")"
With ws
'~~> Find the last row in column B
lRow = .Range("A" & .Rows.Count).End(xlUp).Row
'~~> Insert the formula in the entire range in 1 go
With .Range("B1:B" & lRow)
.Formula = sFormula
'~~> Convert formula to value
.Value = .Value
End With
End With
End Sub
I wrote this code over the weekend and it was blazing fast, when I got to work on Monday I sent an email before testing the code while servers had a load and it is infinitely slower. We are talking from 30 seconds to 15 minutes.
For x = 3 To SRLastRow
If Left(shMacro.Range("D" & x), 3) = "625" Then
shMacro.Range("BW" & x) = WorksheetFunction.XLookup(Arg1:=shMacro.Range("A" & x), Arg2:=WIPFile.Worksheets("Customer Master").Range("B:B"), Arg3:=WIPFile.Worksheets("Customer Master").Range("AD:AD"))
Else
shMacro.Range("BW" & x) = WorksheetFunction.XLookup(Arg1:=shMacro.Range("A" & x), Arg2:=WIPFile.Worksheets("Customer Master").Range("B:B"), Arg3:=WIPFile.Worksheets("Customer Master").Range("AH:AH"))
End If
If shMacro.Range("BW" & x) <> shMacro.Range("BX" & x) Then
shMacro.Range("BX" & x).Interior.ColorIndex = 3
ErrorCount = ErrorCount + 1
End If
Next x
I essentially need to pull in a value, then compare that pulled in value with a value on my Macro worksheet. If the values match do nothing, otherwise color the cell red.
I came up with the following, but haven't tested it fully yet, but the problem remains having to cycle through to find mismatches to color them.
shMacro.Range("BW3").Formula = "=IF(LEFT(D3,3)=""625"",XLOOKUP(TEXT(A3,""000""),'[WORKBOOK]Customer Master'!$AD:$AD),XLOOKUP(TEXT(A3,""000""),'[WORKBOOK]Customer Master'!$B:$B,'[WORKBOOK]Customer Master'!$AH:$AH))"
Range("BW3").AutoFill Destination:=Range("BW3:BW" & SRLastRow)
I've also tried looking into Arrays but I can't seem to figure those out. I think the autofill would be the fastest way to pull in the data, then somehow assign the two arrays (which would be columns BW and BX) and if they match do nothing, wherever they are different color BX + row reference red, and count the number of times it colored something red.
Should be a little faster (reducing cell reads without going "full array mode" and coloring all mismatches in one shot)
Sub Tester()
Dim x As Long, SRLastRow As Long
Dim colReturn As String, v, rngRed As Range, rw As Range
'...
'...
'...
Application.ScreenUpdating = False
For x = 3 To SRLastRow
Set rw = shMacro.Rows(x)
colReturn = IIf(Left(rw.Columns("D").Value, 3) = "625", "AD:AD", "AH:AH")
v = Application.XLookup( _
Arg1:=rw.Columns("A").Value, _
Arg2:=WIPFile.Worksheets("Customer Master").Range("B:B"), _
Arg3:=WIPFile.Worksheets("Customer Master").Range(colReturn))
If Not IsError(v) Then
rw.Columns("BW").Value = v
With rw.Columns("BX")
If v <> .Value Then
BuildRange rngRed, .Cells(1)
ErrorCount = ErrorCount + 1
End If
End With
End If
Next x
'color the mismatches if any
If Not rngRed Is Nothing Then rngRed.Interior.ColorIndex = 3
End Sub
'utility - build a range using Union
Sub BuildRange(ByRef rngTot As Range, rngAdd As Range)
If rngTot Is Nothing Then
Set rngTot = rngAdd
Else
Set rngTot = Application.Union(rngTot, rngAdd)
End If
End Sub
I think the reason for the slow processing is in the location of the WIPFile workbook. Therefore my code below minimizes the need to access it. I couldn't test my code for lack of data but I hope you will try it.
Sub Snippet()
' 214
Dim LookUpRng As Range ' in "Customer Master"
Dim ReturnVals As Variant ' values from "Customer Master"
Dim C As Long ' Lookup column in ReturnRng
Dim Fnd As Range ' search result
Dim x As Long ' loop counter: rows (why "x" and not "R" ?)
Dim ErrorCount As Variant
Dim Spike As String ' collect failed lookups
ErrorCount = 0
With WIPFile.Worksheets("Customer Master")
Set LookUpRng = .Columns("BB")
ReturnVals = .Range(.Columns("AD"), .Columns("AH")).Value
End With
With shMacro
For x = 3 To .Cells(.Rows.Count, "A").End(xlUp).Row
Set Fnd = LookUpRng.Find(.Cells(x, "A").Value, LookIn:=xlValues, LookAt:=xlWhole)
If Fnd Is Nothing Then
If Len(Spike) Then Spike = Spike & vbCr
Spike = Spike & String(6, " ") & """" & .Cells(x, 1).Value & """ in row " & x
Else
C = IIf(Left(.Cells(x, "D").Value, 3) = "625", 1, 5)
With .Cells(x, "BW")
.Value = ReturnVals(Fnd.Row, C)
If .Value <> .Cells(x, "BX").Value Then
.Interior.ColorIndex = 3
ErrorCount = ErrorCount + 1
End If
End With
End If
Next x
End With
If Len(Spike) Then
Spike = "The following look-ups were not successful." & vbCr & _
Spike & IIf(ErrorCount, vbCr, "")
Else
Spike = "All look-ups were successful."
End If
If ErrorCount = 0 Then ErrorCount = "No"
Spike = Spike & vbCr & ErrorCount & " matching error" & _
IIf(ErrorCount = 1, "", "s") & " were highlighted."
MsgBox Spike, vbInformation, "Action report"
End Sub
If my approach shows promise more speed could be gained by reading column B:B into another array and use a MATCH function instead of Find. In that way "Customer Master" would need to be accessed only once. Of course, you could also gain a little time by suspending ScreenUpdating during execution.
No VBA required. Use Formula + Conditional formatting
Entering the formula
Put this formula in BW3 and copy it down. Change SAMPLE.xlsx to the relevant file.
=IF(LEFT(D3,3)=625,XLOOKUP(A3,'[SAMPLE.xlsx]Customer Master'!$B:$B,'[SAMPLE.xlsx]Customer Master'!$AD:$AD),XLOOKUP(A3,'[SAMPLE.xlsx]Customer Master'!$B:$B,'[SAMPLE.xlsx]Customer Master'!$AH:$AH))
Setting up Conditional Formatting
Select the relevant range, starting for row 3.
Click on Home | Conditional formatting | New Rule | Use formula to determine which cells to format
Enter the formula =BW3<>BX3 and set the relevant color.
And you are done.
If you still want VBA then also you do not need any kind of loop or Autofill. You can enter the formula via VBA in all the cells in 1 GO! Here is an example (UNTESTED)
With shMacro
lrow = .Range("D" & .Rows.Count).End(xlUp).Row
.Range("BW3:BW" & lrow).Formula = "=IF(LEFT(D3,3)=625,XLOOKUP(A3,'[SAMPLE.xlsx]Customer Master'!$B:$B,'[SAMPLE.xlsx]Customer Master'!$AD:$AD),XLOOKUP(A3,'[SAMPLE.xlsx]Customer Master'!$B:$B,'[SAMPLE.xlsx]Customer Master'!$AH:$AH))"
End With
For conditional formatting you can use this code
With shMacro.Range("BW3:BW" & lrow)
.FormatConditions.Add Type:=xlExpression, Formula1:="=BW3<>BX3"
.FormatConditions(.FormatConditions.Count).SetFirstPriority
With .FormatConditions(1).Interior
.PatternColorIndex = xlAutomatic
.Color = 255
.TintAndShade = 0
End With
.FormatConditions(1).StopIfTrue = False
End With
So basically your entire code can be written as
With shMacro
lrow = .Range("D" & .Rows.Count).End(xlUp).Row
With .Range("BW3:BW" & lrow)
.Formula = "=IF(LEFT(D3,3)=625,XLOOKUP(A3,'[SAMPLE.xlsx]Customer Master'!$B:$B,'[SAMPLE.xlsx]Customer Master'!$AD:$AD),XLOOKUP(A3,'[SAMPLE.xlsx]Customer Master'!$B:$B,'[SAMPLE.xlsx]Customer Master'!$AH:$AH))"
DoEvents
.FormatConditions.Add Type:=xlExpression, Formula1:="=BW3<>BX3"
.FormatConditions(.FormatConditions.Count).SetFirstPriority
With .FormatConditions(1).Interior
.PatternColorIndex = xlAutomatic
.Color = 255
.TintAndShade = 0
End With
.FormatConditions(1).StopIfTrue = False
End With
End With
long time no see. I am dealing with a little task, that somehow I cannot wrap my head around. I have a huge excel sheet (around 4000 rows) which is being split and sent out to people - they mark yellow or red cells from K column to T column in the specific row and send it back every week, until the range K to T in those 4000 rows that has "X" value (meaning sent out) are marked either yellow or red (received back or not received). The excel sheet has a unique value in column J (so I am using MATCH). So by using this column J, I am going through each line in Data (Master sheet) and checking if this is found in Input sheet (something that has been returned from users), if it is found I go and copy the color of their marking to the original Data sheet. This works great as a charm for those yellow and red colors, the sub itself runs fast - just wondering if there are no errors (last time I did some macros were 3 years ago).
The problem - if the cell is empty, it is being pasted as WHITE back to the Data sheet and the original grid of the excel is gone (hard to read). Can anyone point me into the right direction? Thank you!
Sub test4()
Application.ScreenUpdating = False
Set dat = Sheets("Data")
n = dat.Range("J" & Rows.Count).End(xlUp).Row
Dim test As Long
For i = 2 To n
inputrow = 0
On Error Resume Next
inputrow = Application.WorksheetFunction.Match(Worksheets("Data").Range("J" & i).Value, Sheets("Input").Range("J:J"), 0)
On Error GoTo 0
If inputrow > 0 Then
o = dat.Range("A" & Rows.Count).End(xlUp).Row + 1
dat.Range("K" & i).Interior.Color = Sheets("Input").Range("K" & inputrow).DisplayFormat.Interior.Color
dat.Range("L" & i).Interior.Color = Sheets("Input").Range("L" & inputrow).DisplayFormat.Interior.Color
dat.Range("M" & i).Interior.Color = Sheets("Input").Range("M" & inputrow).DisplayFormat.Interior.Color
dat.Range("N" & i).Interior.Color = Sheets("Input").Range("N" & inputrow).DisplayFormat.Interior.Color
dat.Range("O" & i).Interior.Color = Sheets("Input").Range("O" & inputrow).DisplayFormat.Interior.Color
dat.Range("P" & i).Interior.Color = Sheets("Input").Range("P" & inputrow).DisplayFormat.Interior.Color
dat.Range("Q" & i).Interior.Color = Sheets("Input").Range("Q" & inputrow).DisplayFormat.Interior.Color
dat.Range("R" & i).Interior.Color = Sheets("Input").Range("R" & inputrow).DisplayFormat.Interior.Color
dat.Range("S" & i).Interior.Color = Sheets("Input").Range("S" & inputrow).DisplayFormat.Interior.Color
dat.Range("T" & i).Interior.Color = Sheets("Input").Range("T" & inputrow).DisplayFormat.Interior.Color
End If
Next i
End Sub
DisplayFormat.Interior.ColorIndex = xlNone will be True if the cell has not been colored. Unless you're working with Conditional Formatting you don't need the DisplayFormat
Sub test4()
Dim test As Long, inputrow, dat As Worksheet, wsInput As Worksheet
Dim n As Long, i As Long, c As Long, o
Application.ScreenUpdating = False
Set wsInput = Sheets("Input")
Set dat = Sheets("Data")
n = dat.Range("J" & Rows.Count).End(xlUp).Row
For i = 2 To n
inputrow = Application.Match(dat.Range("J" & i).Value, wsInput.Range("J:J"), 0)
If Not IsError(inputrow) Then 'check for match
o = dat.Range("A" & Rows.Count).End(xlUp).Row + 1
'loop over columns
For c = 11 To 20
With wsInput.Rows(inputrow).Cells(c)
'copy color if cell is not default color
If .Interior.ColorIndex <> xlNone Then
dat.Cells(i, c).Interior.Color = .Interior.Color
End If
End With
Next c
End If 'got match
Next i
End Sub
There is a list of ID's with their chosen subjects in their respective row. I am trying to write code that will read through the subjects, and ensure that any two out of a selected four of the subjects are chosen (out of 15 subjects), and if it isn't be reported back as an error. The subjects needed are either SBC130, SBC150, SBC210 or SBC220, and any combination of the 2 are good out of a range of 15 possible subjects.
This is the code I have so far
Dim programme, module, ID As String
Dim rng As Range
Dim a, b, c, d As Variant
lastidno = Range("A2", Range("A2").End(xlDown)).Count
For i = 2 To lastidno
Sheets("Part B + C Modules").Activate
Set rng = Range("C" & i, Range("C" & i).End(xlToRight))
For j = 1 To 4
Set a = Range("C" & j, Range("C" & j).End(xlToRight)).Find("SBC130", LookIn:=xlValues, lookat:=xlWhole)
Set b = Range("C" & j, Range("C" & j).End(xlToRight)).Find("SBC150", LookIn:=xlValues, lookat:=xlWhole)
Set c = Range("C" & j, Range("C" & j).End(xlToRight)).Find("SBC210", LookIn:=xlValues, lookat:=xlWhole)
Set d = Range("C" & j, Range("C" & j).End(xlToRight)).Find("SBC220", LookIn:=xlValues, lookat:=xlWhole)
If a Is Nothing And b Is Nothing Then
Sheets("Available sub").Activate
Range("F" & i) = "Incorrect 1"
ElseIf a Is Nothing And c Is Nothing Then
Sheets("Available sub").Activate
Range("F" & i) = "Incorrect 2"
ElseIf a Is Nothing And d Is Nothing Then
Sheets("Available sub").Activate
Range("F" & i) = "Incorrect 3"
ElseIf b Is Nothing And c Is Nothing Then
Sheets("Available sub").Activate
Range("F" & i) = "Incorrect 4"
ElseIf b Is Nothing And d Is Nothing Then
Sheets("Available sub").Activate
Range("F" & i) = "Incorrect 5"
ElseIf c Is Nothing And d Is Nothing Then
Sheets("Available sub").Activate
Range("F" & i) = "Incorrect 6"
End If
Next
Next
Please share your thoughts on what the relevant steps are to complete this! Thanks in advance!
Here's a generic function that will check a range against a list of values and determine if the quantity of unique values from the provided list is greater than or equal to a desired threshold:
Function CheckUnqValueQty(ByVal arg_rData As Range, ByVal arg_lThreshold As Long, ByVal arg_aValues As Variant) As Boolean
'This gets the number of unique values listed in arg_aValues found in the arg_rData range
Dim lEvalResult As Long
On Error Resume Next 'Suppress errors if any of the arguments were supplied incorrectly or if any of the data cells contain error values
lEvalResult = Evaluate("SUMPRODUCT(--(COUNTIF(" & arg_rData.Address(External:=True) & ",{""" & Join(arg_aValues, """,""") & """})>0))")
On Error GoTo 0 'Remove the "On Error Resume Next" condition (no longer suppress errors); if there was an error, lEvalResult will be 0
'If the eval result is >= the threshold then return True, else False
CheckUnqValueQty = (lEvalResult >= arg_lThreshold)
End Function
And then you'd call that function from within your loop, like so:
Sub tgr()
'Define the list of subjects
Dim aSubjects() As Variant
aSubjects = Array("SBC130", "SBC150", "SBC210", "SBC220")
'Define the valid threshold
Dim lValidQty As Long
lValidQty = 2
'Make sure we're working with the correct worksheet
With ActiveWorkbook.Worksheets("Part B + C Modules")
'Initiate the loop starting at row 2 and going to last used row
Dim i As Long
For i = 2 To .Cells(.Rows.Count, "A").End(xlUp).Row
'Define the range to check
Dim rCheck As Range
Set rCheck = .Range(.Cells(i, "C"), .Cells(i, .Columns.Count).End(xlToLeft))
'Call the function to check if the appropriate number of different subjects have been selected
If CheckUnqValueQty(rCheck, lValidQty, aSubjects) = True Then
'valid result, 2 or more different required subjects selected
'do something for a valid result here
Else
'invalid result, 0 or 1 required subjects selected
ActiveWorkbook.Worksheets("Available sub").Cells(i, "F").Value = "Incorrect"
End If
Next i
End With
End Sub
If a formula works:
=IF(AND(B1<>B2,COUNTIF(C1:C4,B1)+COUNTIF(C1:C4,B2)=2),"OK","Incorrect")
For some VBA, maybe something like this:
Dim tempstring As String
With Sheets("unknown")
tempstring = .Range("C1").Value & "|" & .Range("C2").Value & "|" & .Range("C3").Value & "|" & .Range("C4").Value
If InStr(tempstring, .Range("B1").Value) > 0 And InStr(tempstring, .Range("B2").Value) > 0 Then
Sheets("Available sub").Range("F1") = "OK"
Else
Sheets("Available sub").Range("F1") = "Incorrect"
End If
End With
Note that you don't qualify the sheets for all you ranges so I used a sheet called "unknown", adjust the code to match you workbook
If your Student ID numbers are in column B (change column as needed) you could loop through each Student ID, and Count the number of cells with constants in the range for each row. Your notification can be a message box or color the Student ID interior color red, with this basic macro.
For Each cel In ActiveSheet.Range("B2", Range("B" & Rows.Count).End(xlUp))
If cel.Resize(, 4).Offset(, 1).SpecialCells(xlCellTypeConstants).Count < 2 Then
MsgBox "Student " & cel.Text & "did not select two subjects"
'Or
cel.Interior.Color = RGB(256, 0, 0)
End If
Next cel
Problem:
I have 1 Excel Sheet with 2 tabs
Tab 1 = Shipment Package
Tab 2 = Mass Update Steps
I want to go through all the values in column B of Tab 2 one by one.
As I go through each row in Tab 2, I will select and copy the values in column C and D of Tab 2.
After selecting and copying, I want to find Tab 2-column B's corresponding values in Tab 1 column G.
If a match is found, I will select column E of Tab 1 (in row where the match was found), and paste there the values copied from Tab 2.
So far this is the code I have which works. However the values from being searched are hard coded. With the values growing in number in Tab 2, the code is hard to maintain. I would like to optimize it. I have googled several possible solutions. But I keep on getting these run-time errors when declaring or setting the range for the 2 sheets. Here is my code.
Private Sub btn_Updt_Steps_Click()
Dim lastRow As Long
With Sheets("Shipment Package")
.Activate
lastRow = .Range("G65000").End(xlUp).Row
For i = 1 To lastRow
If (InStr(1, .Range("G" & i).Value, "Code 001", vbTextCompare) > 0) Then
Sheets("Mass Update Steps").Activate
ActiveSheet.Range("C4:D4").Select
Selection.Copy
Sheets("Shipment Package").Activate
.Range("E" & i).Select
ActiveSheet.Paste
ElseIf (InStr(1, .Range("G" & i).Value, "Code 002", vbTextCompare) > 0) Then
Sheets("Mass Update Steps").Activate
ActiveSheet.Range("C5:D5").Select
Selection.Copy
Sheets("Shipment Package").Activate
.Range("E" & i).Select
ActiveSheet.Paste
ElseIf (InStr(1, .Range("G" & i).Value, "Code 003", vbTextCompare) > 0) Then
Sheets("Mass Update Steps").Activate
ActiveSheet.Range("C6:D6").Select
Selection.Copy
Sheets("Shipment Package").Activate
.Range("E" & i).Select
ActiveSheet.Paste
End If
Next
End With
NotFoundErr:
Debug.Print "value not found"
End Sub
Solution:
Private Sub btn_Updt_Steps_Click()
Dim i As Long
Dim j As Long
Dim Tab2ColC As String
Dim Tab2ColD As String
Dim Tab1ColE As String
Dim Tab1ColF As String
Tab1 = "Shipment Package"
Tab2 = "Mass Update Steps"
With Worksheets(Tab1)
LastRowTab1 = .Cells(.Rows.Count, "G").End(xlUp).Row 'LastRowInColumn(2, Tab1)
End With
With Worksheets(Tab2)
LastRowTab2 = .Cells(.Rows.Count, "B").End(xlUp).Row 'LastRowInColumn(2, Tab2)
End With
For i = 4 To LastRowTab2
Tab2ColumnB = Trim(Sheets(Tab2).Range("B" & i).Value)
Sheets(Tab2).Activate
If Tab2ColumnB <> "" Then
Tab2ColC = "C" & i
Tab2ColD = "D" & i
ActiveSheet.Range(Tab2ColC, Tab2ColD).Copy
For j = 16 To LastRowTab1
Tab1ColumnG = Trim(Sheets(Tab1).Range("G" & j).Value)
If Tab1ColumnG = Tab2ColumnB Then
Sheets(Tab1).Activate
Tab1ColE = "E" & j
Tab1ColF = "F" & j
Sheets(Tab1).Range(Tab1ColE, Tab1ColF).Select
ActiveSheet.Paste
End If
Next
End If
Next
End Sub
For optimization, you can avoid select statements, activate statements etc. Check the code below.
For i = 1 To lastRow
Application.ScreenUpdating = False
If YourCondn1 Then
Sheets("Mass Update Steps").Range("C4:D4").Copy
Sheets("Shipment Package").Range("E" & i).PasteSpecial xlPasteAll
ElseIf YourCondn2 Then
Sheets("Mass Update Steps").Range("C5:D5").Copy
Sheets("Shipment Package").Range("E" & i).PasteSpecial xlPasteAll
ElseIf YourCondn3 Then
Sheets("Mass Update Steps").Range("C6:D6").Copy
Sheets("Shipment Package").Range("E" & i).PasteSpecial xlPasteAll
End If
Application.ScreenUpdating = True
Next
Adding the code that you require. Hope this will work. I haven't tested it. Please check.
Private Sub btn_Updt_Steps_Click()
'Finding LastRow in Tab 2
Tab1 = "Shipment Package"
Tab2 = "Mass Update Steps"
With Worksheets(Tab2)
LastRowTab2 = .Cells(.Rows.Count, 2).End(xlUp).Row 'LastRowInColumn(2, Tab2)
End With
MatchFound = 0
For i = 1 To LastRowTab2
'checking whether value in tab2 column b is same as tab1 column g
Tab2ColumnB = Trim(Sheets(Tab2).Range("B" & i).Value)
Tab1ColumnG = Trim(Sheets(Tab1).Range("G" & i).Value)
If Tab2ColumnB = Tab1ColumnG Then
Tab2ColumnC = Trim(Sheets(Tab2).Range("C" & i).Value)
Tab2ColumnD = Trim(Sheets(Tab2).Range("D" & i).Value)
Sheets(Tab1).Range("E" & i).Value = Tab2ColumnC
Sheets(Tab1).Range("F" & i).Value = Tab2ColumnD
MatchFound = MatchFound + 1
End If
Next
If MatchFound = 0 Then
MsgBox "No matches found"
ElseIf MatchFound > 0 Then
MsgBox MatchFound & " matches were found."
End If
End Sub
I think you can achieve what you want with simple Excel formulas.
In Shipment Package, type the following into E1 and F1 and then drag formula down:
E1 = VLOOKUP(G1,'Mass Update Steps'!$B$1:$D$20,2,0)
F1 = VLOOKUP(G1,'Mass Update Steps'!$B$1:$D$20,3,0)
NB - you'll need to amend $B$1:$D$20 depending on how much data you have in Mass Update
Finally, this assumes that there is always a match. If not, and you want to get rid of those pesky #N/A values, then update the formuals with ISNA e.g.
E1 = IF(ISNA(VLOOKUP(G1,'Mass Update Steps'!$B$1:$D$4,2,0)),"",VLOOKUP(G1,'Mass Update Steps'!$B$1:$D$4,2,0))
Hope that helps.