Macro That Detects More than 5 characters in Cell (zip code) - excel

I have created a function that retrieves a Range based on column name. Here is my code:
Sub sep_Filter()
Dim zip_rng As String
With Sheet2
zip_rng = getColRangeFunction("postalcode")
If Len(Range(zip_rng)) > 5 Then
Range(zip_rng).Interior.Color = RGB(255, 0, 0)
Range(zip_rng).Select
Else
Range(zip_rng).Interior.Color = xlNone
End If
End With
End Sub
Sheet2 Input Column D
Sheet2 Output Column D
Sheet3 Output Column D
088762598
088762598
06610-5000
06610-5000
330161898
330161898
970152880
970152880
112202570
112202570
127420800
127420800
062262040
062262040
07631
07631
10029
10029
11803
11803
99336
99336

EDIT I misunderstood what you were asking, I updated my answer to be tied to your question.
Here's a basic approach that will do what you're asking. It skips row one.
Sub onlyfirst5()
Const pRange As String = "D1"
Dim ws As Worksheet
Set ws = ActiveSheet
Dim crng As Range, cValues()
Set crng = Intersect(ws.UsedRange.Offset(1, 0), ws.UsedRange, ws.Range("D:D"))
cValues = crng.Value
Dim i As Long, j As Long
For i = LBound(cValues) To UBound(cValues)
For j = LBound(cValues, 2) To UBound(cValues, 2)
cValues(i, j) = Left(cValues(i, j), 5)
Next j
Next i
'for same sheet different column
ws.Range("F2").Resize(UBound(cValues), UBound(cValues, 2)) = Application.Transpose(cValues)
'different sheet
Sheets("Sheet2").Range("F2").Resize(UBound(cValues), UBound(cValues, 2)) = Application.Transpose(cValues)
'different file
Workbooks("Zip Code Question.xlsb").Sheets("Sheet3").Range("F2").Resize(UBound(cValues), UBound(cValues, 2)) = Application.Transpose(cValues)
End Sub

Copy Entire Rows If Criteria Met
Option Explicit
Sub Postal5()
' Define constants.
Const srcName As String = "Sheet2"
Const srcFirst As String = "D2"
Const dstName As String = "Sheet3"
Const dstFirst As String = "A2" ' do not change the 'A' (entire row).
Const pLen As Long = 5
' Define workbook.
Dim wb As Workbook: Set wb = ThisWorkbook ' Workbook containing this code.
' Define Source Range.
Dim LastRow As Long
Dim srg As Range
With wb.Worksheets(srcName).Range(srcFirst)
LastRow = .Offset(.Worksheet.Rows.Count - .Row).End(xlUp).Row
Set srg = .Resize(LastRow - .Row + 1)
End With
' 'Combine' critical cells into a range.
Dim brg As Range ' Built Range
Dim cel As Range ' Current Cell Range
For Each cel In srg.Cells
If Len(cel.Value) > pLen Then
If brg Is Nothing Then
Set brg = cel
Else
Set brg = Union(brg, cel)
End If
End If
Next cel
If brg Is Nothing Then Exit Sub
Application.ScreenUpdating = False
' Copy and delete critical rows of Source Range.
With wb.Worksheets(dstName).Range(dstFirst)
.Resize(.Worksheet.Rows.Count - .Row + 1, _
.Worksheet.Columns.Count).Clear
Set brg = brg.EntireRow ' 'Convert' cells into rows.
brg.Copy .Offset ' Copy. 'Offset' because range is in 'With'.
brg.Delete ' Delete.
End With
Application.ScreenUpdating = False
End Sub

Text the next code, please. It uses arrays and it should be very fast for a big range:
Sub testSplitZiPCodeStrings()
Dim sh2 As Worksheet, sh3 As Worksheet, lastR As Long
Dim i As Long, arr, arrZip, arrNoZip, kZ As Long, kN As Long
Set sh2 = ActiveSheet ' Worksheets("Sheet2")
Set sh3 = sh2.Next ' Worksheets("Sheet3")
lastR = sh2.Range("D" & sh2.Rows.count).End(xlUp).row 'last row
arr = sh2.Range("D2:D" & lastR).Value 'put the range in an array
ReDim arrZip(UBound(arr) - 1) 'redim the array to surely have place for all elements
ReDim arrNoZip(UBound(arr) - 1) 'redim the array to surely have place for all elements
For i = 1 To UBound(arr) ' iterate between the array elements
If Len(arr(i, 1)) = 5 Then
arrZip(kZ) = arr(i, 1): kZ = kZ + 1
Else
arrNoZip(kN) = arr(i, 1): kN = kN + 1
End If
Next i
ReDim Preserve arrZip(kZ - 1) 'keep only the array elements having values
ReDim Preserve arrZip(kN - 1) 'keep only the array elements having values
sh2.Range("D2:D" & lastR).Clear 'Clear the initial range
'Drop the Zip array content at once:
sh2.Range("D2").Resize(UBound(arrZip), 1).Value = Application.Transpose(arrZip)
'Drop the NoZip array content at once:
sh3.Range("D2").Resize(UBound(arrNoZip), 1).Value = Application.Transpose(arrNoZip)
End Sub

Here's 2 samples. The first one is more intuitive and uses ranges. The second one is less intuitive but faster by using arrays.
Simple but Slower:
'The easy way, but can be slow if you have lots of zip codes
Sub TrimRange()
Dim InputWorksheet As Worksheet, OutputWorksheet As Worksheet
Dim RangeInput As Range, RangeOutput As Range, Column As Range
Dim HeaderRow As Integer, ColumnNumber As Integer, LastRow As Integer, i As Integer
Dim OutputColumn As Range
Dim ColumnFound As Boolean
Dim fullzipcode As String
Set InputWorksheet = Worksheets("Sheet2")
Set OutputWorksheet = Worksheets("Sheet3")
HeaderRow = 1
'Get Input and Output Range
ColumnNumber = 0
ColumnFound = False
For Each Column In InputWorksheet.Columns
ColumnNumber = ColumnNumber + 1
If Column.Cells(HeaderRow, 1) = "postalcode" Then
LastRow = Column.End(xlDown).Row
'I assume the Output column will be in the same position as the input column
Set OutputColumn = OutputWorksheet.Columns(ColumnNumber)
'If OutputColumn is always in Column 'D' then replace previous line with:
'Set OutputColumn = OutputWorksheet.Columns(4)
Set RangeInput = InputWorksheet.Range(Column.Cells(HeaderRow + 1, 1), Column.Cells(LastRow, 1))
Set RangeOutput = OutputWorksheet.Range(OutputColumn.Cells(HeaderRow + 1, 1), OutputColumn.Cells(LastRow, 1))
ColumnFound = True
Exit For
End If
Next
If ColumnFound Then
'Initialize Interior color to nothing
'and remove values from output column
RangeInput.Interior.ColorIndex = 0
RangeOutput.ClearContents
'Change values and formatting
For i = 1 To RangeInput.Rows.Count
fullzipcode = RangeInput.Cells(i, 1).Value
If Len(fullzipcode) > 5 Then
RangeInput.Cells(i, 1).Interior.Color = RGB(255, 0, 0)
RangeInput.Cells(i, 1).Value = Left(fullzipcode, 5)
End If
RangeOutput.Cells(i, 1).Value = fullzipcode
Next
End If
End Sub
Faster but Less Intuitive
'The harder way, but faster
Sub TrimRange2()
Dim InputWorksheet As Worksheet, OutputWorksheet As Worksheet
Dim RangeInput As Range, RangeOutput As Range, Column As Range
Dim HeaderRow As Integer, ColumnNumber As Integer, LastRow As Integer, i As Integer
Dim InputValues() As Variant, OutputValues() As Variant
Dim OutputColumn As Range
Dim ColumnFound As Boolean
Dim fullzipcode As String
Set InputWorksheet = Worksheets("Sheet2")
Set OutputWorksheet = Worksheets("Sheet3")
HeaderRow = 1
'Get Input and Output Range
ColumnNumber = 0
ColumnFound = False
For Each Column In InputWorksheet.Columns
ColumnNumber = ColumnNumber + 1
If Column.Cells(HeaderRow, 1) = "postalcode" Then
LastRow = Column.End(xlDown).Row
'I assume the Output column will be in the same position as the input column
Set OutputColumn = OutputWorksheet.Columns(ColumnNumber)
'If OutputColumn is always in Column 'D' then replace previous line with:
'Set OutputColumn = OutputWorksheet.Columns(4)
Set RangeInput = InputWorksheet.Range(Column.Cells(HeaderRow + 1, 1), Column.Cells(LastRow, 1))
Set RangeOutput = OutputWorksheet.Range(OutputColumn.Cells(HeaderRow + 1, 1), OutputColumn.Cells(LastRow, 1))
ColumnFound = True
Exit For
End If
Next
If ColumnFound Then
'Initialize Interior color to nothing
'and remove values from output column
RangeInput.Interior.ColorIndex = 0
RangeOutput.ClearContents
'Initialize Arrays (much faster than working with ranges)
InputValues = RangeInput.Value2
OutputValues = RangeOutput.Value2
'Change values and formatting
For i = 1 To RangeInput.Rows.Count
fullzipcode = InputValues(i, 1)
If Len(fullzipcode) > 5 Then
RangeInput.Cells(i, 1).Interior.Color = RGB(255, 0, 0)
InputValues(i, 1) = Left(fullzipcode, 5)
End If
OutputValues(i, 1) = fullzipcode
Next
'Save arrays to ranges
RangeInput.Value2 = InputValues
RangeOutput.Value2 = OutputValues
End If
End Sub

Related

Partial match string from a range to another range

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

Loop through all cells, check if cell value is X then copy cells to different location

I have a CSV file with a single column and a billion rows of text. There is a lot of filler, fluff and unnecessary text in those rows, but also a repeating pattern that I want to copy to another sheet.
The CSV looks something like this:
Column A
Fluff
Above Value
Filter Value
Below Value
Fluff
I need to check for a specific string in the 'Filter Value' cell, and if there is a match populate a table in a different sheet with the Filter Value, Above Value and Below Value, looking like this:
Filter Values
Above Values
Below Values
FValue 1
AValue 1
BValue1
FValue 2
AValue 2
BValue2
...
...
...
The code looks like this. It is not putting the Above / Below values into the proper positions:
Sub CopyRecords()
Dim FilterCol As Range
Dim Filter As Range
Dim PasteCell As Range
Dim PasteCellAbove As Range
Dim PasteCellBelow As Range
' Clear Destination table for testing
ThisWorkbook.Sheets(2).Range("A2:C999").Clear
Set FilterCol = ThisWorkbook.Sheets(1).Range("A1:A999")
For Each Filter In FilterCol
If ThisWorkbook.Sheets(2).Range("A2") = "" Then
Set PasteCell = ThisWorkbook.Sheets(2).Range("A2")
Set PasteCellAbove = ThisWorkbook.Sheets(2).Range("B2")
Set PasteCellBelow = ThisWorkbook.Sheets(2).Range("C2")
Else
Set PasteCell = ThisWorkbook.Sheets(2).Range("A1").End(xlDown).Offset(1, 0)
Set PasteCellAbove = ThisWorkbook.Sheets(2).Range("B1").End(xlDown).Offset(1, 0)
Set PasteCellBelow = ThisWorkbook.Sheets(2).Range("C1").End(xlDown).Offset(1, 0)
End If
If Left(Filter, 5) = "Testo" Then
Range(Filter.End(xlToLeft), Filter.End(xlToRight)).Copy PasteCell
Range(Filter.Offset(1, 0), Filter.Offset(0, 0)).Copy PasteCellAbove
Range(Filter.Offset(-1, 0), Filter.Offset(0, 0)).Copy PasteCellBelow
End If
Next Filter
End Sub
Try this - wherever possible it's much faster to work with arrays of data.
Sub CopyRecords()
Dim data, r As Long, rwOut As Range, v
'get all data as an array
With ThisWorkbook.Sheets(1)
data = ThisWorkbook.Sheets(1).Range("A1:A" & _
.Cells(.Rows.Count, "A").End(xlUp).Row).Value
End With
With ThisWorkbook.Sheets(2) 'reporting sheet
.Range("A2:C999").Clear 'clear destination table
Set rwOut = .Range("A2:C2") 'first row of output
End With
For r = 2 To UBound(data, 1) - 1
v = Trim(data(r, 1))
If v Like "*email:*" Then
rwOut.Value = Array(v, data(r - 1, 1), data(r + 1, 1)) 'write values
Set rwOut = rwOut.Offset(1, 0) 'next row down
End If
Next r
End Sub
If you really have a billion rows in your input file, I don't think you will want to open that in an Excel sheet in order to process it.
Here is a solution that opens a TextStream object and reads the source file line by line rather than reading it all into memory.
It dumps the output into a new worksheet in the Excel file, but depending on how large your output is, I wonder if you ultimately might want to write that out to a CSV file instead.
Anyway, here is a potential solution. Note that I didn't do any parsing of the "Before" and "After" lines.
Option Explicit
Public Sub extractData()
Const sourceName As String = "c:\apps\excel\so demo\input.csv" 'change this as necessary
Const maxOutputRecs As Long = 10000000
Dim fso As Scripting.FileSystemObject
Dim ts As Scripting.TextStream
Dim before, after, cLine As String
Dim n, i As Long
Dim xlSheet As Excel.Worksheet
Dim rng As Excel.Range
Dim data() As Variant
ReDim data(1 To maxOutputRecs, 1 To 3)
'Add header line to output array
data(1, 1) = "Testo"
data(1, 2) = "Before"
data(1, 3) = "After"
Set fso = New Scripting.FileSystemObject
Set ts = fso.OpenTextFile(Filename:=sourceName, IOMode:=ForReading, Create:=False)
i = 0
n = 0
cLine = ""
'read through source file line by line
Do While Not ts.AtEndOfStream
i = i + 1
before = cLine
cLine = ts.ReadLine
If VBA.Left(cLine, 5) = "Testo" Then
n = n + 1
after = ts.ReadLine
data(n + 1, 1) = cLine
data(n + 1, 2) = before
data(n + 1, 3) = after
cLine = after
End If
If n + 1 = maxOutputRecs Then
'end loop - may want to throw an error or write to a log file or do something else
Exit Do
End If
Loop
ts.Close
data = redim2DArrayRows(data, n + 1, 3)
'create a new worksheet for the output
Set xlSheet = ThisWorkbook.Worksheets.Add
xlSheet.Name = "output"
'define the output range in the worksheet based on array size
Set rng = xlSheet.Range( _
xlSheet.Cells(1, 1), _
xlSheet.Cells(UBound(data, 1), UBound(data, 2)) _
)
'Write data out to sheet
rng.Value = data
End Sub
Public Function redim2DArrayRows(ByRef sourceArray() As Variant, ByVal rowBound As Long, ByVal colBound As Long) As Variant()
Dim newArr() As Variant
Dim i As Long
Dim j As Long
ReDim newArr(LBound(sourceArray, 1) To rowBound, LBound(sourceArray, 2) To colBound)
For i = LBound(newArr, 1) To UBound(newArr, 1)
For j = LBound(newArr, 2) To UBound(newArr, 2)
newArr(i, j) = sourceArray(i, j)
Next j
Next i
redim2DArrayRows = newArr
End Function
Extract Data Using FindNext
Option Explicit
Sub ExtractData()
Const ProcTitle As String = "Extract Data"
Const sCriteria As String = "Testo*" ' begins with ("*Testo*" contains)
Const cCount As Long = 3 ' don't change: it's the same for source and dest.
Dim wb As Workbook: Set wb = ThisWorkbook
' Source
Dim sws As Worksheet: Set sws = wb.Worksheets(1)
Dim srg As Range
' Either static...
Set srg = sws.Range("A2:A999") ' no cell above 'A1'
' ... or dynamic:
'Set srg = sws.Range("A2", sws.Cells(sws.Rows.Count, "A").End(xlUp))
Dim sCell As Range
Set sCell = srg.Find(sCriteria, srg.Cells(srg.Rows.Count), xlValues, xlPart)
If sCell Is Nothing Then Exit Sub
Dim FirstAddress As String: FirstAddress = sCell.Address
Dim sTemp As Variant: ReDim sTemp(1 To cCount)
' Destination
Dim dws As Worksheet: Set dws = wb.Worksheets(2)
Dim dCell As Range: Set dCell = dws.Range("A2")
Dim dColl As Collection: Set dColl = New Collection
' Write the 3 values to the Temp array and add the array to the collection.
Do
' Modify here, if you don't need the complete cell contents.
' Cell
sTemp(1) = sCell.Value
' Above
sTemp(2) = sCell.Offset(-1).Value
' Below
sTemp(3) = sCell.Offset(1).Value
dColl.Add sTemp
Set sCell = srg.FindNext(sCell)
Loop Until sCell.Address = FirstAddress
Dim drCount As Long: drCount = dColl.Count
Dim dData As Variant: ReDim dData(1 To drCount, 1 To cCount)
Dim Item As Variant
Dim r As Long
Dim c As Long
' Loop over the arrays in the collection and write the elements
' of each array to a row of the Destination array.
For Each Item In dColl
r = r + 1
For c = 1 To cCount
dData(r, c) = Item(c)
Next c
Next Item
' Write the values of the Destination array to the Destination range.
Dim drg As Range: Set drg = dCell.Resize(drCount, cCount)
drg.Value = dData
' Clear the range below the Destination range.
Dim dcrg As Range: Set dcrg = drg.Resize( _
dws.Rows.Count - drg.Row - drCount + 1).Offset(drCount)
dcrg.Clear
'Debug.Print drg.Address(0, 0), dcrg.Address(0, 0)
MsgBox "Done.", vbInformation, ProcTitle
End Sub

Overwrite & Distribute Values

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.

Change the value of each cell that meets criteria in a column

I am trying to change the value of each cell in column 7 that meets criteria. So far I managed to change the value with one criteria but I would like to add up to 14 criteria. Thanks for your help
Sub ChangeValue()
Dim i As Integer
Dim WK As Worksheet
Dim rg1 As range
Dim rg2 As range
Set WK = Sheet4
Set rg1 = range("AB2")
Set rg2 = range("AB3")
For i = Cells(Rows.Count, 7).End(xlUp).Row To 1 Step -1
If Cells(i, 7).Value = rg1 Then
Cells(i, 7).Value = rg2.Value
End If
Next i
End Sub
I would like to have more conditions something like if = AB3 change to AB4 if= AB4 Change to AB5 and so on...
To create a variable list of value/replace value pairs I would suggest using a dictionary:
Option Explicit
Sub ChangeValue()
Dim d
Set d = CreateObject("Scripting.Dictionary")
Dim r_test_value As Range
Dim r_anchor As Range
Set r_anchor = Range("AB2")
'need at least 2 values
If Not IsEmpty(r_anchor) And Not IsEmpty(r_anchor.Offset(1, 0)) Then
Set r_test_value = Range(r_anchor, _
Cells(Rows.Count, r_anchor.Column).End(xlUp).Offset(-1, 0))
Debug.Print r_test_value.Address
Dim i As Long
i = 0
Dim r As Range
For Each r In r_test_value
d.Add r.Value, r.Offset(i+1, 0).Value
i = i + 1
Next r
For i = Cells(Rows.Count, 7).End(xlUp).Row To 1 Step -1
If d.exists(Cells(i, 7).Value) Then
Cells(i, 7).Value = d.Item(Cells(i, 7).Value)
End If
Next i
End If
End Sub
Search and Replace Cell Values
EDIT
This is a more appropriate solution.
Adjust the starting rows i.e. For i = ? and For k = ?
Second Answer
Sub replaceValues()
' Determine Source Last Row.
Dim sLastRow As Long
sLastRow = Sheet4.Cells(Sheet4.Rows.Count, "AB").End(xlUp).Row
' Determine Destination Last Row.
Dim dLastRow As Long
dLastRow = Sheet4.Cells(Sheet4.Rows.Count, "G").End(xlUp).Row
Dim i As Long ' Destination Range Rows Counter
Dim k As Long ' Source Rows Counter
' Loop through rows of Destination Range.
For i = 2 To dLastRow
' Loop through rows of Source Range.
For k = 1 To sLastRow - 1
' When a value is found...
If Sheet4.Cells(i, "G").Value = Sheet4.Cells(k, "AB").Value Then
' ... replace it with the value below.
Sheet4.Cells(i, "G").Value = Sheet4.Cells(k + 1, "AB").Value
Exit For ' Value has been found and replaced. Stop searching.
' Otherwise you'll end up with the last replace value.
End If
Next k
Next i
' Inform.
MsgBox "Values replaced.", vbInformation, "Success"
End Sub
The First Answer (misunderstood)
The first solution is for using worksheet code names. It can be used for two worksheets. It is one in your case (Sheet4).
The second solution shows how to use it in two worksheets using worksheet names.
The code will loop through a column range of values and replace each value found in a row range of 'search values' with an associated 'replace value' in another same sized row range (in this case the ranges are adjacent, one below the other).
The Code
Option Explicit
Sub replaceValuesWorksheetCodeNames()
' Source
' Make sure the following two are of the same size.
Const srcAddress As String = "AB2:AO2"
Const rplAddress As String = "AB3:AO3"
' Destination
Const dFirstCell As String = "G2"
' Write Source Row Ranges to Source Arrays (Search and Replace).
With Sheet4
Dim srcData As Variant: srcData = .Range(srcAddress).Value
Dim rplData As Variant: rplData = .Range(rplAddress).Value
End With
' Define Destination Column Range.
Dim rg As Range
Dim RowOffset As Long
With Sheet4.Range(dFirstCell)
RowOffset = .Row - 1
Set rg = .Resize(.Worksheet.Rows.Count - RowOffset) _
.Find("*", , xlFormulas, , , xlPrevious)
If rg Is Nothing Then Exit Sub
Set rg = .Resize(rg.Row - RowOffset)
End With
' Write values from Destination Column Range to Data Array.
Dim rCount As Long: rCount = rg.Rows.Count
Dim Data As Variant
If rCount > 1 Then
Data = rg.Value
Else
ReDim Data(1 To 1, 1 To 1): Data = rg.Value
End If
' Search and replace values in Data Array.
Dim cValue As Variant
Dim cIndex As Variant
Dim i As Long
For i = 1 To rCount
cValue = Data(i, 1)
If Not IsError(cValue) Then
If Len(cValue) > 0 Then
cIndex = Application.Match(cValue, srcData, 0)
If IsNumeric(cIndex) Then
' When the replace data is in a row range.
Data(i, 1) = rplData(1, cIndex)
' When the replace data is in a column range.
'Data(i, 1) = rplData(cIndex, 1)
End If
End If
End If
Next i
' Write possibly modified values from Data Array back
' to Destination Column Range.
rg.Value = Data
' Inform.
MsgBox "Values replaced.", vbInformation, "Success"
End Sub
Sub replaceValuesWorksheetNames()
' Source
Const sName As String = "Sheet1"
' Make sure the following two are of the same size.
Const srcAddress As String = "AB2:AO2"
Const rplAddress As String = "AB3:AO3"
' Destination
Const dName As String = "Sheet2"
Const dFirstCell As String = "G2"
' Workbook
Dim wb As Workbook: Set wb = ThisWorkbook
' Write Source Row Ranges to Source Arrays (Search and Replace).
With wb.Worksheets(sName)
Dim srcData As Variant: srcData = .Range(srcAddress).Value
Dim rplData As Variant: rplData = .Range(rplAddress).Value
End With
' Define Destination Column Range.
Dim rg As Range
Dim RowOffset As Long
With wb.Worksheets(dName).Range(dFirstCell)
RowOffset = .Row - 1
Set rg = .Resize(.Worksheet.Rows.Count - RowOffset) _
.Find("*", , xlFormulas, , , xlPrevious)
If rg Is Nothing Then Exit Sub
Set rg = .Resize(rg.Row - RowOffset)
End With
' Write values from Destination Column Range to Data Array.
Dim rCount As Long: rCount = rg.Rows.Count
Dim Data As Variant
If rCount > 1 Then
Data = rg.Value
Else
ReDim Data(1 To 1, 1 To 1): Data = rg.Value
End If
' Search and replace values in Data Array.
Dim cValue As Variant
Dim cIndex As Variant
Dim i As Long
For i = 1 To rCount
cValue = Data(i, 1)
If Not IsError(cValue) Then
If Len(cValue) > 0 Then
cIndex = Application.Match(cValue, srcData, 0)
If IsNumeric(cIndex) Then
' When the replace data is in a row range.
Data(i, 1) = rplData(1, cIndex)
' When the replace data is in a column range.
'Data(i, 1) = rplData(cIndex, 1)
End If
End If
End If
Next i
' Write possibly modified values from Data Array back
' to Destination Column Range.
rg.Value = Data
' Inform.
MsgBox "Values replaced.", vbInformation, "Success"
End Sub

How to transpose specific cell values in a row based on "Y" or "N" input

The ultimate goal is to set the status of a particular row to "Yes" and have the data of that row that is highlighted RED automatically be entered into another sheet in order to be printed in a format required for a Zlabel printer.
If you can imagine this raw data on a larger scale and having to print 50+ rows daily. I do this manually now but really hoping to streamline this process
This is how I'm hoping the data will look on a separate sheet when the status is set to "Yes" regardless of how many rows there are I could print in bulk
Open to any other suggestions that may include VBA macros or any other recommended solutions.
Any advice or help is extremely appreciated!
Try,
Sub test()
Dim Ws As Worksheet, toWs As Worksheet
Dim vDB, vR()
Dim i As Long, n As Long, r As Long
Set Ws = Sheets(1) 'Data sheet
Set toWs = Sheets(2) 'Result sheet
vDB = Ws.Range("a1").CurrentRegion
r = UBound(vDB, 1)
For i = 1 To r
If vDB(i, 9) = "Yes" Then
n = n + 5
ReDim Preserve vR(1 To n)
vR(n - 4) = vDB(i, 1)
vR(n - 3) = vDB(i, 4)
vR(n - 2) = vDB(i, 5)
vR(n - 1) = vDB(i, 7)
End If
Next i
With toWs
.UsedRange = Empty
.Range("a1").Resize(n) = WorksheetFunction.Transpose(vR)
End With
End Sub
If you have Excel O365, then you could also opt for a formula. If your data has to start in Sheet2!A1 onwards then in A1:
=IF(MOD(ROW(),5)>0,INDEX(INDEX(FILTER(Sheet1!A:H,Sheet1!I:I="Yes"),SEQUENCE(COUNTIF(Sheet1!I:I,"Yes")),{1;4;5;7}),ROUNDUP(ROW()/5,0),MOD(ROW(),5)),"")
Drag down.
Copy By Criteria
The following automatically clears the contents of the Target Worksheet
("Sheet2") and copies all data specified by Crit ("Yes") to
it ("Sheet2"), when any data in the Criteria Column ("I") of
the Source Worksheet ("Sheet1") is manually changed (i.e.
it could be written to run more efficiently).
If you don't want it to run automatically, then remove the code from
the Sheet Module and just run the first Sub (maybe using a
button) when needed (which was my first idea).
You can change tgtGap, the number of rows in between data blocks.
You can add or remove columns to the Cols array.
Standard Module e.g. Module1
Option Explicit
Public Const CriteriaColumn As Variant = "I" ' e.g. "A" or 1
Sub copyByCriteria()
' Source
Const srcName As String = "Sheet1"
Const FirstRow As Long = 2
Const Crit As String = "Yes"
Dim Cols As Variant: Cols = Array("A", "D", "E", "G") ' or 1, 4, 5, 7
' Target
Const tgtName As String = "Sheet2"
Const tgtFirstCell As String = "A1"
Const tgtGap As Long = 1
' Workbook
Dim wb As Workbook: Set wb = ThisWorkbook
' Collect data from Source Worksheet.
Dim ws As Worksheet: Set ws = wb.Worksheets(srcName)
Dim Criteria As Variant
getColumn Criteria, ws, CriteriaColumn, FirstRow
If IsEmpty(Criteria) Then Exit Sub
Dim ubC As Long: ubC = UBound(Criteria)
Dim ubD As Long: ubD = UBound(Cols)
Dim Data As Variant: ReDim Data(ubD)
Dim j As Long
For j = 0 To ubD
Data(j) = ws.Cells(FirstRow, Cols(j)).Resize(ubC)
Next j
Dim critCount As Long
critCount = Application.WorksheetFunction _
.CountIf(ws.Columns(CriteriaColumn), Crit)
' Write data from Data Arrays to Target Array.
Dim Target As Variant, i As Long, k As Long
ReDim Target(1 To critCount * (ubD + 1 + tgtGap) - tgtGap, 1 To 1)
For i = 1 To ubC
If Criteria(i, 1) = Crit Then
For j = 0 To ubD
k = k + 1
Target(k, 1) = Data(j)(i, 1)
Next j
k = k + tgtGap
End If
Next i
' Write Target Array to Target Worksheet.
Set ws = wb.Worksheets(tgtName)
ws.Cells.ClearContents
ws.Range(tgtFirstCell).Resize(UBound(Target)).Value = Target
End Sub
Sub getColumn(ByRef Data As Variant, _
Sheet As Worksheet, _
Optional aColumn As Variant = 1, _
Optional FirstRow As Long = 1)
Dim rng As Range
Set rng = Sheet.Columns(aColumn).Find("*", , xlValues, , , xlPrevious)
If rng Is Nothing Then Exit Sub
If rng.Row < FirstRow Then Exit Sub
If rng.Row > FirstRow Then
Data = Sheet.Range(Sheet.Cells(FirstRow, aColumn), rng).Value
Else
ReDim Data(1 To 1, 1 To 1): Data(1, 1) = rng.Value
End If
End Sub
Sheet Module e.g. Sheet1
Option Explicit
Private Sub Worksheet_Change(ByVal Target As Range)
If Not Intersect(Target, Me.Columns(CriteriaColumn)) Is Nothing Then
copyByCriteria
End If
End Sub

Resources