I have a dataset in excel which looks like this:
There can be more cells with data. I am trying to extract data from these big cells and paste values to more comprehensive table, which should look like this:
What would be the best way to proceed? I imagine process should look like this:
Select range of filled cells, store row count as value
Do a loop for that many rows as value
Store whole cell value as string
Find "Btc = *" and store it as btc value. Paste that value into prefered table
Find "Qua= *" and store it as qua value. Paste that value into prefered table
..etc
Clean up cells in new table using Replace
I am stuck on extracting part of text to value. What function can I use to assign that "Btc = *" to variable? Like operator gets be a whole string, but I only need parts of it
Or maybe you have ideas on how to do this task easier?
Please, try the next code. It uses arrays and will be very fast (working mostly in memory). Please adapt your real sheet used as destination, where the processed result to be dropped (shDest). Now, the code returns in the next sheet. If you want it as it is, please insert/have an empty sheet after the one to be processed:
Sub extractValTranspose()
Dim sh As Worksheet, shDest As Worksheet, lastR As Long, arr, arrFin
Dim i As Long, k As Long, j As Long
Set sh = ActiveSheet 'use here the sheet to be processed
Set shDest = sh.Next 'use here the sheet where you want the processed result to be dropped
lastR = sh.Range("A" & sh.Rows.Count).End(xlUp).Row 'last row in A:A
arr = sh.Range("A2:A" & lastR).Value2 'place the range in an array for faster iteration/procesing
ReDim arrFin(1 To UBound(arr) / 10 + 1, 1 To 10) 'redim the final array (to keep the processed data)
'extract headers array:
For i = 1 To 10
arrFin(1, i) = Split(Replace(arr(i, 1), " ", ""), "=")(0) 'place the header on the first array row
arrFin(2, i) = Split(Replace(arr(i, 1), " ", ""), "=")(1) 'place the data on the second row
Next i
k = 3: j = 1 'initialize variables keeping the (next) row and columns
'place the rest of rows data:
For i = 11 To UBound(arr)
arrFin(k, j) = Split(Replace(arr(i, 1), " ", ""), "=")(1): j = j + 1
If j = 11 Then k = k + 1: j = 1 'reinitialize the variable for each 10 rows
Next
'drop the array content, at once and do a little formatting:
With shDest.Range("A1").Resize(UBound(arrFin), UBound(arrFin, 2))
.Value2 = arrFin
.EntireColumn.AutoFit
.Borders.Color = vbBlack
.BorderAround 1, xlMedium
With .Rows(1)
.Font.Bold = True
.BorderAround 1, xlMedium
.HorizontalAlignment = xlCenter
End With
End With
MsgBox "Ready..."
shDest.Activate
End Sub
Edited:
Nothing from your question made me understand that all groups of 10 strings to be processed are in the same cell...
Please, test the next piece of code able to process the above mentioned range type:
Sub extractValSameCellTranspose()
Dim sh As Worksheet, shDest As Worksheet, lastR As Long
Dim arr, arrCell, arrFin, i As Long, k As Long, j As Long
Set sh = ActiveSheet 'use here the sheet to be processed
Set shDest = sh.Next 'use here the sheet where you want the processed result to be dropped
lastR = sh.Range("A" & sh.rows.count).End(xlUp).row 'last row in A:A
arr = sh.Range("A2:A" & lastR).Value2 'place the range in an array for faster iteration/procesing
ReDim arrFin(1 To UBound(arr), 1 To 10) 'redim the final array (to keep the processed data)
'extract headers and first row of data:
arrCell = Split(arr(1, 1), vbLf) 'split the cell content on vbLf (end of line)
For i = 0 To UBound(arrCell) 'iterate between the above array elements
arrFin(1, i + 1) = Split(VBA.replace(arrCell(i), " ", ""), "=")(0) 'place the header on the first array row
arrFin(2, i + 1) = Split(VBA.replace(arrCell(i), " ", ""), "=")(1) 'place the data on the second row
Next i
k = 3 'initialize variable keeping the array rows
'place the rest of rows data:
For i = 3 To UBound(arr)
arrCell = Split(arr(i, 1), vbLf)
For j = 0 To UBound(arrCell)
arrFin(k, j + 1) = Split(VBA.replace(arrCell(j), " ", ""), "=")(1)
Next j
k = k + 1
Next
'drop the array content, at once and do a little formatting:
With shDest.Range("A1").Resize(UBound(arrFin), UBound(arrFin, 2))
.Value2 = arrFin 'drop the array content
.EntireColumn.AutoFit 'autofit all columns
.Borders.Color = vbBlack 'place some thin borders on each cell
.BorderAround 1, xlMedium 'place a thicker border arround the whole range
With .rows(1) 'format the first (headers) row:
.Font.Bold = True 'make the font bold
.BorderAround 1, xlMedium 'place a thicker border arround
.HorizontalAlignment = xlCenter 'center the string horizontaly
End With
End With
MsgBox "Ready..."
shDest.Activate 'activate the sheet where the processed result has been dropped
End Sub
Please, send some feedback after testing it.
Transform Data With Split
Sub TransformData()
' Define constants.
Const SRC_NAME As String = "Sheet1"
Const SRC_COLUMN As Long = 1
Const SRC_ROW_DELIMITER As String = vbLf ' vbCrLf?
Const SRC_COL_DELIMITER As String = "="
Const DST_NAME As String = "Sheet2"
Const DST_FIRST_COLUMN As Long = 2
Const OVERWRITE_MODE As Boolean = False
Dim wb As Workbook: Set wb = ThisWorkbook ' workbook containing this code
' Write the source data to an array, the source array ('sData').
Dim sws As Worksheet: Set sws = wb.Sheets(SRC_NAME)
Dim sData() As Variant, srCount As Long
With sws.UsedRange
srCount = .Rows.Count - 1
If srCount = 0 Then
MsgBox "No data in the source worksheet.", vbExclamation
Exit Sub
End If
With .Columns(SRC_COLUMN).Resize(srCount).Offset(1) ' source data range
If srCount = 1 Then
ReDim sData(1 To 1, 1 To 1): sData(1, 1) = .Value
Else
sData = .Value
End If
End With
End With
' Write the destination header data to an array ('hData') and reference
' the first destination row range ('dfrrg').
Dim dws As Worksheet: Set dws = wb.Sheets(DST_NAME)
Dim dfrrg As Range, hData() As Variant, dcCount As Long, dc As Long
With dws.UsedRange
Dim dcOffset As Long: dcOffset = DST_FIRST_COLUMN - 1
dcCount = .Columns.Count - dcOffset
With .Columns(1).Resize(, dcCount).Offset(, dcOffset) ' header range
If OVERWRITE_MODE Then
Set dfrrg = .Rows(1).Offset(1)
Else
Set dfrrg = .Rows(1).Offset(.Rows.Count)
End If
hData = .Value
End With
End With
' Write the destination header data to a dictionary.
Dim hDict As Object: Set hDict = CreateObject("Scripting.Dictionary")
hDict.Comparemode = vbTextCompare
For dc = 1 To dcCount: hDict(hData(1, dc)) = dc: Next dc
If hDict.Count < dcCount Then
MsgBox "No duplicates are allowed in the destination header row.", _
vbExclamation
Exit Sub
End If
Erase hData
' Define the destination array ('dData').
' Note that fewer rows are possible if blank or missing source values.
Dim dData() As Variant: ReDim dData(1 To srCount, 1 To dcCount)
' Using the dictionary, write the matching values from the source array
' to the destination array.
Dim sr As Long, sc As Long, sPos As Long, dr As Long, vLen As Long
Dim sArr() As String, sString As String, sTitle As String, sValue As String
Dim InNextRow As Boolean
For sr = 1 To srCount
sArr = Split(CStr(sData(sr, 1)), SRC_ROW_DELIMITER)
For sc = 0 To UBound(sArr)
sString = CStr(sArr(sc))
sPos = InStr(sString, SRC_COL_DELIMITER)
If sPos > 1 Then ' column delimiter it not the first character
sTitle = Left(sString, sPos - 1)
If hDict.Exists(sTitle) Then ' title found in the dictionary
vLen = Len(sString) - sPos
If vLen > 0 Then ' has a value
sValue = Right(sString, vLen)
If Not InNextRow Then dr = dr + 1: InNextRow = True
dData(dr, hDict(sTitle)) = sValue
End If
End If
End If
Next sc
If InNextRow Then InNextRow = False
Next sr
If dr = 0 Then
MsgBox "No values found in the source worksheet.", vbExclamation
Exit Sub
End If
Erase sData
' Write the values from the destination array to the destination range.
dfrrg.Resize(dr).Value = dData ' 'dr', not 'srCount'
If OVERWRITE_MODE Then ' clear below
dfrrg.Resize(dws.Rows.Count - dfrrg.Row - dr + 1).Offset(dr).Clear
End If
' Inform.
MsgBox "Data transformed.", vbInformation
End Sub
Related
I've been trying to tinker with this source code that transposes 1 column separated by spaces.
Sub Transpose()
Dim lastrow As Long, i As Long, j As Long, iStart As Long, iEnd As Long
Dim ws As Worksheet
Application.ScreenUpdating = False
With Sheets("Sheet1")
Set ws = Sheets.Add(after:=Sheets(Sheets.Count))
lastrow = .Cells(Rows.Count, "A").End(xlUp).row
iStart = 1
For i = 1 To lastrow + 1
If .Range("A" & i).Value = "" Then
iEnd = i
j = j + 1
.Range(.Cells(iStart, 1), .Cells(iEnd, 1)).Copy
ws.Range("A" & j).PasteSpecial Paste:=xlPasteValues, Transpose:=True
iStart = iEnd + 1
End If
Next i
End With
Application.CutCopyMode = False
Application.ScreenUpdating = True
End Sub
I'm trying to take take 4 columns ranges with variable rows
[
And transpose each range adjacently so that it looks like this:
Any input appreciated.
Try this out:
Sub Transpose()
Dim ws As Worksheet, cCopy As Range, cPaste As Range
Set ws = Sheets("Sheet1")
Set cCopy = ws.Range("A1") 'top-left of first data block
Set cPaste = ws.Range("F1") 'first output cell
Do While Len(cCopy.Value) > 0 'while have data to transpose
With cCopy.CurrentRegion
Debug.Print "Copying", .Address, " to ", cPaste.Address
cPaste.Resize(.Columns.Count, .Rows.Count) = _
Application.Transpose(.Value)
Set cPaste = cPaste.Offset(.Columns.Count + 1) 'next paste position
Set cCopy = cCopy.Offset(.Rows.Count + 1) 'next data block
End With
Loop
End Sub
Took way too long to do this and the most atrocious architecture but it works.
r = 1
c = 1
cl = 6
rw = 1
For r = 1 To 13
For c = 1 To 4
If Cells(r, c) <> "" Then
Cells(rw, cl) = Cells(r, c)
rw = rw + 1
End If
Next
'If Cells(r, c) = "" Then cl = 6
rw = 1
cl = cl + 1
Next
rw = 5
cl = 6
For r = 1 To 4
For c = 10 To 12
Cells(rw, cl) = Cells(r, c)
cl = cl + 1
Next
rw = rw + 1
cl = 6
Next
rw = 9
cl = 6
For r = 1 To 4
For c = 14 To 18
Cells(rw, cl) = Cells(r, c)
cl = cl + 1
Next
rw = rw + 1
cl = 6
Next
Range("J1:R4").ClearContents
Try this code:
Sub SubRollData()
'Declarations.
Dim RngSource As Range
Dim RngTarget As Range
Dim DblRowOffset As Double
Dim DblColumnOffset As Double
'Settings.
Set RngSource = Range("A1")
Set RngTarget = Range("F1")
'Checkpoint for the block processing.
CP_Block:
'Covering each column.
For DblColumnOffset = 0 To 3
'Setting DblRowOffset to start covering for the first row of the block.
DblRowOffset = 0
'Covering each row of the block of the given column until an empty cell is fount.
Do Until RngSource.Offset(DblRowOffset, DblColumnOffset) = ""
'Reporting the data with switched offset.
RngTarget.Offset(DblColumnOffset, DblRowOffset).Value = RngSource.Offset(DblRowOffset, DblColumnOffset).Value
'Setting DblRowOffset for the next row.
DblRowOffset = DblRowOffset + 1
Loop
Next
'Setting RngSource to aim at the next block.
If RngSource.Offset(1, 0) = "" Then
Set RngSource = RngSource.Offset(2, 0)
Else
Set RngSource = RngSource.End(xlDown).Offset(2, 0)
End If
'Setting RngSource to aim at the next empty row to fill in with data.
If RngTarget.Offset(1, 0) = "" Then
Set RngTarget = RngTarget.Offset(1, 0)
Else
Set RngTarget = RngTarget.End(xlDown).Offset(1, 0)
End If
'If RngSource has no data, there is no more block to be processed. Otherwise, the next block is processed.
If RngSource.Value <> "" Then GoTo CP_Block
End Sub
It works with the example you've given and also with isoletd (single row) source data.
Just for fun, here is a possible formula based solution to be placed in cell F1 and dragged:
=IF(COLUMN(F1)-COLUMN($F1)+1>=AGGREGATE(15,6,1/($A:$A="")*ROW($A:$A),QUOTIENT(ROW(F1)-ROW(F$1),4)+1)-IF(QUOTIENT(ROW(F1)-ROW(F$1),4)=0,0,AGGREGATE(15,6,1/($A:$A="")*ROW($A:$A),QUOTIENT(ROW(F1)-ROW(F$1),4))),"",INDEX($A:$D,COLUMN(F1)-COLUMN($F1)+1+IF(QUOTIENT(ROW(F1)-ROW(F$1),4)=0,0,AGGREGATE(15,6,1/($A:$A="")*ROW($A:$A),QUOTIENT(ROW(F1)-ROW(F$1),4))),MOD(ROW(F1)-ROW(F$1),4)+1))
Naturally it's really heavy and stupidly complicated, but as i said: made it just for fun.
Transpose Groups of Data to a New Worksheet
Sub TransposeGroups()
' Source - use as-is (read (copy) from)
Const sName As String = "Sheet1"
Const sFirstRowAddress As String = "A1:D1"
Const sMandatoryColumnIndex As Long = 1 ' dictates if empty row (gap)
' Destination - delete if exists and put last (write (paste) to)
Const dName As String = "Result"
Const dFirstCellAddress As String = "A1"
' Workbook
Dim wb As Workbook: Set wb = ThisWorkbook ' workbook containing this code
' Write the values from the source range to a 2D one-based array
' and write its upper limits to variables.
Dim sws As Worksheet: Set sws = wb.Worksheets(sName)
Dim sData As Variant
Dim srCount As Long
Dim scCount As Long
With sws.Range(sFirstRowAddress)
Dim lCell As Range
With .Columns(sMandatoryColumnIndex)
Set lCell = .Resize(sws.Rows.Count - .Row + 1) _
.Find("*", , xlFormulas, , , xlPrevious)
End With
If lCell Is Nothing Then
MsgBox "No data in column " & sMandatoryColumnIndex & ".", _
vbCritical
Exit Sub
End If
scCount = .Columns.Count
srCount = lCell.Row - .Row + 1
sData = .Resize(srCount).Value
End With
' Loop through the rows of the source array and map the first row,
' the last row and the following gap count (empty rows) in each row
' of three columns of another 2D one-based array.
' Note that this array has the same number of rows as the source array,
' but the data of interest will be in much fewer rows ('mr').
' (Probably a collection of collections (or three element arrays)
' would have been a better choice.)
Dim mArr() As Long: ReDim mArr(1 To srCount, 1 To 3)
Dim sr As Long
Dim sValueFound As Boolean
Dim mr As Long
Dim ccCount As Long
Dim dcCount As Long
Dim GapCount As Long
For sr = 1 To srCount
If Len(CStr(sData(sr, sMandatoryColumnIndex))) > 0 Then
If Not sValueFound Then
mr = mr + 1
mArr(mr, 1) = sr ' first row
sValueFound = True
End If
Else
If sValueFound Then
sValueFound = False
mArr(mr, 2) = sr - 1 ' last row
ccCount = sr - mArr(mr, 1)
If ccCount > dcCount Then dcCount = ccCount
End If
mArr(mr, 3) = mArr(mr, 3) + 1 ' gap
GapCount = GapCount + 1 ' to determine the number of rows of 'dData'
End If
Next sr
' The very last row (of interest).
mArr(mr, 2) = srCount
ccCount = sr - mArr(mr, 1)
If ccCount > dcCount Then dcCount = ccCount
' Using the source array and the information from the mapping array,
' write the results to the destination array.
Dim drCount As Long: drCount = mr * scCount + GapCount
Dim dData As Variant: ReDim dData(1 To drCount, 1 To dcCount)
Dim drFirst As Long
Dim sc As Long
Dim dc As Long
For mr = 1 To mr
For sc = 1 To scCount
For sr = mArr(mr, 1) To mArr(mr, 2)
dc = dc + 1
dData(drFirst + sc, dc) = sData(sr, sc)
Next sr
dc = 0
Next sc
drFirst = drFirst + sc + mArr(mr, 3) - 1
Next mr
' Write the values from the destination array to a new worksheet.
' Check if a sheet with the same name exists.
Dim dsh As Object
On Error Resume Next
Set dsh = wb.Sheets(dName)
On Error GoTo 0
' If it exists, delete it without confirmation.
If Not dsh Is Nothing Then
Application.DisplayAlerts = False
dsh.Delete
Application.DisplayAlerts = True
End If
' Add a new worksheet and rename it accordingly.
Dim dws As Worksheet
Set dws = wb.Worksheets.Add(After:=wb.Sheets(wb.Sheets.Count))
dws.Name = dName
' Write the values from the destination array to the destination worksheet.
With dws.Range(dFirstCellAddress)
.Resize(drCount, dcCount).Value = dData
End With
' Inform.
MsgBox "Groups transposed.", vbInformation
End Sub
I have an Excel sheet with 4 pages that takes input budgetary adjustment data and reformats into two different formats for entry into different budget softwares.
On the sheets first page, the upload page (feed data), data is given in rows of budget adjustments. These adjustments always come in even numbers of rows because for every account that has money adjusted out of it, another account has that money adjusted into it. In each row there are a number of non-unique qualitative columns that describe the adjustment and then 12 month columns that record the monetary aspects of the adjustment. Some rows have only one month of the 12 filled and can be left alone. Other adjustments occur over several months and thus have several of the months columns filled.
For input into the two budget softwares, these rows that have multiple month columns filled with data need to be expanded into multiple new rows with only one of the 12 columns filled. For clarity, here's what the transformation should look like:
Input:
Output:
How can you do this with input data where some rows don't need to be transformed, some include 2 months of transactions, and some could include up to 12?
Option Explicit
Sub Only_one_data_value_per_row()
Dim myR As Range
Dim rowCt As Integer
Dim actRange As Range
Dim dataCt As Integer
Dim iCt As Integer
Dim myCell As Range
Set actRange = Range("A1").CurrentRegion
For rowCt = actRange.Rows.Count To 2 Step -1
With ActiveSheet.Rows(rowCt)
dataCt = Application.WorksheetFunction.Count(.Range("E1:P1"))
'Debug.Print .Range("E1:P1").Address, dataCt)
For iCt = 1 To dataCt - 1
Rows(rowCt + 1).EntireRow.Insert
Rows(rowCt).Range("A1:D1").Copy Rows(rowCt + 1).Range("A1")
Next iCt
iCt = 0
For Each myCell In Rows(rowCt).Range("E1:P1")
'Debug.Print rowCt; ":"; (nonEmptyCell)
If myCell.Value <> "" Then
Debug.Print myCell.Value
If Val(myCell.Value) = 0 Then
MsgBox "The value of the cell " & myCell.Address & _
" is 0! The cell will be deleted!"
myCell.Value = ""
Else
If iCt > 0 Then
myCell.Offset(iCt, 0).Value = myCell.Value
myCell.Value = ""
End If
iCt = iCt + 1
End If
End If
Next myCell
End With
Next rowCt
End Sub
Input:
Output:
Transform Data: One Value Per Row
Adjust the values in the constants section.
Option Explicit
Sub TransformOneValuePerRow()
' Source
Const sName As String = "Sheet1"
Const sFirstCellAddress As String = "C4"
' Destination
Const dName As String = "Sheet2"
Const dFirstCellAddress As String = "A1"
' Both
Const FixedColumnsCount As Long = 4
Dim wb As Workbook: Set wb = ThisWorkbook ' workbook containing this code
' Reference the current region starting with the first cell.
Dim sws As Worksheet: Set sws = wb.Worksheets(sName)
Dim sFirstCell As Range: Set sFirstCell = sws.Range(sFirstCellAddress)
Dim srg As Range
With sFirstCell.CurrentRegion
Set srg = sFirstCell.Resize(.Row + .Rows.Count - sFirstCell.Row, _
.Column + .Columns.Count - sFirstCell.Column)
End With
' Using 'GetTransformOneValuePerRow', return the transformed data
' in a 2D one-based array.
Dim Data As Variant
Data = GetTransformOneValuePerRow(srg, FixedColumnsCount)
If IsEmpty(Data) Then
MsgBox "An error occurred.", vbCritical
Exit Sub
End If
' Write to the destination range and clear below.
Dim rCount As Long: rCount = UBound(Data, 1)
With wb.Worksheets(dName).Range(dFirstCellAddress).Resize(, UBound(Data, 2))
.Resize(rCount).Value = Data
.Resize(.Worksheet.Rows.Count - .Row - rCount + 1).Offset(rCount).Clear
End With
MsgBox "Data transformed.", vbInformation
End Sub
Function GetTransformOneValuePerRow( _
ByVal SourceRange As Range, _
Optional ByVal FixedColumnsCount As Long = 1, _
Optional ByVal IncludeBlanks As Boolean = False) _
As Variant
Const ProcName As String = "GetTransformOneValuePerRow"
On Error GoTo ClearError
Dim sData As Variant ' Source Array
Dim srCount As Long ' Source Rows Count
Dim cCount As Long ' Source/Destination Columns Count
Dim drCount As Long ' Destination Rows Count
With SourceRange
srCount = .Rows.Count
cCount = .Columns.Count
With .Resize(srCount - 1, cCount - FixedColumnsCount) _
.Offset(1, FixedColumnsCount - 1) ' Values Range
drCount = .Rows.Count * .Columns.Count + 1
If Not IncludeBlanks Then _
drCount = drCount - Application.CountBlank(.Cells)
End With
sData = .Value
End With
Dim dData As Variant: ReDim dData(1 To drCount, 1 To cCount) ' Dest. Array
Dim fvCol As Long: fvCol = FixedColumnsCount + 1 ' First Value Column
Dim dr As Long: dr = 1 ' Destination Row
Dim sr As Long ' Source Row
Dim fc As Long ' Fixed Column
Dim vc As Long ' Value Column
' Write headers.
For fc = 1 To cCount
dData(dr, fc) = sData(1, fc)
Next fc
' Write rest.
If IncludeBlanks Then ' all
For sr = 2 To srCount
For vc = fvCol To cCount
dr = dr + 1
dData(dr, vc) = sData(sr, vc)
For fc = 1 To FixedColumnsCount
dData(dr, fc) = sData(sr, fc)
Next fc
Next vc
Next sr
Else ' non-blank
For sr = 2 To srCount
For vc = fvCol To cCount
If Len(CStr(sData(sr, vc))) > 0 Then
dr = dr + 1
dData(dr, vc) = sData(sr, vc)
For fc = 1 To FixedColumnsCount
dData(dr, fc) = sData(sr, fc)
Next fc
End If
Next vc
Next sr
End If
GetTransformOneValuePerRow = dData
ProcExit:
Exit Function
ClearError:
Debug.Print "'" & ProcName & "' Run-time error '" _
& Err.Number & "':" & vbLf & " " & Err.Description
Resume ProcExit
End Function
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.
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
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