VBA macro to loop through cells, find matches, and copy adjacent cells - excel

I'm looking to have a macro that, when run, will look for matches in two columns (column M on the DISPLAY sheet and column A on the REPORT_DOWNLOAD sheet), and then when there's a match copy the adjacent 3 cells on the REPORT_DOWNLOAD sheet (cells B, C & D) and paste them in cells S, T & U respectively of the DISPLAY sheet.
There will only be one match for each cell. I've tried to work off some previous vba code that was looking for multiple instances of each match, but I think I've confused myself too much at this point :(
Any help would be greatly appreciated.
Sub Display()
Dim ws1 As Worksheet, ws2 As Worksheet
Set ws1 = ThisWorkbook.Sheets("DISPLAY")
Set ws2 = ThisWorkbook.Sheets("REPORT_DOWNLOAD")
Dim arr_1 As Variant, arr_2 As Variant, arr_result As Variant
arr_1 = ws1.Range("K2:K" & ws2.Range("D" & ws2.Rows.Count).End(xlUp).Row).Value2
arr_2 = ws2.Range("A2:L" & ws2.Range("A" & ws2.Rows.Count).End(xlUp).Row).Value2
ReDim arr_result(LBound(arr_2) To UBound(arr_2), 1 To 3)
Dim i As Long, j As Long
For i = LBound(arr_1, 1) To UBound(arr_1, 1)
For j = LBound(arr_2, 1) To UBound(arr_2, 1)
If arr_1(i, 1) = arr_2(j, 1) Then
arr_result(i, 1) = arr_2(j, 6)
arr_result(i, 2) = arr_2(j, 7)
arr_result(i, 3) = arr_2(j, 8)
End If
Next j
Next i
ws1.Cells(2, 17).Resize(UBound(arr_result, 1), 3).Value2 = arr_result
End Sub

This is usually done like this:
Sub updateDisplayList()
Rem Just define work sheets:
Dim wsSource As Worksheet: Set wsSource = Worksheets("REPORT_DOWNLOAD")
Dim wsTarget As Worksheet: Set wsTarget = Worksheets("DISPLAY")
Rem
Dim rSearch As Range, rWhat As Range, rBase As Range, oCell As Range
Dim vVar As Variant
Rem Column A of source sheet:
Set rSearch = Application.Intersect(wsSource.UsedRange, wsSource.Columns(1)).Offset(1, 0)
Rem 3 first cells in columns which will be copied
Set rBase = wsSource.Range("B1:D1")
Rem Range with data to search: used part of column M
Set rWhat = Application.Intersect(wsTarget.UsedRange, wsTarget.Range("M:M"))
For Each oCell In rWhat
If Not IsEmpty(oCell) Then
vVar = Application.Match(oCell.Value, rSearch, 0)
If Not IsError(vVar) Then
rBase.Offset(vVar, 0).Copy Destination:=oCell.Offset(0, 6)
Rem If you want to clear target cells when value not found in source sheet:
Else
oCell.Offset(0, 6).Resize(1, 3).ClearContents
End If
End If
Next oCell
End Sub
(Not sure about column M - in your code you use values of column K)

Related

Excel VBA - For Loop IS taking far far too long to execute

First question ever here, I am the newbiest newbie..
So.. what I am trying to get is:
to find if in sheet1 and sheet2 there are cells with the same value on column E from sheet1 and column F from sheet2. if there are, then copy the value from sheet2 column A row x to sheet2 column P row y.
rows x and y are where the identical values are on each sheet.
this is my code:
Sub ccopiazanrfact()
Dim camion As Worksheet
Dim facturi As Worksheet
Set camion = ThisWorkbook.Sheets("B816RUS")
Set facturi = ThisWorkbook.Sheets("EVIDENTA FACTURI")
Dim nrcomanda As String
Dim nrfactura As String
For a = 2 To facturi.Range("F" & Rows.Count).End(xlUp).Row
nrcomanda = facturi.Range("F" & a).Value
For b = 4 To camion.Range("E" & Rows.Count).End(xlUp).Row
If camion.Range("E" & b).Value = facturi.Range("F" & a).Value Then
camion.Range("P" & b) = facturi.Range("A" & a).Value
Exit For
End If
Next b
Next a
End Sub
I would recommend using arrays to achieve what you want. Nested looping over ranges can make it very slow. Is this what you are trying? (UNTESTED). As I have not tested it, I would recommend making a backup of your data before you test this code.
I have commented the code. But if you still have a question or find an error/bug in the below code then simply ask.
Option Explicit
Sub ccopiazanrfact()
Dim Camion As Worksheet
Dim Facturi As Worksheet
Set Camion = ThisWorkbook.Sheets("B816RUS")
Set Facturi = ThisWorkbook.Sheets("EVIDENTA FACTURI")
'~~> Declare 2 arrays
Dim ArCamion As Variant
Dim ArFacturi As Variant
Dim LRow As Long
'~~> Find last row in Col E of Sheets("B816RUS")
LRow = Camion.Range("E" & Camion.Rows.Count).End(xlUp).Row
'~~> Store Values from E4:P last row in the array. We have taken E:P
'~~> because we are replacing the value in P if match found
ArCamion = Camion.Range("E4:P" & LRow).Value
'~~> Find last row in Col E of Sheets("EVIDENTA FACTURI")
LRow = ArFacturi.Range("F" & ArFacturi.Rows.Count).End(xlUp).Row
'~~> Store Values from A2:F last row in the array. We have taken A:F
'~~> because we are replacing the value in P with A
ArFacturi = Facturi.Range("A2:F" & LRow).Value
Dim i As Long, j As Long
For i = 2 To UBound(ArFacturi)
For j = 4 To UBound(ArCamion)
'~~> Checking if camion.Range("E" & j) = facturi.Range("F" & i)
If ArCamion(j, 1) = ArFacturi(i, 6) Then
'~~> Replacing camion.Range("P" & j) with facturi.Range("A" & i)
ArCamion(j, 12) = ArFacturi(i, 1)
Exit For
End If
Next j
Next i
'~~> Write the array back to the worksheet in one go
Camion.Range("E4:P" & LRow).Resize(UBound(ArCamion), 12).Value = ArCamion
End Sub
in the end, I came up with this and works instantly, get’s all the data filled within a blink of an eye. When I tried it first time I thought i forgot to clear the data before running the code:
Sub FindMatchingValues()
'Declare variables for the worksheets
Dim ws1 As Worksheet
Dim ws2 As Worksheet
'Set the variables to refer to the worksheets
Set ws1 = Worksheets("B816RUS")
Set ws2 = Worksheets("EVIDENTA FACTURI")
'Declare variables for the ranges to compare
Dim rng1 As Range
Dim rng2 As Range
'Set the ranges to the columns to compare
Set rng1 = ws1.Range("E1", ws1.Range("E" & Rows.Count).End(xlUp))
Set rng2 = ws2.Range("F1", ws2.Range("F" & Rows.Count).End(xlUp))
'Loop through each cell in the first range
For Each cell1 In rng1
'Use the Match function to find the matching value in the second range
Dim match As Variant
match = Application.match(cell1.Value, rng2, 0)
'If a match was found, copy the value from column A in the second worksheet to column P in the first worksheet
If Not IsError(match) Then
ws1.Range("P" & cell1.Row).Value = ws2.Range("A" & match).Value
End If
Next cell1
End Sub
Please, test the next code. It should be very fast, using arrays and Find function:
Sub ccopiazaNrfact()
Dim camion As Worksheet, facturi As Worksheet, cellMatch As Range, rngE As Range
Set camion = ThisWorkbook.Sheets("B816RUS")
Set facturi = ThisWorkbook.Sheets("EVIDENTA FACTURI")
Set rngE = camion.Range("E4:E" & camion.Range("E" & camion.rows.count).End(xlUp).row)
Dim a As Long, arrFact, arrP, nrComanda As String
arrP = camion.Range("P1:P" & camion.Range("E" & rows.count).End(xlUp).row).Value
arrFact = facturi.Range("A2:F" & facturi.Range("F" & rows.count).End(xlUp).row).Value
Debug.Print UBound(arrP): Stop
For a = 1 To UBound(arrFact)
nrComanda = arrFact(a, 6)
Set cellMatch = rngE.Find(What:=nrComanda, After:=rngE.cells(1, 1), LookIn:=xlValues, lookAt:=xlWhole)
If Not cellMatch Is Nothing Then
arrP(cellMatch.row, 1) = arrFact(a, 1)
End If
Next a
camion.Range("P1").Resize(UBound(arrP), 1).Value = arrP
MsgBox "Ready..."
End Sub
Please, send some feedback after testing it...
A VBA Lookup: Using Arrays and a Dictionary
Option Explicit
Sub CopiazaNrFact()
Dim wb As Workbook: Set wb = ThisWorkbook
' Write the values from the Source Compare and Value ranges to arrays.
' f - Facturi (Source), c - Compare, v - Value
Dim frg As Range, fcData() As Variant, fvData() As Variant, frCont As Long
With wb.Sheets("EVIDENTA FACTURI")
' Compare
Set frg = .Range("F2", .Cells(.Rows.Count, "F").End(xlUp))
frCont = frg.Rows.Count
fcData = frg.Value ' write to array
' Value
Set frg = frg.EntireRow.Columns("A")
fvData = frg.Value ' write to array
End With
' Write the unique values from the Source Compare array to the 'keys',
' and their associated values from the Source Values array to the 'items'
' of a dictionary.
Dim fDict As Object: Set fDict = CreateObject("Scripting.Dictionary")
fDict.CompareMode = vbTextCompare
Dim fr As Long, NrFacturi As String
For fr = 1 To frCont
NrFacturi = CStr(fcData(fr, 1))
If Len(NrFacturi) > 0 Then ' exclude blanks
fDict(NrFacturi) = fvData(fr, 1)
End If
Next fr
' Write the values from the Destination Compare range to an array
' and define the resulting same-sized Destination Value array.
' c - Camion (Destination), c - Compare, v - Value
Dim crg As Range, ccData() As Variant, cvData() As Variant, crCont As Long
With wb.Sheets("B816RUS")
' Compare
Set crg = .Range("E4", .Cells(.Rows.Count, "E").End(xlUp))
crCont = crg.Rows.Count
ccData = crg.Value ' write to array
' Value
Set crg = crg.EntireRow.Columns("P")
ReDim cvData(1 To crCont, 1 To 1) ' define
End With
' For each value in the Destination Compare array, attempt to find
' a match in the 'keys' of the dictionary, and write the associated 'item'
' to the same row of the Destination Value array.
Dim cr As Long, NrCamion As String
For cr = 1 To crCont
NrCamion = CStr(ccData(cr, 1))
If fDict.Exists(NrCamion) Then cvData(cr, 1) = fDict(NrCamion)
Next cr
' Write the values from the Destination Value array
' to the Destination Value range.
crg.Value = cvData
End Sub

VBA - Loop through and copy/paste value on range based on different cell value

I have been struggling with this code. I want to loop through Column E beginning with E5, on the Sheet titled "pivot of proposal" (which is a pivot table); and every time it finds a cell with the value of "check" I want it to copy/paste value of cells A & B of the corresponding row to the sheet titled Check Payments in E & F, moving down a row each time but beginning on row 4. I tried to piece together other bits of code but it is not doing what I need it to.
Sub Loop_Check_Payments()
Dim c As Range
Dim IRow As Long, lastrow As Long, krow as long
Dim copyrow As Integer
Dim rSource As Range
Dim DataOrigin As Worksheet, DataDest As Worksheet, DataDestACH As Worksheet
On Error GoTo Whoa
'~~> Sheet Where "L" needs to be checked
Set DataOrigin = ThisWorkbook.Sheets("Pivot of proposal")
'~~> Output sheet
Set DataDest = ThisWorkbook.Sheets("CHECK PAYMENTS")
Set DataDestACH = ThisWorkbook.Sheets("ACH_WIRE PAYMENTS CASH POOLER")
Application.ScreenUpdating = False
'~~> Set you input range
Set rSource = Range("Payment_Method")
'~~> Search for the cell which has "L" and then copy it across to sheet1
For Each c In rSource
If c.Value = "Check" Then
DataDest.Cells(4 + IRow, 5) = DataOrigin.Cells(c.Row, 1)
DataDest.Cells(4 + IRow, 6) = DataOrigin.Cells(c.Row, 2)
IRow = IRow + 1
Else
DataDestACH.Cells(4 + kRow, 7) = DataOrigin.Cells(c.Row, 1)
DataDestACH.Cells(4 + kRow, 8) = DataOrigin.Cells(c.Row, 2)
kRow = kRow + 1
End If
Next c
Whoa:
MsgBox Err.Description
End Sub
Instead of trying to Copy/paste - you can do something like this (as PeterT alluded to in comments)
this will put values from columns A&B (ordinal 1 & 2) of the SOURCE to the same row/column in the destination:
If c.Value = "Check" Then
DataDest.Cells(c.Row, 1) = DataOrigin.Cells(c.Row, 1)
DataDest.Cells(c.Row, 2) = DataOrigin.Cells(c.Row, 2)
End If

Speed up and simplify

I cobbled together something that does work for me as is, but it runs very slowly and I'm sure the code can be simplified.
Sub CopyPasteValues()
Dim strSht1, strSht2 As String
Dim c, rng As Range
strSht1 = "Edit"
strSht2 = "LOB"
With ThisWorkbook.Sheets(strSht1)
Set rng = Range("J2:AJ37")
For Each c In rng
If Not c.Value = 0 Then
Cells(c.Row, 2).Copy
ThisWorkbook.Sheets(strSht2).Range("B" & Rows.Count).End(xlUp).Offset(1).PasteSpecial xlPasteValues
Range(Cells(c.Row, 4), Cells(c.Row, 5)).Copy
ThisWorkbook.Sheets(strSht2).Range("C" & Rows.Count).End(xlUp).Offset(1).PasteSpecial xlPasteValues
c.Copy
ThisWorkbook.Sheets(strSht2).Range("F" & Rows.Count).End(xlUp).Offset(1).PasteSpecial xlPasteValues
Cells(c.Column).Copy
ThisWorkbook.Sheets(strSht2).Range("G" & Rows.Count).End(xlUp).Offset(1).PasteSpecial xlPasteValues
End If
Next c
End With
End Sub
I appreciate any assistance.
As BigBen Mentioned, array method.
Super Fast.
Sub Move_Values_Array_Method()
Dim SourceSheet As Worksheet 'Source Worksheet
Dim DestinationSheet As Worksheet 'Destination Worksheet
Dim RG As Range 'Source Range
Dim InArr() 'Data In Array
Dim OutArr() 'Data Out Array
Dim X As Long 'Array X Position for purposes of iterating through array.
Dim Y As Long 'Array Y Position for purposes of iterating through array.
Dim Cnt As Long 'Found Value Count
Set SourceSheet = ThisWorkbook.Worksheets("Edit") 'Set Source Worksheet
Set DestinationSheet = ThisWorkbook.Worksheets("LOB") 'Set Dest Worksheet
Set RG = SourceSheet.Range("J2:AJ37") 'Set Source Range
ReDim OutArr(1 To RG.Cells.Count) 'Count Cells in Range, resize output array to be at least that big.
InArr = RG 'Transfer Range Data to Array
Cnt = 0
Debug.Print LBound(InArr, 1) & " - " & UBound(InArr, 1) 'Rows
Debug.Print LBound(InArr, 2) & " - " & UBound(InArr, 2) 'Columns
For Y = 1 To UBound(InArr, 1) 'For Each Row in Array (or each Y position)
For X = 1 To UBound(InArr, 2) 'For Each Column in Array (or each X position)
If InArr(Y, X) <> "" Then 'If not blank Value (you can change this to "If InArr(Y, X) <> 0 Then" if that works best for you.
Cnt = Cnt + 1 'Increment "found value count" by 1
OutArr(Cnt) = InArr(Y, X) 'Add found value to output array
End If
Next X
Next Y
'Output to Dest Sheet
DestinationSheet.Range("F2").Resize(UBound(OutArr, 1), 1).Value = Application.Transpose(OutArr())
End Sub
Based on the information in your previous comments, try these alternative solution using formulas and filters...
1) Array Formulas
To note:
I have put everything on one sheet for clarity, but it works just as well over multiple sheets, or even workbooks.
If you want to filter the entire sheet, with same column order, you only need to enter formula once and expand "Array" criteria in formula to encapsulate entire data set.
Formula used in cell "J4" = "=FILTER($I$4:$I$30,$C$4:$C$30>0)"
(filter range I4 to I30 to show rows where value in range C4 to C30 is greater than 0)
2) Directly Filter
Alternatively, you could (either manually or programmatically) copy all data to LOB sheet, (or selectively copy), then filter for Qty>0.

Excel VBA: How to use Dictionary between two workbooks for Vlookup?

I have a worksheet (in my desktop) which have over 13.000 rows with duplicate data which I add 3 new column with vlookup from another file (called ProductList) located on sharedrive. I tried to use Dictionary but I don't know how can I add a different workbook to my code? I add ProductList to my file as Sheet2 and successfully run the code.
Sub ckv()
Dim Rng As Range, Dn As Range, n As Long, Tic As Object, ray As Variant
'This is normally my ProductList workbook, which has 4 columns
With Sheets("Sheet2")
ray = .Range("A1").CurrentRegion.Resize(, 4)
End With
Set Tic = CreateObject("scripting.dictionary")
Tic.CompareMode = vbTextCompare
For n = 2 To UBound(ray, 1)
Tic(ray(n, 1)) = n
Next
'And this is my file in my desktop
With Sheets("Sheet1")
Set Rng = .Range(.Range("I2"), .Range("I" & Rows.Count).End(xlUp))
For Each Dn In Rng
If Tic.Exists(Dn.Value) Then
Dn.Offset(, -2) = ray(Tic(Dn.Value), 2)
Dn.Offset(, -1) = ray(Tic(Dn.Value), 3)
Dn.Offset(, 1) = ray(Tic(Dn.Value), 4)
End If
Next Dn
End With
End Sub
regards

Return MULTIPLE corresponding values for one Lookup Value at a time and different ranges

I'm new in this forum and in vba language so i'm hoping for some guidance. I have a workbook with different sheets but right now there are only 3 that matter. The first and thrid sheet have data that will be interconnected in the Sheet2.
In Sheet1 and Sheet3 I have Sheet1_Sheet3_Test. And this is Sheet 2 Sheet2_Test which is, in a first fase all empty and I want to automatize it since i was doing this work manually before. In the image is what I need to get. So far I have the following code, which works and fills column C of Sheet2.
But i'm having problems with Column A. I was trying to simply use a formula like:
{=IF(A3=A2;INDEX(Sheet3!$A$3:$A$16;SMALL(IF(ISNUMBER(SEARCH(Sheet1!$B$3;Sheet3!$C$3:$C$16));MATCH(ROW(Sheet3!$C$3:$C$16);ROW(Sheet3!$C$3:$C$16)));ROW(A1)));INDEX(Sheet3!$A3:$A$16;SMALL(IF(ISNUMBER(SEARCH(Sheet1!$B3;Sheet3!$C$3:$C$16));MATCH(ROW(Sheet3!$C$3:$C$16);ROW(Sheet3!$C$3:$C$16)));ROW(A$1))))}
The problem is I get an error when the text in column C changes and right now I'm stuck. I don't know if it will be better to develop another macro or if there is something I can change in the formula.
I'm sorry if it is difficult to understand what I'm asking but it is kind of hard to explain it.
I need to go throught every row in sheet1, so for example: in Sheet 1 I have in row 3, INST - I_1 and ID - AA. The formula searches for AA on sheet3 and returns all values in order and fills column A in sheet 2. Then it will go to row 4 in sheet 1 again and repeat the process once again until there are no more values on Sheet1.
Sub TestSheet2()
Dim Rng As Range
Dim InputRng As Range, OutRng As Range
xTitleId = "Sheet1"
Sheets("Sheet1").Select
Set InputRng = Application.Selection
On Error Resume Next
Set InputRng = Application.InputBox("Select:", xTitleId, InputRng.Address, Type:=8)
xTitleId = "Sheet2"
Sheets("Sheet2").Select
Set OutRng = Application.InputBox("Select:", xTitleId, Type:=8)
Set OutRng = OutRng.Range("A1")
For Each Rng In InputRng.Rows
xValue = Rng.Range("A1").Value
xNum = Rng.Range("C1").Value
OutRng.Resize(xNum, 1).Value = xValue
Set OutRng = OutRng.Offset(xNum, 0)
Next
End Sub
Based on the images provided, I was able to loop through a couple of arrays and come up with this.
Sub fill_er_up()
Dim a As Long, b As Long, c As Long
Dim arr1 As Variant, arr2() As Variant, arr3 As Variant
With Worksheets("sheet1")
With .Range(.Cells(3, 1), .Cells(Rows.Count, 2).End(xlUp))
.Cells.Sort key1:=.Columns(2), order1:=xlAscending, _
key2:=.Columns(1), order2:=xlAscending, _
Orientation:=xlTopToBottom, Header:=xlNo
arr1 = .Cells.Value2
End With
End With
With Worksheets("sheet3")
With .Range(.Cells(3, 1), .Cells(Rows.Count, 3).End(xlUp))
.Cells.Sort key1:=.Columns(3), order1:=xlAscending, _
key2:=.Columns(1), order2:=xlAscending, _
Orientation:=xlTopToBottom, Header:=xlNo
arr3 = .Cells.Value2
End With
End With
For a = LBound(arr1, 1) To UBound(arr1, 1)
For c = LBound(arr3, 1) To UBound(arr3, 1)
'Do While arr3(c, 3) <> arr1(a, 2): c = c + 1: Loop
If arr3(c, 3) = arr1(a, 2) Then
b = b + 1
ReDim Preserve arr2(1 To 3, 1 To b)
arr2(1, b) = arr3(c, 1)
arr2(2, b) = arr3(c, 3)
arr2(3, b) = arr1(a, 1)
End If
Next c
Next a
With Worksheets("sheet2")
Dim arr4 As Variant
arr4 = my_2D_Transpose(arr4, arr2)
.Cells(3, 1).Resize(UBound(arr4, 1), UBound(arr4, 2)) = arr4
End With
Erase arr1: Erase arr2: Erase arr3: Erase arr4
End Sub
Function my_2D_Transpose(a1 As Variant, a2 As Variant)
Dim a As Long, b As Long
ReDim a1(1 To UBound(a2, 2), 1 To UBound(a2, 1))
For a = LBound(a2, 1) To UBound(a2, 1)
For b = LBound(a2, 2) To UBound(a2, 2)
a1(b, a) = Trim(a2(a, b))
Next b
Next a
my_2D_Transpose = a1
End Function
I added in the id to the second column of the results in sheet2. It seemed a reasonable way to fill blank cells.
      
I was able to recreate your results table with the code below, filtering the range on Sheet3.
Option Explicit
Sub MergeIDs()
Dim instSh As Worksheet
Dim compfSh As Worksheet
Dim mergeSh As Worksheet
Dim inst As Range
Dim compf As Range
Dim merge As Range
Dim lastInst As Long
Dim lastCompf As Long
Dim allCompf As Long
Dim i As Long, j As Long
Dim mergeRow As Long
'--- initialize ranges
Set instSh = ThisWorkbook.Sheets("Sheet1")
Set compfSh = ThisWorkbook.Sheets("Sheet3")
Set mergeSh = ThisWorkbook.Sheets("Sheet2")
Set inst = instSh.Range("A3")
Set compf = compfSh.Range("A2")
Set merge = mergeSh.Range("A3")
lastInst = instSh.Cells(instSh.Rows.Count, "A").End(xlUp).Row
allCompf = compfSh.Cells(compfSh.Rows.Count, "A").End(xlUp).Row
'--- clear destination
mergeSh.Range("A:C").ClearContents
merge.Cells(0, 1).Value = "COMPF"
merge.Cells(0, 3).Value = "INST"
'--- loop and build...
mergeRow = 1
For i = 1 To (lastInst - inst.Row + 1)
'--- set the compf range to autofilter
compfSh.AutoFilterMode = False
compf.Resize(allCompf - compf.Row, 3).AutoFilter
compf.Resize(allCompf - compf.Row, 3).AutoFilter Field:=3, Criteria1:=inst.Cells(i, 2).Value
'--- merge the filtered values with the inst value
lastCompf = compfSh.Cells(compfSh.Rows.Count, "A").End(xlUp).Row
For j = 1 To (lastCompf - compf.Row)
merge.Cells(mergeRow, 1).Value = compf.Cells(j + 1, 1).Value
merge.Cells(mergeRow, 3).Value = inst.Cells(i, 1).Value
mergeRow = mergeRow + 1
Next j
Next i
End Sub

Resources