How to Find a target value in a column? - vba

Haha! I'll stump you yet Excel gurus. ;-D
I want to set a selection range boundary based on some delimiting text.
If I do Cells.Find the entire worksheet is searched and the multiple instances are found.
The one I want found is likely the 3rd or 4th instance of the delimiter. Actually it is in a specific column, B. However it is a non-contiguous range and the actual search start in the column is a few hundred cells down.
How do I search within that column and set my reusable range begin variable, set to the delimiter cell (not including the delimiter cell)?
I've tried this:
Dim selectionStart As Range, selectionEnd As Range
Dim currentCell As Range, dataRange As Range
Dim lastRow As Range, insertRows As Range, destinationCell As Range
Range("b1", Range("b65536").End(xlUp)).Select
Set selectionStart = Selection.Find(What:="<-RANGE START->", After:=ActiveCell, LookIn _
:=xlValues, LookAt:=xlPart, SearchOrder:=xlByRows, SearchDirection:= _
xlNext, MatchCase:=False, SearchFormat:=False)
It selects the range but doesn't set the variable.
I'm trying out all these things annoyingly big so I can see what they do without too much eyestrain. Elegance is not required.
TIA

Do you mean this?
Option Explicit
Sub test()
Dim selectionStart As Range, selectionEnd As Range
Dim currentCell As Range, dataRange As Range
Dim lastRow As Range, insertRows As Range, destinationCell As Range
Dim rngtoSearch As Range
Dim foundValue As Variant
Dim foundAddress As String
Dim foundRow As Long
With sheetWhatever 'change to whatever sheet codename required
Set rngtoSearch = .Range("b1", .Range("b65536").End(xlUp))
Set selectionStart = rngtoSearch.Find(What:="<-RANGE START->", LookIn _
:=xlValues, LookAt:=xlPart, SearchOrder:=xlByRows, SearchDirection:= _
xlNext, MatchCase:=False, SearchFormat:=False)
'check it actually found a range
If Not selectionStart Is Nothing Then
'If found set the variable
foundValue = selectionStart.Value 'set as value
foundAddress = selectionStart.Address 'set as address string
foundRow = selectionStart.Row ' set as row
End If
End With
End Sub

Related

Trying Set Values Instead of copying and pasting

I have been able to modify most of my VBA procedures to set ranges to equal other ranges to avoid copy and paste. It has speed up my code incredibly. However, there is a few cases where I can't figure out how to not use copy and paste. Below is one example:
Dim Creation2 As Worksheet
Dim HoleOpener As Worksheet
Dim Dal As Range
Dim Lad As Range
Dim Pal As Range
Dim LastRow As Long
Dim ws As Worksheet
Dim fndList As Variant
Dim rplcList As Variant
Set HoleOpener = Worksheets("HoleOpener")
LastRow = HoleOpener.Range("C" & Rows.Count).End(xlUp).Row
On Error Resume Next
For Each ws In ActiveWorkbook.Worksheets
If ws.Name <> "Creation2" And ws.Name <> "BitInfoTable" And ws.Name <> "DailyBitInfoTable" And ws.Name <> "BitRunInfoTable" And ws.Name <> "HoleOpener" Then
Set Lad = ws.Cells.Find(What:="StartCopy", LookIn:= _
xlFormulas, LookAt:=xlPart, SearchOrder:=xlByRows, SearchDirection:= _
xlNext, MatchCase:=True, SearchFormat:=False).Resize(21, 25).Copy
Sheets("HoleOpener").Cells(Rows.Count, "A").End(xlUp).Offset(1).PasteSpecial xlPasteValues
End If
Next
When I search I can't find any examples of doing something similar to this without copy/paste.
You will simply need to swap the ranges I listed with your ranges in FoundRange and PasteRange and adjust the Resize to fit your needs ((21, 25)). You have to use .value here as well.
Here is a generic example of how to set two ranges equal to each other. Since your range is greater than one, you will need to ensure each range is of equal size in terms of rows and columns spanned.
Dim FoundRange As Range, PasteRange As Range
Set FoundRange = Range("A1:B10") 'Swap this for your found value ("Lad" in your code)
Set PasteRange = Range("C1").Resize(10, 2) 'Swap this for your destination value
PasteRange.Value = FoundRange.Value
Using the same logic as above & your code, the result will look something like this:
Dim Lad As Range, PasteRange As Range
For Each ws In ActiveWorkbook.Worksheets
If ws.Name <> "Creation2" And ws.Name <> "BitInfoTable" And ws.Name <> "DailyBitInfoTable" And ws.Name <> "BitRunInfoTable" And ws.Name <> "HoleOpener" Then
Set Lad = ws.Cells.Find(What:="StartCopy", LookIn:=xlFormulas, LookAt:=xlPart, SearchOrder:=xlByRows, SearchDirection:=xlNext, MatchCase:=True).Resize(21, 25)
Set PasteRange = Sheets("HoleOpener").Cells(Rows.Count, "A").End(xlUp).Offset(1).Resize(21, 25)
Destiation.Value = Lad.Value
End If
Next ws
Basically, you are finding a range that meets a specific criteria, copying that range and pasting it in a new range.
This is how I would solve it without using copy/paste:
Find range as you have done
Iterate all cells in range (by column or row)
Store value of each cell in a variable
Set value of destination cell to variable stored in Step 3
As you can guess, it will require a lot more code, so it really is a trade-off between spending time writing and maintaining more code or optimizing for performance

Macro to clean Data from Blank using Autofilter with headers name

I'm trying to make a Macro to clean Dataset by deleting blank cells using an Autofilter method with a header instead of a column number. As you can see. There is no Cells number in this Macro and there will not. Everything has to be automatic. That is the idea.
I wrote 90% of the code. I arrived at the water source but I can not drink.
I got the error for the last line.
Error 1004: AutoFilter method of Range class failed.
Here is the code:
Sub DeleteBlank()
Dim WrkS As Worksheet, LsC As Range, FsC As Range, Tab As Range
Dim LsH As Range, RNbr As Long, CNbr As Long, HdrRow As Range, FltCol As Variant
Set WrkS = Worksheets("data")
' Last cells
Set LsC = Cells(Cells.Find(what:="*", SearchOrder:=xlRows, _
SearchDirection:=xlPrevious, LookIn:=xlValues).row, _
Cells.Find(what:="*", SearchOrder:=xlByColumns, _
SearchDirection:=xlPrevious, LookIn:=xlValues).Column)
' First cells
Set FsC = Cells(Cells.Find(what:="*", after:=LastCell, SearchOrder:=xlRows, _
SearchDirection:=xlNext, LookIn:=xlValues).row, _
Cells.Find(what:="*", after:=LastCell, SearchOrder:=xlByColumns, _
SearchDirection:=xlNext, LookIn:=xlValues).Column)
FsC.Activate
RNbr = ActiveCell.row
LsC.Activate
CNbr = ActiveCell.Column
'to set the last header
Set LsH = Cells(RNbr, CNbr)
' to set the header Row
Set HdrRow = Range(FsC, LsH)
Set Tab = WrkS.UsedRAnge
' to get the Column name in which I have to delete all blank
With HdrRow
FltCol = .Find(what:="name", LookAt:=xlWhole).Column
End With
' the problem is below
' Error 1004: AutoFilter method of Range class failed.
WrkS.Tab.AutoFilter Field:=FltCol, Criteria1:="="
End Sub
Can you try this? I couldn't declare a variable called "Tab". Since it was already defined as a range on WrKS you don't need the sheet reference on the AF line. Also, when using Find best to check the value is found to avoid errors. You should really use sheet references everywhere (or activate the sheet at the beginning).
Sub DeleteBlank()
Dim WrkS As Worksheet, LsC As Range, FsC As Range, Tab1 As Range
Dim LsH As Range, RNbr As Long, CNbr As Long, HdrRow As Range, FltCol As Variant
Set WrkS = Worksheets("data")
Set LsC = Cells.Find(what:="*", SearchOrder:=xlRows, SearchDirection:=xlPrevious, LookIn:=xlValues)
Set FsC = Cells.Find(what:="*", after:=LastCell, SearchOrder:=xlRows, SearchDirection:=xlNext, LookIn:=xlValues)
If Not FsC Is Nothing Then
If Not LsC Is Nothing Then
RNbr = FsC.Row
CNbr = LsC.Column
Set LsH = Cells(RNbr, CNbr)
Set HdrRow = Range(FsC, LsH)
Set Tab1 = WrkS.UsedRange
FltCol = HdrRow.Find(what:="name", LookAt:=xlWhole).Column
Tab1.AutoFilter Field:=FltCol, Criteria1:="="
End If
End If
End Sub
maybe you can shorten it down to this
Option Explicit
Sub DeleteBlank()
With Worksheets("data").UsedRange ' reference relevant worksheet "usedrange"
With Intersect(.Rows(1).Find(what:="name", LookAt:=xlWhole).EntireColumn, .Cells) 'reference its column whose top cell content is "name"
.AutoFilter Field:=1, Criteria1:="=" 'filter referenced column blank cells
If Application.WorksheetFunction.Subtotal(103, .Cells) > 1 Then .Resize(.Rows.Count - 1).Offset(1).SpecialCells(xlCellTypeVisible).EntireRow.Delete ' if any filtered cells other than first row (header) then delete their entire row
End With
.Parent.AutoFilterMode = False
End With
End Sub

VBA function: Find same string multiple times, use offset to generate sum, terminate at the end of dynamic range

I am trying to write a function which dynamically generates a range based on where the function is called from. It should then iterate from the top of that range looking for a string matching the string at the beginning of the row where the function is called from. When a string matches it will add a value two columns over to a sum. Once it has iterated through the range it should terminate.
Here I have two solutions that almost work. Both fail on the code that does the summing, but work absolutely fine otherwise. I have commented out where each fails.
Function Average_Power()
Dim rngSearch As Range, rngLast As Range, rngFound As Range, cell As Range
Dim CallerAddr As String, strFirstAddress As String, strFamilyName As String, teststring As String
Dim Sum As Double
Sum = 0
teststring = ""
With Application.Caller
CallerAddr = .Address
End With
strFamilyName = Application.Caller.Offset(0, -3).Value
Set rngSearch = ActiveSheet.Range("B15", Range(CallerAddr))
Set rngLast = rngSearch.Cells(rngSearch.Cells.Count)
Set rngFound = rngSearch.Find(What:=strFamilyName, After:=rngLast, LookIn:=xlValues, LookAt:=xlWhole, SearchOrder:=xlByRows, SearchDirection:=xlNext, MatchCase:=False, SearchFormat:=False)
For Each cell In rngSearch.Cells
' If InStr(cell.Value, strFamilyName) Then
' Sum = Sum + cell.Offset(0, 2).Value
' End If
Next cell
If Not rngFound Is Nothing Then
strFirstAddress = rngFound.Address
Sum = Sum + rngFound.Offset(0, 2).Value
Do
' Set rngFound = rngSearch.FindNext(rngFound)
' Sum = Sum + rngFound.Offset(0, 2).Value
Loop Until rngFound.Address = strFirstAddress
End If
Average_Power = Sum
End Function
This is my output at the moment since I am still trying to sum the values, I would be looking for the final Average Power to be 6000 (The sum of 4000 and 2000):
https://i.stack.imgur.com/CMHEz.png
ERROR: "There are one ore more circular references where a formula refers to its own cell either directly or indirectly. This might cause them to calculate incorrectly. Try removing or changing these references, or moving the formulas to a different cells" I guess the solution might be if there were a way to move the range one column to the left so that the function isn't iterated over? (this would still include all necessary cells to do the calculation)
I seems that .FindNext is not usable in the context of a UDF. This is not surprising; because .FindNext needs to rely on memorizing and updating some "state" on the worksheet, and UDF's are not allowed to change the state.
However, working around it with yet another .Find seems to solve the issue. Besides, I noticed that your way of iterating over the matches is not accurate. Your code can be re-written like this:
Function Average_Power() As Double
Dim rngSearch As Range, rngFound As Range
Dim strFirstAddress As String, strFamilyName As String
strFamilyName = Application.caller.Offset(0, -3).Value
Set rngSearch = Application.caller.Parent.Range("B15", Application.caller)
Set rngFound = rngSearch.Find(What:=strFamilyName, LookIn:=xlValues, LookAt:=xlWhole, SearchOrder:=xlByRows, SearchDirection:=xlNext, MatchCase:=False, SearchFormat:=False)
If rngFound Is Nothing Then Exit Function
strFirstAddress = rngFound.Address
Do
Average_Power = Average_Power + rngFound.Offset(0, 2).Value
' FindNext re-written like this will work:
Set rngFound = rngSearch.Find(What:=strFamilyName, After:=rngFound, LookIn:=xlValues, LookAt:=xlWhole, SearchOrder:=xlByRows, SearchDirection:=xlNext, MatchCase:=False, SearchFormat:=False)
'''''''''''' ^^^^^^^^^^^^^^^^
Loop Until rngFound.Address = strFirstAddress
End Function

Object Required Run-Time Error VBA when searching different workbooks

I am fairly new to VBA so a lot of my code is what I have researched on the internet and put together. A bit background to what I am trying to achieve: -
I have two works books which have an identical layout. One work book is my original where the VBA code is held and the other is a type of overlay document. I have a column with a codes in the Overlay and need to search the original work book same column for this code if its found then copy entire row from overlay into the original and deleting the row found in the original, if its not found in the original just to copy row across.
The line of code I am getting the run-time error on is: -
Set rngFound = Workbooks("OverLay").Worksheets("Overlay").Range("G:G").Find(What:=r.Value, LookIn:=xlValues, SearchOrder:=xlByRows, SearchDirection:=xlNext, MatchCase:=False)
Below is an extract of the code I am using.
Dim sht1 As Worksheet 'Current active worksheet (original version)
Dim sht2 As Worksheet 'Worksheet in OverLay
Dim rngFound As Range
Set sht2 = Workbooks("Overlay").Worksheets("Overlay")
With Workbooks("Original").Worksheets("Formatted")
lastRow = .Range("G" & .Rows.Count).End(xlUp).Row
End With
With sht2
For Each Row In .Range("G:G")
Set rngFound = Workbooks("OverLay").Worksheets("Overlay").Range("G:G").Find(What:=r.Value, LookIn:=xlValues, SearchOrder:=xlByRows, SearchDirection:=xlNext, MatchCase:=False)
If Not rngFound Is Nothing Then
rngFound.Copy
Workbooks("Original").Worksheets("Formatted").Range(rngFound).PasteSpecial
End If
Next
End With
I'll start by showing you what's wrong:
Dim sht1 As Worksheet '// <~~ This never gets used?
Dim sht2 As Worksheet 'Worksheet in OverLay
Dim rngFound As Range
Set sht2 = Workbooks("Overlay").Worksheets("Overlay")
With Workbooks("Original").Worksheets("Formatted")
lastRow = .Range("G" & .Rows.Count).End(xlUp).Row
End With
With sht2
For Each Row In .Range("G:G")
'// 'Row' in the above line will be treated as a variant as it hasn't been declared.
'// As such, it will most likely default to a Range object, which means you are
'// actually looping through each cell in that column. The lesson here is "be explicit"
'// and make sure the code is looking at exactly what you want it to look at.
Set rngFound = Workbooks("OverLay").Worksheets("Overlay").Range("G:G").Find(What:=r.Value, LookIn:=xlValues, SearchOrder:=xlByRows, SearchDirection:=xlNext, MatchCase:=False)
'// You've already set this sheet to 'sht2' so just use that instead. Also, as
'// we know - 'r' has not been set and so causes an error.
If Not rngFound Is Nothing Then
rngFound.Copy
Workbooks("Original").Worksheets("Formatted").Range(rngFound).PasteSpecial
'// 'rngFound' is already a range object, no need to wrap it in a Range() method.
End If
Next
End With
This can be re-written as such:
Dim originalWS As Worksheet '// give your variables meaningful names!
Dim overlayWS As Worksheet
Dim rngSearchParam As Range
Dim rngFound As Range
Set originalWS = Workbooks("Original").Sheets("Formatted")
Set overlayWS = Workbooks("Overlay").Sheets("Overlay")
With overlayWS
For Each rngSearchParam In Intersect(.Range("G:G"), .UsedRange)
Set rngFound = .Range("G:G").Find(rngSearchParam.Value, LookIn:=xlValues, _
SearchOrder:=xlByRows, SearchDirection:=xlNext, MatchCase:=False)
If Not rngFound Is Nothing Then
originalWS.Range(rngFound.Address).Value = rngFound.Value
End If
Next
End With
Although it seems like your searching a column, for a value defined by a cell in the same column - so not sure what the "end goal" is here. Hopefully it clarifies the issues you've been having though

.Find function and Offset cells working for constants but not formulas

The following code is similar to a Vlookup function. Was wondering why the same For Each...Next loop works when applied to Constants but not when it's applied to Formulas.
Thank you
Dim ws1 As Worksheet, ws2 As Worksheet
Dim SourceRange As Range, TargetRange As Range, TargetCell As Range,
Dim SourceCell As Range, SourceColumn As Range, TargetColumn As Range,
Dim TargetRangeConstant As Range, TargetRangeFormula As Range
On Error Resume Next
'set Worksheets and Ranges
Set ws1 = Worksheets("Sheet1")
Set ws2 = Worksheets("Sheet1")
Set SourceRange = ws1.Range("A:A")
Set TargetRange = ws2.Range("L:L")
Set SourceColumn = ws1.Range("C:C")
Set TargetColumn = ws2.Range("O:O")
Set TargetRangeConstant = TargetRange.SpecialCells(xlConstants)
Set TargetRangeFormula = TargetRange.SpecialCells(xlFormulas)
'For Constants
For Each TargetCell In TargetRangeConstant
Set SourceCell = SourceRange.Find(What:=TargetCell, LookIn:=xlValues, _
LookAt:=xlWhole, SearchOrder:=xlByRows, SearchDirection:=xlNext, _
MatchCase:=False, SearchFormat:=False)
If Not TargetCell Is Nothing Then
'"copies" cells in source to target
TargetCell.Offset(, TargetColumn.Column - TargetRange.Column) = SourceCell.Offset(, SourceColumn.Column - SourceRange.Column)
End If
Next
'Same Function but for Formulas
For Each TargetCell In TargetRangeFormula
Set SourceCell = SourceRange.Find(What:=TargetCell, LookIn:=xlFormulas, _
LookAt:=xlWhole, SearchOrder:=xlByRows, SearchDirection:=xlNext, _
MatchCase:=False, SearchFormat:=False)
If Not TargetCell Is Nothing Then
'"copies" cells in source to target
**TargetCell.Offset(, TargetColumn.Column - TargetRange.Column) = SourceCell.Offset(, SourceColumn.Column - SourceRange.Column)**
End If
Next
You should be using TargetCell.Formula in the second block. In my sample code below, A1 in Sheet1 has =SUM(B1:C1). In Sheet2, it is in D1. It returns the correct address.
Sub Test()
Dim TargetCell As Range
Dim TargetF, TestS As String
Set TargetCell = Sheet1.Range("A1")
TargetF = TargetCell.Formula
TestS = Sheet2.Cells.Find(What:=TargetF, LookIn:=xlFormulas).Address
MsgBox TestS 'Returns D1.
End Sub
Let us know if this works.