Excel VBA: Copying across unique rows from one sheet to another - vba

I'm trying to copy across 70 rows randomly selected based on certain criteria across to another sheet but ensuring only 70 unique rows exist in the second sheet once copied across.
My below code copies over the 70 rows correctly as per the required criteria but it's also copying across duplicate rows as there's no logic to select another row if there's a duplicate value in the array.
Any help on modifying the code to select another row if the row already exists in the array would be greatly appreciated.
I think I need to store the random selected rows and then check that the next selected row is not in that array already else select another row?
Sub MattWilliams()
Dim rawDataWs As Worksheet, randomSampleWs As Worksheet
Dim map, i As Long, n As Long, c As Long, rand, col
Dim rng As Range
Dim keyArr, nRowsArr
Set rawDataWs = Worksheets("Master")
Set randomSampleWs = Worksheets("Checks")
randomSampleWs.UsedRange.ClearContents
'EDIT: dynamic range in ColA
Set rng = rawDataWs.Range("AT9:AT" & rawDataWs.Cells(Rows.Count, 1).End(xlUp).Row)
Set map = RowMap(rng)
keyArr = Array("ALS", "Customer") '<== keywords
nRowsArr = Array(65, 5) '<== # of random rows
Debug.Print "Key", "#", "Row#"
For i = LBound(keyArr) To UBound(keyArr) 'loops through lower and upper bound of the KeyArr
If map.exists(keyArr(i)) Then
Set col = map(keyArr(i))
n = nRowsArr(i)
For c = 1 To n
'select a random member of the collection
rand = Application.Evaluate("RANDBETWEEN(1," & col.Count & ")")
Debug.Print keyArr(i), rand, col(rand)
If rawDataWs.Range("S" & col(rand)).Value = "FTF" Then
rawDataWs.Rows(col(rand)).Copy _
randomSampleWs.Cells(Rows.Count, 1).End(xlUp).Offset(1, 0)
If col.Count = 0 Then
If c < n Then Debug.Print "Not enough rows for " & keyArr(i)
c = c - 1
End If
Else
c = c - 1
End If
'col.Remove rand 'remove the "used" row
If col.Count = 0 Then
If c < n Then Debug.Print "Not enough rows for " & keyArr(i)
End If
Next c
Else
Debug.Print "No rows for " & keyArr(i)
End If
Next i
End Sub
'get a map of rows as a dictionary where each value is a collection of row numbers
Function RowMap(rng As Range) As Object
Dim dict, c As Range, k
Set dict = CreateObject("scripting.dictionary")
For Each c In rng.Cells
k = Trim(c.Value)
If Len(k) > 0 Then
If Not dict.exists(k) Then dict.Add k, New Collection
dict(k).Add c.Row
End If
Next c
Set RowMap = dict
End Function
If you need any more information please let me know
Regards,
Matt

You need to use an array of unique random numbers to assure that they are not the same. Unique random numbers function can be found here. (drop a upvote if useful)
Sub MattWilliams()
Dim rawDataWs As Worksheet, randomSampleWs As Worksheet
Dim map, i As Long, n As Long, c As Long, rand, col
Dim rng As Range
Dim keyArr, nRowsArr
Dim samplepattern() As Long ' dim the samplepattern
Set rawDataWs = Worksheets("Master")
Set randomSampleWs = Worksheets("Checks")
randomSampleWs.UsedRange.ClearContents
'EDIT: dynamic range in ColA
Set rng = rawDataWs.Range("AT9:AT" & rawDataWs.Cells(Rows.Count, 1).End(xlUp).Row)
Set map = RowMap(rng)
keyArr = Array("ALS", "Customer") '<== keywords
nRowsArr = Array(65, 5) '<== # of random rows
Debug.Print "Key", "#", "Row#"
For i = LBound(keyArr) To UBound(keyArr) 'loops through lower and upper bound of the KeyArr
If map.exists(keyArr(i)) Then
Set col = map(keyArr(i))
n = nRowsArr(i)
'''''''''''''''''''''''''''''''''''''''''
'solution starts here
samplepattern = UniuqeRandom(1, col.Count,n) 'see link "here"
For c = 1 To n
Debug.Print keyArr(i), samplepattern(n), col(rand)
If rawDataWs.Range("S" & col(samplepattern(n))).Value = "FTF" Then
rawDataWs.Rows(col(samplepattern(n))).Copy _
randomSampleWs.Cells(Rows.Count, 1).End(xlUp).Offset(1, 0)
' end of solution
'''''''''''''''''''''''''''''''''''''''
If col.Count = 0 Then
If c < n Then Debug.Print "Not enough rows for " & keyArr(i)
c = c - 1
End If
Else
c = c - 1
End If
'col.Remove rand 'remove the "used" row
If col.Count = 0 Then
If c < n Then Debug.Print "Not enough rows for " & keyArr(i)
End If
Next c
Else
Debug.Print "No rows for " & keyArr(i)
End If
Next i
End Sub
So basically you get a set of random numbers, all unique before hand. Then you loop through your set and copy all rownumbers that are contained in that set.
example: samplepattern() = [2,3,7,17] are 4 unique random numbers between 1 and 20. Now I go ahead and loop through all members of samplepattern() and copy the rows(samplepattern(i)). So i copy row number 2,3,7 and 17.

Related

Capture dynamic column headers and copy the cell content in repeating rows in another sheet

In WO_formatted2, i have Col C.Value = WO-1, WO-9 etc. that has matching data in WO_dump2(master data).
Some col headers in WO_dump2 are dynamically added, dumped from an application with user inputs -
estimate.estimateTableRowList.0.skillSet.skillSetId,
estimate.estimateTableRowList.0.totalEffort
estimate.estimateTableRowList.0.comment.. other similar cols with index 0.
and
estimate.estimateTableRowList.1.skillSet.skillSetId,
estimate.estimateTableRowList.1.totalEffort
estimate.estimateTableRowList.1.comment.. other similar cols with
index 1.
This continues as long as there is col data in the WO_dump2 sheet for a specific WO-1 or WO-9.
There is a fixed col for every dynamic col set -
estimate.projOsTotEffort.
There is also a calculated field in every dynamic col set -
estimate.estimateTableRowList.0.point =
(estimate.estimateTableRowList.0.totalEffort / 4.5)
I have some common cols that I have built into WO_formatted2 from WO_dump2.
Now the above dynamic col set will have to be built into WO_formatted2 starting Col I for each WO-1, WO-9 etc, built from WO_dump2.
I cannot capture any dynamic col headers, take its cell content, do some calculations & paste under specific cols in WO_formatted2, all being driven by every Col C.Value in WO_formatted2.
Hope I could make my requirement clear.
Below is what I use to build the common cols, a working piece of code -
Sub ColumnCopy()
' **** common wo field copy - based on col headers ****
Dim arrCols
Dim shtSrc As Worksheet
Dim rngDest As Range, hdr, pn
arrCols = Array("estimate.reqType", "estimate.refNumber",
"documentInfo.workorderno", _
"documentInfo.workorderDesc", "documentInfo.currentStatus.value", _
"documentInfo.woPriority", "documentInfo.woReleaseMonth", _
"documentInfo.woReleaseYear", "estimate.selectedComplexity") '<- col
headers to copy
Set shtSrc = ThisWorkbook.Worksheets("WO_dump2")
Set rngDest = ThisWorkbook.Worksheets("WO_formatted2").Range("A1")
'loop over columns
For Each hdr In arrCols
pn = Application.Match(hdr, shtSrc.Rows(1), 0)
If Not IsError(pn) Then
shtSrc.Range(shtSrc.Cells(1, pn), _
shtSrc.Cells(Rows.Count, pn).End(xlUp)).Copy rngDest
Else
rngDest.Value = hdr
rngDest.Interior.Color = vbRed
End If
Set rngDest = rngDest.Offset(0, 1)
Next hdr
End Sub
Sub MainProcessing()
'declare variables
Dim src As Worksheet, ws As Worksheet
Dim q As Integer
Dim lastRow As Long, RowToTest As Long
Set src = ThisWorkbook.Worksheets("WO_dump2")
Set ws = ThisWorkbook.Worksheets("WO_formatted2")
'filter only "Submitted" status from the main list
For RowToTest = Cells(Rows.Count, 4).End(xlUp).Row To 2 Step -1
With Cells(RowToTest, "E")
If .Value <> "Submitted" _
Then _
Rows(RowToTest).EntireRow.Delete
End With
Next RowToTest
'convert number to month name for a specific range
For x = 2 To 50000
If ws.Range("G" & x) <> "" Then
ws.Range("G" & x) = MonthName(ws.Range("G" & x), True)
ws.Range("G" & x).Value = ws.Range("G" & x).Value & " " &
ws.Range("H" & x)
End If
Next x
'delete the not-required year col after mth-year formatting
Columns("H:H").Delete
'insert 5 blank rows after each valid data row
q = 3
Do
Rows(q).Resize(5).Insert
q = q + 6
Loop Until IsEmpty(Cells(q, "C"))
End Sub

Find the cell adresses for each cell that starts with a specific number

I am looking for a code, that can find each cell that starts with the number "2347" in column L. I want to get the cell adresses for these cells and display it in a MessageBox for example "Msgbox: Cells L3500:L3722 has a value starts starts with "2347" "
Sub Findrow()
Dim MyVal As Integer
Dim LastRow As Long
MyVal = LEFT(c.Value,4) = "2347" _
LastRow = Cells(Rows.Count, "L").End(xlUp).Row
For Each c In Range("L2:L" & LastRow)
If c.Value = Myval Then
This is my code so far. Hope someone can help me!
Using arrays is quite fast
Option Explicit
Public Sub FindIDInColL()
Const VID = "2347" 'Value to find
Dim ws As Worksheet, arrCol As Variant, found As Variant
Set ws = ActiveSheet 'Or Set ws = ThisWorkbook.Worksheets("Sheet3")
arrCol = ws.Range(ws.Cells(2, "L"), ws.Cells(ws.Rows.Count, "L").End(xlUp))
ReDim found(1 To UBound(arrCol))
Dim r As Long, f As Long, msg As String
f = 1
For r = 1 To UBound(arrCol) 'Iterate vals in col L, excluding header row
If Not IsError(arrCol(r, 1)) Then 'Ignore errors
If Len(arrCol(r, 1)) > 3 Then 'Check only strings longer than 3 letters
If Left$(arrCol(r, 1), 4) = VID Then 'Check first 4 letters
found(f) = r + 1 'Capture rows containing value (header offset)
f = f + 1
End If
End If
End If
Next
If f > 1 Then 'If any cells found
ReDim Preserve found(1 To f - 1) 'Drop unused array items
msg = "Cells in col L starting with """ & VID & """" & vbNewLine & vbNewLine
MsgBox msg & " - L" & Join(found, ", L"), , "Total Found: " & f - 1
Else
MsgBox "No cells starting with """ & VID & """ found in col L", , "No matches"
End If
End Sub
Even faster when using the string versions of these functions
Left$() Mid$() Right$() Chr$() ChrW$() UCase$() LCase$()
LTrim$() RTrim$() Trim$() Space$() String$() Format$()
Hex$() Oct$() Str$() Error$
They are more efficient (if Null is not a concern), as pointed out by QHarr
You may try this:
Option Explicit
Sub Findrow()
Dim MyVal As String ' "2347" is a String
Dim LastRow As Long
Dim c As Range, myCells As Range
MyVal = "2347"
LastRow = cells(Rows.Count, "L").End(xlUp).row
Set myCells = Range("M2") 'initialize cells with a dummy cell certainly out of relevant one
For Each c In Range("L2:L" & LastRow)
If Left(c.Value2, 4) = MyVal Then Set myCells = Union(myCells, c) ' if current cell matches criteria then add it to cells
Next
If myCells.Count > 1 Then MsgBox "Cells " & Intersect(myCells, Range("L:L")).Address(False, False) & " have values starting with ‘2347’" ' if there are other cells than the dummy one then get rid of this latter and show their addresses
End Sub

Error when matching records

Problem :
Code returns 0 matches.
Code :
Sub searchNames()
Dim loc As String
Call location(loc)
Dim loadWb As Workbook
Dim loadWs As Worksheet
' ~~ Load file location
Set loadWb = Workbooks.Open(loc)
Set loadWs = loadWb.Sheets("Sheet1")
' ~~ Init rows in loaded excel
Dim lrow As Long
With loadWs
' ~~ Set range for lookup value
lrow = .Range("G" & .rows.Count).End(xlUp).Row
End With
' ~~ Loop to remove trailing spaces
Dim TrimCounter As String
Dim NewString As String
For ind = 2 To lrow
' ~~ Set rows for trim
TrimCounter = loadWs.Range("G" & ind).Value
NewString = Trim(TrimCounter)
' ~ Write trimmed values
loadWs.Range("G" & ind).Value = NewString
Next ind
' ~~ Set output worksheet
Dim ws As Worksheet
Set ws = ThisWorkbook.Sheets("ALL BRANDS")
Dim lrowWs As Long
With ws
lrowWs = .Range("D" & .rows.Count).End(xlUp).Row
End With
Dim counter As Long
Dim rows As Long
Dim nameCounter As String
counter = 0
' ~~ Get controlPointNumber in ALL BRANDS
For ind = 2 To lrowWs
' ~~ Set controlPointNumber
nameCounter = ws.Range("D" & ind).Value
' ~~ Start with row 2 in loaded Excel to omit header
For ind2 = 2 To lrow
' ~~ Check if the name matches in ALL BRANDS
If loadWs.Range("G" & ind2).Value = nameCounter Then
counter = counter + 1
End If
Next ind2
' ~~ Write the value in Worksheet 'ALL BRANDS' equal to the results
ws.Range("L" & ind).Value = counter
' ~~ Init counter to 0 and check other controlPointNumber
counter = 0
rows = rows + 1
Next ind
' ~~ Close workbook ~ Byeee
loadWb.Close False
MsgBox "Scan finished! Scanned " & rows & " rows"
End Sub
Screenshots :
Am I missing something? Any ideas?
EDIT:
Problem located. There are spaces in the values in COLUMN G
Change the part of the code like this:
For ind = 2 To lrowWs
Debug.Print lrowWs
nameCounter = ws.Range("D" & ind).value
Debug.Print nameCounter
For ind2 = 2 To lrow
If loadWs.Range("G" & ind2).value = nameCounter Then
Debug.Print loadWs.Range("G" & ind2).value
counter = counter + 1
End If
Next ind2
ws.Range("L" & ind).value = counter
Stop
counter = 0
rows = rows + 1
Next ind
Then, when you reach the stop, you should have 3 different values in the immediate window. Take a good look at them, analyze them and repair the whole code correspondingly.
Edit:
Probably the error comes from the idea, that you can use something like this:
Dim rows As Long
Thus, VBA does not know what you mean, when you say rows.Count. Long story short, change the Dim rows as Long to Dim lngRows as long and fix correspondingly everywhere.
I always worked with the .find Method. For me it's easier and if you combined it with a dictionary you can do the whole range and can be sure that no Value will be missing. The code will take the range with values from column A and will count how often the value appears in the range. Hope the code can help you.
Sub Makro1()
'Excel objects.
Dim wb As Workbook
Dim ws As Worksheet
Dim rngLockin As Range
Dim rngFind As Range
Dim idx As Integer
Dim idxRow As Integer
idxRow = 2
Dim strAddress As String
'Initialize the Excel objects.
Set wb = ThisWorkbook
Set ws = wb.Worksheets("Tabelle1")
Set dicSearch = CreateObject("Scripting.Dictionary")
LastRow = ws.UsedRange.Rows.Count
Set rngLockin = ws.Range("A2:A22").SpecialCells(xlCellTypeConstants)
For Each rngcell In rngLockin
'I Value is not in dic, insert it and start counting
If Not dicSearch.Exists(rngcell.Value) Then
dicSearch.Add rngcell.Value, ""
'Search the four columns for any constants.
'Retrieve all columns that contain X. If there is at least one, begin the DO/WHILE loop.
idx = 0
With rngLockin
Set rngFind = .Find(What:=rngcell.Value, LookIn:=xlValues)
If Not rngFind Is Nothing Then
strAddress = rngFind.Address
idx = idx + 1
rngFind.Select
'Unhide the column, and then find the next X.
Do
rngFind.EntireColumn.Hidden = False
Set rngFind = .FindNext(rngFind)
rngFind.Select
If Not rngFind Is Nothing And rngFind.Address <> strAddress Then idx = idx + 1
Loop While Not rngFind Is Nothing And rngFind.Address <> strAddress
End If
End With
Cells(idxRow, 3) = rngcell.Value
Cells(idxRow, 4).Value = idx
idxRow = idxRow + 1
End If
Next
End Sub
Fell free to ask if you have a question.

VBA/Macro to copy random rows based on multiple conditions

I need help to be able to get random rows from another workbook with specific conditions:
If i click a button/run a macro, I should get something like this :
4 random rows for all rows that has "AU"
1 random row for all rows that has "FJ"
1 random row for all rows that has "NC"
3 random rows for all rows that has "NZ"
1 random row for all rows that has "SG12"
ALL FROM Raw Data_Park Sampling.xlsx "Sheet1" sheet and paste it to Park Sampling Tool.xlsm "Random Sample" sheet.
All should happen in one click.
Below is the whole code i got.
Sub MAINx1()
'Delete current random sample
Sheets("Random Sample").Select
Cells.Select
Range("C14").Activate
Selection.Delete Shift:=xlUp
Dim rawDataWs As Worksheet, randomSampleWs As Worksheet
Dim map, i As Long, n As Long, c As Long, rand, col
Dim keyArr, nRowsArr
Dim rng As Range
Set rawDataWs = Workbooks("Raw Data_Park Sampling.xlsx").Worksheets("Sheet1")
Set randomSampleWs = Workbooks("Park Sampling Tool.xlsm").Worksheets("Random Sample")
randomSampleWs.UsedRange.ClearContents
'Set map = RowMap(rawDataWs.Range("A2:A923"))
Set rng = rawDataWs.Range("A2:A" & _
rawDataWs.Cells(Rows.Count, 1).End(xlUp).Row)
Set map = RowMap(rng)
keyArr = Array("AU", "FJ", "NC", "NZ", "SG12", "ID", "PH26", "PH24", "TH", "ZA", "JP", "MY", "PH", "SG", "VN") '<== keywords
nRowsArr = Array(4, 1, 1, 3, 1, 3, 3, 1, 3, 4, 2, 3, 1, 3, 2) '<== # of random rows
'Debug.Print "Key", "#", "Row#"
For i = LBound(keyArr) To UBound(keyArr)
If map.exists(keyArr(i)) Then
Set col = map(keyArr(i))
n = nRowsArr(i)
For c = 1 To n
'select a random member of the collection
rand = Application.Evaluate("RANDBETWEEN(1," & col.Count & ")")
'Debug.Print keyArr(i), rand, col(rand)
rawDataWs.Rows(col(rand)).Copy _
randomSampleWs.Cells(Rows.Count, 1).End(xlUp).Offset(1, 0)
col.Remove rand 'remove the "used" row
If col.Count = 0 Then
If c < n Then Debug.Print "Not enough rows for " & keyArr(i)
Exit For
End If
Next c
Else
'Debug.Print "No rows for " & keyArr(i)
End If
Next i
MsgBox "Random Sample: Per Day Successfully Generated!"
End Sub
'get a map of rows as a dictionary where each value is a collection of row numbers
Function RowMap(rng As Range) As Object
Dim dict, c As Range, k
Set dict = CreateObject("scripting.dictionary")
For Each c In rng.Cells
k = Trim(c.value)
If Len(k) > 0 Then
If Not dict.exists(k) Then dict.Add k, New Collection
dict(k).Add c.Row
End If
Next c
Set RowMap = dict
End Function
Simplified from your original code to focus on the approach:
Sub MAIN()
Dim rawDataWs As Worksheet, randomSampleWs As Worksheet
Dim map, i As Long, n As Long, c As Long, rand, col
Dim keyArr, nRowsArr, rng
Set rawDataWs = Worksheets("Sheet1")
Set randomSampleWs = Worksheets("Sheet2")
randomSampleWs.UsedRange.ClearContents
'EDIT: dynamic range in ColA
Set rng = rawDataWs.Range("A2:A" & _
rawDataWs.Cells(Rows.Count, 1).End(xlUp).Row)
Set map = RowMap(rng)
keyArr = Array("AU", "FJ", "NC", "NZ", "SG12") '<== keywords
nRowsArr = Array(4, 1, 1, 3, 10) '<== # of random rows
Debug.Print "Key", "#", "Row#"
For i = LBound(keyArr) To UBound(keyArr)
If map.exists(keyArr(i)) Then
Set col = map(keyArr(i))
n = nRowsArr(i)
For c = 1 To n
'select a random member of the collection
rand = Application.Evaluate("RANDBETWEEN(1," & col.Count & ")")
Debug.Print keyArr(i), rand, col(rand)
rawDataWs.Rows(col(rand)).Copy _
randomSampleWs.Cells(Rows.Count, 1).End(xlUp).Offset(1, 0)
col.Remove rand 'remove the "used" row
If col.Count = 0 Then
If c < n Then Debug.Print "Not enough rows for " & keyArr(i)
Exit For
End If
Next c
Else
Debug.Print "No rows for " & keyArr(i)
End If
Next i
End Sub
'get a map of rows as a dictionary where each value is a collection of row numbers
Function RowMap(rng As Range) As Object
Dim dict, c As Range, k
Set dict = CreateObject("scripting.dictionary")
For Each c In rng.Cells
k = Trim(c.Value)
If Len(k) > 0 Then
If Not dict.exists(k) Then dict.Add k, New Collection
dict(k).Add c.Row
End If
Next c
Set RowMap = dict
End Function
Not sure if I can follow the logic as it is too complex for me. If you don't mind, I worked out an alternative code.
EDIT:
I was assuming you can modify the code to get the source/destination. I tested this in excel 2013 and assuming:
the code is running from another work book (not source/destination).
Key is in the first column.
you will modify the oKey and oCnt as per your requirement.
Dim oWS As Worksheet
Dim oWSSrc As Worksheet
Dim oWBSrc As Workbook
Dim oWBDest As Workbook
Dim oRng As Range
Dim oStart As Range
Dim oLast As Range
Dim oMatch As Range
Dim oDest As Range
Dim oKey As Variant
Dim oCnt As Variant
Dim iCnt As Integer
Dim iTot As Integer
Dim iMatch As Integer
oKey = Split("AU,FJ,NZ", ",") '<= modify this
oCnt = Split("4,1,3", ",") ' <= modify this
'Open Destination
Set oWBDest = Application.Workbooks.Open("Tool.xlsm")
Set oWS = oWBDest.Sheets.Add
'Open source workbook
Set oWBSrc = Application.Workbooks.Open("Rawdata.xlsx")
Set oWSSrc = oWBSrc.Sheets("Sheet1")
Set oRng = oWSSrc.Range(Cells(1, 1), Cells(1, 1).End(xlToRight).End(xlDown))
oRng.Copy oWS.Cells(1, 1)
oWBSrc.Close
'assume key
Set oStart = oWS.Cells(1, 1)
Set oRng = oWS.Range(oStart, oStart.End(xlToRight).End(xlDown).Offset(1))
oWBDest.Sheets("Random Sample").UsedRange.Clear
Set oDest = oWBDest.Sheets("Random Sample").Cells(1, 1)
Randomize
'Assign random numbers for sorting
For iCnt = 1 To oRng.Rows.Count - 1 ' last row is a dummy row do not assign
oRng.Cells(iCnt, oRng.Columns.Count + 1) = Rnd()
Next
'sort by key (col1) and random number (last col)
With oWS.Sort
.SortFields.Clear
.SortFields.Add oWS.Columns(1)
.SortFields.Add oWS.Columns(oRng.Columns.Count + 1)
.SetRange oWS.Range(oStart, oStart.End(xlToRight).End(xlDown))
.Apply
End With
For iCnt = LBound(oKey) To UBound(oKey)
'Find the first match
Set oStart = oRng.Find(oKey(iCnt), oRng.Cells(oRng.Rows.Count, 1), xlValues, xlWhole, xlByRows, xlNext)
Set oLast = oStart ' initiliase
If Not oStart Is Nothing Then
'-1 as the first one has been detected
For iMatch = 1 To CInt(oCnt(iCnt)) - 1
Set oMatch = oRng.Find(oKey(iCnt), oLast, xlValues, xlWhole, xlByRows, xlNext)
' Match the same as start exit (means there are not enough row)
If oMatch.Address = oStart.Address Then
Exit For
Else
Set oLast = oMatch
End If
Next
'copy the match to output
Set oStart = oWS.Range(oStart, oLast.Offset(, oRng.Columns.Count - 1))
oStart.Copy oDest
If oDest.Offset(1).Value <> "" Then
Set oDest = oDest.End(xlDown).Offset(1)
Else
Set oDest = oDest.Offset(1)
End If
End If
Next
'Cleaning up
Application.DisplayAlerts = False
oWS.Delete
Application.DisplayAlerts = True
oWBDest.Save
oWBDest.Close

excel vba macro to match cells from two different workbooks and copy and paste accordingly

i have 2 workbooks, workbook A and workbook B. Each workbook has a table. workbook A has 2 columns. All three columns are filled.
product_id
Machine_number and
Workbook B has the same 2 columns but only one column, Product_id, is filled. The other 1 column is vacant.
I need to match the cells of product_id of both workbooks. If the product_id found in workbook A matches workbook B, then the machine number of that product id should be copied from workbook A to workbook B.
I have performed this using this code:
Sub UpdateW2()
Dim w1 As Worksheet, w2 As Worksheet
Dim c As Range, FR As Long
Application.ScreenUpdating = False
Set w1 = Workbooks("workbookA.xlsm").Worksheets("Sheet1")
Set w2 = Workbooks("workbookB.xlsm").Worksheets("Sheet1")
For Each c In w1.Range("A2", w1.Range("A" & Rows.Count).End(xlUp))
FR = 0
On Error Resume Next
FR = Application.Match(c, w2.Columns("A"), 0)
On Error GoTo 0
If FR <> 0 Then w2.Range("C" & FR).Value = c.Offset(, 0)
Next c
Application.ScreenUpdating = True
End Sub
There is a cell that says "machine 4" in product number column. This cell does not get copied and pasted alongside the corresponding product_id value in workbook B.
The rest of the machine numbers for the product ids get copied and pasted accordingly.
These are the screenshots of results
The first screenshot is
Workbook B
The second screenshot is
Workbook A
I have no idea why this happens, can someone please give me the reason for this?
................................................................................
UPDATE
I found that the issue ive descriped in the question arises when the product_id(style_number) repeats.
Say if product_id GE 55950 is present in 2 cells,in both workbooks. Then when i execute the macro only one of the cells is detected.
I tried the coding in both answers but neither solved this problem.
Below is a screenshot of the results.
In the screenshots the cell with machine 7 is not shown. Can someone tell me why this happens?
try this
Sub UpdateW2()
Dim Dic As Object, key As Variant, oCell As Range, i&
Dim w1 As Worksheet, w2 As Worksheet
Set Dic = CreateObject("Scripting.Dictionary")
Set w1 = Workbooks("workbookA.xlsm").Sheets("Sheet1")
Set w2 = Workbooks("workbookB.xlsm").Sheets("Sheet1")
i = w1.Cells.SpecialCells(xlCellTypeLastCell).Row
For Each oCell In w1.Range("D2:D" & i)
If Not Dic.exists(oCell.Value) Then
Dic.Add oCell.Value, oCell.Offset(, -3).Value
End If
Next
i = w2.Cells.SpecialCells(xlCellTypeLastCell).Row
For Each oCell In w2.Range("A2:A" & i)
For Each key In Dic
If oCell.Value = key Then
oCell.Offset(, 2).Value = Dic(key)
End If
Next
Next
End Sub
UPDATE AGAINST NEW REQUIREMENTS
use this
Sub UpdateW2()
Dim key As Variant, oCell As Range, i&, z%
Dim w1 As Worksheet, w2 As Worksheet
Dim Dic As Object: Set Dic = CreateObject("Scripting.Dictionary")
Dim Dic2 As Object: Set Dic2 = CreateObject("Scripting.Dictionary")
Set w1 = Workbooks("workbookA.xlsm").Sheets("Sheet1")
Set w2 = Workbooks("workbookB.xlsm").Sheets("Sheet1")
'-------------------------------------------------------------------------
'get the last row for w1
i = w1.Cells.SpecialCells(xlCellTypeLastCell).Row
'-------------------------------------------------------------------------
' fill dictionary with data for searching
For Each oCell In w1.Range("D2:D" & i)
'row number for duplicates
z = 1: While Dic.exists(oCell.Value & "_" & z): z = z + 1: Wend
'add data with row number to dictionary
If Not Dic.exists(oCell.Value & "_" & z) Then
Dic.Add oCell.Value & "_" & z, oCell.Offset(, -3).Value
End If
Next
'-------------------------------------------------------------------------
'get the last row for w2
i = w2.Cells.SpecialCells(xlCellTypeLastCell).Row
'-------------------------------------------------------------------------
'fill "B" with results
For Each oCell In w2.Range("A2:A" & i)
'determinate row number for duplicated values
z = 1: While Dic2.exists(oCell.Value & "_" & z): z = z + 1: Wend
'search
For Each key In Dic
If oCell.Value & "_" & z = key Then
oCell.Offset(, 2).Value = Dic(key)
End If
Next
'correction of the dictionary in case
'when sheet "A" has less duplicates than sheet "B"
If oCell.Offset(, 2).Value = "" Then
Dic2.RemoveAll: z = 1
For Each key In Dic
If oCell.Value & "_" & z = key Then
oCell.Offset(, 2).Value = Dic(key)
End If
Next
End If
'add to dictionary already passed results for
'the next duplicates testing
If Not Dic2.exists(oCell.Value & "_" & z) Then
Dic2.Add oCell.Value & "_" & z, ""
End If
Next
End Sub
output results below
I tried to replicate your workbooks, I believe they go something like this
Before
After
Code changes are minor,
Sub UpdateW2()
Dim w1 As Worksheet, w2 As Worksheet
Dim c As Range, FR As Long
Application.ScreenUpdating = False
Set w1 = Workbooks("BookOne.xlsm").Worksheets("Sheet1")
Set w2 = Workbooks("BookTwo.xlsm").Worksheets("Sheet1")
For Each c In w1.Range("D2", w1.Range("D" & Rows.Count).End(xlUp))
FR = 0
On Error Resume Next
FR = Application.Match(c, w2.Columns("A"), 0)
On Error GoTo 0
If FR <> 0 Then w2.Range("C" & FR).Value = c.Offset(, -3)
Next c
Application.ScreenUpdating = True
End Sub