I want to know how exactly to incorporate a VLOOKUP function into my Excel worksheet via VBA; I am completely comfortable with the VLOOKUP function when entering it directly into a cell, but I am completely new to VBA.
A little info:
I have ~5500 rows of data;
I have a number of named ranges (such as catNo, catNoRange, to name but two);
I want to use VBA to check catNo against catNoRange and return the value of the sixth (6th) column in catNoRange;
I also want to know how - and where - to display the result once I have it; ideally I would like it to appear in cell J4 (and the corresponding cells down to J5500).
How can I achieve this?
Additional Info:
For those of you wondering why I don't just use a regular VLOOKUP to achieve this: I want to use VBA because I have written a script which checks to see if certain cells are empty, and if at least one of them is populated, then the VLOOKUP will execute, taking the value of the populated cell for the main search critera.
Code Update:
Following some advice, I have opted to attempt to use the Find function instead of VLOOKUP. It doesn't work; here is what I have:
Sub findCode()
Result = WorksheetFunction.Find("ABI0010P", "5012616173004", "33787")
Range("J4").Value
End Sub
For the find function you want to have something like this
Dim ws As Worksheet
Set ws = Worksheets(2)
Rowz = ws.Cells(rows.Count, 1).End(xlUp).Row
k = 2
searchvalue= ws.Cells(k, 2)
Set SEARCHRANGE= ws.Range("A2:A" & Rowz)
Set Findx = SEARCHRANGE.find(searchvalue, LookIn:=xlValues)
If Not Findx Is Nothing Then ws.Cells(k, 4) = "what ever you want"
Related
This question already has answers here:
Excel VBA Sum from Multiple Sheets
(3 answers)
Closed 3 years ago.
I need to sum values across multiple sheets. The sheets will always have different names as they are set by the date.
I have a summary sheet as the first sheet in my workbook.
From a field in my summary sheet, I'd like to read through each sheet and if the value of the fields match it adds the values together.
For example, pseudo code would be, Go through each worksheet and if SummarySheet.Range("A24").value is in range(G1:G200) sum up the corresponding cell in range("H1:H200").
I've tried sum if and sum product.
I've tried the below code. Which bombed!
For Each Cell In Range("A24:A224")
For Each ws In ThisWorkbook.Worksheets
For Each i In ThisWorkbook.ActiveSheet.Range("G2:H200")
If Cell.Value = i.Value Then
ActiveCell.Offset(0, 1).Select
Cell.Value = Cell.Value + i.Value
End If
Next i
Next ws
Next Cell
Your code is referencing "ActiveSheet" and "ActiveCell" but it's not activating anything within the loop.
It is very difficult to guess what your code is trying to do. This answer is more about good coding practice than specific recommendations. My hope is this will allow you to correct your own code.
ActiveCell.Offset(0, 1).Select serves no purpose. The ActiveCell is the cell containing the cursor. You do not position the cursor at the beginning of the routine to a first cell and you neither read from nor write to the ActiveCell during the rest of the macro.
For Each ws In ThisWorkbook.Worksheets serves no purpose since you do not use ws. In For Each i In ThisWorkbook.ActiveSheet.Range("G2:H200") did you mean to write For Each i In ws.Range("G2:H200")? I cannot see anywhere else you might have meant to use ws.
Each Cell In Range("A24:A224") references the ActiveSheet. This is whatever worksheet happens to be active when the macro was started. My guess is that Range("A24:A224") is in the summary worksheet. If the user happens to be looking at another worksheet when they start the macro, that other worksheet is the ActiveSheet and not the summary worksheet. I assume you want to accumulate totals in the summary worksheet. If the wrong worksheet is active when the macro is started, that worksheet will be corrupted beyond recovery. UnDo cannot undo what a macro has done so you will have to revert to the previous copy of the workbook. I hope you create a new copy of the workbook before trying experimental macros so you can revert to an undamaged version. You need something like:
Dim WshtSumm As Worksheet
Set WshtSumm = Worksheets("Summary")
For Each Cell In WshtSumm.Range("A24:A224")
: : : : : : :
Next
In my opinion, you should not use names like Cell because it is too similar to the reserved word Cells. You should not use names like i because such names are meaningless. Perhaps it does not matter in such a small macro but, as your macros get bigger, meaningful names will be a real help when you or someone else looks at this macro in six or twelve months. Few macros last unchanged for ever; they are updated every few months to address changing requirements. I do not know what Cell and i represent but perhaps CellSumm and CellData would be better names.
Consider ThisWorkbook.Worksheets and Range("A24:A224"). I have just told you that you should have identified which worksheet holds Range("A24:A224"). This was because your workbook holds several worksheets any of which could be active. If you have a workbook that has only one worksheet, Range("A24:A224") might be OK. Personally, I avoid unqualified ranges because it is so easy to add a new worksheet. However, is it likely that you will have two workbooks or more workbooks open? Yes, it is possible to run a macro in one workbook from another workbook but is this likely? ThisWorkbook identifies the workbook holding the macro. If you are concerned that the wrong workbook will be active, you are absolutely correct to specify which collection of worksheets you wish to access because each workbook has its own collection. However, Worksheets on its own will normally be sufficient. This is something you need to think about, particularly as your macros get more complicated: do I need to qualify this range or worksheet or anything of which you might have more than one?
Consider:
For Each ws In ThisWorkbook.Worksheets
For Each i In ws.Range("G2:H200")
I have not qualified ws because a worksheet has a property Parent which identifies its workbook. You qualified ws when you created it.
One of the worksheets in ThisWorkbook.Worksheets will be the summary worksheet. Do you want to examine cells in the summary worksheet? I assume not. You need something like:
Dim WshtSumm As Worksheet
Dim WshtOther As Worksheet
Set WshtSumm = Worksheets("Summary")
For Each WshtOther In Worksheets
If WshtOther.Name <> WshtSumm.Name Then
: : : : : : :
End If
Next
This code only examines worksheets whose name does not match the summary worksheet's name. Whenever you look through an entire collection of something, you need to ask yourself: do I really want to look at everyone?
Consider:
If Cell.Value = i.Value Then
ActiveCell.Offset(0, 1).Select
Cell.Value = Cell.Value + i.Value
End If
Where you have written ActiveCell, I suspect you meant Cell giving:
If Cell.Value = i.Value Then
Cell.Offset(0, 1).Select
Cell.Value = Cell.Value + i.Value
End If
This would make a little more sense but not much. You search Range("G2:H200") of every worksheet for a value that matches a value in Range("A24:A224") of the summary worksheet. If you find that matching value you add it to the next cell in Range("A24:A224"). You tell us nothing about the summary worksheet or the data worksheets so this may be sensible but it does not feel sensible. If you find a match on "A30", you add the value of "A30" to "A31". But then you search for a match on the amended "A31". I cannot imagine any requirement for which this would be sensible.
Even if I thought this was sensible, I don’t think it will work. If I write:
For X = 1 to 10
: :
Next
I cannot change the value of X within the For Loop. I have not tried recently but my recollection is that the attempt to change X is ignored. You are trying to change the equivalent of X for a For Each statement. I have not tried but I suspect your attempt to change Cell will fail. Even if it can change Cell you should not. Select is a slow command and you should only use it if it is essential. Try something like:
If Cell.Value = i.Value Then
Cell.Offset(0, 1).Value = Cell.Offset(0, 1).Value + i.Value
End If
I hope the above helps. If it does not, please provide details of the two ranges so I can better understand what you are trying to do.
I just tried this and it worked perfect.
Sub GenerateTheFormula()
Dim x, Formula
Formula = "=SUM(" 'Formula begins with =SUM(
For x = 3 To Sheets.Count
Formula = Formula & Sheets(x).Name & "!A1," 'Add SheetName and Cell and Comma
Next x
Formula = Left(Formula, Len(Formula) - 1) & ")" 'Remove trailing comma and add parenthesis
Range("B1").Formula = Formula 'Where do you want to put this formula?
End Sub
Idea from here:
Excel VBA Sum from Multiple Sheets
I have a table with column A containing incrementing numerical values, and column B being a bunch of names. I need to filter the table according the names, and have column C update with the difference between the value in column A in the current row and the cell above..
For example, I'd like to have something like this
which,
when filtered according to the Name column, should update the difference like so
I have tried to use SUBTOTAL function in a few different ways but to no avail. Ideally it'd update once the filter in the table is changed. I tried to do this in VBA but so far I've gotten macro that only filters with the hard-coded filter criteria.
Solutions in either excel formulas/python/vba are all welcomed and greatly appreciated!
I apologise in advance if this question isn't up to standards as Im new here :) Thank you in advance!
#JvdV: This is the outcome of me trying to implement your formula, This is after filtering.
REVISED ANSWER
So after your explenation I have looked into a formula that will give you the difference of the current row B-value minus the B-value of occurance of the A-value before that.
=IFERROR(B2-LOOKUP(2,1/($A$1:A1=A2),$B$1:B2),0)
Taking your sample data, it would look like this:
Then when you apply the filter, it would look like this:
So with this workaround you dont have the correct value when no filter is applied, but in this case I assumed you are interested in the difference when it IS filtered!
The formula is entered in cell C2 and dragged down.
EDIT
If this is not the answer you'r after and you DO need the values when it is not filtered, make use of a UDF like below:
Public Function LastVisibleCell(CL As Range) As Long
Dim RW As Long, X As Long
RW = CL.Row - 1
On Error GoTo 1
If RW > 1 Then
For X = RW To 1 Step -1
If ActiveSheet.Rows(X).EntireRow.Hidden Then
Else
LastVisibleCell = Cells(CL.Row, 2).Value - Cells(X, 2).Value
Exit For
End If
Next X
Else
1: LastVisibleCell = 0
End If
End Function
Call it from cell C2 like: =LastVisibleCell(A2) and drag down. When you apply your filter, the cells will update.
Beware, this will take ages to update on large datasets!
After 3 days of intense (albeit ineffective) Google-ing, I finally came across this answer also on stack overflow.
However, as I'm working on a large set of data (>150,000 rows), the method in the question uses too much memory. Using VBA to paste the formulas into visible cells only does not seem to alleviate the problem.
Sub CopyPasteFormula()
Dim Ws As Worksheet
Dim LRow As Long
Dim PasteRng As Range
Set Ws = Worksheets("Translated Data")
Ws.Range("$D$2:$D$200000").AutoFilter Field:=4, Criteria1:="<>-", Operator:=xlFilterValues
LRow = Ws.Range("D" & Rows.Count).End(xlUp).Row
Set PasteRng = Ws.Range("A3:A" & LRow).SpecialCells(xlCellTypeVisible)
Ws.Range("A3").Copy
PasteRng.PasteSpecial xlPasteFormulas
Application.CutCopyMode = False
End Sub
Above is my macro code to attempt reduce the memory use... Appreciate any feedback!
EDITED WITH BETTER EXAMPLE
I'm trying to use the Evaluate function to evaluate a formula reference for a named range. However, when using the Evaluate function, if you do not explicitly state the sheet reference along with the cell reference, it will assume the active sheet as the cell reference. This causes the wrong result
In my real project I'm trying to only evaluate a part of the named range's formula, so it makes it even trickier.
Using a basic example of what I'm trying to do, let's say you have the following formula in Sheet 1 cell A1 whose name is MyCell:
="Don't evaluate this part"&"My Result Is " & A2
If the Active Sheet is Sheet 2 and you run the following code it will give you the wrong results (this is a quick and dirty example to illustrate the problem)
Dim s As String
s = Replace(Range("MyCell").Formula, """Don't evaluate this part""&", "")
Debug.Print Evaluate(s)
Instead of giving me the value that is in cell A2 of Sheet 1, it gives me the value that is in cell A2 of Sheet2.
Any ideas around this?
This is closest I found, but it is not my exact problem (despite similar titles) and it doesn't provide a solution:
Excel VBA evaluate formula from another sheet
The problem you are having is that by design Excel will assume all unspecific cell references are referring to the existing worksheet. This is why whenever possible it is recommended to explicitly state the worksheet in all code.
The cleanest way (verified with some MSDN definintion hunting) is to just explicitly state the worksheet without activating it:
Sub test2()
Debug.Print Range("MyCell").Worksheet.Evaluate(Range("MyCell").Formula)
End Sub
Alternatively this code will change the active worksheet to the correct one and then change it back after evaluation. Not recommended to perform sheet activations like the code below without extenuating circumstances. Not even here.
Sub test()
Dim ws As Worksheet
Set ws = ActiveSheet
Dim s As String
s = Replace(Range("MyCell").Formula, """Don't evaluate this part""&", "")
Range("MyCell").Worksheet.Activate ' Don't remember if .Worksheet or .Parent ??
Debug.Print Evaluate(s)
ws.Activate
End Sub
As pointed out in the comments by ThunderFrame, it is important to remember that this code assumes MyCell is a simple cell reference as stated in the question. Otherwise you will need to use other methods to determine the target worksheet name (or hardcode it).
Its nearly always better to use Worksheet.Evaluate rather than the default Application.Evaluate: as Mark Balhoff points out that allows you to control unqualified references.
But Worksheet.Evaluate is also usually twice as fast as Application.Evaluate.
See my blog post here for details
https://fastexcel.wordpress.com/2011/11/02/evaluate-functions-and-formulas-fun-how-to-make-excels-evaluate-method-twice-as-fast/
Your line:
Debug.Print Evaluate(Range("MyCell").Formula)
is equivalent to:
Debug.Print Evaluate("=""My Result Is "" & A2")
which is why you get results according to the value of A2 in the ActiveSheet.
If you want to inspect the contents of the formula, you can use this line:
Debug.Print [MyCell].Formula
If you want the value of MyCell with respect to Sheet1, then you have 2 options:
1 - Use Debug.Print Range("Sheet1!MyCell").Value
2 - Use Debug.Print Sheet1.Range("MyCell").Value
I'm new to Excel VBA, and this is my first attempt to make a useful macro to speed up my work process.
What I'm trying to do
I have a data file of abut 15 thousand rows by about 15 columns. What I would like my macro to do is once I hit a button on a separate sheet, the code takes a string I have typed into a specific cell on that sheet, goes to the sheet with all of the data on, then uses the find function on one of the columns to find all instances of the string which I have defined.
Once all of the instances of the string have been located, I want to copy the corresponding rows and paste them into the sheet I ran the macro from.
To clarify, the column I want to locate the string in contains descriptions typed by people - there isn't just one word to look at; that is why I have been trying to use the Find function.
My attempt so far:
Sub FindTextBasicData()
'Define variables
Dim sourceSht As Worksheet
Dim outputSht As Worksheet
Dim strSearch As String
Dim searchRange As Range
Dim outputRange As Range
'Set the sheet variables to those present in the workbook
Set sourceSht = Sheets("Basic Data")
Set outputSht = Sheets("Output")
'Set the value of the string variable to the contents of cell C2 in the output sheet
strSearch = outputSht.Range("C2")
'Set the range variable to the range in the data sheet that I want to check
Set searchRange = sourceSht.Range("C6:C15448")
'Use the Find function to look through the range I defined and select all rows where the
'string variable can be found, setting the second range variable to these values
Set outputRange =searchRange.Find(What:=strSearch, After:=.Cells(3, 6), LookIn:=xlFormulas, _
LookAt:=xlWhole, SearchOrder:=xlByRows, SearchDirection:=xlNext, _
MatchCase:=False).EntireRow
'Copy the results of the Find function to the clipboard
outputRange.Copy
'Select the 5th row of the output sheet as the location to paste the data, and then paste
outputSht.Select
Rows(5).Select
ActiveSheet.Paste
End Sub
I know that I'm definitely doing something wrong with the find function, but I can't figure the thing out - I think my problem lies with the After parameter in that it doesn't do what I think it does (references cell C6 as the place to start using Find from?). I tried looking at the guide to the Find function on Ozgrid, but I think I just confused myself more.
If I can get this macro to work correctly I will be able to use it a lot to vastly streamline the analysis of data that I have to do for these 15000 records. Any help for this would be greatly appreciated, and I'm of course happy to clarify if I haven't explained something well enough.
The reference .Cells(3, 6) needs to be qualified using a With block or just directly refer to a Worksheet or Range object. Easiest solution here would be sourceSht.Cells...
Also, Cells(3, 6) is cell F3 whereas you want cell C6. Put these together and you should therefore have After:=sourceSht.Cells(6, 3)
As mentioned above. You use dot operator in front of .Cells(3, 6) without With. The best way is to reference it to concrete sheet directly sourceSht in your case. If you want to reference to cell C6 then you can use for example :
sourceSht.Range("C6") or sourceSht.Cells(6,3) or sourceSht.Cells(3,"C") etc..
But I think that shouldnt cause problem (provided the reference is valid) because After parameter is not relevant (and optional) if all you want to do is search in the whole range. In fact only What is required parameter.
set outputRange = searchRange.Find(strSearch).EntireRow should do the trick. Moreover if you specify After parameter, the search doesnt look into that cell.
Anyway, this only gives you the first cell in the row in which the string was found. Not all of them. You might want to put the search code then into a cycle in combination with FindNext method or just using the After parameter of Find method.
Instead of the Range.Find method, use the Range.AutoFilter method to filter the rows on the first sheet based on the value on the second sheet, then copy only the visible rows. IMHO, this is better than the Range.Find method (which is still better than looping).
There are numerous examples on this site about how to copy visible rows to another sheet, here is one example: Excel Macros - Copy and paste filtered rows
This question stems off another post I had. (see Search through column in excel for specific strings where the string is random in each cell)
Using the above image as reference, I am trying to search through column B (actually over 1000 lines) using column E as the "lookup values." The end goal would be for "just" the names to be displayed in column C. The trick is all the randomly generated characters the encompass the names. Below is what I would want the datasheet to look like. A formula or module should work, but the vlookup and other lookup function I can't get to work.
For a worksheet function approach, you could enter in C3 and fill down this formula:
=LOOKUP(8^5,SEARCH(E$3:E$7,B3),E$3:E$7)
The constant 8^5=32768 is chosen to be larger than the maximum possible string length so that LOOKUP returns the last matching value. The formula returns #N/A if no string is found.
Another possibility, which may be easier to understand then assylias post initially, but also may be a bit more time consumptive (although with 1,000 rows, I don't think it will matter much) is below.
This requires that you name the range in column E as myNames (or whatever name you wish, just update the code - alternatively, you cuold just write Range("E1:E6")). Also, if you move the random values from column B, update that in the code as well.
Sub findString()
Dim celString As Range, rngString As Range, celSearch As Range, rngSearch As Range
Dim wks As Worksheet
Set wks = Sheets("Sheet1") 'change sheet reference to whatever your sheet name is
Set rngString = wks.Range("myNames")
Set rngSearch = Intersect(wks.UsedRange, wks.Range("B1").EntireColumn)
For Each celString In rngString
For Each celSearch In rngSearch
If InStr(1, celSearch.Text, celString.Value) > 0 Then
celSearch.Offset(, 1) = celString.Value
End If
Next
Next
End Sub
Since, I worked on your original question as well, I would suggest getting the counts through Siddharth's answer and then running this, or assylias's code above to get the names next to the columns. You could put a button the sheet, or just use the Macro dialog box to run the macro.