I'm trying to return the partial match string on a column right beside the column with the text I'm trying to search within. This is the code I tried to write. What would be a better way to do this?
Essentially I have a column with:
Column 1
aaaaa1111
...
zzzzz9999
Column 2
aaa
bbb
..
zzz
I want to return column 2 values to the column adjacent to column 1 where the column 2's string can be found within column 1.
Sub match()
Dim ws As Worksheet
Dim vendors As Range
Dim description As Range
Dim match As Range
Dim cell As Range
Dim j As Integer
Dim i As Integer
Set vendors = ws.Range("ae2:ae1007").Text
Set description = ws.Range("o2:o32609")
Set match = ws.Range("p2:p32609")
For i = 2 To 32609
For j = 2 To 1007
If InStr(description.Cells(i, "O"), vendors.Range(j, "AE")) > 0 Then
match.Cells(i, "P") = vendors.Range(j, "AE").Text
Else: match.Cells(i, "P") = "#N/A"
End If
Next j
Next i
End Sub
Update: (I get run-time error '91' on line 9)
Sub match()
Dim ws As Worksheet
Dim cell As Range
Dim j As Integer
Dim i As Integer
For i = 2 To 32609
For j = 2 To 1007
If InStr(ws.Cells(i, "O"), ws.Cells(j, "AE")) > 0 Then
ws.Cells(i, "P") = ws.Cells(j, "AE").Text
Else: ws.Cells(i, "P") = "#N/A"
End If
Next j
Next i
End Sub
You are getting error 91 because you declared ws but did not set ws to any worksheet.
The code below should run pretty fast since it process the data in an array (read/write from cells is a very slow process).
Option Explicit
Sub FindMatch()
Const vendorCol As String = "AE"
Const descCol As String = "O"
Const matchCol As String = "P"
Dim ws As Worksheet
Set ws = ThisWorkbook.Worksheets("Sheet1") 'Change name accordingly
'==== Get a list of unique vendor names
Dim vendorDict As Object
Set vendorDict = CreateObject("Scripting.Dictionary")
vendorDict.CompareMode = vbTextCompare
Dim vendorLastRow As Long
Dim vendorInput As Variant
'Assign the values of the vendor names to array
vendorLastRow = ws.Cells(ws.Rows.Count, vendorCol).End(xlUp).Row
vendorInput = ws.Range(ws.Cells(2, vendorCol), ws.Cells(vendorLastRow, vendorCol)).Value
'Loop through the array and add to dictionary if it's not already in it
Dim n As Long
For n = 1 To UBound(vendorInput, 1)
If Not vendorDict.Exists(vendorInput(n, 1)) Then vendorDict.Add vendorInput(n, 1), 1
Next n
Dim vendorArr As Variant
vendorArr = vendorDict.keys
Set vendorDict = Nothing
Erase vendorInput
'====
'Assign the values of description to array
Dim descLastRow As Long
Dim descArr As Variant
descLastRow = ws.Cells(ws.Rows.Count, descCol).End(xlUp).Row
descArr = ws.Range(ws.Cells(2, descCol), ws.Cells(descLastRow, descCol)).Value
'Create an array of the same size as the description for match result, will be used to write in to the worksheet once at the end
Dim matchArr() As Variant
ReDim matchArr(1 To UBound(descArr, 1), 1 To 1) As Variant
'Loop through the description array and within the loop, check if there's a match in the vendor array
Dim i As Long
For i = 1 To UBound(descArr, 1)
For n = 0 To UBound(vendorArr)
If InStr(1, descArr(i, 1), vendorArr(n), vbTextCompare) <> 0 Then
'If match found, assign the vendor name to the match array
matchArr(i, 1) = vendorArr(n)
Exit For
End If
Next n
'If no match, return NA error
If matchArr(i, 1) = vbNullString Then matchArr(i, 1) = CVErr(xlErrNA)
Next i
ws.Cells(2, matchCol).Resize(UBound(matchArr, 1)).Value = matchArr
Erase descArr
Erase matchArr
End Sub
Compare Two Columns
This is a basic example that loops through column O and compares each value against each value in column AE. Match is no good because the values in AE need to be contained in O. You can always improve efficiency by using arrays as illustrated in Raymond Wu's answer.
On the other hand, you could loop through column AE and use the Find and FindNext methods to find all matches in column O which might also be more efficient.
Option Explicit
Sub MatchVendors()
' s - Source (read from ('vendors'))
' d - Destination (read from ('description') and written to ('match'))
Dim wb As Workbook: Set wb = ThisWorkbook ' workbook containing this code
Dim sws As Worksheet: Set sws = wb.Worksheets("Sheet1") ' adjust, often...
Dim dws As Worksheet: Set dws = wb.Worksheets("Sheet1") ' ... different
Dim slRow As Long: slRow = sws.Range("AE" & sws.Rows.Count).End(xlUp).Row
If slRow < 2 Then Exit Sub ' no data in source
Dim srg As Range: Set srg = sws.Range("AE2:AE" & slRow)
Dim dlRow As Long: dlRow = dws.Range("O" & dws.Rows.Count).End(xlUp).Row
If dlRow < 2 Then Exit Sub ' no data in destination
Dim drg As Range: Set drg = dws.Range("O2:O" & dlRow)
Application.ScreenUpdating = False
Dim sCell As Range
Dim dCell As Range
Dim IsMatch As Boolean
For Each dCell In drg.Cells
' Read (Search)
For Each sCell In srg.Cells
' Either 'contains'...
If InStr(1, dCell.Value, sCell.Value, vbTextCompare) > 0 Then
' ... or 'begins with':
'If InStr(1, dCell.Value, sCell.Value, vbTextCompare) = 1 Then
IsMatch = True
Exit For
End If
Next sCell
' Write
If IsMatch Then
dCell.EntireRow.Columns("P").Value = sCell.Value
IsMatch = False
Else
dCell.EntireRow.Columns("P").Value = "#N/A"
End If
Next dCell
Application.ScreenUpdating = True
MsgBox "Vendors matched to Descriptions.", vbInformation
End Sub
Related
I use this VBA code which works very well. It searches in column A of an external Excel file for all the terms in column D, and replaces the matches, with the content of column B of the external file in the found row. So for instance:
if D5 matches A11 of the external file, then B11 from external file is written to D5.
I am now trying to modify it so that it still searches in column 4 for matches in column A of external file, but for any matches found, replaces the column E with column B of the external file. So:
If D5 matches A11, then B11 from external file is written to E5.
Well, I've tried many changes in the replace loop but it throws errors every time. I suppose I don't use the correct command!
Private Sub CommandButton1_Click()
Dim NameListWB As Workbook, thisWb As Workbook
Dim NameListWS As Worksheet, thisWs As Worksheet
Dim i As Long, lRow As Long
'This is the workbook from where code runs
Set thisWb = ThisWorkbook
Set thisWs = thisWb.Sheets("Sheet1")
'External file
Set NameListWB = Workbooks.Open("E:\Data.xlsx")
Set NameListWS = NameListWB.Worksheets("Sheet1")
With NameListWS
'Detect end row in Col A of Data.xlsx
lRow = .Range("A" & .Rows.Count).End(xlUp).Row
'Loop though Column A
For i = 1 To lRow
'... and perform replace action
thisWs.Columns(4).Replace What:=.Range("A" & i).Value, _
Replacement:=.Range("B" & i).Value, _
SearchOrder:=xlByColumns, _
MatchCase:=False
Next i
End With
End Sub ```
Untested:
Private Sub CommandButton1_Click()
Dim NameListWB As Workbook
Dim NameListWS As Worksheet, thisWs As Worksheet, n As Long
Dim i As Long, arrList, arrD, rngD As Range
Set thisWs = ThisWorkbook.Sheets("Sheet1") 'This is the workbook from where code runs
'get an array from the column to be searched
Set rngD = thisWs.Range("D1:D" & thisWs.Cells(Rows.Count, "D").End(xlUp).Row)
arrD = rngD.Value
'Open external file and get the terms and replacements as an array
Set NameListWB = Workbooks.Open("E:\Data.xlsx")
With NameListWB.Worksheets("Sheet1")
arrList = .Range("A1:B" & .Cells(Rows.Count, "A").End(xlUp).Row).Value
End With
For n = 1 To UBound(arrD, 1) 'check each value from ColD
For i = 1 To UBound(arrList, 1) 'loop over the array of terms to search for
If arrD(n, 1) = arrList(i, 1) Then 'exact match ?
'If InStr(1, arrD(n, 1), arr(i, 1)) > 0 Then 'partial match ?
rngD.Cells(n).Offset(0, 1).Value = arrList(i, 2) 'populate value from ColB into ColE
Exit For 'got a match so stop searching
End If
Next i
Next n
End Sub
A VBA Lookup (Application.Match)
Adjust (play with) the values in the constants section.
Compact
Sub VBALookup()
' Source
Const sPath As String = "E:\Data.xlsx"
Const sName As String = "Sheet1"
Const slCol As String = "A" ' lookup
Const svCol As String = "B" ' value
Const sfRow As Long = 2
' Destination
Const dName As String = "Sheet1"
Const dlCol As String = "D" ' lookup
Const dvCol As String = "E" ' value
Const dfRow As Long = 2
Application.ScreenUpdating = False
' Source
Dim swb As Workbook: Set swb = Workbooks.Open(sPath)
Dim sws As Worksheet: Set sws = swb.Worksheets(sName)
Dim slRow As Long: slRow = sws.Cells(sws.Rows.Count, slCol).End(xlUp).Row
If slRow < sfRow Then Exit Sub ' no data in lookup column range
Dim srCount As Long: srCount = slRow - sfRow + 1
Dim slrg As Range: Set slrg = sws.Cells(sfRow, slCol).Resize(srCount)
Dim svData As Variant
With slrg.EntireRow.Columns(svCol)
If srCount = 1 Then ' one cell
ReDim svData(1 To 1, 1 To 1): svData(1, 1) = .Value
Else ' multiple cells
svData = .Value
End If
End With
' Destination
Dim dwb As Workbook: Set dwb = ThisWorkbook ' workbook containing this code
Dim dws As Worksheet: Set dws = dwb.Worksheets(dName)
Dim dlRow As Long: dlRow = dws.Cells(dws.Rows.Count, dlCol).End(xlUp).Row
If dlRow < dfRow Then Exit Sub ' no data in lookup column range
Dim drCount As Long: drCount = dlRow - dfRow + 1
Dim dlrg As Range: Set dlrg = dws.Cells(dfRow, dlCol).Resize(drCount)
Dim dData As Variant
If drCount = 1 Then ' one cell
ReDim dData(1 To 1, 1 To 1): dData(1, 1) = dlrg.Value
Else ' multiple cells
dData = dlrg.Value
End If
' Loop.
Dim srIndex As Variant
Dim dValue As Variant
Dim dr As Long
Dim MatchFound As Boolean
For dr = 1 To drCount
dValue = dData(dr, 1)
If Not IsError(dValue) Then
If Len(dValue) > 0 Then
srIndex = Application.Match(dValue, slrg, 0)
If IsNumeric(srIndex) Then MatchFound = True
End If
End If
If MatchFound Then
dData(dr, 1) = svData(srIndex, 1)
MatchFound = False
Else
dData(dr, 1) = Empty
End If
Next dr
' Close the source workbook.
swb.Close SaveChanges:=False
' Write result.
dlrg.EntireRow.Columns(dvCol).Value = dData
' Inform.
Application.ScreenUpdating = True
MsgBox "VBA lookup has finished.", vbInformation
End Sub
enter image description hereFor a larger project I need to change the source of values for a column all n*k steps, with n being a rational and k a natural number.
Edit for better understanding:
I have a column with multiple entries (filled by a loop in a makro) and need to find all entries with a common divisor called "testwer" in my makro. This "testwer" should later be editable in an excel sheet via a cellinput (in this case G2)
I've tried by writing a macro , a simplified example looks like the following:
Sub testmam()
Dim testwer, i, j
i = 1
j = 1
testwer = Range("g2").Value 'gets the rational number n
Do Until i = 18 'until end of entries in column is reached
If Cells(i, 1).Value = testwer * j Then 'if cellvalue = n*1,2,...,infty
Cells(i, 2).Value = j 'some output in another cell to check wether the detection was sucessfull
j = j + 1 'check coming cells for next value of n*k
End If
i = i + 1
Loop
End Sub
However, when I run this, it only detects the first few (3-5) solutions. For example for n being 1.5 it found only 1.5, 3 and 4.5 to be true. Starting from values 6 to all following multipliers of 1.5 the if condition seems to turn out false.
Does someone know how this could happen? As the if condition is true for multiple steps, I assume the syntax isn't completely wrong.
Greets
Detect Multipliers
Compact
Sub TestMamCompact()
Const wsName As String = "Sheet1"
Const SourceFirstCellAddress As String = "A1"
Const TestCellAddress As String = "G2"
Const DestinationColumn As String = "B"
Dim wb As Workbook: Set wb = ThisWorkbook ' workbook containing this code
Dim ws As Worksheet: Set ws = wb.Worksheets(wsName)
Dim sfCell As Range: Set sfCell = ws.Range(SourceFirstCellAddress)
Dim slCell As Range
Set slCell = ws.Cells(ws.Rows.Count, sfCell.Column).End(xlUp)
If slCell.Row < sfCell.Row Then Exit Sub ' no data
Dim srg As Range: Set srg = ws.Range(sfCell, slCell)
Dim TestWert As Double: TestWert = ws.Range(TestCellAddress).Value
Dim k As Long: k = 1
Dim sCell As Range
For Each sCell In srg.Cells
If IsNumeric(sCell) Then
If sCell.Value = TestWert * k Then
ws.Cells(sCell.Row, DestinationColumn).Value = k
k = k + 1
End If
End If
Next sCell
MsgBox "Results written.", vbInformation
End Sub
Argumented
Sub TestMamTEST()
Const wsName As String = "Sheet1"
Const SourceFirstCellAddress As String = "A1"
Const TestCellAddress As String = "G2"
Const DestinationColumn As String = "B"
Dim wb As Workbook: Set wb = ThisWorkbook ' workbook containing this code
Dim ws As Worksheet: Set ws = wb.Worksheets(wsName)
Dim sfCell As Range: Set sfCell = ws.Range(SourceFirstCellAddress)
TestMam sfCell, TestCellAddress, DestinationColumn
' or just:
'TestMam sfCell, "G2", "B"
End Sub
Sub TestMamOneLinerTEST()
TestMam ThisWorkbook.Worksheets("Sheet1").Range("A1"), "G2", "B"
End Sub
Sub TestMam( _
ByVal SourceFirstCell As Range, _
ByVal TestCellAddress As String, _
ByVal DestinationColumn As String)
Dim ws As Worksheet: Set ws = SourceFirstCell.Worksheet
Dim slCell As Range
Set slCell = ws.Cells(ws.Rows.Count, SourceFirstCell.Column).End(xlUp)
If slCell.Row < SourceFirstCell.Row Then Exit Sub ' no data
Dim srg As Range: Set srg = ws.Range(SourceFirstCell, slCell)
Dim TestWert As Double: TestWert = ws.Range(TestCellAddress).Value
Dim k As Long: k = 1
Dim sCell As Range
For Each sCell In srg.Cells
If IsNumeric(sCell) Then
If sCell.Value = TestWert * k Then
ws.Cells(sCell.Row, DestinationColumn).Value = k
k = k + 1
End If
End If
Next sCell
MsgBox "Results written.", vbInformation
End Sub
I have a loop that goes through two-dimensional range
set rng = Range("A1:C3")
For each cell in rng
Debug.Print cell.value
next cell
It prints like:
A1
B1
C1
A2
B2...
I would prefer:
A1
A2
A3
B1
B2...
I know there is a way to do that looping with indexes but for each is much more elegant and on the top of that my range can be non-contiguous like:
Range("A1:C3,G9:H16,B14:D18")
Is there a way how to change default (horizontal) excel behavior?
Loop Through the Cells of a Non-Contiguous Range (Multi-Range)
By Areas
The numbers in the yellow cells in the image are illustrating the looping order by areas and by rows. The results of the procedures in the J and K columns are copied from the Immediate window and are showing both of the looping orders.
Option Explicit
Sub WriteCellsByAreaRows()
Const sAddress As String = "A1:C3,G9:H16,B14:D18"
Dim ws As Worksheet: Set ws = ActiveSheet
Dim mrg As Range: Set mrg = ws.Range(sAddress)
Dim arg As Range
Dim aCell As Range
For Each arg In mrg.Areas
For Each aCell In arg.Cells
Debug.Print aCell.Address(0, 0) & " = " & aCell.Value
Next aCell
Next arg
End Sub
Sub WriteCellsByAreaColumns()
Const sAddress As String = "A1:C3,G9:H16,B14:D18"
Dim ws As Worksheet: Set ws = ActiveSheet
Dim mrg As Range: Set mrg = ws.Range(sAddress)
Dim arg As Range
Dim acrg As Range
Dim aCell As Range
For Each arg In mrg.Areas
For Each acrg In arg.Columns
For Each aCell In acrg.Cells
Debug.Print aCell.Address(0, 0) & " = " & aCell.Value
Next aCell
Next acrg
Next arg
End Sub
EDIT
Area Independent (The Challenge)
The numbers in the yellow cells in the image are illustrating the looping order by rows. The results of the procedures in the J and K columns are copied from the Immediate window and are showing both of the looping orders.
Short Function Description (By Columns)
It will loop through the columns of each area and write the column number to an Array List which will finally be sorted. When looping through the elements of the Array List (each column number), a reference to the intersection of the worksheet column and the initial multi-range will be created and each single range reference will be written to an array. The array will be (bubble) sorted by the row numbers of every single column range and then will be added as an element of the resulting array of arrays.
Sub WriteCellsByColumn()
Const sAddress As String = "A1:C3,G9:H16,B14:D18"
Dim ws As Worksheet: Set ws = ActiveSheet
On Error Resume Next
Dim mrg As Range: Set mrg = ws.Range(sAddress)
On Error GoTo 0
If mrg Is Nothing Then Exit Sub
Dim ColumnRanges As Variant: ColumnRanges = GetSortedColumnRanges(mrg)
If IsEmpty(ColumnRanges) Then Exit Sub ' e.g. 'ArrayList' is not working
Dim cCell As Range
Dim a As Long
Dim b As Long
For a = 0 To UBound(ColumnRanges)
For b = 0 To UBound(ColumnRanges(a))
For Each cCell In ColumnRanges(a)(b).Cells
Debug.Print cCell.Address(0, 0) & " = " & cCell.Value
Next cCell
Next b
Next a
End Sub
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
' Purpose: Returns the sorted column range references of a range ('mrg')
' in an array of arrays.
' Remarks: Supports non-contiguous ranges (multi-ranges).
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
Function GetSortedColumnRanges( _
ByVal mrg As Range) _
As Variant
If mrg Is Nothing Then Exit Function
On Error Resume Next ' The ArrayList needs 'Microsoft .NET Framework 3.5'.
Dim arl As Object: Set arl = CreateObject("System.Collections.ArrayList")
On Error GoTo 0
If arl Is Nothing Then Exit Function
Dim arg As Range ' Area (Single) Range
Dim crg As Range ' Column Range (in Area Range)
Dim cColumn As Long ' Column Number (of Column Range)
For Each arg In mrg.Areas
For Each crg In arg.Columns
cColumn = crg.Column
If Not arl.Contains(cColumn) Then
arl.Add cColumn
End If
Next crg
Next arg
arl.Sort
' Create a reference to the worksheet ('ws').
Dim ws As Worksheet: Set ws = mrg.Worksheet
' Define the Multi Range Array ('mrgArr').
Dim mrgArr As Variant: ReDim mrgArr(0 To arl.Count - 1)
Dim icrg As Range ' Intersect Column Range (in Worksheet Column)
Dim trg As Range ' Temp Range
Dim trgArr() As Range ' Temp Range Array
Dim Key As Variant '
Dim aUpper As Long
Dim a As Long
Dim b As Long
Dim n As Long
For Each Key In arl
Set icrg = Intersect(mrg, ws.Columns(Key))
aUpper = icrg.Areas.Count - 1
If aUpper = 0 Then
' Write current Intersect Row Range reference to Temp Range Array.
ReDim trgArr(0 To 0): Set trgArr(0) = icrg
Else
' Write current Intersect Row Range references to Temp Range Array.
ReDim trgArr(0 To aUpper)
For a = 0 To aUpper
Set trgArr(a) = icrg.Areas(a + 1)
Next a
' (Bubble) Sort the references by rows.
For a = 0 To aUpper - 1
For b = a To aUpper
If trgArr(a).Row > trgArr(b).Row Then
Set trg = trgArr(a)
Set trgArr(a) = trgArr(b)
Set trgArr(b) = trg
End If
Next b
Next a
End If
' Write the current Temp Range Array to the current element
' of the Multi Range Array.
mrgArr(n) = trgArr
n = n + 1
Next Key
GetSortedColumnRanges = mrgArr
End Function
Sub WriteCellsByRow()
Const sAddress As String = "A1:C3,G9:H16,B14:D18"
Dim ws As Worksheet: Set ws = ActiveSheet
On Error Resume Next
Dim mrg As Range: Set mrg = ws.Range(sAddress)
On Error GoTo 0
If mrg Is Nothing Then Exit Sub
Dim RowRanges As Variant: RowRanges = GetSortedRowRanges(mrg)
If IsEmpty(RowRanges) Then Exit Sub ' e.g. 'ArrayList' is not working
Dim rCell As Range
Dim a As Long
Dim b As Long
For a = 0 To UBound(RowRanges)
For b = 0 To UBound(RowRanges(a))
For Each rCell In RowRanges(a)(b).Cells
Debug.Print rCell.Address(0, 0) & " = " & rCell.Value
Next rCell
Next b
Next a
End Sub
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
' Purpose: Returns the sorted row range references of a range ('mrg')
' in an array of arrays.
' Remarks: Supports non-contiguous ranges (multi-ranges).
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
Function GetSortedRowRanges( _
ByVal mrg As Range) _
As Variant
If mrg Is Nothing Then Exit Function
On Error Resume Next ' The ArrayList needs 'Microsoft .NET Framework 3.5'.
Dim arl As Object: Set arl = CreateObject("System.Collections.ArrayList")
On Error GoTo 0
If arl Is Nothing Then Exit Function
Dim arg As Range ' Area (Single) Range
Dim rrg As Range ' Row Range (in Area Range)
Dim cRow As Long ' Row Number (of Row Range)
For Each arg In mrg.Areas
For Each rrg In arg.Rows
cRow = rrg.Row
If Not arl.Contains(cRow) Then
arl.Add cRow
End If
Next rrg
Next arg
arl.Sort
' Create a reference to the worksheet ('ws').
Dim ws As Worksheet: Set ws = mrg.Worksheet
' Define the Multi Range Array ('mrgArr').
Dim mrgArr As Variant: ReDim mrgArr(0 To arl.Count - 1)
Dim irrg As Range ' Intersect Row Range (in Worksheet Row)
Dim trg As Range ' Temp Range
Dim trgArr() As Range ' Temp Range Array
Dim Key As Variant '
Dim aUpper As Long
Dim a As Long
Dim b As Long
Dim n As Long
For Each Key In arl
Set irrg = Intersect(mrg, ws.Rows(Key))
aUpper = irrg.Areas.Count - 1
If aUpper = 0 Then
' Write current Intersect Row Range reference to Temp Range Array.
ReDim trgArr(0 To 0): Set trgArr(0) = irrg
Else
' Write current Intersect Row Range references to Temp Range Array.
ReDim trgArr(0 To aUpper)
For a = 0 To aUpper
Set trgArr(a) = irrg.Areas(a + 1)
Next a
' (Bubble) Sort the references by rows.
For a = 0 To aUpper - 1
For b = a To aUpper
If trgArr(a).Column > trgArr(b).Column Then
Set trg = trgArr(a)
Set trgArr(a) = trgArr(b)
Set trgArr(b) = trg
End If
Next b
Next a
End If
' Write the current Temp Range Array to the current element
' of the Multi Range Array.
mrgArr(n) = trgArr
n = n + 1
Next Key
GetSortedRowRanges = mrgArr
End Function
Maybe
Sub Test()
Dim rng As Range, rRow As Range, rCol As Range
Set rng = Range("A1:C3")
For Each rCol In rng.Columns
For Each rRow In rCol.Cells
Debug.Print rRow.Value
Next rRow
Next rCol
End Sub
As a proof-of-concept with non-contiguous ranges that may overlap. Determine the lowest row and right most column of the areas, create a array that encloses all those areas, and fill it with the cell addresses of the cells in each area. Then traverse the array horizontally or vertically skipping over the blank entries.
Option Explicit
Sub demo_by_column()
Dim ar, a, cell As Range, rng As Range, i As Long
Dim rmax As Long, cmax As Long, rmin As Long, cmin As Long
Dim r As Long, c As Long
rmin = Rows.Count
cmin = Columns.Count
' non-contiguous range
Set rng = Sheet1.Range("A1:C3,D9:H16,B14:D18,A21:J21")
' dimension an array that encloses all the areas
For Each a In rng.Areas
r = a.Row + a.Rows.Count - 1
If r > rmax Then rmax = r
If a.Row < rmin Then rmin = a.Row
c = a.Column + a.Columns.Count - 1
If c > cmax Then cmax = c
If a.Column < cmin Then cmin = a.Column
Next
ReDim ar(1 To rmax, 1 To cmax)
' fill array with cell addresses
For Each a In rng.Areas
For Each cell In a
ar(cell.Row, cell.Column) = cell.Address
Next
Next
' loop the array by column, result to sheet2
i = 1
For c = cmin To cmax
For r = rmin To rmax
If Len(ar(r, c)) > 0 Then
Sheet2.Cells(i, 1) = Sheet1.Range(ar(r, c))
i = i + 1
End If
Next
Next
' show array on sheet3
Sheet3.Range("A1").Resize(rmax, cmax) = ar
End Sub
Sheet1 is a continuous list of everything being recorded and kept.
Sheet2 is an updated list that is retrieved, with updated lines and new lines. Within the lists in column A is a unique ID for every entry in numeric value.
I am trying to go through every unique ID in sheet2, look for a match in sheet1
if there is a match, replace that entire row values with the new values from sheet2
if there is no match it needs to be placed in the last blank row (+1 from xlUp).
I have tried other ways that are not below like using scripting.dictionary.
The way I am trying to do this results in every cell that the “for” is looking at to be true for the if not equal. Every item is posted multiple times below xlUp.
Sub test()
Dim enter As Worksheet
Dim take As Worksheet
Set enter = Worksheets("Sheet1")
Set take = Worksheets("Sheet2")
Dim a1 As Long
Dim b1 As Long
Dim c1 As Long
a1 = take.Cells(Rows.Count, 1).End(xlUp).Row
b1 = enter.Cells(Rows.Count, 1).End(xlUp).Row
c1 = enter.Cells(Rows.Count, 1).End(xlUp).Row + 1
For i = 1 To a1 'this statement works fine to find the matching value to replace.
For K = 1 To b1
If take.Cells(i, 1) = enter.Rows(K, 1) Then
enter.Rows(i).EntireRow = take.Rows(K).EntireRow.Value
End If
Next
Next
'below is other things i have tried
'For I = 1 To a1
' For J = 1 To b1
' If enter.Cells(J, 1) <> take.Cells(I, 1) Then
' enter.Rows(c1).EntireRow = take.Rows(I).EntireRow.Value
' c1 = c1 + 1
' End If
' Next
'Next
'For i = 1 To a1
' For j = 1 To b1
' If take.Cells(i, 1) = enter.Cells(j, 1) Then
' enter.Rows(j).EntireRow = take.Rows(i).EntireRow.Value
' GoTo Skip
' ElseIf j = b1 Then
' enter.Rows(c1).EntireRow = take.Rows(i).EntireRow.Value
' c1 = c1 + 1
' End If
' Next
'Skip:
'Next
End Sub
hy
Public Sub MyCopy()
Dim wsSource As Worksheet, wsTarget As Worksheet
Set wsSource = ThisWorkbook.Worksheets("ws1")
Set wsTarget = ThisWorkbook.Worksheets("ws2")
Dim col As String
col = "A"
Dim i As Long, targetRow As Long, q As Long
Dim sourceRange As Range
With wsSource
For i = 1 To .Cells(.Rows.Count, col).End(xlUp).Row
Set sourceRange = .Range(col & i)
targetRow = GetDataRow(wsTarget, col, sourceRange.value)
For q = 0 To 30
wsTarget.Range(col & targetRow).Offset(0, q).value = sourceRange.Offset(0, q).value
Next q
Next i
End With
End Sub
Private Function GetDataRow(ws As Worksheet, col As String, value As String) As Long
With ws
Dim lastRow As Long, i As Long
lastRow = .Cells(.Rows.Count, col).End(xlUp).Row
For i = 1 To lastRow
If .Range(col & i).value = value Then
GetDataRow = i
GoTo exitFunc
End If
Next i
GetDataRow = lastRow + 1
End With
exitFunc:
End Function
Update Worksheet (For Each ... Next, Application.Match)
Option Explicit
Sub UpdateWorksheet()
Const sName As String = "Sheet2"
Const sFirst As String = "A1"
Const dName As String = "Sheet1"
Const dFirst As String = "A1"
Dim wb As Workbook: Set wb = ThisWorkbook ' workbook containing this code
Dim sws As Worksheet: Set sws = wb.Worksheets(sName)
Dim slCell As Range
Set slCell = sws.Cells(sws.Rows.Count, sws.Range(sFirst).Column).End(xlUp)
Dim srg As Range: Set srg = sws.Range(sFirst, slCell)
Dim dws As Worksheet: Set dws = wb.Worksheets(dName)
Dim dlCell As Range:
Set dlCell = dws.Cells(dws.Rows.Count, dws.Range(dFirst).Column).End(xlUp)
Dim drg As Range: Set drg = dws.Range(dFirst, dlCell)
Application.ScreenUpdating = False
Dim sCell As Range
Dim cIndex As Variant
For Each sCell In srg.Cells
cIndex = Application.Match(sCell.Value, drg, 0)
If IsNumeric(cIndex) Then
drg.Cells(cIndex).EntireRow.Value = sCell.EntireRow.Value
Else
Set dlCell = dlCell.Offset(1)
dlCell.EntireRow.Value = sCell.EntireRow.Value
End If
Next sCell
Application.ScreenUpdating = True
End Sub
There are around 1000 different "customer_ids" in total. These can also occur several times in the file on several worksheets.
The "customer_id" data records should be automatically overwritten with a new name. The designation represents a format consisting of a fixed sequence of letters + a consecutive, ascending number -> ABC1, ABC2, ..., ABCn. See figure above left.
The name of the row-header and its position can be different in the worksheets. This means that the "customer_id" can also be found as "cust_id" in columns other than "A". See figures.
The recurring customer_id's should have the same name on all worksheets, see figures.
Please, test the next (working) solution:
Edited:
Please, try the next version (using arrays) which should be much faster:
Option Explicit
Sub ChangeIDPart2()
Const idBaseName As String = "ABC"
Const ColNamesList As String = "customer_id,cust_id" ' add more
Const HeaderRow As Long = 1
Dim wb As Workbook: Set wb = ThisWorkbook ' workbook containing this code
Dim fRow As Long: fRow = HeaderRow + 1
Dim ColNames() As String: ColNames = Split(ColNamesList, ",")
Dim cUpper As Long: cUpper = UBound(ColNames)
Dim dict As Object: Set dict = CreateObject("Scripting.Dictionary")
dict.CompareMode = vbTextCompare ' ignore case of 'idBaseName'
Dim ws As Worksheet ' Current Worksheet
Dim rrg As Range ' Entire Row of Headers
Dim arr As Variant ' ID Column Range array (changed...)
Dim cCell As Range ' Current Cell in ID Column Range
Dim cIndex As Variant ' Current ID Column (could be an error value)
Dim Key As Variant ' Current ID (string)
Dim lRow As Long ' ID Column Last Non-Empty (Not Hidden) Row
Dim n As Long ' New ID Incrementer
Dim i As Long ' Column Names (Titles, Headers) Counter
Dim foundHeader As Boolean ' Found Header Boolean
For Each ws In wb.Worksheets
fRow = HeaderRow + 1
Set rrg = ws.Rows(HeaderRow)
For i = 0 To cUpper
cIndex = Application.Match(ColNames(i), rrg, 0)
If IsNumeric(cIndex) Then
foundHeader = True
Exit For
End If
Next i
If Not foundHeader Then
Dim k As Long
For k = 1 To 5
Set rrg = ws.Rows(HeaderRow + k)
For i = 0 To cUpper
cIndex = Application.Match(ColNames(i), rrg, 0)
If IsNumeric(cIndex) Then
fRow = rrg.row + 1
foundHeader = True
Exit For
End If
Next i
If foundHeader Then Exit For
Next k
End If
If Not foundHeader Then MsgBox "In sheet " & ws.Name & _
" an appropriate header could not be found in first 6 rows..."
If foundHeader Then
foundHeader = False ' reset
lRow = ws.Cells(ws.Rows.Count, cIndex).End(xlUp).Row
If lRow > 1 Then ' check if any id's
arr = ws.Range(ws.Cells(fRow, cIndex), _
ws.Cells(lRow, cIndex)).Value 'put the range in array (to iterate faster)
For i = 1 To UBound(arr)
Key = CStr(arr(i, 1))
If Key <> "" Then
If Not dict.Exists(Key) Then
n = n + 1
dict.Add Key, idBaseName & n
End If
arr(i, 1) = dict(Key)
End If
Next i
ws.Range(ws.Cells(fRow, cIndex), _
ws.Cells(lRow, cIndex)).Value = arr 'drop back in the range the processed array
End If
End If
Next ws
MsgBox "Done.", vbInformation, "Change ID Part 2"
End Sub
Please, test it and send some feedback. I am curious how much it takes. Theoretically, it should be obviously faster.