Using Left() for variable - vba

I'm new to VBA.
So i was trying to append prefixes to a set of values, depending on the first number of each value.
However i get a Type mismatch error on line 4. I suppose it is because I am trying to use Left() on a variable, and it is supposed to be used on a string (or something of that sorts?)
How do i accomplish that?
Thank you in advance
Sub test()
a = UsedRange.Rows.Count
Testvariable = Range("A1", "A" & a)
FirstNo = Left(Testvariable, 1)
For i= 1 To a
If FirstNo(i,1) = "1" Then
Cells(i,2) = "abc" & FirstNo(i,1)
Else Cells(i,2) = "def" & FirstNo(i,1)
End if
Next
End Sub

Trouble is you are trying to take the left of a Range object that happens to give you an array when assigned without the set keyword, when left wants a string.
This post here explains how to convert a range to a string, that you can then pass to left.
How can I convert a range to a string (VBA)?
Sub test()
dim a as long
a = UsedRange.Rows.Count
dim Testvariable as string
Testvariable = RangeToString(Range("A1", "A" & a))
dim FirstNo as string
FirstNo = Left(Testvariable, 1)
dim i as long
For i= 1 To a
If FirstNo(i,1) = "1" Then
Cells(i,2) = "abc" & FirstNo(i,1)
Else
Cells(i,2) = "def" & FirstNo(i,1)
End if
Next
End Sub
Function RangeToString(ByVal myRange as Range) as String
RangeToString = ""
If Not myRange Is Nothing Then
Dim myCell as Range
For Each myCell in myRange
RangeToString = RangeToString & "," & myCell.Value
Next myCell
'Remove extra comma
RangeToString = Right(RangeToString, Len(RangeToString) - 1)
End If
End Function
Also, be sure to always declare your variables correctly.
a = UsedRange.Rows.Count means create a variable called a of type variant and give it the value of UsedRange.Rows.Count
Testvariable = Range("A1", "A" & a) means create a variable called Testvariable of type variant and give it the .value of the range object (an array)
If these were declared properly
dim a as long
a = UsedRange.Rows.Count
dim Testvariable as string
Testvariable = Range("A1", "A" & a)
The assignment of Testvariable would have failed with a much more obvious error, informing you that you can't convert an array to a string.

Testvariable = Range("A1", "A" & a)
Testvariable is an array (unless a=1, which is not your case here)
Left is flexible enough to accept numbers in addition to strings, but NOT arrays.

If I follow your flow, I think this will get you what you are looking for.
Sub test()
Dim ws As Worksheet
Set ws = ActiveWorkbook.ActiveSheet
a = ws.UsedRange.Rows.Count
TestVariable = Range("A1", "A" & a)
For i = 1 To a
FirstNo = Left(TestVariable(i, 1), 1)
If FirstNo = "1" Then
Cells(i, 2) = "abc" & FirstNo(i, 1)
Else
Cells(i, 2) = "def" & FirstNo
End If
Next i
End Sub
I think the issue was, in addition to the other two answers, was that you need to loop through the cells and output something based on each cells value. Therefore, just throw your code just above your for loop inside and change as needed.

VBA does a lot of things behind your back, to "make things easier". The problem is that these things come back and bite you later.
Undeclared variables, for example. Without Option Explicit specified at the top of every module, VBA will happily compile code that uses variables that aren't declared. Sounds useful? It would be, if the types were inferred. But instead VBA declares an on-the-fly Variant, that holds whatever you put into it (and changes its type to accomodate whatever you're assigning).
a = UsedRange.Rows.Count
Here a is an implicit Variant/Long. Declare it as a Long integer:
Dim a As Long
Even better, give it a meaningful name:
Dim usedRows As Long
usedRows = UsedRange.Rows.Count
Now the fun part:
Testvariable = Range("A1", "A" & a)
Here Testvariable is an implicit Variant/Array. How so? The code that VBA sees looks something like this:
Testvariable = Range("A1", "A" & a).Value
And because a wouldn't be 1, that .Value is referring to more than one single cell - Excel's object model gives you an array that contains the values of all cells in the specified range, and that's what Testvariable contains.
If you wanted Testvariable to refer to a range rather than just their values, you would declare a Range object variable, and assign it with the Set keyword:
Dim testVariable As Range
Set testVariable = ActiveSheet.Range("A1:A" & a)
Notice the explicit ActiveSheet here: without it the code does exactly the same thing, so why put it in? Because you want to be as explicit as possible: unqualified Range, Cells, Rows, Columns and Names calls all implicitly refer to the active worksheet - and that's yet another source of bugs (just google up "range error 1004", you'll find hundreds of Stack Overflow questions about it).
If you meant testVariable to contain the values of every cell in that specified range, then you would declare and assign testVariable like this:
Dim testVariable As Variant
testVariable = ActiveSheet.Range("A1:A" & a).Value
And now you have an array that contains all values in range "A1:A" & a of the active worksheet: that's what your code does.. implicitly.
FirstNo = Left(Testvariable, 1)
So now we want the "first number", and we're reading it off a variant array.
The Left (or Left$) function is from the VBA.Strings module, and means to works with strings; it can work with other types too (with implicit type conversions), but if you give it an object reference or an array, it won't know how to convert it for you, and VBA will raise a run-time error.
The testVariable variant array contains Variant values: most cells will contain a Double floating-point value. Others will contain a String. Some cells can contain an error value (e.g. #N/A, #VALUE!, or #REF!) - and VBA will not be able to implicitly convert such error values either.
So before you read any cell's value, you need to make sure you can read it; use the IsError function for that:
If IsError(testVariable(1, 1)) Then
Exit Sub ' cell contains an error; cannot process
If Not IsNumeric(testVariable(1, 1)) Then
Exit Sub ' cell doesn't contain a number; cannot process
End If
' now that we KNOW our value is a numeric value...
Dim firstNumber As Double
firstNumber = testVariable(1, 1)
Notice that (1, 1)? That's because testVariable is a 1-based 2D array (without Option Base 1 implicitly sized arrays are always 0-based, except when you're getting one from a Range), so to read the value in the first row / first column, you need to read the value at index (1, 1).
But that's not what you're trying to do.
So I was trying to append prefixes to a set of values, depending on the first number of each value.
"Each value" means you need to iterate the values, so you have a loop there.
Dim i As Long
For i = 1 To a
'...
Next
It's not the "first number" we want, it's the firstDigit, of every cell we're looping over.
If FirstNo(i,1) = "1" Then
Here you've lost track of the types you're dealing with: FirstNo was assigned before the loop started, so its value will be constant at every iteration.
You mean to do this:
Dim values As Variant
values = ActiveSheet.Range("A1:A" & usedRows).Value
Dim i As Long
For i = 1 To usedRows
If Not IsError(values(i)) Then
Dim representation As String
representation = CStr(values(i))
Dim prefix As String
If Left$(representation, 1) = "1" Then
prefix = "abc"
Else
prefix = "def"
End If
ActiveSheet.Range("B" & i).Value = prefix & representation
End If
Next
Now that everything is explicit and properly indented, it's much easier to see what's going on... and now I'd seriously question why you need VBA to do that:
[B1] = IFERROR(IF(LEFT(A1, 1) = "1", "abc" & A1, "def" & A1), "")
And drag the formula down.

Related

VBA - search for a date in a range of cells, returning cell address

I am trying to write a function to search for a specific date, entered as a parameter, in a range of cells in an adjacent worksheet. On finding the date, the function should return a string, "found: " and the cell reference.
All seems to be working well enough, but the function returns 'nothing' even when there is a (deliberately entered) date, in date format, both in the cell range and the cell referred to when the function is called.
Have I missed something critical when calling find when using a Date?
A note, the function looks in the same row that it is called from, in the other sheet. This may help explain how i'm setting rng
Public Function d_scan(targ As Date) As String
Dim ws As Worksheet
Dim targetSheet As Worksheet
Dim ret As String
Dim rng As String
Dim scanner As Date
Dim found As Range
Set targetSheet = ThisWorkbook.Worksheets("2018")
Set ws = Application.Caller.Worksheet
Let intRow = Application.Caller.Row
Let intCol = Application.Caller.Column
Let rng = "F" & intRow & ":" & "X" & intRow
Set found = targetSheet.Range(rng).Find(What:=targ, LookAt:=xlWhole)
If found Is Nothing Then
Let ret = "nothing"
Else
Let ret = "found: " & found
End If
d_scan = ret
End Function
date issues are quite subtle and their solution may depend on the actual scenario (what variable type is used, what data format is used in the sheet,...)
for a start, you may want:
specify all relevant Find() method parameters, since undefined ones will be implicitly assumed as per its last usage (even from Excel UI!)
convert Date to String via the CStr() function
so, you may want to try this code:
Option Explicit
Public Function d_scan(targ As Date) As String
Dim rng As String
Dim found As Range
Dim intRow As Long
intRow = Application.Caller.Row
rng = "F" & intRow & ":" & "X" & intRow
Set found = ThisWorkbook.Worksheets("2018").Range(rng).Find(What:=CStr(targ), LookAt:=xlWhole, LookIn:=xlValues) ' specify 'LookIn' parameter, too
If found Is Nothing Then
d_scan = "nothing"
Else
d_scan = "found: " & found
End If
End Function
I think you are comparing day/hour/minute/second with day/hour/minute/second and getting no matches (everything's too specific). I used this to massage targ into "today" at 12:00 AM, but you would need to do something to massage the data on the sheet like this as well for the range.find to work.
targ = Application.WorksheetFunction.Floor(targ, 1)
I suggest using a method other than range.find... Looping perhaps, looking for a difference between targ and the cell that's less than 1?

Excel VBA macro type mismatch error

I found out that an office at my work spent weeks manually going through an Excel spreadsheet containing a database with >500,000 rows looking for duplicate rows matching certain criteria. The duplicates could not simply be erased before being researched, as a single mistake could have potentially lost hundreds of thousands of dollars in lost production. I decided simply flagging them and referencing the originating row would be the best answer in this case. So I decided to look into macros to see how much time could have been saved by using a simple macro instead. I am using this as a programming learning experience, so please no "here's a =function()" answers.
I've written a macro and changed it several times to no avail (most current is below). I wanted to use String variables because there's no telling what has been entered into the cells that will be checked. Here's what I've tried, failed, and learned(?) from this site:
Initially, I tried declaring a variable, and attaching a value from a cell directly to it. e.g. Dim myString As String Set myString = Cells(x, x).Value However, I kept getting object errors. Thanks to Michael's response here, I learned that you have to use the Range variable to use Set.
My next issue has been getting a "type mismatch" error. I'm trying to assign and compare a stored variable against another stored variable, and I'm sure this is causing the issue. I initially tried Dim myRange As Range, myString As String Set myRange = Cells(x, x).Value myString = myRange. This obviously didn't work, so I tried using the CStr() "change to string" function to convert the Range variable to the String variable I want. And that's where I'm stuck.
Sub Duplicate()
'Declare the variables
Dim NSNrange, PNrange, KitIDrange As Range
Dim NSN, PN, KitID As String
Dim NSNCheck, PNCheck, KitIDCheck As String
Dim i, j, printColumn, rowCount As Integer
'Set which column we want to print duplicates on, and count the number of rows used
rowCount = ActiveSheet.UsedRange.Rows.Count
printColumn = 9
'Lets get started!
'Clear the duplicate list column for a fresh start
Columns(printColumn).EntireColumn.Delete
'Start on line 2, and grab the cell values for the NSN, Part number and kit ID.
For i = 2 To rowCount
Set NSNrange = Cells(i, 5).Value
Set PNrange = Cells(i, 7).Value
Set KitIDrange = Cells(i, 2).Value
'Change whatever is contained in those cells into a string and store them into their respective containers
NSN = CStr(NSNrange)
PN = CStr(PNrange)
KitID = CStr(KitIDrange)
'Now let's look through the rest of the sheet and find any others that match the 3 variables that we stored above
For j = 2 To rowCount
'To avoid needless checks, we'll check to see if it's already had a duplicate found. If so, we'll just skip to the next row
If Cells(j, printColumn).Value = "" Then
'If the print column is blank, we'll grab the 3 values from the current row to compare against the above variables
Set NSNrange = Cells(j, 5).Value
Set PNrange = Cells(j, 7).Value
Set KitIDrange = Cells(j, 2).Value
'Now we store the contents into their very own container
NSNCheck = CStr(NSNrange)
PNCheck = CStr(PNrange)
KitIDCheck = CStr(KitIDrange)
'Check the initial row with the current row to see if the contents match. If so, print which row it is duplicated on.
If NSN = NSNCheck And PN = PNCheck And KitID = KitIDCheck Then Cells(j, printColumn).Value = "Duplicated on row " & i
End If
Next j
Next i
MsgBox "Search Complete"
End Sub
As you asked for comments in relation to type errors. There are a number of place where confusion could arise
1) Every line where you do multiple declarations on the same line like this:
Dim NSNrange, PNrange, KitIDrange As Range
Only the last variable is explicitly type declared (in this case as a Range). The others are implicit Variant. So, I have gone through and put on separate lines and declared them as I believe you may have meant them to be.
2) Using Activesheet and, in other places, just Cells or Range, which implicitly references the Activesheet, means if you have changed sheets by then you may longer be referring to the sheet you intended. So whilst I have kept Activesheet in, and used an overarching With Activesheet statement that then allows me to say .Cells or .Range etc, you should change this to using explicit sheet names.
3) Where ever you use the Set keyword the expectation is your are working with an object (e.g. a Range). Going by your naming convention I am going to say that you mean
Set NSNrange = Cells(i, 5)
when you say
Set NSNrange = Cells(i, 5).Value
Which sets a range to another range rather than a cell value.
4) I have changed your Integers to Longs. You are working with rows which can go beyond what Integer type can handle so you risked overflow. Long is safer.
5) Rather than doing a conversion on the Range as follows
NSN = CStr(NSNrange)
Where the default property of the range, .Value, will be taken, as you want a string you can drop the CStr conversion and just take the .Text property which will give you the string you want.
6) Rather than the empty string literal "" comparison, I have used vbNullString which is faster to assign and to check.
Option Explicit
Sub Duplicate()
Dim NSNrange As Range
Dim PNrange As Range
Dim KitIDrange As Range
Dim NSN As String
Dim PN As String
Dim KitID As String
Dim NSNCheck As String
Dim PNCheck As String
Dim KitIDCheck As String
Dim i As Long
Dim j As Long
Dim printColumn As Long
Dim rowCount As Long
With ActiveSheet
rowCount = .UsedRange.Rows.Count
printColumn = 9
.Columns(printColumn).EntireColumn.Delete
For i = 2 To rowCount
Set NSNrange = .Cells(i, 5)
Set PNrange = .Cells(i, 7)
Set KitIDrange = .Cells(i, 2)
NSN = NSNrange.Text
PN = PNrange.Text
KitID = KitIDrange.Text
For j = 2 To rowCount
If .Cells(j, printColumn).Value = vbNullString Then
Set NSNrange = .Cells(j, 5)
Set PNrange = .Cells(j, 7)
Set KitIDrange = .Cells(j, 2)
NSNCheck = NSNrange.Text
PNCheck = PNrange.Text
KitIDCheck = KitIDrange.Text
If NSN = NSNCheck And PN = PNCheck And KitID = KitIDCheck Then
.Cells(j, printColumn).Value = "Duplicated on row " & i
End If
End If
Next j
Next i
End With
MsgBox "Search Complete"
End Sub
So it's correct that you assign objects with set (not just range). A cell is an object and can be assigned to a range variable. But when you use methods and properties of objects, in this case .Value, it does not mean that the return value is a range object.
So if you need to know what all propertys and methods to, i highly recommend the microsoft documentation.
So when you use .Value, you get back a variant (depending on the type of the value). In your use-case you can just assign it to a string, i.e Dim str as string: str = Cells(1,1).Value. If you just want the cellas an object that you can reference: Dim cell as Range: Set cell = Cells(1,1). Now can can adress all propertys and methods for example: cell.Value instead of Cells(1,1).Value.
Just a few other things that are useful to know. In VBA not like in VB.Net, that you better not mix up, if you Dim var1, var2 as String only var2 is a string, var1 is a variant. So its required to specify the type for each variable, Dim var1 as String, var2 as String.
Another thing that you might want to change is assigning Cells, Range to a specific Worksheet. Depending on which module your code is in, it can happen that you code runs on the wrong worksheet. (Also it minimizes errors when other people adjust/run the code), but mainly you just have to change one variable, if you want to refer to another Worksheet. It can be done with using Worksheet-Object.
Dim ws as Worksheet
Dim str as String
Set ws = Worksheets(1)
'Now adress methods and properties with ws
str = ws.Cells(1,1).Value
Also note here the object is Worksheet without an s. Worksheets is the collection of the Worksheet of the current Workbook.
You can also use the RemoveDuplicates method.
'Remove duplicates based on the data in columns; 2 "Kit", 5 "NSN", and 7 "PN".
ActiveSheet.UsedRange.RemoveDuplicates Columns:=Array(2, 5, 7), Header:=xlYes

Replace cell values in specific sheets with defined name

I am trying to run some code that replaces the cell values in a specific column with a defined name. In addition, I have a condition that the replacement should only take place if the first 9 characters of the values are xxxxxxxxx.
More precisely, it should change the values in C:C in 2 specific worksheets (I don't want to loop through the whole workbook).
I am not sure why nothing happens in the code (no error messages, nothing).
I presume, however, that I should not use With if I want the code to work in these 2 specific worksheets. I am also aware that my use of Range is probably not totally correct.
Sub ChangeMe()
Dim cl As Range
For Each cl In Worksheets("Sheet1").Range("C:C").End(xlUp)
With Worksheets("Sheet2").Range("C:C").End(xlUp)
If Left(cl.Value, 9) = "XXXXXXXXX" Then
cl.Value = ThisWorkbook.Names("MyDefinedName").RefersToRange
End If
End With
Next cl
End Sub
In answer your original questions:
I am not sure why nothing happens in the code (no error messages, nothing).
Nothing happens because your worksheet values are lowercase xxxxxxxxx, whilst your code checks for uppercase XXXXXXXXX.
I presume, however, that I should not use With if I want the code to work in these 2 specific worksheets.
Actually, you can use With with multiple sheets, as I will demonstrate below.
I am also aware that my use of Range is probably not totally correct.
That is true. If you were to fix the uppercase issue, only C1 would be changed. This is because .End() works on a single cell. If you supply a multi-cell range, it uses the top left most cell. So .Range("C:C").End(xlUp) is equivalent to .Range("C1").End(xlUp) which evaluates to just C1.
The following will answer your updated question:
Option Explicit
Public Sub ChangeMe()
Const l_xxxxxxxxx As String = "xxxxxxxxx"
Const l_MyDefinedName As String = "MyDefinedName"
Const s_Delimiter As String = ","
Const s_WorkSheetNames As String = "Sheet1,Sheet2"
Const s_ColumnToChange As String = "C:C"
Dim varWorkSheetName As Variant
For Each varWorkSheetName In Split(s_WorkSheetNames, s_Delimiter)
With Worksheets(varWorkSheetName).Range(s_ColumnToChange)
Dim rngCell As Range
For Each rngCell In .Resize(.Cells(Rows.Count).End(xlUp).Row)
With rngCell
Dim strCellValue As String: strCellValue = .Value2
If Left(strCellValue, Len(l_xxxxxxxxx)) = l_xxxxxxxxx Then
.Value2 _
= Names(l_MyDefinedName).RefersToRange.Value2 _
& Right$(strCellValue, Len(strCellValue) - Len(l_xxxxxxxxx))
End If
End With
Next rngCell
End With
Next varWorkSheetName
End Sub
Notes:
It is a good idea to use constants so all literal values are typed once only and kept grouped together.
Using .Value2, instead of .Value, is the recommended way to access a cell's value as it avoids implicit casting and is therefore faster. (Using .Value can also sometimes cause issues.)
Surprisingly, in VBA there are good reasons to put a variable declaration as close as possible to the first use of the variable. Two such reasons are 1) it improves readability, and 2) it simplifies future refactoring. Just remember that the variable is not reinitialised every time the Dim is encountered. Initialisation only occurs the first time.
If I understood your post correctly (which I doubt it), I think you want to loop through column "C" in both "Sheet1" and "Sheet2". Every cell that starts with 9 "XXXXXXXXX", should be replaced with the value in "MyDefinedName" Named Range.
Code
Option Explicit
Sub ChangeMe()
Dim cl As Range
Dim sht As Worksheet
For Each sht In ThisWorkbook.Sheets
With sht
If .Name = "Sheet1" Or .Name = "Sheet2" Then
For Each cl In .Range("C1:C" & .Cells(.rows.Count, "C").End(xlUp).Row)
If Left(cl.Value, 9) = "XXXXXXXXX" Then
cl.Value = ThisWorkbook.Names("MyDefinedName").RefersToRange
End If
Next cl
End If
End With
Next sht
End Sub
Let's imagine that this is your input:
In this case, you want to change the values in range A1:A2 to the value in C1 (named range xxxx123), because it starts with xxxx123. This is the code to achieve it:
Public Sub TestMe()
Dim myCell As Range
Dim myNamedRange As String
myNamedRange = "xxxx123"
For Each myCell In Range("A1:A2")
If Left(myCell, Len(myNamedRange)) = myNamedRange Then
myCell.Value = Range(myNamedRange)
End If
Next myCell
End Sub

Efficiency through functions, I am lost

I struggle with VBA - I tend to write as though I am the Macro Recorder and as a result write really ugly macros and end up making things far more complicated than needs be.
Can you possibly have a look and help identify some efficiencies? I want to learn to write good code, but need to compare and contrast and its hard from looking at other peoples examples.
Sub ColumnSearch()
'Filepath variables - the filename changes daily but always contains the name notifications, this seemed to be the easiest method
Dim filepath As String
filepath = ActiveWorkbook.Path + "\"
Application.ScreenUpdating = False
Dim file As String
Dim fullfilepath As String
file = Dir$(filepath & "*Notifications*" & ".*")
fullfilepath = filepath & file
Application.EnableEvents = False
Workbooks.Open (fullfilepath)
Windows(file).Activate
Application.EnableEvents = True
'variables set as string and range respetively
Dim strDoN As String, strOffice As String, strARN As String, strPIN As String, strAN As String, strAT As String, strSoS As String
Dim rngDoN As Range, rngOffice As Range, rngARN As Range, rngPIN As Range, rngAN As Range, rngAT As Range, rngSoS As Range
Dim rng2DoN As Range, rng2Office As Range, rng2ARN As Range, rng2PIN As Range, rng2AN As Range, rng2AT As Range, rng2SoS As Range
Dim myRange As Range
Dim NumCols, i As Integer
'str variables set as the text in row 1 (title cells)
strDoN = "Date of Notification"
strOffice = "Office Centre"
strARN = "Appeal Reference Number"
strPIN = "PIN"
strAN = "Appellant Name"
strAT = "Appeal Type"
strSoS = "SoS Decision Date"
Sheets("New Appeals").Activate
'For loop to find the address of the strings above
For i = 1 To 11
Select Case Cells(1, i).Value
Case strDoN
Set rngDoN = Cells(1, i) '
Case strOffice
Set rngOffice = Cells(1, i)
Case strARN
Set rngARN = Cells(1, i)
Case strPIN
Set rngPIN = Cells(1, i)
Case strAN
Set rngAN = Cells(1, i)
Case strAT
Set rngAT = Cells(1, i)
Case strSoS
Set rngSoS = Cells(1, i)
Case Else
'no match - do nothing
End Select
Next i
'Identify the count of cells to be copied from one sheet to the other
RowLast = Cells(Rows.Count, rngOffice.Column).End(xlUp).Row
Cells(RowLast - 1, rngOffice.Column).Select
Range(Selection, Selection.End(xlUp)).Offset(1, 0).Copy
'activate the other workbook, run the same search for loop but with rng2 being set (rng and rng2 can be different as there are sometimes extra columns that are not required
Workbooks("Book2.xlsm").Activate
Sheets("New Appeals").Select
For i = 1 To 11
Select Case Cells(1, i).Value
Case strDoN
Set rng2DoN = Cells(1, i) '<~~ set the range object to this cell
Case strOffice
Set rng2Office = Cells(1, i)
Case strARN
Set rng2ARN = Cells(1, i)
Case strPIN
Set rng2PIN = Cells(1, i)
Case strAN
Set rng2AN = Cells(1, i)
Case strAT
Set rng2AT = Cells(1, i)
Case strSoS
Set rng2SoS = Cells(1, i)
Case Else
'no match - do nothing
End Select
Next i
Dim RowLast2 As Long
'find the last cell that was updated (every day the list will grow, it has to be pasted at the bottom of the last update)
RowLast2 = Cells(Rows.Count, rng2Office.Column).End(xlUp).Row
Cells(RowLast2, rng2Office.Column).Offset(1, 0).Select
Selection.PasteSpecial
Workbooks(file).Activate
Sheets("New Appeals").Select
'start from scratch again but with the next variable etc
RowLast = Cells(Rows.Count, rngARN.Column).End(xlUp).Row
Cells(RowLast - 1, rngARN.Column).Select
Range(Selection, Selection.End(xlUp)).Offset(1, 0).Copy
Workbooks("Book2.xlsm").Activate
Sheets("New Appeals").Select
RowLast2 = Cells(Rows.Count, rng2ARN.Column).End(xlUp).Row
Cells(RowLast2, rng2ARN.Column).Offset(1, 0).Select
Selection.PasteSpecial
Workbooks(file).Activate
Sheets("New Appeals").Select
End Sub
If this is inapropriate let me know and I'll delete it if needed!
I would consider the following:
Macro description: The comments below the subroutine header should be concise and explain what the macro does, if it is not clear from its name. Your subroutine searches columns. You might want to include what is searched, i.e., "searches a predefined set of strings, selects [...] and copies from [...] to [...]. I would avoid details such as "this seemed to be the easiest method".
Notation / Variable names: It is good practice to give consistent names to your variables. In VBA CamelCase is commonplace. Also, prepending the object type in the variable name is very common, i.e., strDoN as String. If you do so though, make sure you do it everywhere (so filepath should be strFilePath). See here for the naming conventions.
Type declaration: Place all Dim statements at the beginning of the subroutine.
Events: Be careful with enabling and disabling events. If you disable events they won't be re-enabled automatically, so in case of an error (exception) there should be additional actions that re-enable the events. See this post for details on error handling.
As Chris Neilsen mentioned in the comments, avoid using Select and Activate.
Proper Dim-ing: When you do Dim NumCols, i as Integer you actually do Dim NumCols as Variant, i as Integer. If you want both of them to be integers, use Dim Numcols as Integer, i as Integer. Also, prefer Long to Integer.
Explicit Declarations: Put Option Explicit on top of your modules/worksheets. This means that every variable that is used should have been declared first (with a Dim statement). This is useful to avoid bugs from typos, and to have all your variables defined in a single place. Some of your variables, such as RowLast are not defined explicitly now, and they are of Variant type, while their type could had been more specific.
Avoid hardcoding: It is good practice to not refer explicitly to whatever the user can change in a worksheet. Example: Sheets("New Appeals").Activate will work if the sheet name is New Appeals, but if the user changes the name, it will break. Use the sheet's ID instead. Similarly, in your code you assign string variables to hardcoded strings ("Date of Notification", etc). It is safer if you design an area in your sheet from where you can pull this data every time.
Dealing with lots of Cases: the best solution is to use a Dictionary object. It is more elegant, and the code is less cluttered. An example is here.
Copying and Pasting: Use the Range.Copy and Range.PasteSpecial Methods instead of the Selection ones. also, it is not always necessary to Activate a sheet in order to copy/paste there. The Range object can do useful stuff (searching, specialcells, etc.). Check it out.
Fully qualify Ranges: When you copy-paste data from different sheets/files, always use the full name of your Range/Cells objects, to avoid bugs. Relevant SO post here.
Dealing with Large Ranges: Passing data between Excel and VBA can be time-consuming when the numbers get bigger. A good practice is to pass the excel data to a Variant Array, do whatever processing and then dump them back to excel. This is a good read.
Use of With blocks: When you refer to an object's properties/methods multiple times, it enhances readability. Example here.
I hope this helps. This is by not means an exhaustive list, but it might be useful to start with. Happy coding!

Type Mismatch Error after MsgBox

my data is as below .
Updated Question
Sub Solution()
Dim shData As Worksheet
Set shData = Sheets("Sheet1") 'or other reference to data sheet
Dim coll As Collection, r As Range, j As Long
Dim myArr As Variant
Dim shNew As Worksheet
shData.Activate
'get unique values based on Excel features
Range("a1").AutoFilter
Set coll = New Collection
On Error Resume Next
For Each r In Range("A1:A10")
coll.Add r.Value, r.Value
Next r
On Error GoTo 0
'Debug.Print coll.Count
For j = 1 To coll.Count
MsgBox coll(j)
myArr = coll(j)
Next j
Range("a1").AutoFilter
Dim i As Long
For i = 0 To UBound(myArr)
shData.Range("$A$1").AutoFilter Field:=1, Criteria1:=myArr(i), _
Operator:=xlAnd
On Error Resume Next
Sheets(myArr(i)).Range("A1").CurrentRegion.ClearContents
If Err.Number = 0 Then
Range("A1").CurrentRegion.Copy Sheets(myArr(i)).Range("A1")
Else
Set shNew = Sheets.Add(After:=Sheets(Sheets.Count))
shData.Range("A1").CurrentRegion.Copy shNew.Range("A1")
shNew.Name = myArr(i)
Err.Clear
End If
Next i
'removing filter in master sheet
shData.Range("a1").AutoFilter
End Sub
When I run above macro I don't know why it is giving Type Mismatch Error after MsgBox coll(j) , simply I want to store data in Array and I'm passing that data , Here I am using For Each r In Range("A1:A10") Where A10 length is static how can I find last written column?
When you add something to collection the key needs to be a string so use:
coll.Add r.Value, CStr(r.Value)
instead of:
coll.Add r.Value, r.Value
You are still assigning coll(j) to a Variant which is not an array.
You need to:
ReDim myArr(1 to coll.Count)
Before your for loop and then in the loop:
myArr(j) = coll(j)
Before attempting to respond to this question, I would like to write what I believe you are trying to accomplish; when you confirm this is what you are trying to do, I will try to help you get working code to achieve it. This would normally be done with comments, but the threads of comments so far are a bit disjointed, and the code is quite complex...
You have data in a sheet (called "sheet1" - it might be something else though)
The first column contains certain values that might be repeated
You don't know how many columns there might be... you would like to know that though
You attempt to find each unique value in column A (call it the "key value"), and display it (one at a time) in a message box. This looks more like a debug step than actual functionality for the final program.
You then turn on the autofilter on column A; selecting only rows that match a certain value
Using that same value as the name of a sheet, you see if such a sheet exists: if it does, you clear its contents; if it does not, then you create it at the end of the workbook (and give it the name of the key)
You select all rows with the same (key) value in column A on sheet1, and copy them to the sheet whose name is equal to the value in column A that you filtered on
You want to repeat step 5-8 for each of the unique (key) values in column A
When all is done, I believe you have (at least) one more sheet than you had key values in column A (you also have the initial data sheet); however you do not delete any "superfluous" sheets (with other names). Each sheet will have only rows of data corresponding to the current contents of sheet1 (any earlier data was deleted).
During the operation you turn autofiltering on and off; you want to end up with auto filter disabled.
Please confirm that this is indeed what you are attempting to do. If you could give an idea of the format of the values in column A, that would be helpful. I suspect that some things could be done rather more efficiently than you are currently doing them. Finally I do wonder whether the whole purpose of organizing your data in this way might be to organize the data in a specific way, and maybe do further calculations / graphs etc. There are all kinds of functions built in to excel (VBA) to make the job of data extraction easier - it's rare that this kind of data rearranging is necessary to get a particular job done. If you would care to comment on that...
The following code does all the above. Note the use for For Each, and functions / subroutines to take care of certain tasks (unique, createOrClear, and worksheetExists). This makes the top level code much easier to read and understand. Also note that the error trapping is confined to just a small section where we check if a worksheet exists - for me it ran without problems; if any errors occur, just let me know what was in the worksheet since that might affect what happens (for example, if a cell in column A contains a character not allowed in a sheet name, like /\! etc. Also note that your code was deleting "CurrentRegion". Depending on what you are trying to achieve, "UsedRange" might be better...
Option Explicit
Sub Solution()
Dim shData As Worksheet
Dim nameRange As Range
Dim r As Range, c As Range, A1c As Range, s As String
Dim uniqueNames As Variant, v As Variant
Set shData = Sheets("Sheet1") ' sheet with source data
Set A1c = shData.[A1] ' first cell of data range - referred to a lot...
Set nameRange = Range(A1c, A1c.End(xlDown)) ' find all the contiguous cells in the range
' find the unique values: using custom function
' omit second parameter to suppress dialog
uniqueNames = unique(nameRange, True)
Application.ScreenUpdating = False ' no need for flashing screen...
' check if sheet with each name exists, or create it:
createOrClear uniqueNames
' filter on each value in turn, and copy to corresponding sheet:
For Each v In uniqueNames
A1c.AutoFilter Field:=1, Criteria1:=v, _
Operator:=xlAnd
A1c.CurrentRegion.Copy Sheets(v).[A1]
Next v
' turn auto filter off
A1c.AutoFilter
' and screen updating on
Application.ScreenUpdating = True
End Sub
Function unique(r As Range, Optional show)
' return a variant array containing unique values in range
' optionally present dialog with values found
' inspired by http://stackoverflow.com/questions/3017852/vba-get-unique-values-from-array
Dim d As Object
Dim c As Range
Dim s As String
Dim v As Variant
If IsMissing(show) Then show = False
Set d = CreateObject("Scripting.Dictionary")
' dictionary object will create unique keys
' have to make it case-insensitive
' as sheet names and autofilter are case insensitive
For Each c In r
d(LCase("" & c.Value)) = c.Value
Next c
' the Keys() contain unique values:
unique = d.Keys()
' optionally, show results:
If show Then
' for debug, show the list of unique elements:
s = ""
For Each v In d.Keys
s = s & vbNewLine & v
Next v
MsgBox "unique elements: " & s
End If
End Function
Sub createOrClear(names)
Dim n As Variant
Dim s As String
Dim NewSheet As Worksheet
' loop through list: add new sheets, or delete content
For Each n In names
s = "" & n ' convert to string
If worksheetExists(s) Then
Sheets(s).[A1].CurrentRegion.Clear ' UsedRange might be better...?
Else
With ActiveWorkbook.Sheets
Set NewSheet = .Add(after:=Sheets(.Count))
NewSheet.Name = s
End With
End If
Next n
End Sub
Function worksheetExists(wsName)
' adapted from http://www.mrexcel.com/forum/excel-questions/3228-visual-basic-applications-check-if-worksheet-exists.html
worksheetExists = False
On Error Resume Next
worksheetExists = (Sheets(wsName).Name <> "")
On Error GoTo 0
End Function