Getting vector of values from loop in VBA - excel

I have a column which may have N/As. I am running a loop which checks if there is N/A error and pops up MsgBox with location of an error.
What I have problem with is storing a value from corresponding column where there's an error.
Option Explicit
Dim i As Integer
Dim where As Variant
Dim numbers(1 To 20) As Variant
For Each i In Range("b2:b" & lastrow)
If IsError(i) = True Then
where = Range("B" & i.Row).Address
MsgBox "Missing data was found in " & where
numbers (i) = Range("D" & i.Row).Value
End If
Next i
There's something wrong with this code, as a get mismatch error (run time error '13').
Please point me in the right direction what I am doing wrong here.
Thanks
Edit: I quoted just part of code, thats why there's no Sub/End Sub

Private Const lastrow As Integer = 10
Sub GetErrors()
Dim oneCell As Range
Dim where As Variant
Dim numbers As Variant
where = vbCrLf
For Each oneCell In Range("b2:b" & lastrow).Cells
If IsError(oneCell.Value) = True Then
where = where & Range("B" & oneCell.Row).Address & vbCrLf
If (Not IsArray(numbers)) Then
ReDim numbers(0)
Else
ReDim Preserve numbers(UBound(numbers) + 1)
End If
numbers(UBound(numbers)) = Range("D" & oneCell.Row).Value
End If
Next oneCell
MsgBox "Error was found in : " & where
End Sub

Related

Identify duplicate values with MsgBox

I have written VBA code to find the duplicate value and bulk upload the data to another sheet.
If any duplicate in A, B, C Columns I need a message box, and to cancel the bulk upload.
Example of my columns - marked in red are duplicate values:
Option Explicit
Private Sub CommandButton1_Click()
Dim rng As Range
Dim l As Long, r As Long, msg As String
Dim lRow, lRow1 As Long
Application.ScreenUpdating = False
l = Range("A" & Rows.Count).End(xlUp).Row
For r = 2 To l
If Evaluate("COUNTIFS(A:A,A" & r & ",B:B,B" & r & ",C:C,C" & r & ")") > 1 Then msg = msg & vbCr & r
Next
MsgBox msg, vbInformation, "DUPLICATE ROWS"
Exit Sub
lRow = [Sheet2].Cells(Rows.Count, 1).End(xlUp).Row
lRow1 = [Sheet3].Cells(Rows.Count, 1).End(xlUp).Row + 1
[Sheet2].Range("A4:N" & lRow).Copy
[Sheet3].Range("A" & lRow1).PasteSpecial xlPasteValues
Application.CutCopyMode = False
Sheet3.Select
[Sheet3].Range("A1").Select
Sheet2.Select
[Sheet2].Range("A1").Select
End Sub
Something like this should work fine:
For r = 2 To l
If Evaluate("COUNTIFS(A:A,A" & r & ",B:B,B" & r & ",C:C,C" & r & ")") > 1 Then
msg = msg & vbCr & r
End If
Next r
If Len(msg) > 0 Then
MsgBox msg, vbInformation, "DUPLICATE ROWS"
Exit Sub
End If
Extended Formula evaluation without loops
Extending on Tim's row-wise formula evaluation a couple of tips:
Fully qualify your range references; without explicit indications VBA assumes the active sheet, which needn't be the one you have in mind.
Execute a worksheet-related evaluation for the same reason; doing so it suffices here to indicate e.g. "A:A" instead of inserting a sheet prefix "Sheet1!..." each time.
Example procedure
Option Explicit ' force declaration of variables on top of code module
Sub IdentifyDuplicateRows()
With Sheet1 ' using the project's Sheet Code(Name)
'1. get last row & build formula
Dim l As Long
l = .Range("A" & Rows.Count).End(xlUp).Row
Dim myFormula As String
myFormula = "=IF(COUNTIFS(A:A,A2:A" & l & ",B:B,B2:B" & l & ",C:C,C2:C" & l & ")>1,""Duplicate Row "" & Row(A2:A" & l & "),"""")"
'2. get results & write to target
Dim results As Variant
results = .Evaluate(myFormula) ' note the "."-prefix!
With .Range("D2").Resize(UBound(results))
.Value = results 'write results to target
End With
'3. optional additional MsgBox info (see below)
' ...
End With
End Sub
Note to optional message box info
If you prefer a further info via message box you could insert the following block before End With:
'3. optional display in message box
'filter only elements containing "Dup" (change to flat & eventually 0-based array)
results = Application.Transpose(results)
results = Filter(results, "Dup") ' omitted default argument Include:=True
'count duplicate rows and display message
Dim cnt As Long
cnt = UBound(results) + 1
MsgBox Join(results, vbNewLine), vbInformation, cnt & " Duplicate Rows"

VBA For Each in Array type mismatch

I have been using the following code to send out emails, but I get a "run time error 13" when there is only one value in column M.
It works fine if I have more than two values. Any help please?
Sub testDemo()
Dim outlookApp As Object
Dim objMail As Object
Dim Region
Dim rng As Range
Dim Mailaddr As String
Dim MyRange As String
Dim arr As Variant
Dim lastrow As Long
Dim lastrow2 As Long
' Create email
Set outlookApp = CreateObject("Outlook.Application")
' Update with your sheet reference
With Sheets("Escalate")
lastrow = Range("A65536").End(xlUp).Row
lastrow2 = Range("M65536").End(xlUp).Row
Set rng = .Range("A1:I" & lastrow)
End With
arr = Range("M2:M" & lastrow2).Value
For Each Region In arr
myrangename = Worksheets("email").Range("C2:D200")
Mailaddr = WorksheetFunction.VLookup(Region, myrangename, 2, False)
On Error Resume Next
With outlookApp.CreateItem(0)
' Add table to Email body
.SentOnBehalfOfName = "script Tracking"
.cc = "Pearson.S#cambridgeenglish.org; Tracking.S#cambridgeenglish.org"
.HTMLBody = "Dear Team," & "<br><br>" & _
"blahblah " & "<br><br>" & _
GenerateHTMLTable(rng, CStr(Region), True) & "<br><br>" & _
"Many thanks in advance " & "<br><br>" & _
"Kind regards "
.To = Mailaddr
.Subject = "Region " & Region & " Outstanding scripts - " & Range("L1")
.Display
End With
skip:
Next Region
End Sub
Public Function GenerateHTMLTable(srcData As Range, Region As String, Optional FirstRowAsHeaders As Boolean = True) As String
Dim InputData As Variant, HeaderData As Variant
Dim HTMLTable As String
Dim i As Long
' Declare constants of table element
Const HTMLTableHeader As String = "<table>"
Const HTMLTableFooter As String = "</table>"
' Update with your sheet reference
If FirstRowAsHeaders = True Then
HeaderData = Application.Transpose(Application.Transpose(srcData.Rows(1).Value2))
InputData = Range(srcData.Rows(2), srcData.Rows(srcData.Rows.Count)).Value2
' Add Headers to table
HTMLTable = "<tr><th>" & Join(HeaderData, "</th><th>") & "</th></tr>"
End If
' Loop through each row of data and add selected region to table output
For i = LBound(InputData, 1) To UBound(InputData, 1)
' Test Region against chosen option
If Region = InputData(i, 9) Then
' Add row to table for output in email
HTMLTable = HTMLTable & "<tr><td>" & Join(Application.Index(InputData, i, 0), "</td><td>") & "</td></tr>"
End If
Next i
GenerateHTMLTable = HTMLTableHeader & HTMLTable & HTMLTableFooter
End Function
This will explain it better
Sub Sample()
Dim arr
lastrow2 = 2
arr = Range("M2:M" & lastrow2).Value
lastrow2 = 3
arr = Range("M2:M" & lastrow2).Value
End Sub
When the lastrow2 = 2, arr holds only one cell value and hence it becomes a Variant/(String/Double...etc depending on the value in cell M2)
When the lastrow2 > 2, arr becomes a 2D array and hence it becomes a Variant/Variant(1 to 2, 1 to 1)
The above can be verified using a Watch on arr in VBA.
This is the reason why your code works when you have more than one cell.
Because it is not a collection or an array, it is a single value - you can test this by checking IsArray(arr) before you run the For Each
There are several ways to fix this, but the fastest would be to include the line If Not IsArray(Arr) Then Arr = Array(Arr) before your For Each, to turn it into a 1-element array.
Other points to consider:
What is the purpose of your On Error Resume Next?
What is the purpose of your skip: label?
Variable myrangename is not defined - consider adding Option Explicit to the top of your Module, so that "Debug > Compile VBA Project" will catch those errors for you

Mismatch error in completing combo box VBA

I have a problem when trying to type in my combo box (in a userform) in order to find a match. When I type a wrong letter/number it immediately gives a Mismatch Error and directs me to the VBA code. How can I avoid that? Is there something I can add to my code or to change in the properties? Because for the user it is common to type something wrong and I don't want to redirect the users to the code.
This is the code for my combo box:
Private Sub ComboBox3_Change()
If Me.ComboBox3.Value <> "" Then
Dim sh As Worksheet
Set sh = ThisWorkbook.Sheets("11")
Set ph = ThisWorkbook.Sheets("22")
Dim i As String
i = Application.Match((Me.ComboBox3.Value), sh.Range("A:A"), 0)
Me.TextBox8.Value = ph.Range("D" & i).Value
Me.TextBox13.Value = ph.Range("P" & i).Value
Me.TextBox41.Value = ph.Range("B" & i).Value
End If
End Sub
Private Sub UserForm_Activate()
Dim i As Integer
Me.ComboBox3.Clear
Me.ComboBox3.AddItem ""
For i = 2 To sh.Range("A" & Application.Rows.Count).End(xlUp).Row
Me.ComboBox3.AddItem sh.Range("A" & i).Value
Next i
You need to use error handling statement to skip the part that is generating the error.
Private Sub ComboBox3_Change()
If Me.ComboBox3.Value <> "" Then
Dim sh As Worksheet
Set sh = ThisWorkbook.Sheets("11")
Set ph = ThisWorkbook.Sheets("22")
Dim i As String
or error go to MyHandler
i = Application.Match((Me.ComboBox3.Value), sh.Range("A:A"), 0)
Me.TextBox8.Value = ph.Range("D" & i).Value
Me.TextBox13.Value = ph.Range("P" & i).Value
Me.TextBox41.Value = ph.Range("B" & i).Value
End If
MyHandler:
' Expected behavior on error
End Sub

Move Two characters from beginning to end of string VBA

I need to create a VBA script in excel that chanages an order number from having "CD" at the front to "CD" at the end so from "CD00001" to "00001CD"
Any help would be awesome. all of the order numbers are in Column B and start at row 5. please help.
What i have so far:
Private Sub OrderNumber_Click()
Dim Val As String
Dim EndC As Integer
EndC = Worksheets("Raw Data Upload").Range("A1048576").End(xlUp).Row
For i = 5 To EndC
Val = Right("B" & i, Len("B" & i) - 2) & Left("B" & i, 2)
Range("B" & i).Value = Val
Next
End Sub
This replaces the order numbers with B5, B6 and so on but if i put this function into Excel itself it works fine.
Like this? DO you want it in column B?
Option Explicit
Private Sub OrderNumber_Click()
Dim i As Long
Dim val As String
Dim EndC As Long
Dim ws As Worksheet
Set ws = ThisWorkbook.Worksheets("Raw Data Upload")
EndC = ws.Range("A1048576").End(xlUp).Row
For i = 5 To EndC
val = ws.Cells(i, "A")
Range("B" & i).Value = Mid$(val, 3, Len(val) - 2) & Left$(val, 2)
Next i
End Sub
dim beginStr, endStr, originalStr, outputStr as string
dim rng as range
'put the below into a loop, assigning a rng to the desired cell each time
originalStr = rng.value ' Change to chosen range
beginStr = left(originalStr,2)
endStr = right(originalStr, len(originalStr) - 2)
outputStr = endStr + beginStr
Range("B" & i).Value = outputStr
I haven't got a copy of Excel to test this on but it should work.
Simply use:
Right(Range("B" & i), Len(Range("B" & i)) - 2) & Left(Range("B" & i), 2)
An alternative is to set up the cell as a Range():
Sub t()
Dim cel As Range
Dim endC As Long
endC = Worksheets("Raw Data Upload").Range("A1048576").End(xlUp).Row
For i = 5 To endC
Set cel = Range("B" & i)
myVal = Right(cel, Len(cel) - 2) & Left(cel, 2)
Range("B" & i).Value = myVal
Next
End Sub
Currently, when you do Right("B" & i, Len("B" & i) - 2) & Left("B" & i, 2), for row 5, this becomes Right("B5", Len("B5") - 2) & Left("B5", 2) then this evaluates to simply:
Right("B5",0) & Left("B5",2), which is
[nothing] & B5, finally becoming
B5
Note the lack of using B5as a range. Instead it's being treated as a string.
(Also, I'm assuming this is to be run on the ActiveSheet. If not, please add the worksheet before the range, i.e. Worksheets("Raw Data Upload").Range("B" & i)...)
Try this
Private Sub OrderNumber_Click()
Dim cell As Range
With Worksheets("Raw Data Upload")
For Each cell in .Range("B5", .Cells(.Rows.Count, 2).End(xlUp))
cell.Value = Right(cell.Value, Len(cell.Value) - 2) & Left(cell.Value, 2)
Next
End With
End Sub

VBA code only deletes row when run in debug mode

Im having trouble deleting Rows when running the code not in debug mode. I put stars next to the line giving me a problem. Works in debug mode but not normally running the code. Any help? I have tried using doevent but in the beginning of the for loop but that didnt work.
Public Sub ItemUpdate(ByVal startRow As Integer, ByVal endRow As Integer, ByVal itemCol As String, ByVal statusCol As String, ByVal manuPNCol As String)
Dim orgSheet As Worksheet
Dim commonSheet As Worksheet
Dim partDesCol As String
Dim partDes As String
Dim vendorColNumber As Integer
Dim vendorColLetter As String
Dim manuPN As String
Dim counter As Integer
Dim replaceRnge As Range
Set orgSheet = ThisWorkbook.ActiveSheet
partDesCol = FindPartDesCol()
Set commonSheet = ThisWorkbook.Worksheets("Common Equipment")
For counter = startRow To endRow
'Get part description value
partDes = Range(partDesCol & counter).Value
'Delete row of empty cells if there is any
If partDes = "" Then
'deleteing empty row
orgSheet.Rows(counter).Delete '************************** Only works in
debug mode.
endRow = endRow - 1
If counter < endRow Then
counter = counter - 1
Else
Exit For
End If
Else
manuPN = Range(manuPNCol & counter).Value
'Search for user part in common sheet
Set rangeFind = commonSheet.Range("1:200").Find(partDes, lookat:=xlWhole)
If rangeFind Is Nothing Or partDes = "" Then
Debug.Print "Part " & partDes & " not found in Common Equipment"
'MsgBox "Part " & partDes & " not found in Common Equipment"
'Now check if manuPN is in common equipment
Set rangeFind = commonSheet.Range("1:200").Find(manuPN, lookat:=xlWhole)
If rangeFind Is Nothing Or partDes = "" Then
Debug.Print "PartNumber " & manuPN & " not found in Common Equipment"
'Now check if vendor value of item is empty
'Get vendor col
vendorCol = FindSearchCol()
If orgSheet.Range(vendorCol & counter).Value = "" Then
'Copy and paste manufact. data to vendor
'converting from letter column to number and visa versa
vendorColNumber = Range(vendorCol & 1).Column
ManuColTemp = vendorColNumber - 2
ManuPNColTemp = vendorColNumber - 1
VendorPNColTemp = vendorColNumber + 1
ManuCol = Split(Cells(1, ManuColTemp).Address(True, False), "$")(0)
manuPNCol = Split(Cells(1, ManuPNColTemp).Address(True, False), "$")(0)
VendorPNCol = Split(Cells(1, VendorPNColTemp).Address(True, False), "$")
(0)
orgSheet.Range(ManuCol & counter & ":" & manuPNCol & counter).Copy Range(vendorCol & counter & ":" & VendorPNCol & counter)
End If
Else
'Copy new data from common equipment and paste in place of old data
'Get value of status
If statusCol <> "error" Then
orderStatus = orgSheet.Range(statusCol & counter).Value
End If
commonSheet.Rows(rangeFind.Row).EntireRow.Copy
orgSheet.Range(itemCol & counter).PasteSpecial xlPasteValues
If statusCol <> "error" Then
orgSheet.Range(statusCol & counter).Value = orderStatus
End If
End If
Else
'Copy new data from common equipment and paste in place of old data
'Get value of status
If statusCol <> "error" Then
orderStatus = orgSheet.Range(statusCol & counter).Value
End If
commonSheet.Rows(rangeFind.Row).EntireRow.Copy
orgSheet.Range(itemCol & counter).PasteSpecial xlPasteValues
If statusCol <> "error" Then
orgSheet.Range(statusCol & counter).Value = orderStatus
End If
End If
End If
Next counter
'call renumber item numbers
Call NumberItems(0, 0, 0, False)
End Sub
Most likely, you need to step backwards through your range. When you step forward, as you are doing, the counter will skip a row whenever you delete a row:
For counter = startRow To endRow
Change to
For counter = endRow To startRow Step -1
Also, you should declare endRow and startRow as data type Long. The range of Integer will not cover all the rows in an Excel worksheet; and also VBA is said to convert Integers to Longs when doing the math anyway.

Resources