Excel VBA macro type mismatch error - vba

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

Related

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

Using Left() for variable

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.

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!

VBA Replace is Ignoring Column/Sheet Restrictions

I'm trying to use VBA for a find/replace. The goal is to iterate through a "Data_Pairs" sheet which contains all the pairs to find/replace, and to find/replace those pairs only in Column A and only in a specified range of sheets in the workbook (which does not include "Data_Pairs").
For some reason, every matching value is replaced, regardless of which column it's in. Values are also replaced in sheets whose index falls outside the defined range.
Any help would be greatly appreciated.
I'm using the following code:
Sub Replace_Names()
Dim row As Integer
Dim row2 As Integer
Dim sheet As Integer
Dim findThisValue As String
Dim replaceWithThisValue As String
For row = 1 To 10
Worksheets("Data_Pairs").Activate
findThisValue = Cells(row, "A").Value
replaceWithThisValue = Cells(row, "B").Value
For sheet = 2 To 10
Worksheets(sheet).Columns("A").Replace What:= findThisValue, Replacement:=replaceWithThisValue
Next sheet
Next row
End Sub
To give a concrete example of the issue: if Data_Pairs A1 = A and Data_Pairs B1 = 1, every single value of 1 in the entire workbook is replaced with A.
I observe this works as-expected in Excel 2010, echoing Greg and chancea's comments above.
HOWEVER, I also observe that if you have previously opened the FIND dialog (for example you were doing some manual find/replace operations) and changed scope to WORKBOOK, then the observed discrepancies will occur, as discussed here:
http://www.ozgrid.com/forum/showthread.php?t=118754
This may be an oversight, because it does not appear to have ever been addressed. While the Replace dialog allows you to specify Workbook versus Worksheet, there is no corresponding argument you can pass to the Replace method (documentation).
Implement the hack from the Ozgrid thread -- for some reason, executing the .Find method seems to reset that. This appears to work:
Sub Replace_Names()
Dim row As Integer
Dim row2 As Integer
Dim sheet As Integer
Dim findThisValue As String
Dim replaceWithThisValue As String
Dim rng As Range
For row = 1 To 10
Worksheets("Data_Pairs").Activate
findThisValue = Cells(row, "A").Value
replaceWithThisValue = Cells(row, "B").Value
For sheet = 2 To 3
Set rng = Worksheets(sheet).Range("A:A")
rng.Find ("*") '### HACK
rng.Replace What:=findThisValue, Replacement:=replaceWithThisValue
Next sheet
Next row
End Sub
You have a Worksheets("Data_Pairs").Activate inside your For ... Next loop. That would seem to indicate that the command is called 9× more that it has to be. Better not to reply on .Activate to provide the default parent of Cells.
Sub Replace_Names()
Dim rw As long, ws As long
Dim findThis As String, replaceWith As String
with Worksheets(1)
For rw = 1 To 10
findThis = .Cells(rw , "A").Value
replaceWith = .Cells(rw , "B").Value
For ws = 2 To 10 ' or sheets.count ?
with Worksheets(ws)
.Columns("A").Replace What:= findThis, Replacement:=replaceWith
end with
Next ws
Next rw
end with
End Sub
See How to avoid using Select in Excel VBA macros for more on getting away from Select and Acticate.

Search and fill the cell

I require the searching functionality in Excel Macro.
Scenario:
Step 1) Excel1.xlsx have one column as primary key which I have to find in the Excel2.xlsx
Step 2) If the matching is found in the above step, then have to take a value of another column in Excel2.xlsx (from the same row which match the step 1) and fill in the Excel1.xlsx.
Is there any VBA macro to solve this issue?
PREFACE: As noted in the commentary by #L42, functions like VLOOKUP are a simpler solution, depending on the project size and need to be dynamic. That having been said, assuming you have a NEED for VBA, as your question is tagged, I have provided this code.
Without knowing the columns you are trying to use, I have provided an example scenario. I am SURE you can streamline this more by omitting some of the variables. I have included them to allow for the names to be set more easily with minimal modifications. Feel free to reduce the amount of variables needed once you understand the concepts behind them of you desire.
In the example, I'm using Column A for the lookup value and Column D as the value to be copied to the other sheet IF the conditions are met. The Name and Date are irrelevant data just to give context. A & D are the only columns being scanned, or used.
WARNING: THE FOLLOWING LINE CAN CAUSE PROBLEMS IF YOU ENCOUNTER AN INFINITE LOOP. If you encounter an infinite loop, the normal break mode will not work. If you don't want that to be included, it's not a problem, comment out this line and you will be prompted with a BREAK when the second workbook opens. Just hit continue.
Look carefully at the naming of the sheets. You will need to input your own values.
Application.EnableCancelKey = xlDisabled 'Disables breaking when opening new book
TESTED:
Sub UpdateExternalBook()
Dim s1Sheet As Worksheet, s2Sheet As Worksheet
Dim path As String
Dim s2Name As String, s1SheetName As String, s2SheetName As String
Dim lookupVal As String, moveVal As String
Dim lastS1Row As Long, lastS2Row As Long
'CONFIGURE TO YOUR FILE PATHS HERE
path = "YOUR FILE PATH HERE" '"C:\Users\Owner\Documents\"
s2Name = "SECOND BOOK NAME WITH EXTENSION" '"Book1.xlsm" or "Book1.xlsx"
'Name your Sheets here.
s1SheetName = "MASTER" 'SOURCE BOOK SHEET NAME
s2SheetName = "Sheet1" 'SECOND BOOK SHEET NAME
Application.EnableCancelKey = xlDisabled 'Disables breaking when opening new book
Set s1Sheet = ThisWorkbook.Sheets(s1SheetName)
Set s2Sheet = Workbooks.Open(path & s2Name).Sheets(s2SheetName)
lastS1Row = s1Sheet.Range("A" & Rows.count).End(xlUp).row
lastS2Row = s2Sheet.Range("A" & Rows.count).End(xlUp).row
'BEGIN LOOPING THROUGH ORIGINAL SHEET
For lRow = 2 To lastS1Row
lookupVal = s1Sheet.Cells(lRow, "A") 'Lookup Value in Column "A"
moveVal = s1Sheet.Cells(lRow, "D") 'Value to Copy to second book
For tRow = 2 To lastS2Row
If s2Sheet.Cells(tRow, "A") = lookupVal Then
s2Sheet.Cells(tRow, "D") = moveVal 'Copy data from source to target
End If
Next tRow
Next lRow
'WRAP UP AND CLOSE SECOND WORKBOOK
'Activate and close the second workbook
s2Sheet.Activate
ActiveWorkbook.Close SaveChanges:=True
s1Sheet.Activate
End Sub
Original Sheet:
Target BEFORE ---> AFTER:
Yes you can, first use below DataPreparation in goal to create dataset, data in column A is your Excel1 dataset, columns B and C are your Excel2 dataset, column D contain solution. My solution works on single Excel file as described above, it uses Excel's vlookup function.
Sub DataPreparation()
Range("A1:A5") = Application.WorksheetFunction.Transpose(Array("a", "b", "c", "d", "e"))
Range("B1:B5") = Application.WorksheetFunction.Transpose(Array("c", "d", "a", "a", "f"))
Range("C1:C5") = Application.WorksheetFunction.Transpose(Array(3, 4, 1, 1, 6))
End Sub
Sub vLookupMacro()
On Error Resume Next
Dim r1 As Range
Dim r2 As Range
Dim rOutput As Range
Set r1 = Range("A1:A5")
Set r2 = Range("B1:C5")
Set rOutput = Range("D1:D5")
For Each x In rOutput
x.Value = Application.WorksheetFunction.VLookup(r1.Cells(x.Row, 1), r2, 2, 0)
Next x
End Sub
As new user please take the tour :
https://stackoverflow.com/tour