VBA: How can I limit a 'For Each' function - vba

I am having a macro that checks the matching values from column A and row 2 in sheet2. Based on each value in the range B3 to C6 (dynamic field may get changed (there is maximum 7 location and below that 5 roles, may appears here ) in sheet1.
Problem with my code is that my loop "j" is not working as expected... It will result in executing the code 8 to 16 times in per below scenario (where I am expected it to run only 4 times)
Sub GetRowNum()
Dim rLoc
Dim rRol
Dim LocSrch1
Dim RolSrch1
Dim disRangeLoc As Range
Dim disRangeRol As Range
Dim i
Dim j
Dim shtA As Worksheet
Dim lRow As Long
Dim lCol As Long
Dim lInter As Variant
Dim Table As Range
Set shtA = Sheets ("Sheet1") 'storing the sheets...
Set shtB = Sheets ("Sheet2")
shtA.Activate
rLoc = shtA.Range("B2").End(xlDown).Row
rRol = shtA.Range("C2").End(xlDown).Row 'the last row of the list
LocSrch1 = 2 'column A... changed if you need
Set disRangeLoc = Range(Cells(3, LocSrch1), Cells(rLoc, LocSrch1)) 'here need to change the 2 for
'1 if you do not want headers
RolSrch1 = 3 'column A... changed if you need
Set disRangeRol = Range(Cells(3, RolSrch1), Cells(rRol, RolSrch1))
For Each i In disRangeLoc 'for each item inside the list of prod going to discount
For Each j In disRangeRol
MsgBox i
MsgBox j
shtB.Activate
Set Table = shtB.Range("A1:H7")
On Error Resume Next
lRow = shtB.Application.WorksheetFunction.Match(j, Range("A:A"), 0)
On Error GoTo 0
If lRow > 0 Then
End If
On Error Resume Next
lCol = shtB.Application.WorksheetFunction.Match(i, Range("2:2"), 0)
On Error GoTo 0
If lRow > 0 Then
End If
On Error Resume Next
lInter = Application.WorksheetFunction(lCol, lRow).Value
On Error GoTo 0
If lRow > 0 Then
MsgBox Table.Cells(lRow, lCol).Value
End If
On Error GoTo 0
Next j
Next i
End Sub
My final target is to find the revenue under D7 as shown in image1 (sheet1) and this code is 1st step towards it... If someone had a better suggestion to calculate in such a simple way, kindly guide me.
Someone, please help me to correct my code... And I hope u understand my requirement... Else please ask, I will try to explain better
Thanks in advance

If you set For Each j In disRangeRol then it will take each value in the range you already defined. if you keep Set J = I.Offset(0, 1) then it will consider and check the value in 'i' if true it will take the value just right to it and won't go for Each values in disRangeRol, Try below code
Sub GetRowNum() 'find the value from Sheet2 if Location and Role matches
Dim rLoc
Dim rRol
Dim LocSrch1
Dim RolSrch1
Dim disRangeLoc As Range
Dim disRangeRol As Range
Dim I
Dim J
Dim shtA As Worksheet
Dim shtB As Worksheet
Dim lRow As Long
Dim lCol As Long
Dim lInter As Variant
Dim Table As Range
Set shtA = Sheets("Sheet1")
Set shtB = Sheets("Sheet2")
shtA.Activate
rLoc = shtA.Range("B2").End(xlDown).row
rRol = shtA.Range("C2").End(xlDown).row 'the last row of the list
'with the discounted prods
'If you do not want headers,
'use A1 here
LocSrch1 = 2 'column B... changed if you need
Set disRangeLoc = Range(Cells(3, LocSrch1), Cells(rLoc, LocSrch1)) 'here need to change the 2 for
'1 if you do not want headers
RolSrch1 = 3 'column A... changed if you need
Set disRangeRol = Range(Cells(3, RolSrch1), Cells(rRol, RolSrch1))
For Each I In disRangeLoc 'for each item inside the list of prod going to discount
Set J = I.Offset(0, 1) 'it will check the value in i if yes it will take the value just right to it
shtB.Activate
Set Table = shtB.Range("A1:H7")
On Error Resume Next
lRow = shtB.Application.WorksheetFunction.Match(J, Range("A:A"), 0)
On Error GoTo 0
If lRow > 0 Then
End If
On Error Resume Next
lCol = shtB.Application.WorksheetFunction.Match(I, Range("2:2"), 0)
On Error GoTo 0
If lRow > 0 Then
End If
On Error Resume Next
lInter = Application.WorksheetFunction(lCol, lRow).Value
On Error GoTo 0
If lRow > 0 Then
'MsgBox I
'MsgBox J
MsgBox Table.Cells(lRow, lCol).Value
RevValue = Table.Cells(lRow, lCol).Value 'it will set the values each time the loop run
End If
On Error GoTo 0
shtA.Activate ' help to make sure you feed the date in right sheet, else data will get feed to Sheet2
ActiveCell.Value = RevValue & "," & ActiveCell.Value 'this will feed the date into the field using a comma separation
Next I
shtA.Activate
End Sub
Updated the code to feed the data into specific column as well

Related

If range A value not equal to any of range D value, add range A value to range D

I would like to ask whats the logic that I should use in order to search for Range A value in Range D. If Range A value is not equal to any of Range D, would like to add it under Range D.
I had came out with this code. However for the Range1, is it possible to set to last row in range A which contain value?
Sub find()
Dim range1 As Range
Set range1 = Range("A1:A9")
Dim range2 As Range
Set range2 = Range("D:D")
Dim rgvalue1 As Variant
Dim rgvalue2 As Variant
Dim rgfound As Variant
For Each rgvalue1 In range1
Set rgfound = range2.find(rgvalue1)
If rgfound Is Nothing Then
Range("D1").End(xlDown).Offset(1, 0) = rgvalue1
End If
Next rgvalue1
Dim i As Integer
i = 2
Do Until Cells(i, 4) = ""
Cells(i, 5).FormulaR1C1 = "=INDEX(C[-3],MATCH(RC[-1],C[-4],0),0)"
i = i + 1
Loop
End Sub
Never use a name for your procedure that is already used by VBA. You name your procedure Sub find() but find is already the name of a VBA method. This will soon or later cause strange issues and is a very bad practice.
Instead use meaningful names like Sub FindMissingProductsAndCopyThem().
I suggest the following code:
Option Explicit
Public Sub FindMissingProductsAndCopyThem()
Dim ws As Worksheet
Set ws = ThisWorkbook.Worksheets("Sheet1") 'define which worksheet
Dim lRowSource As Long
lRowSource = ws.Cells(ws.Rows.Count, "A").End(xlUp).Row 'find last row in column A
Dim lRowDestination As Long
lRowDestination = ws.Cells(ws.Rows.Count, "D").End(xlUp).Row 'find last row in column D
Dim FoundRow As Long
Dim iRow As Long
For iRow = 2 To lRowSource 'loop throug data of column A
FoundRow = 0 'initialize/reset
On Error Resume Next 'next line throws an error if not matched, catch that error
'try to find/match the data of column A in column D
FoundRow = Application.WorksheetFunction.Match(ws.Cells(iRow, "A"), ws.Columns("D"), 0)
On Error GoTo 0 're-activate error reporting
'if Match threw an error, then FoundRow is still 0
If FoundRow = 0 Then 'product was not found, so add it
lRowDestination = lRowDestination + 1
ws.Cells(lRowDestination, "D").Value = ws.Cells(iRow, "A")
End If
Next iRow
End Sub
The logic would be:
Set a pointer to first cell in A
Start Loop
Find the value of pointer in D. (either using WorksheetFunction.MATCH or FIND)
If not found then
Find Next Blank Row in D
Copy pointer value to that row, column D
(optionally, blank cell in A)
Advance pointer to next cell
If you've reached the last cell in A then stop, else loop back to start of loop
Now you can translate that to VBA code

Search a range and display matches in a new column with VBA

I'm trying to write something up that will search a specific range for specific numbers.
EX:
Dim cell As Range
For Each cell In Range("E5:E112")
If InStr(cell.Value, "260") > 0 Then
DO THIS
ElseIf InStr(cell.Value, "154") > 0 Then
DO THIS
etc...
I used instr since the cell will have things like "word 1 word 2 260 word 3."
For every match it finds within that range, I want to put a certain value into the same row in a different column.
Suggestions? Thanks in advance!
Try This:
Sub testing()
Dim cell As Range
For Each cell In Range("E5:E112")
If InStr(cell.Value, "260") > 0 Then
cell.Offset(0, 2).Value = "Found 260"
ElseIf InStr(cell.Value, "154") > 0 Then
cell.Offset(0, 2).Value = "Found 154"
End If
Next
End Sub
create an array of the items you want to look up then loop that with a built in lookup function.
Then use the row number returned to find the value you want. It will be quicker
Dim lkupArr()
lkupArr = Array(260, 154)
Dim i As Long
For i = LBound(lkupArr) To UBound(lkupArr)
Dim lkuprow As Long
lkuprow = 0
On Error Resume Next
lkuprow = Application.WorksheetFunction.Match("*" & lkupArr(i) & "*", ActiveSheet.Range("E:E"), 0)
On Error GoTo 0
If lkuprow > 0 Then
MsgBox lkupArr(i) & " found on row " & lkuprow & "."
'Then just use the return to return the value from the column you want
'The following returns the value in column F on the same row.
Dim ret
ret = ActiveSheet.Cells(lkuprow, "F").Value
Debug.Print ret
End If
Next i
Maybe not the most elegant solution, however does not make extensive use of the spreadsheet, so performance wise (if you have a lot of data to process), should be better than other solutions so far.
Function SearchAndFind()
Dim wb As Workbook
Dim ws As Worksheet
Dim rngValues As Range
Dim arrRng As Variant, arrFind As Variant
Dim i As Long, j As Long, newColOffset As Long
'Adjust as needed
Set wb = ThisWorkbook
Set ws = wb.Sheets("Sheet1")
Set rngValues = ws.Range("E5:E112")
arrRng = rngValues
arrFind = Array("260", "154")
newColOffset = 2
For i = LBound(arrRng) To UBound(arrRng) 'loop through the given range, first column only
For j = LBound(arrFind) To UBound(arrFind) 'loop through items to find
If InStr(arrRng(i, 1), arrFind(j)) > 0 Then 'found the value
'Return the values
rngValues.Cells(1, 1).Offset(i - 1, newColOffset).Value = arrRng(i, 1)
Exit For
End If
Next j
Next i
End Function

Nested If statement to cut & paste rows to different worksheet

Could someone help with this code?
I'm comparing two workbooks. I've built a For loop to check to see if the unique ids in workbook1 match the ids in workbook2.
If they match I'm assigning the returned row # to variable lrow. I then need to check the value in column C for the returned row.
Depending on the value in lrow, column C I need to cut the row in workbook1, sheet1 and paste to different sheets in workbook1. I also
need to delete the row that was cut so I dont have blank rows when done.
I'm getting a syntax error on the nested Else If statements. They are all highlighted in red. I'm also getting a Compile error on
these lines that says "Must be first statement on the line".
Could you let me know what I'm missing with the nested if and also verify if my cut and paste operation is valid.
Thanks for your assistance.
Option Explicit
Sub Complete()
Dim Lastrow, Newrow As Long
Dim i, lrow As Long
Dim wb1, wb2 As Workbook
Dim ws1, ws2 As Worksheet
' Turn off notifications
Application.ScreenUpdating = False
Workbooks.OpenText Filename:="C:\workbook2.xlsx"
Set wb1 = ThisWorkbook
Set wb2 = Workbooks("workbook2.xlsx")
Set ws1 = wb1.Worksheets("Sheet1")
Set ws2 = wb2.Worksheets("Sheet1")
With wb1.Worksheets(ws1)
Lastrow = .Cells(.Rows.Count, "A").End(xlUp).Row
For i = 2 To Lastrow
If Not IsError(Application.Match(.Cells(i, "G").Value, ws2.Columns("A"), 0)) Then
lrow = Application.Match(.Cells(i, "G").Value, ws2.Columns("A"), 0)
If ws2.Cells(lrow,"C") = 18 Then
Newrow = wb1.Worksheets("Sheet3").Range("A1").End(xlDown).Row + 1
ws1.Cells(i,"G").EntireRow.Cut wb1.Worksheets("Sheet3").Cells(newrow,"A")
ws1.Cells(i,"G").EntireRow.Delete
ElseIf ws2.Cells(lrow,"C") = 23 Then
Newrow = wb1.Worksheets("Sheet4").Range("A1").End(xlDown).Row + 1
ws1.Cells(i,"G").EntireRow.Cut wb1.Worksheets("Sheet4").Cells(newrow,"A")
ws1.Cells(i,"G").EntireRow.Delete
ElseIf ws2.Cells(lrow,"C") = 24 Then
Newrow = wb1.Worksheets("Sheet4").Range("A1").End(xlDown).Row + 1
ws1.Cells(i,"G").EntireRow.Cut wb1.Worksheets("Sheet4").Cells(newrow,"A")
ws1.Cells(i,"G").EntireRow.Delete
ElseIf ws2.Cells(lrow,"C") = 36 Then
Newrow = wb1.Worksheets("Sheet5").Range("A1").End(xlDown).Row + 1
ws1.Cells(i,"G").EntireRow.Cut wb1.Worksheets("Sheet5").Cells(newrow,"A")
ws1.Cells(i,"G").EntireRow.Delete
End If
End If
Next i
End With
Workbooks("workbook2.xlsx").Close savechanges:=False
' Turn on notifications
Application.ScreenUpdating = True
' Message Box showing that process is complete.
MsgBox "Done!"
End Sub
From the last comment I made to #paulbica I corrected the line to read:
If Not IsError(Application.Match(.Cells(i, "G").Value, ws2.Columns("A"), 0)) Then
The code now runs correctly. I've update the post to reflect the changes made.
Thanks.
It's good that you solved the Type mismatch error, but there are a couple of issues left
The line With wb1.Worksheets(ws1) will throw another Type mismatch error because the Worksheets function takes the sheet name or index as an argument and ws1 is a Worksheet object, so it should be changed to With wb1.Worksheets(ws1.Name) or simply With ws1
The loop implemented like that will skip rows if they are contiguous. For example, if you start with a total of 5 rows, all of which need to be moved, in the first iteration i is 2 and row 2 will be deleted. Next iteration row 3 had become row 2 after deletion, but i is now 3, so the initial row 3 is skipped and processing moves to current row 3 which previously was 4
Depending on how much data you have your code is quite slow because it interacts with the ranges very often. For example it's extracting the value of ws2.Cells(lrow,"C") for every If branch, extracting the last row in sheets 3, 4, and 5 for every cut operation, and deleting rows one at the time
This is how I'd write the code:
Option Explicit
Public Sub Complete()
Dim i As Long, toDel As Range, copyCell As Range
Dim ws11 As Worksheet, ws13 As Worksheet, ws14 As Worksheet, ws15 As Worksheet
Dim ws13LR As Long, ws14LR As Long, ws15LR As Long
Dim wb2 As Workbook, ws21 As Worksheet, wb2row As Variant, wb2colA As Variant
Application.ScreenUpdating = False
Workbooks.OpenText Filename:="C:\workbook2.xlsx"
Set wb2 = Workbooks("workbook2.xlsx")
Set ws11 = Sheet1
Set ws13 = Sheet3: ws13LR = ws13.Cells(ws13.Rows.Count, 1).End(xlUp).Row + 1
Set ws14 = Sheet4: ws14LR = ws14.Cells(ws14.Rows.Count, 1).End(xlUp).Row + 1
Set ws15 = Sheet5: ws15LR = ws15.Cells(ws15.Rows.Count, 1).End(xlUp).Row + 1
Set ws21 = wb2.Sheets(1): wb2colA = ws21.UsedRange.Columns("A").Value2
For i = 2 To ws11.Cells(ws11.Rows.Count, 1).End(xlUp).Row + 1
wb2row = Application.Match(ws11.UsedRange.Cells(i, "G").Value, wb2colA, 0)
If Not IsError(wb2row) Then
Set copyCell = Nothing
Select Case ws21.Cells(wb2row, "C").Value2
Case 18: Set copyCell = ws13.Cells(ws13LR, "A"): ws13LR = ws13LR + 1
Case 23, 24: Set copyCell = ws14.Cells(ws14LR, "A"): ws14LR = ws14LR + 1
Case 36: Set copyCell = ws15.Cells(ws15LR, "A"): ws15LR = ws15LR + 1
End Select
If Not copyCell Is Nothing Then
With ws11.UsedRange
.Rows(i).EntireRow.Copy copyCell
If toDel Is Nothing Then
Set toDel = .Rows(i)
Else
Set toDel = Union(toDel, .Rows(i))
End If
End With
End If
End If
Next i
wb2.Close False
toDel.EntireRow.Delete
Application.ScreenUpdating = True
MsgBox "Done!"
End Sub
I moved all unnecessary operations out of the For loop, and created a new range of rows to be deleted at the end, in one operation

vba to delete blank columns in multiple worksheets.worksheet count is a variable

I am trying to delete all blank columns in multiple worksheets where the count of those worksheets are variable.
I have tried the following code and it works, however it gives me the error when it cannot locate the next sheet. I intend to share this and would like to be error free.
Sub delete_columns()
i = ThisWorkbook.Worksheets.count
a = 1
Do Until a = i
Dim MyRange As Range
Dim iCounter As Long
Set MyRange = ActiveSheet.UsedRange
For iCounter = MyRange.Columns.count To 1 Step -1
If Application.CountA(Columns(iCounter).EntireColumn) = 0 Then
Columns(iCounter).Delete
End If
Next iCounter
i = i + 1
ActiveSheet.Next.Select
Loop
End Sub
The following should work:
Sub delete_columns()
Dim ws As Worksheet
Dim MyRange As Range
Dim iCounter As Long
'Loop through each worksheet - no need to Activate or Select each one
For Each ws In ThisWorkbook.Worksheets
Set MyRange = ws.UsedRange
For iCounter = MyRange.Columns.count To 1 Step -1
'Need to reference columns of MyRange, rather than columns of the
'worksheet, because column 5 of MyRange might be column 7 of the
'worksheet (if the first used cell was in column C)
If Application.CountA(MyRange.Columns(iCounter).EntireColumn) = 0 Then
MyRange.Columns(iCounter).Delete
End If
Next iCounter
'Or, if you want to delete empty columns which exist to the left
'of the UsedRange, you could do the following
'For iCounter = MyRange.Columns(MyRange.Columns.count).Column To 1 Step -1
' If Application.CountA(ws.Columns(iCounter).EntireColumn) = 0 Then
' ws.Columns(iCounter).Delete
' End If
'Next iCounter
Next
End Sub

Match Columns on two excel worksheets and copy data

I have two data sheets within the same excel file:
Sheet1 as "Data" with 7 columns:
The second sheet is "Main" with 5 columns:
The same column to match the two files is "name". I want to have a VBA code that matches the name on both sheet and copy data from proc1 - Proc4 from sheet "Main" to sheet "data" by matching the column names on both sheets.
I searched stack overflow for similar question and here is the code that I found (modified it slightly):
Sub CopyData()
Dim shtImport As Worksheet
Dim shtMain As Worksheet
Set shtImport = ThisWorkbook.Sheets("Data")
Set shtMain = ThisWorkbook.Sheets("Main")
Dim CopyColumn As Long
Dim CopyRow As Long
Dim LastColumn As Long
'- for each column in row 1 of import sheet
For CopyColumn = 1 To shtImport.Cells(1, shtImport.Columns.Count).End(xlToRight).Column
'- check what the last column is with data in column
LastRowOfColumn = shtImport.Cells(shtImport.Columns.Count, CopyColumn).End(xlToRight).Column
'if last column was larger than one then we will loop through rows and copy
If LastColumn > 1 Then
For CopyRow = 1 To LastColumn
'- note we are copying to the corresponding cell address, this can be modified.
shtMain.Cells(CopyRow, CopyColumn).value = shtImport.Cells(CopyRow, CopyColumn).value
Next CopyRow
End If
Next CopyColumn
End Sub
This is not working the way I want it to work. Can somebody please help me with this problem. Thanks a lot!
Try this code:
Sub CopyData()
Dim shtImport As Worksheet
Dim shtMain As Worksheet
Set shtImport = ThisWorkbook.Sheets("Data")
Set shtMain = ThisWorkbook.Sheets("Main")
'From Main to Data
Dim rngImpTitles As Range
Set rngImpTitles = shtImport.Rows(1)
Dim rngImpNames As Range
Set rngImpNames = shtImport.Columns(1)
Dim CopyColumn As Long
Dim CopyRow As Long
Dim foundRow As Long
Dim foundCol As Long
On Error Resume Next
'for each column in row 1 of import sheet
For CopyColumn = 2 To shtMain.Cells(1, shtMain.Columns.Count).End(xlToLeft).Column
foundCol = rngImpTitles.Find(shtMain.Cells(1, CopyColumn).Value2).Column
If Err.Number <> 0 Then
MsgBox "Not such a col title in importsheet for " & vbNewLine & _
shtMain.Cells(1, CopyColumn)
Err.Clear
GoTo skip_title
End If
For CopyRow = 2 To shtMain.Cells(shtMain.Rows.Count, 1).End(xlUp).Row
foundRow = rngImpNames.Find(shtMain.Cells(CopyRow, 1)).Row
If Err.Number <> 0 Then
MsgBox "Not such a row name in importsheet for " & vbNewLine & _
shtMain.Cells(CopyRow, 1)
Err.Clear
GoTo skip_row
End If
If Len(shtMain.Cells(CopyRow, CopyColumn)) <> 0 Then
shtMain.Cells(CopyRow, CopyColumn).Copy shtImport.Cells(foundRow, foundCol)
End If
skip_row:
Next CopyRow
skip_title:
Next CopyColumn
End Sub