Iterate Over All Cells That Are Found in Do Until Loop - vba

I am attempting to find duplicated values and in a column in excel and put 3 values in the corresponding columns next to the item. The problem I am struggling with is that my search range is finding the same range over and over again rather than moving on to the next range that's found to contain the string or setting the replaceValuesRange to Nothing.
I subsequently decided to change the string value to MainInputSHeet.Cells(loopCounter, 4).Value = findString & " FOUND" in an attempt to change the value of the string, but I still get stuck in the find loop, which I assume is because the string is still existing within the cell.
Any ideas? The issue exists in the Do Until Loop. Everything else is working fine
This is what the immediate window looks like iterating through the DoLoop:
$D$5
$D$5
$D$5
$D$5
$D$5
$D$5
$D$5
$D$5
$D$5
$D$5
Main code block
Option Explicit
Public Const DIR_SDR_PRODUCT_DEFINITIONS_FILEPATH As String = "U:\Research_Dev Docs\DevFolder\SDR Sheet\SDRProductDefinitionsICE.xlsx"
Public Sub TranslateAndPullProductInformation()
Dim lastRow As Integer, loopCounter As Integer
Dim sdrICEDefinitions As Workbook
Dim DefinitionsSheet As Worksheet
Dim findString As String, productTypeString As String, marketTypeString As String, contractTypeString As String
Dim searchRange As Range, findValuesRange As Range, replaceValuesRange As Range
'Checks file exists in specificed path, assigns workbook name, assigns proper worksheet for information definitions
If Len(Dir(DIR_SDR_PRODUCT_DEFINITIONS_FILEPATH)) = 0 Then GoTo BAIL_OUT
Set sdrICEDefinitions = Workbooks.Open(DIR_SDR_PRODUCT_DEFINITIONS_FILEPATH)
Set DefinitionsSheet = sdrICEDefinitions.Sheets(1)
Set searchRange = DefinitionsSheet.Range("A:A")
lastRow = MainInputSheet.Cells(MainInputSheet.Rows.Count, "D").End(xlUp).Row
For loopCounter = 2 To lastRow
If IsEmpty(MainInputSheet.Cells(loopCounter, 3)) = True Then
findString = MainInputSheet.Cells(loopCounter, 4).Value
Set findValuesRange = searchRange.Find(findString)
Set replaceValuesRange = MainInputSheet.Range(MainInputSheet.Cells(2, 4), MainInputSheet.Cells(lastRow, 4)).Find(findString)
Do Until replaceValuesRange Is Nothing
'Product Name
MainInputSheet.Cells(loopCounter, 1) = findValuesRange.Offset(0, 1).Value
'Market Type
MainInputSheet.Cells(loopCounter, 2) = findValuesRange.Offset(0, 2).Value
'Contract Type
MainInputSheet.Cells(loopCounter, 3) = findValuesRange.Offset(0, 3).Value
MainInputSheet.Cells(loopCounter, 4).Value = findString & " FOUND"
Set replaceValuesRange = MainInputSheet.Range(MainInputSheet.Cells(2, 4), MainInputSheet.Cells(lastRow, 4)).FindNext
Debug.Print replaceValuesRange.Address
Loop
End If
Next
Exit Sub
BAIL_OUT:
MsgBox ("ProductDefinitions File not found")
End Sub

Your problem is, that
Set replaceValuesRange = MainInputSheet.Range(MainInputSheet.Cells(2, 4), MainInputSheet.Cells(lastRow, 4)).FindNext
will start searching from the begging every time. To continue searching (show next occurrence of searched value) you have to use:
Set replaceValuesRange = MainInputSheet.Range(MainInputSheet.Cells(2, 4), MainInputSheet.Cells(lastRow, 4)).FindNext(replaceValuesRange)
Now the problem is, that the Do Until loop will go infinitely, but for every cell with searched value. I am not sure if there is some smarter way to stop the loop, but I just save row number of first found cell to fstRow variable, and then change StopStop variable for True when the loop will find this first cell once more:
Dim StopStop As Boolean
Dim fstRow As Long
StopStop = False
Set replaceValuesRange = MainInputSheet.Range(MainInputSheet.Cells(2, 4), MainInputSheet.Cells(lastRow, 4)).Find(findString)
If Not replaceValuesRange Is Nothing Then fstRow = replaceValuesRange.Row
Do Until replaceValuesRange Is Nothing Or StopStop
Set replaceValuesRange = MainInputSheet.Range(MainInputSheet.Cells(2, 4), MainInputSheet.Cells(lastRow, 4)).FindNext(replaceValuesRange)
If replaceValuesRange.Row = fstRow Then StopStop = True
Loop

I wrote an article recently about Find, but it's in Portuguese, I think it won't help you.
In that article I've put an example code, which you can study and understand how it does work:
Sub FindTest()
Dim Interval As Range
Dim Value As String
Dim Result As Range
Dim PreviousResult As Range
Set Interval = Range("A1:A1000")
Value = "Pedro*"
Set Result = Intervalo.Find(Value, LookIn:=xlFormulas, LookAt:=xlWhole, _
SearchOrder:=xlByColumns)
If Result Is Nothing Then
Debug.Print "Value not found"
Exit Sub
End If
Do
Debug.Print Result.Value
Set PreviousResult = Result
Set Result = Interval.Find(Value, After:=PreviousResult, _
LookIn:=xlFormulas, LookAt:=xlWhole, SearchOrder:=xlByColumns)
Loop Until Result.Row < PreviousResult.Row
End Sub
It is a good idea to read Find documentation to understand each parameter. I hope that it helps you.

It looks to me like your problem is that you have set replaceValuesRange as a range object. You then use the do loop to run until that range is nothing. The problem is that the range is still an object and will stay an object until you do something else to that variable. I would change this to another for loop and iterate through the range.
Maybe something like
for i = 2 to lastrow 'your variable range starts at row 2 and goes until the last row.
code here
next i

Related

How to append address of current cell to range object

I've been trying to write a function that goes through an Excel worksheet to find a range of cells fulfilling a certain condition (two cells in the same row that have to be equal).
I've written the following code that goes through the Worksheet row by row and checks if the condition is fulfilled.
If a cell is found for which the condition is true I would like the address of the cell to be added to a range.
The output of the function should finally be this range which is subsequently used to populate a dropdown menu in a dialog with the entries fulfilling the condition.
Private Function DetermineRange(WorksheetName As String, Column1 As Integer, Column2 As Integer) As Range
Dim rng As Range
'Go through rows of specified worksheet
For currRow = 1 To Worksheets(WorksheetName).Cells(Rows.Count, 3).End(xlUp).Row
'Compare cells in specified columns of current row
If Worksheets(WorksheetName).Cells(currRow, Column1).Value = Worksheets(WorksheetName).Cells(currRow, Column2).Value _
And Not (Worksheets(WorksheetName).Cells(currRow, Column1).Value = "") Then
'If cells are equal, but not empty, append current adress of current cell to range
If Not rng Is Nothing Then
Set rng = Union(rng, Worksheets(WorksheetName).Cells(currRow, 2))
Else
Set rng = Worksheets(WorksheetName).Cells(currRow, 2)
End If
End If
Next currRow
If Not rng Is Nothing Then
'return found Range
Set DetermineRange = rng
MsgBox ("Range is: " & rng)
Else
'DEBUG: Throw error message if rng is empty,
MsgBox ("DEBUG DetermineRange Function:" & vbCrLf & _
"Error! No corresponding Cells found in Sheet" & WorksheetName)
End If
End Function
Cycling through the rows works fine, however I don't seem to be able to add the addresses for the cells after the condition is checked to the range object.
I have also tried the following, which results in a
Runtime error 424: Object required
'If cells are equal, but not empty, append current address of current cell to range
If Not rng Is Nothing Then
Set rng = Union(rng, Worksheets(WorksheetName).Cells(currRow, 2).Address)
Else
Set rng = Worksheets(WorksheetName).Cells(currRow, 2).Address
End If
I've been looking around, but can't seem to find much information on how to add cells to range objects however...
Maybe one of you could help! Any kind of pointer in the right direction is highly appreciated!
Thanks in advance for any kind of help!
Edit:
I am calling the function like this:
Set NameRng = DetermineRange("Features", ProjectColumn, TCGroupColumn)
cb_FcnName.RowSource = Worksheets(3).Name & "!" & NameRng.Address
But I get the following error:
Runtime Error 380: Not able to set property RowSource
One method is to capture the cell addresses. Concatenate these and use the final value to build a new range.
Example:
Public Function DetermineRange(WorksheetName As String, Column1 As Integer, Column2 As Integer) As Range
Dim rng As Range
Dim currRow As Integer
Dim targetSheet As workSheet ' Shortcut to requested sheet.
Dim matchesFound As String ' Address of matching cells.
' This line will raise an error if the name is not valid.
Set targetSheet = ThisWorkbook.Sheets(WorksheetName)
'Go through rows of specified worksheet
For currRow = 1 To targetSheet.UsedRange.Rows(targetSheet.UsedRange.Rows.Count).Row
'Compare cells in specified columns of current row
If targetSheet.Cells(currRow, Column1).Value <> "" Then
If targetSheet.Cells(currRow, Column1).Value = targetSheet.Cells(currRow, Column2).Value Then
' Capture address of matching cells.
If Len(matchesFound) > 0 Then
matchesFound = matchesFound & "," & targetSheet.Cells(currRow, Column1).Address
Else
matchesFound = targetSheet.Cells(currRow, Column1).Address
End If
End If
End If
Next currRow
' DEBUG: Throw error message if no matches found.
If Len(matchesFound) = 0 Then
Err.Raise vbObjectError + 101, "DetermineRange", "No matching cells found."
End If
' Return range.
Set DetermineRange = targetSheet.Range(matchesFound)
End Function
The code is a little rough and ready. I can't help but feel there are few too many lines. But the basic approach works.

Iterating through row range group data and take an action

I am getting to know Excel VBA. I have a working program that uses an action button on one sheet opens a source workbook and data worksheet, selects data and puts that into a second workbook and destination sheet. I then sort the data as needed and it looks like this
Destination sheet, sorted and annotated duplicates
I am now trying to select the data based on col 2 "B" where the items are duplicated and/or not duplicated then perform an action (send an email to the manager about the staff under their control). I can get an email to work but its selecting the data that I'm having trouble with.
the output data would be col 1 & col 3 to 5 e.g.
Dear Manager1,
you staff member/s listed below have achieved xyz
Person1 22/06/2017 11/08/2017 22/08/2017
Person11 22/06/2017 11/08/2017 22/08/2017
Person15 22/06/2017 11/08/2017 22/08/2017
congratulations....
So what I hope somebody can help me with is a clue how I get to look at the data in col 2
add the Row data required to an array or something then to check the next Row add it to the same something until it is different to the next Row Pause do the action then do the next iteration. Resulting in:
Manager1 .....Person 1,11,15action
Manager10 ..... Person 10action
Manager2 ..... Person 12,16,2,25,28action
Manager3 ..... Person 13,17,26,29,3action
until last line is reached.
I am so confused with arrays / lookups and loops I have lost the plot somewhere along the way.
I have a variable lastTmp which tells me the last line of data in the set, this will vary each month.
The Range is:
Set rng1 = Range("B5:B" & Cells(Rows.Count, "B").End(xlUp).row)
The last piece of my working code is:
Dim lp As Integer
lp = 1
For Each cell In rng1
If 1 < Application.CountIf(rng1, cell.Value) Then
With cell
.Offset(0, 4) = "duplicate : "
.Offset(0, 5) = lp
End With
Else
With cell
.Offset(0, 4) = "NOT duplicate : "
.Offset(0, 5) = 0
End With
End If
Next cell
You will be better placed to confront confusion if you do your indenting more logically. Related For / Next, If / Else / End If and With / End With should always be on the same indent level for easier reading. I rearranged your original code like this:-
For Each Cell In Rng1
If 1 < Application.CountIf(Rng1, Cell.Value) Then
With Cell
.Offset(0, 4) = "duplicate : "
.Offset(0, 5) = lp
End With
Else
With Cell
.Offset(0, 4) = "NOT duplicate : "
.Offset(0, 5) = 0
End With
End If
Next Cell
It now becomes apparent that the With Cell / End With need not be duplicated. I have further presumed that your variable lp actually was intended to hold the count. That made me arrive at the following compression of your code.
Dim Rng1 As Range
Dim Cell As Range
Dim lp As Integer
' the sheet isn't specified: uses the ActiveSheet
Set Rng1 = Range("B5:B" & Cells(Rows.Count, "B").End(xlUp).Row)
For Each Cell In Rng1
With Cell
lp = Application.CountIf(Rng1, .Value)
.Offset(0, 4) = IIf(lp, "", "NOT ") & "duplicate : "
.Offset(0, 5) = lp
End With
Next Cell
Consider using a Dictionary or Collection, whenever, checking for duplicates.
Here I use a Dictionary of Dictionaries to compile lists of Persons by Manager.
Sub ListManagerList1()
Dim cell As Range
Dim manager As String, person As String
Dim key As Variant
Dim dictManagers As Object
Set dictManagers = CreateObject("Scripting.Dictionary")
For Each cell In Range("B5:B" & Cells(Rows.Count, "B").End(xlUp).Row)
manager = cell.Value
person = cell.Offset(0, -1).Value
If Not dictManagers.Exists(manager) Then
dictManagers.Add manager, CreateObject("Scripting.Dictionary")
End If
If Not dictManagers(manager).Exists(person) Then
dictManagers(manager).Add person, vbNullString
End If
Next
For Each key In dictManagers
Debug.Print key & " -> "; Join(dictManagers(key).Keys(), ",")
Next
End Sub
I recommend you wanting Excel VBA Introduction Part 39 - Dictionaries
Assuming your data is as in the image
Then following code will give you result as in the image below.
Sub Demo()
Dim srcSht As Worksheet, destSht As Worksheet
Dim lastRow As Long, i As Long
Dim arr1(), arr2()
Dim dict As Object
Set dict = CreateObject("scripting.dictionary")
Set srcSht = ThisWorkbook.Sheets("Sheet2") 'change Sheet2 to your data sheet
Set destSht = ThisWorkbook.Sheets("Sheet1") 'change Sheet1 to your output sheet
arr1 = Application.Index(srcSht.Cells, [row(1:7000)], Array(2, 1)) 'See note below
arr2 = arr1
For i = 1 To UBound(arr1, 1)
If Not dict.exists(LCase$(arr1(i, 1))) Then
dict.Add LCase$(arr1(i, 1)), i
Else
arr2(i, 1) = vbNullString
arr2(dict.Item(LCase$(arr1(i, 1))), 2) = arr2(dict.Item(LCase$(arr1(i, 1))), 2) & "," & arr1(i, 2)
End If
Next
destSht.Range("A1").Resize(UBound(arr1, 1), UBound(arr1, 2)) = arr2 'display result
destSht.Columns("a").SpecialCells(xlBlanks).EntireRow.Delete
End Sub
Note : For details on assigning range to array see this.

vba range.find method stops on random cells

I wrote this quick For Loop as part of my macro to find cells in utwbk, based on a list of values in dewbk. The loop seems to go fine for a few cells and then crashes. The problem is, it crashes for different values each time but the values DO exist in utwbk. Here is my code:
Dim utpath As String
Dim utwbk As Workbook
Dim ogfund As String
Dim ogcell As Range
Dim newfund As String
Dim newcell As Range
Dim t As Long
For t = 2 To tempfundlastrow
If dewbk.Sheets("Macro").Cells(t, 1).Value <> "" Then
Set ogcell = dewbk.Sheets("Macro").Cells(t, 1)
ogfund = Trim(ogcell.Value)
With utwbk.Sheets("Report").Range(Cells(1, 1), Range("AAA1").SpecialCells(xlCellTypeLastCell))
Set newcell = .Find(ogfund, LookIn:=xlValues, lookat:=xlWhole)
End With
newfund = newcell.Value
newcell.Offset(2, 0).Value = ogcell.Offset(0, 8).Value
newcell.Offset(3, 0).Value = ogcell.Offset(0, 9).Value
newcell.Offset(4, 0).Value = ogcell.Offset(0, 11).Value
newcell.Offset(6, 0).Value = ogcell.Offset(0, 10).Value
Else
'nothing
End If
Next t
The code crashes with run-time error 91: 'Object variable of with block variable not set' on this line:
newfund = newcell.Value
In the previous line where I define newcell, ogfund has a value and I can find that value in utwbk so really not sure what's going on. I am assuming my syntax for the .Find is incorrect but I do not know how to rectify this. As usual, any help is greatly appreciated!
After the line
Set newcell = .Find(ogfund, LookIn:=xlValues, lookat:=xlWhole)
Type this
If newcell Is Nothing Then
MsgBox "Not found"
Exit Sub
End If
If you see the message box that means .Find couldn't find the search text and since it couldn't find, the newcell.Value will break the code as newcell is Nothing
BTW if you look for a word BLAH and in your cell you have BLAH with leading and trailing spaces then your .Find will not find the word because you are using lookat:=xlWhole. Maybe you want lookat:=xlPart
It's a common mistake. In
utwbk.Sheets("Report").Range(Cells(1, 1), Range("AAA1").SpecialCells(xlCellTypeLastCell))
Cells(1, 1) and Range("AAA1") refer to ranges in the currently active sheet
What you probably want is more like
With utwbk.Sheets("Report")
With .Range(.Cells(1, 1), .Range("AAA1").SpecialCells(xlCellTypeLastCell))
Set newcell = .Find(ogfund, LookIn:=xlValues, lookat:=xlWhole)
End With
End With
or just
With utwbk.Sheets("Report").UsedRange
Set newcell = .Find(ogfund, LookIn:=xlValues, lookat:=xlWhole)
End With

Find value in many tables, then act on first blank row within matched table

Background: I am writing a macro that copies information from a change log into the matched row within a master file made of several sheets which each contain one table. I have the changes written and working, but need to also include a procedure for when the change request is to add a new row. I have a list in Col A that matches with the request, so I need to search for a matched Col A value within the file, then go to the first blank row in the table (there is a lot of empty space still held within the tables), then copy over the relevant information.
Function AddMatch(LOBID As String) As Range
Dim arrSheets, s As Worksheet, f As Range
Dim addr1 As String
arrSheets = Array("Sheet names all go here")
For Each s In arrSheets
Set s = ActiveWorkbook.Sheets(s)
Set f = s.Columns("A").Find(LOBID, Cells(Rows.Count, "A"), xlValues, xlWhole)
If Not f Is Nothing Then
addr1 = f.Address()
Do
Set AddMatch = f.EntireRow
Exit Function
Set f = s.Columns("A").Find(LOBID, f, xlValues, xlWhole)
Loop While f.Address() <> addr1
End If
Next s
Set AddMatch = Nothing
End Function
I have this function which finds me the row within the sheets where LOBID is a match. It may be overcomplicated, but I used the same function with two criteria for another action so it was an easy copy-paste. I need to act upon the first blank row within the table where that match was found, which is where I'll be adding the new information (copied from the change log).
This returns the first row with a match in Col A.
Set destRNG = AddMatch(CStr(changeWS.Cells(i, "A")))
I have a feeling there's a very simple addition to get me from this row to the first blank within the table, but I keep getting a Method 'Range' of object'_Global' error on just about everything I've tried. Is there a simpler way to do this? Thank you in advance for your help.
FOLLOW-UP: Trying to copy a range from the change log to the new row. This throws a Method 'Range' of object'_Global' error, not sure how to adjust it to work.
rw.Range(Cells(1, 1), Cells(1, 10)) = changeWS.Range(Cells(i, 1), Cells(i, 10))
Something like this:
Sub Tester()
Dim rw As Range
Set rw = FirstEmptyRow("AAA")
If Not rw Is Nothing Then
Debug.Print "Found empty row on " & rw.Parent.Name, rw.Address()
Else
Debug.Print "Not found..."
End If
End Sub
Function FirstEmptyRow(LOBID As String) As Range
Dim arrSheets, s, rv As Range
arrSheets = Array("Sheet1", "Sheet2", "Sheet3")
For Each s In arrSheets
Set s = ActiveWorkbook.Sheets(s)
'any match on this sheet?
If Not IsError(Application.Match(LOBID, s.Columns("A"), 0)) Then
Set rv = s.Rows(2) 'start here and work down...
Do While Application.CountA(rv) > 0
Set rv = rv.Offset(1, 0)
Loop
Exit For
End If
Next s
Set FirstEmptyRow = rv
End Function
EDIT:
rw.Range(Cells(1, 1), Cells(1, 10)) = changeWS.Range(Cells(i, 1), Cells(i, 10))
Here you have a problem because Cells() without a worksheet qualifier always points to the ActiveSheet, so you're essentially trying to create a range across multiple sheets, hence the error.
rw.Range(rw.Cells(1, 1), rw.Cells(1, 10)).Value = _
changeWS.Range(changeWS.Cells(i, 1), changeWS.Cells(i, 10)).Value
would be more correct, but I would prefer this type of approach:
rw.Cells(1, 1).Resize(1, 10).Value = changeWS.Cells(i, 1).resize(i, 10).Value

Store location of cell address to variable in VBA

I'm using VBA in Excel and I'm using a function to find the first empty row then adding some values, after that I need to pass the address of the cell to another function but using the code below I get a runtime error. firstEmptyRow is a function that returns a range,e.g. $A$280.
Dim addCell as Range
'Find first empty row in the table
With firstEmptyRow
'Enter Values
.Value = taskID
.Offset(0, 1).Value = jobID
.Offset(0, 2).Value = jobName
.Offset(0, 6).Value = taskTypeID
.Offset(0, 8).Value = taskName
.Offset(0, 9).Value = desc
.Offset(0, 11).Value = estMins
.Offset(0, 13).Value = "No"
set addCell = .Address //Gives a runtime error
End With
What is the correct way to save the address of the cell so I can pass it to another function? Below is the code for firstEmptyRow
Public Function firstEmptyRow() As Range 'Returns the first empty row in the Schedule sheet
Dim i, time As Long
Dim r As Range
Dim coltoSearch, lastRow As String
Dim sheet As Worksheet
Set sheet = Worksheets("Schedule")
time = GetTickCount
coltoSearch = "A"
For i = 3 To sheet.Range(coltoSearch & Rows.Count).End(xlUp).Row
Set r = sheet.Range(coltoSearch & i)
If Len(r.Value) = 0 Then
Set firstEmptyRow = sheet.Range(r.Address)
'r.Select
Exit For 'End the loop once the first empty row is found
End If
Next i
'Debug.Print "firstEmptyRow time: " & GetTickCount - time, , "ms"
End Function
The .Address property returns a string so you'll need to to set the addCell variable like so:
Set addCell = Worksheets("Schedule").Range(.Address)
I am a total "noob" at vba and learning by trying code snipits and routines that appear to to me to answer to a problem I am stuck on. When I tried the code posted by Vityata I encountered "Run-time error '424': Object Required
at the following stmts:
Set myCell = Sheet.Range(coltoSearch & i)
Set FirstEmptyRow = Sheet.Range(coltoSearch & i + 1)
Changing the stmts as follows resolved the errors for me:
Set myCell = wks.Range(coltoSearch & i)
Set FirstEmptyRow = wks.Range(coltoSearch & i + 1)
I also added a Debug.Print stmt between the last End If and the End Function stmts so I could quickly see the result when testing as follows:
End If
Debug.Print "The 1st empty cell address is "; coltoSearch & i
End Function
If I have violated some posting rule, please accept my apology. Hopefully sharing this will avoid future confusion by others who use a similar learning process.
You do not need to overcomplicate the code with something like Set addCell = Worksheets("Schedule").Range(.Address) because it really makes the readability a bit tough. In general, try something as simple as this: Set FirstEmptyRow = myCell. Then the whole code could be rewritten to this:
Public Function FirstEmptyRow() As Range
Dim myCell As Range
Dim coltoSearch As String, lastRow As String
Dim wks As Worksheet
Set wks = Worksheets(1)
coltoSearch = "A"
Dim i As Long
For i = 3 To wks.Range(coltoSearch & Rows.Count).End(xlUp).Row
Set myCell = sheet.Range(coltoSearch & i)
If IsEmpty(myCell) Then
Set FirstEmptyRow = myCell
Exit For
End If
Next i
If i = 2 ^ 20 Then
MsgBox "No empty rows at all!"
Else
Set FirstEmptyRow = sheet.Range(coltoSearch & i + 1)
End If
End Function
The last part of the code makes sure, that if there is no empty cell before the last row and row number 3, then next cell would be given as an answer. Furthermore, it checks whether this next row is not the last row in Excel and if it is so, it gives a MsgBox() with some information.