Handling pairs of pattern matching in multiple excel files through VB macros - excel

I have three files - Excel_In1.xls, Excel_In2.xls and Excel_Out.xls.
There are two column say column L,M in Excel_In1.xls which contains keywords (of form product123 and companyABC) I need to search for these pairs of keywords in two columns (say A,B) in Excel_In2.xls. There are multiple rows in Excel_In2.xls with the keywords in that columns A,B.
I need to copy particular columns (say for instance three columns B,X,Z) in all the rows which contain the keyword product123 in the column A and companyABC in column B in Excel_In2.xls to the file Excel_Out.xls.
What is the best and simple way to accomplish this in VB script macros in Excel_Out.xls?
I also want to open the files Excel_In1.xls and Excel_In2.xls in macro in Excel_Out.xls.

my assumptions:
Excel_In1.xls == catalog.xlsx (table has unique key pair columnA & columnB in each row)
Excel_In2.xls == factdata.xlsx (table has multiple duplicate key pairs; and contains data in fixed columns to be copied)
Excel_Out.xls == book Out.xlsm
Option Explicit
Private Type TState
catalog As Object
selectedData As Object
End Type
Private this As TState
Public Sub ExampleSubMain()
Init
PickData
ExecuteCopying
End Sub
Private Sub Init()
InitCatalogDictionary
Set this.selectedData = CreateObject("Scripting.Dictionary")
End Sub
Private Sub InitCatalogDictionary()
MakeTheBookOpened "D:\vba\somefolder\", "catalog.xlsx"
Dim wb As Workbook
Set wb = Workbooks("catalog.xlsx")
Dim dataRange As Range
Set dataRange = wb.Worksheets("catalogSheet").Range("a2:b10") 'for example "a2:b10"
Set this.catalog = MakeDict(dataRange)
End Sub
Private Function MakeDict(ByVal dataRange As Range) As Object
Dim result As Object
Set result = CreateObject("Scripting.Dictionary")
Dim row As Range
For Each row In dataRange.Rows
'asumes column A,B are true keys and their pairs are unique, value = empty string
result.Add Join(Array(row.Cells(1), row.Cells(2))), ""
Next row
Set MakeDict = result
End Function
Private Sub MakeTheBookOpened(ByVal pathWithSeparator As String, _
ByVal wbName As String)
If TheBookIsOpenedAlready(wbName) Then Exit Sub
Workbooks.Open Filename:=pathWithSeparator & wbName, ReadOnly:=True
End Sub
Private Function TheBookIsOpenedAlready(ByVal Name As String) As Boolean
Dim wb As Workbook
For Each wb In Workbooks
If wb.Name = Name Then TheBookIsOpenedAlready = True: Exit Function
Next wb
End Function
Private Sub PickData()
MakeTheBookOpened "D:\vba\somefolder\", "factdata.xlsx"
Dim wb As Workbook
Set wb = Workbooks("factdata.xlsx")
Dim dataRange As Range
Set dataRange = wb.Worksheets("factSheet").Range("a2:k10") 'for example "a2:k10"
Dim row As Range
For Each row In dataRange.Rows
Dim key As String
key = Join(Array(row.Cells(4), row.Cells(6))) 'asumes product123, companyABC columns are there
If this.catalog.Exists(key) Then this.selectedData.Add row, ""
Next row
End Sub
Private Sub ExecuteCopying()
If this.selectedData.Count = 0 Then Exit Sub
Dim rowsNum As Long
rowsNum = this.selectedData.Count
Dim columnsNum As Long
columnsNum = 3 'for example 3
Dim resultArr As Variant
ReDim resultArr(1 To rowsNum, 1 To columnsNum)
Dim pos As Long
pos = 1
Dim item As Variant
For Each item In this.selectedData
Dim row As Range
Set row = item
resultArr(pos, 1) = row.Cells(2) 'B
resultArr(pos, 2) = row.Cells(7) 'G
resultArr(pos, 3) = row.Cells(10) 'J
pos = pos + 1
Next item
'book Out.xlsm
ThisWorkbook.Worksheets(1).Range("a1").Resize(rowsNum, columnsNum) = resultArr
End Sub

Given two basic input files roughly matching your description:
and
And assuming the macro would reside in the out file, we could construct a multi-function macro to accomplish this with a few steps.
The first part of the macro knows filenames and parameters. You used column L in the first input file, but let's make that configurable. The same with most of the other parameters, like the first line to start on so our input files can have headers.
Second, we need to open the first input file and read the keywords. There's several ways to do this, but a very simple way to do it is to do a plan CSV line, so that from the first file, you can extract your "keywords" (your term): product123,product456. This can then be iterated over with a For Each loop through the second file.
In the second file, a very simple construct would be to loop over all entries. Depending on your needs, you may need to iterate through the second file only once if it is prohibitively large. Both of these function assume the first blank line terminates the input. If the row in the 2nd input file matches your target, you will perform your copy.
Finally, the copy also takes a CSV line for which columns to keep (keeping it configurable). Each column, as well as the first keyword, will be copied to the target worksheet, starting at row 1 with a configurable column start.
The final output in the output sheet looks something like this:
The output starts in the second column because that was what was specified in the configuration.
There may be more elegant approaches, but this is a straight-forward approach to it.
Const EXCEL_1 As String = "\Excel1.xls"
Const EXCEL_1_KW_COL As String = "A"
Const EXCEL_2 As String = "\Excel2.xls"
Const EXCEL_2_KW_COL As String = "A"
Const EXCEL_2_COPY_COLS As String = "B,E,G"
Const EXCEL_3 As String = "\Excel3.xls"
Const EXCEL_3_TARGET As String = "B"
Public Function LoadInformation3()
Dim Location As String, Keywords As String
Application.ScreenUpdating = False
Location = Application.ActiveWorkbook.Path
Keywords = LoadKeywords(Location & EXCEL_1, EXCEL_1_KW_COL)
Debug.Print "Keys=" & Keywords
Dim L, CurrentDestRow As Long
For Each L In Split(Keywords, ",")
SearchKeywordAndCopy CurrentDestRow, Location & EXCEL_2, Location & EXCEL3, L, EXCEL_2_KW_COL, EXCEL_2_COPY_COLS, EXCEL_3_TARGET
Next
Application.ScreenUpdating = True
End Function
Public Function LoadKeywords(ByVal File As String, ByVal ColumnId As String, Optional ByVal FirstRow As Long = 2)
Dim Wb1 As Workbook
Dim Value As String, N As Long
Set Wb1 = Workbooks.Open(File)
N = FirstRow
LoadKeywords = ""
Do While True
Value = Wb1.Sheets(1).Range(ColumnId & N).Text
If Value = "" Then Exit Do
LoadKeywords = LoadKeywords & IIf(LoadKeywords = "", "", ",") & Value
N = N + 1
Loop
Wb1.Close SaveChanges:=False
End Function
Public Sub SearchKeywordAndCopy(ByRef CurrentDestRow As Long, ByVal FileSource As String, ByVal FileTarget As String, ByVal Keyword As String, ByVal SourceColumn As String, ByVal SourceCopyFrom As String, ByVal DestCopyTo As String)
Dim WbSource As Workbook, WbDest As Workbook
Dim Value As String, N As Long
Set WbDest = Application.ActiveWorkbook
Set WbSource = Workbooks.Open(FileSource)
N = 2
Do While True
Value = WbSource.Sheets(1).Range(SourceColumn & N).Text
If Value = "" Then Exit Do
If Value <> Keyword Then GoTo NextRow
Dim L, M As Long
CurrentDestRow = CurrentDestRow + 1
WbDest.Sheets(1).Range(DestCopyTo & CurrentDestRow).Value = Keyword
M = 0
For Each L In Split(SourceCopyFrom, ",")
Dim CopyValue As String
CopyValue = WbSource.Sheets(1).Range(L & N).Text
M = M + 1
WbDest.Sheets(1).Range(DestCopyTo & CurrentDestRow).Offset(, M).Value = CopyValue
Next
NextRow:
N = N + 1
Loop
WbSource.Close SaveChanges:=False
End Sub

Your setup as best I could understand it:
And... This is the code I wrote:
Option Explicit
Option Base 1
Sub CopyData()
Dim XLout As Workbook 'Excel_Out.xls
Dim XLin1 As Workbook 'Excel_In1.xls
Dim XLin2 As Workbook 'Excel_In2.xls
Dim ProductList 'Product/Company List from XLin1
Dim ProductListO() 'Concatenated Version of above
Dim DataList 'Product/Company List from XLin2
Dim DataXcol 'Extra Data to pull from Column X in XLin2
Dim DataZcol 'Extra Data to pull from Column Z in XLin2
Dim Output() 'Output Array for XLout
Dim i As Long 'Iterations
Dim counter As Long 'Item number
Dim TimeCount
TimeCount = Timer
' >>> All Workbooks
Set XLout = ThisWorkbook
Set XLin1 = Workbooks.Open("C:\Users\ccritchlow\Documents\A\Test\Excel_In1.xls")
Set XLin2 = Workbooks.Open("C:\Users\ccritchlow\Documents\A\Test\Excel_In2.xls")
' >>> Store Source Data in Arrays
With XLin2.Sheets(1)
DataList = .Range("A2:B" & .Range("A" & Rows.Count).End(xlUp).Row)
DataXcol = .Range("X2:X" & .Range("A" & Rows.Count).End(xlUp).Row)
DataZcol = .Range("Z2:Z" & .Range("A" & Rows.Count).End(xlUp).Row)
End With
' >>> Store Product List Data in Arrays
ProductList = XLin1.Sheets(1).Range("L2:M" & XLin1.Sheets(1).Range("M" & Rows.Count).End(xlUp).Row)
ReDim ProductListO(1 To UBound(ProductList, 1))
For i = 1 To UBound(ProductList, 1)
ProductListO(i) = ProductList(i, 1) & "-" & ProductList(i, 2)
Next i
' >>> Move entries from XLin2 (that exist on XLin1) into "Output" Array
ReDim Preserve Output(UBound(DataList, 1), 3)
counter = 1
For i = 1 To UBound(DataList, 1)
DataList(i, 1) = DataList(i, 1) & "-" & DataList(i, 2)
If Not IsError(Application.Match(DataList(i, 1), ProductListO(), 0)) Then
Debug.Print
Output(counter, 1) = DataList(i, 2)
Output(counter, 2) = DataXcol(i, 1)
Output(counter, 3) = DataZcol(i, 1)
counter = counter + 1
End If
Next i
' >>> Output to XLout
XLout.Sheets(1).Range("A2").Resize(UBound(Output, 1), 3) = Output()
Application.StatusBar = "Total Time to review " & UBound(DataList, 1) & " lines = " & Timer - TimeCount
End Sub
It does the following
Is stored on "Excel_Out.xls"
Opens both "Excel_In#.xls" workbooks
Stores all required data in arrays
Identifies data on XLin2 whose "company&productname" exist on XLin1
Outputs that data to "Excel_Out.xls"
This is how it looks:

Related

Comparing 2 Pair Data with Loop in Ms Excel VBA

Is anyone can help me, pls take a look at the picture i attached.
I want to compare 2 pair of data from 2 different excel file, Station (left file column B) with Station (right file column A) AND Time (left file row 1) with Tendancy (right file Column F).
The left file is the report that im about to finished, the right file is the reference data. If the station and the time data is match each other, it will filled with "1", if not it will stay empty.
The data will start filling from cell C2 until Z32. Im stuck with FOR and IF looping i used. And here's the example:
Cell C2 will filed with "1" bcs there is station 2000001 (cell A2) at 00UTC (cell F2) on the right file.
Cell E2 will stay empty bcs there is station 2000001 BUT NOT at 02UTC on the right file.
Cell C3 will stay empty bcs there is station 2000002 BUT NOT at 00UTC on the right file.
Dim countSM As Long
Dim countSS As Long
Dim countWM As Long
Dim countWS As Long
Dim resultCol As Long
Dim resultRow As Long
Dim lastSM As Long
Dim lastSS As Long
Dim lastWM As Long
Dim lastWS As Long
Dim lastRCol As Long
Dim lastRRow As Long
lastSM = wb1.Sheets("Sheet1").Cells(Rows.count, 1).End(xlUp).Row
lastSS = wb2.Sheets("Worksheet").Cells(Rows.count, 1).End(xlUp).Row
lastWM = wb1.Sheets("Sheet1").Cells(1, Columns.count).End(xlToLeft).Column
lastWS = wb2.Sheets("Worksheet").Cells(Rows.count, 1).End(xlUp).Row
lastRCol = wb1.Sheets("Sheet1").Cells(1, Columns.count).End(xlToLeft).Column
lastRRow = wb1.Sheets("Sheet1").Cells(Rows.count, 1).End(xlUp).Row
For countSM = 2 To lastWM
For countSS = 2 To lastWS
If wb1.Sheets("Sheet1").Cells(countSM, "B") = wb2.Sheets("Worksheet").Cells(countSS, "A") Then
For countWM = 3 To lastWM
For countWS = 2 To lastWS
If wb1.Sheets("Sheet1").Cells(1, countWM) = wb2.Sheets("Worksheet").Cells(countWS, "F") Then
For resultRow = 2 To lastRRow
For resultCol = 3 To lastRCol
wb1.Sheets("Sheet1").Cells(resultRow, resultCol) = "1"
Next resultCol
Next resultRow
Next countSS
ElseIf wb1.Sheets("Sheet1").Cells(1, countWM) <> wb2.Sheets("Worksheet").Cells(countWS, "F") Then
wb1.Sheets("Sheet1").Cells(resultRow, resultCol) = ""
Next countWM
End If
Next countSS
ElseIf wb1.Sheets("Sheet1").Cells(countSM, "B") <> wb2.Sheets("Worksheet").Cells(countSS, "A") Then
wb1.Sheets("Sheet1").Cells(resultRow, resultCol) = ""
Next countSM
End If
I made a code that may work for you. Just count how many rows got the station and UTC value you want to check. If the answer is zero, leave the cell empty. If not, then return 1.
My code is designed on same workbook but it can be adapted yo work with 2 different workbooks easily.
My fake dataset:
My code:
Sub test()
'<------>
'
'
'
'
'YOUR CODE TO OPEN BOTH FILES
'
'
'
'<---->
Dim LeftSheet As Worksheet
Dim RightSheet As Worksheet
Dim MyData As Range 'range to store the data (right file)
Dim LR As Long 'Last row of left file, column Station
Dim LC As Long 'Lastcolumn of left file, (whatever UTC it is)
Dim i As Long
Dim zz As Long
Dim MiF As WorksheetFunction
Set MiF = WorksheetFunction
Dim MyStation As String
Dim MyUTC As String
'Probably you'll need just to adjust references to worksheets from different workbooks
Set LeftSheet = ThisWorkbook.Worksheets("Destiny")
Set RightSheet = ThisWorkbook.Worksheets("Source")
'we store all data into array
Set MyData = RightSheet.Range("A1").CurrentRegion
'data starts at index 2, and we want data from columns 1 and 6 on the range
'Columns 1 and 6 mean columns A and F
'I guess maybe you'll need to adapt this too.
With LeftSheet
LR = .Range("B" & .Rows.Count).End(xlUp).Row
LC = .Cells(1, .Columns.Count).End(xlToLeft).Column
'we count how many rows got the station and tendancy value (intersection) on the right file
' if the count is 0, do nothing. If not zero, return 1 on the cell
'our references will be always at column 2 and row 1
For i = 2 To LR Step 1 'we start at row 2 on left file
MyStation = .Range("B" & i).Value
For zz = 3 To LC Step 1 'we start at column 3, that means column C
MyUTC = .Cells(1, zz).Value
If MiF.CountIfs(MyData.Columns(1), MyStation, MyData.Columns(6), MyUTC) <> 0 Then .Cells(i, zz).Value = 1
Next zz
Next i
End With
'clean variables
Set MyData = Nothing
Set LeftSheet = Nothing
Set RightSheet = Nothing
End Sub
Output after executing code:
Give this solution a try:
Option Explicit
Private Type TWorksheetData
WrkSheet As Worksheet
LastRow As Long
LastColumn As Long
End Type
Sub CopyCompare()
'Organize the variables by referenced worksheet
Dim worksheetData As TWorksheetData
Dim sheet1Data As TWorksheetData
'your solution will provide separate Workbooks for the code below
'ActiveWorkbook (in my case) had both worksheets in order to develop the solution
sheet1Data = SetupWorksheetData(Application.ActiveWorkbook, "Sheet1", sheet1Data)
worksheetData = SetupWorksheetData(Application.ActiveWorkbook, "Worksheet", worksheetData)
Dim refData As Dictionary
Set refData = New Dictionary
'Load the reference data (key = station, value = collection of UTCs)
Dim station As Long
Dim countRow As Long
For countRow = 2 To worksheetData.LastRow
station = CLng(worksheetData.WrkSheet.Range("A" & CStr(countRow)).Value)
If Not refData.Exists(station) Then
refData.Add station, New Collection
End If
refData(station).Add worksheetData.WrkSheet.Range("F" & CStr(countRow)).Value
Next countRow
'Load the UTC header columns from Sheet1
Dim outputMap As Dictionary '(key = UTCXX, value = column Number)
Set outputMap = LoadUTCHeaderColumns(sheet1Data)
'Operate on the Sheet1 data to set the value
For countRow = 2 To sheet1Data.LastRow
station = CLng(sheet1Data.WrkSheet.Range("B" & CStr(countRow)).Value)
Dim utcRef As Variant
If refData.Exists(station) Then
Dim utc As Variant
For Each utc In refData(station)
If InputSheetHasUTCEntry(utc, outputMap) Then
sheet1Data.WrkSheet.Cells(countRow, outputMap(utc)) = "1"
End If
Next
End If
Next countRow
End Sub
Private Function InputSheetHasUTCEntry(ByVal utc As String, ByVal outputMap As Dictionary) As Boolean
InputSheetHasUTCEntry = False
Dim utcRef As Variant
For Each utcRef In outputMap.Keys
If utc = utcRef Then
InputSheetHasUTCEntry = True
Exit Function
End If
Next utcRef
End Function
Private Function LoadUTCHeaderColumns(ByRef sheetData As TWorksheetData) As Dictionary
Set LoadUTCHeaderColumns = New Dictionary
Dim columnHeader As String
Dim outputCol As Long
For outputCol = 1 To sheetData.LastColumn
columnHeader = sheetData.WrkSheet.Cells(1, outputCol).Value
If InStr(columnHeader, "UTC") > 0 Then
LoadUTCHeaderColumns.Add columnHeader, outputCol
End If
Next outputCol
End Function
Private Function SetupWorksheetData(ByVal wb As Workbook, ByVal sheetName As String, ByRef wrksheetData As TWorksheetData) As TWorksheetData
SetupWorksheetData = wrksheetData
Set SetupWorksheetData.WrkSheet = wb.Sheets(sheetName)
SetupWorksheetData.LastRow = SetupWorksheetData.WrkSheet.Cells(Rows.Count, 1).End(xlUp).Row
SetupWorksheetData.LastColumn = SetupWorksheetData.WrkSheet.Cells(1, Columns.Count).End(xlToLeft).Column
End Function
Solution comments:
Loads static reference data from from sheet Worksheet (recommend a different sheet name)
Loads static column number information from Sheet1
There are lots of variables holding similar data for each worksheet used. This indicates an opportunity for using a UserDefinedType (TWorksheetData in this case). This organizes and reduces the number of variables to declare and track.
#1 and #2 uses a Dictionary to retain and correlate static information (requires adding a reference to the Microsoft Scripting Runtime).
Other comments:
(Best Practice) Always declare Option Explicit at the top of modules. Forces all variables used to be declared.
(Best Practice) Don't Repeat Yourself (DRY) - there is a lot of repeated expressions in the original code. This is especially important with Strings. More could be done with the solution provided, but (for example) you will notice that the worksheet name strings only appear once.

Concatenate two strings into one cell

I have code I created with the help of the internet.
It loops through a list to find a specific string. Then it takes column B cell and pastes it onto another workbook.
But I have two different strings in the list. each time my code overwrites the first string.
I want to concatenate the two but with a comma between. e.g. [16, 5]
DATA
[C]_GA-M126_ST16_1.5_1 16
[C]_GA-M126_ST16_1.5_2 16
[C]_GA-M126_ST16_1.5_3 16
[C]_GA-M126_ST16_1.5_4 16
[C]_GA-M126_ST16_1.5_159 5
[C]_GA-M126_ST16_1.5_160 5
[C]_GA-M126_ST16_1.5_161 5
[C]_GA-M126_ST16_1.5_162 5
Sub POP_LT_UNC()
Dim W_DIP As Workbook
Dim W_PD As Workbook
Dim WDir As String
Dim CTRL As String
Dim PD_CTRL As Long
Dim nRow As Long
Dim I As Long
Dim C As Long
Dim PD_CELL As Range
Dim firstaddress As String
Dim LT_NUM As String
Dim first_LT As String
Dim ALL_LT As String
'=============================
' Set Pointer to WorkSheets
'=============================
WDir = ActiveWorkbook.Path
W_PD_DIR = WDir & ".\_database\POINT-DATA-ALL COLLECTOINS_v2.xlsx"
Set W_DIP = ThisWorkbook
Workbooks.Open (W_PD_DIR)
Set W_PD = Workbooks("POINT-DATA-ALL COLLECTOINS_v2.xlsx")
GDETrow = 17
Do Until GDETrow = 41
GDETrow = GDETrow + 1
With W_PD.Sheets(1).Range("A:A")
CTRL = W_DIP.Sheets("AFTER_SURVEY").Cells(GDETrow, 2)
Set PD_CELL = Range("A:A").Find(What:=CTRL)
If Not PD_CELL Is Nothing Then
firstaddress = PD_CELL.Address
Do
cRow = PD_CELL.Row
LT_NUM = W_PD.Sheets(1).Cells(cRow, 2)
Set PD_CELL = .FindNext(PD_CELL)
first_LT = LT_NUM
ALL_LT = first_LT & ", " & LT_NUM
W_DIP.Sheets("AFTER_SURVEY").Cells(GDETrow, 19) = ALL_LT
Loop While Not PD_CELL Is Nothing And PD_CELL.Address <> firstaddress
End If
End With
Loop
W_PD.Close
End Sub
Assuming W_DIP.Sheets("AFTER_SURVEY").Cells(GDETrow, 19) = ALL_LT is the line where the text is written, you can to this:
Dim rangeToChange as Excel.Range
Set rangeToChange = W_DIP.Sheets("AFTER_SURVEY").Cells(GDETrow, 19)
If IsEmpty(rangeToChange.Value2) Then
rangeToChange.Value2 = ALL_LT
Else 'already text in the Cell, add value with comma
rangeToChange.Value2 = rangeToChange.Value2 & ", " & ALL_LT
End If
Just replace the Line above with the Code provided.
This will fill the Cell with the value provided and only add the comma if there already is value inside the Cell.
.Value2 is used to avoid an implicit call to the default property of the cell.

How to separate a column of cells with multiple values (delimited) into new columns and add a header

I have a spreadsheet containing a column that has data that looks like this:
Data;X=7.9;Y=9.1;Z=2;V=4;G=8
What I'm trying to do is essentially ignore 'Data;', then create new columns to the right with the associated values, in addition to adding the identifier (X,Y,Z,V or G) as the column header. Then delete the original column where the data string came from.
I have tried to do this with Text to Column, but it puts data in cells like this: X=7.9 etc.. When I wanted 7.9 in the field and so on per row, with the top (header) first cell contains X,Y,Z,V or G.
I suppose I may be able to utilize split text after, but at this point would prefer to do this in vba if possible.
I tried this, but couldn't get it to work with ; instead of |, but I'm not sure if it would add the column header as well, so probably not what I'm looking for.
DECLARE #t table (
piped varchar(50)
)
INSERT INTO #t (piped)
VALUES ('pipe|delimited|values')
, ('a|b|c');
; WITH x AS (
SELECT piped
, CharIndex('|', piped) As first_pipe
FROM #t
)
, y AS (
SELECT piped
, first_pipe
, CharIndex('|', piped, first_pipe + 1) As second_pipe
, SubString(piped, 0, first_pipe) As first_element
FROM x
)
, z AS (
SELECT piped
, first_pipe
, second_pipe
, first_element
, SubString(piped, first_pipe + 1, second_pipe - first_pipe - 1) As second_element
, SubString(piped, second_pipe + 1, Len(piped) - second_pipe) As third_element
FROM y
)
SELECT *
FROM z
Maybe you could try the following code, using dictionaries. It's rather messy, and it could probably be done in simpler way, but it seems to work. Just edit the datarange line.
Option Explicit
Sub DataRange()
Dim DataRange As Range
Dim DataSheet As Worksheet
Dim c As Range
Dim DataString As String
Dim DataDictionary As Object 'SCRIPTING.DICTIONARY
Dim TargetColumns As Object 'SCRIPTING.DICTIONARY
Dim TargetColumn As Long
Dim Key As String
Dim TargetAddress As String
Dim xlCurrentCalculation As XlCalculation
Dim i As Long
TargetColumn = 2
xlCurrentCalculation = Application.Calculation
Application.Calculation = xlCalculationManual
'#############################################################################################
Set DataRange = ActiveSheet.Range("A2:A21") 'EDIT THIS LINE WITH ACTUAL DATA ADDRESS
'#############################################################################################
Set DataSheet = DataRange.Parent
Set TargetColumns = CreateObject("SCRIPTING.DICTIONARY")
For Each c In DataRange
DataString = CStr(c)
Set DataDictionary = DataStringToDataDictionary(DataString)
For i = 0 To DataDictionary.Count - 1
Key = DataDictionary.keys()(i)
If Not TargetColumns.exists(Key) Then
TargetColumns.Add Key, TargetColumn
TargetColumn = TargetColumn + 1
End If
TargetAddress = TargetColumns(Key) & c.Row
DataSheet.Cells(c.Row, TargetColumns.Item(Key)) = DataDictionary.items()(i)
Next i
Next c
For i = 0 To TargetColumns.Count - 1
DataSheet.Cells(1, TargetColumns.items()(i)) = TargetColumns.keys()(i)
Next i
'Uncomment the following line to delete the column of the data containing the range.
'It will create an offset in written data though
'DataRange.EntireColumn.Delete
Application.Calculate
Application.Calculation = xlCalculationAutomatic
End Sub
Function DataStringToDataDictionary(DataString As String)
Dim DataArray() As String
Dim DataSubArray() As String
Dim DataDictionary As Object 'SCRIPTING.DICTIONARY
Dim Key As String
Dim Value As String
Dim i As Long
DataArray = Split(DataString, ";")
'We ignore first element of the array, as we assume it contains the word "Data"
Set DataDictionary = CreateObject("SCRIPTING.DICTIONARY")
For i = 1 To UBound(DataArray)
DataSubArray = Split(DataArray(i), "=")
Key = DataSubArray(0)
Value = DataSubArray(1)
DataDictionary.Add Key, Value
Next i
Set DataStringToDataDictionary = DataDictionary
End Function
Edit :
If the previous code does not work, you could also try the following :
Sub SplittingDataWithoutDictionary()
Dim DataRange As Range
Dim DataSheet As Worksheet
Dim c As Range
Dim DataArray() As String
Dim DataSubArray() As String
Dim HeadersLabels() As Variant
Dim i As Long
Dim FirstWriteBackColumn As Long
Dim Index As Long
Dim Value As String
Dim xlCurrentCalculation As XlCalculation
xlCurrentCalculation = Application.Calculation
Application.Calculation = xlCalculationManual
HeadersLabels = Array("X", "Y", "Z", "V", "G")
Set DataRange = ActiveSheet.Range("A2:A20") 'Edit with the actual range you want to split
Set DataSheet = DataRange.Worksheet
FirstWriteBackColumn = 2 'Edit with the first column in which you want to write back data
For Each c In DataRange
DataArray = Split(CStr(c), ";")
For i = 1 To UBound(DataArray)
DataSubArray = Split(DataArray(i), "=")
Index = FetchIndexInArray(HeadersLabels, DataSubArray(0))
Value = DataSubArray(1)
DataSheet.Cells(c.Row, FirstWriteBackColumn + Index) = Value
Next i
Next c
For i = 0 To UBound(HeadersLabels)
DataSheet.Cells(1, i + FirstWriteBackColumn) = HeadersLabels(i)
Next i
'Datarange.entirecolumn.delete
Application.Calculation = xlCurrentCalculation
End Sub
Function FetchIndexInArray(StringArray() As Variant, LookFor As String) As Long
Dim i As Long
For i = LBound(StringArray) To UBound(StringArray)
If StringArray(i) = LookFor Then
FetchIndexInArray = i
Exit Function
End If
Next i
End Function

Excel VBA For each cell in range seems to run through the same cell multiple number of times

The "for-each cell in range" statement seems to be running through the same cell multiple number of times.
See the screenshot.
It runs through the cell that has the word "Product" four time, because it is merged across four rows.
Is there a way to make it run only once, regardless of the design of the worksheet (in other words, I prefer not to use the fact that it is merged across four rows to be taken into account when coding).
Public Sub ProcessBeijingFile(Excel_UNC_Path As String)
Dim src As Workbook
Dim ProdPushWorkbook As Workbook
Set ProdPushWorkbook = ActiveWorkbook
Set src = Workbooks.Open(Excel_UNC_Path, True, True)
Dim c As Range
Dim r As Range
Dim LastRow As Long
Dim text As String
src.Sheets("Page 1").Activate
src.ActiveSheet.Range("A1").Select
LastRow = src.ActiveSheet.Range("A30000").End(xlUp).Row
text = LastRow
text = "A2:BA" + CStr(text)
Set r = Range(text)
Dim i As Integer
For i = 1 To MaxItems
PONumber(i) = ""
Next
Dim PageCounter As Integer
PageCounter = 0
RecordCounter = 0
Dim NextPONumber As String
NextPONumber = ""
For Each c In r
If Left(Trim(c.Value), 5) = "PO No" Then
NextPONumber = Trim(Replace(c.Value, "PO No.:", ""))
NextPONumber = Trim(Replace(NextPONumber, "PO No:", ""))
End If
....
If you don't care about performance and just want simple code, below demonstrates how you can go about skipping MergedCells. It displays address and value of non empty cells from Cell B1 in Immediate window until it reach empty cell. Kind of what you need.
Option Explicit
Sub Sample()
Dim oRng As Range
Set oRng = Range("B1")
Do Until IsEmpty(oRng)
Debug.Print oRng.Address, oRng.Value
Set oRng = oRng.Offset(1)
Loop
Set oRng = Nothing
End Sub
David pointed me in the right direction.
Here is the key:
if c.MergeCells then
If Trim(GetFirstWord(c.MergeArea.Address, ":")) = c.Address Then
'the first of merged cells, then process, else don't process...
Function Needed:
Public Function GetFirstWord(ByVal SearchString As String, Optional ByVal Delimeter As String = " ") As String
If SearchString = "" Then
GetFirstWord = ""
Else
Dim ary As Variant
ary = Split(SearchString, Delimeter)
GetFirstWord = ary(LBound(ary))
End If
' GetFirstWord = ary(LBound(ary))
'GetFirstWord = ary(LBound(ary))
End Function

Split one column into multiple columns

I was wondering if anybody can kindly advise how to split a string with comma-separated values into multiple columns. I have been trying to figure this out but have been having a hard time finding a good solution. (also checked online, seems several that comes close but not necessarily fit what I exactly need)
Let's say I have a worksheet, call it "example", for instance,
and in the worksheet has the following strings under multiple
rows but all in column "A".
20120112,aaa,bbb,ccc,3432
20120113,aaa,bbb,ccc
20120113,ddd,bb,ccc,ddd,eee,fff,ggg,hhhh
20120132,aaa,bbb,ccc
20120112,aaa,bbb,ccc
20120112,xxx,bbb,ggg,ggg,333
20120112,aaa,bbb,ccc
20120112,abbd,bbb,ccc
How can I create a macro that will split the above into multiple columns.
Just several points
(1) I should be able to specify the worksheet name
ex: something like
worksheets("example").range(A,A) '
(2) The number of columns and rows are not fixed, and so I do not
know how many comma-separated values and how many rows there
would be before I run the vba script.
You could use InputBox() function and get the name of the sheet with data which shlould be splitted.
Then copy the data into variant array, split them and create new array of splitted values.
Finally assign the array of splitted values back to excel range. HTH
(Notice that the source data are modified directly so finally it is separated into columns and original un-splitted state is lost. But it is possible to modify the code so the original data won't be overwritten.)
Option Explicit
Private Const sourceColumnName As String = "A"
Private Const delimiter As String = ","
Public Sub Splitter()
' splits one column into multiple columns
Dim sourceSheetName As String
Dim sourceSheet As Worksheet
Dim lastRow As Long
Dim uboundMax As Integer
Dim result
On Error GoTo SplitterErr
sourceSheetName = VBA.InputBox("Enter name of the worksheet:")
If sourceSheetName = "" Then _
Exit Sub
Set sourceSheet = Worksheets(sourceSheetName)
With sourceSheet
lastRow = .Range(sourceColumnName & .rows.Count).End(xlUp).row
result = SplittedValues(data:=.Range(.Cells(1, sourceColumnName), _
.Cells(lastRow, sourceColumnName)), _
partsMaxLenght:=uboundMax)
If Not IsEmpty(result) Then
.Range(.Cells(1, sourceColumnName), _
.Cells(lastRow, uboundMax)).value = result
End If
End With
SplitterErr:
If Err.Number <> 0 Then _
MsgBox Err.Description, vbCritical
End Sub
Private Function SplittedValues( _
data As Range, _
ByRef partsMaxLenght As Integer) As Variant
Dim r As Integer
Dim parts As Variant
Dim values As Variant
Dim value As Variant
Dim splitted As Variant
If Not IsArray(data) Then
' data consists of one cell only
ReDim values(1 To 1, 1 To 1)
values(1, 1) = data.value
Else
values = data.value
End If
ReDim splitted(LBound(values) To UBound(values))
For r = LBound(values) To UBound(values)
value = values(r, 1)
If IsEmpty(value) Then
GoTo continue
End If
' Split always returns zero based array so parts is zero based array
parts = VBA.Split(value, delimiter)
splitted(r) = parts
If UBound(parts) + 1 > partsMaxLenght Then
partsMaxLenght = UBound(parts) + 1
End If
continue:
Next r
If partsMaxLenght = 0 Then
Exit Function
End If
Dim matrix As Variant
Dim c As Integer
ReDim matrix(LBound(splitted) To UBound(splitted), _
LBound(splitted) To partsMaxLenght)
For r = LBound(splitted) To UBound(splitted)
parts = splitted(r)
For c = 0 To UBound(parts)
matrix(r, c + 1) = parts(c)
Next c
Next r
SplittedValues = matrix
End Function
If you don't need to work on this task later again, here is a manual way as workaround:
Use a text editor (Notepad++) to replace "," to "tab".
Copy the content and paste into an empty Excel sheet.
Or you can try Excel import the data from file ("," as separator).
In case you need an automatic script, try this:
1) Press Ctrl+F11 to open VBA editor, insert a Module.
2) click the Module, add code inside as below.
Option Explicit
Public Function LastRowWithData(ByRef sht As Excel.Worksheet, Optional colName As String = "A") As Long
LastRowWithData = sht.Range(colName & sht.Rows.Count).End(xlUp).Row
End Function
Sub SplitToColumns(ByRef sColNames As String, ByRef strSeparator As String, ByRef rngDest As Excel.Range)
Dim arrColNames As Variant, i As Long
arrColNames = Split(sColNames, strSeparator)
For i = LBound(arrColNames) To UBound(arrColNames)
rngDest.Offset(0, i).Value = arrColNames(i)
Next i
End Sub
Sub PerformTheSplit()
Dim totalRows As Long, i As Long, sColNames As String
totalRows = LastRowWithData(Sheet1, "A")
For i = 1 To totalRows
sColNames = Sheet1.Range("A" & i).Value
Call SplitToColumns(sColNames, ",", Sheet2.Range("A" & i))
Next i
End Sub
3) Suppose you have the column name in Sheet1:
Press "Alt+F8" to run macro "PerformTheSplit", you will see result in Sheet2:
I would just use the Text-to-Columns wizard, with VBA routines to allow you to select the sheet and range to process, as you request above.
The Input boxes are used to obtain the sheet and range to process, and will default to the Active Sheet and Selection. This could certainly be modified in a variety of ways.
The built-in text to columns feature is then called, and, although you did not so specify, ti seems your first column represents a date in YMD format, so I added that as an option -- it should be obvious how to remove or change it if required.
Let me know how it works for you:
Option Explicit
Sub TTC_SelectWS_SelectR()
Dim WS As Worksheet, R As Range
Dim sMB As String
Dim v
On Error Resume Next
Set WS = Worksheets(Application.InputBox(Prompt:="Enter Worksheet Name: ", _
Title:="Select Worksheet", _
Default:=ActiveSheet.Name, _
Type:=2))
If Err.Number <> 0 Then
sMB = MsgBox("Invalid Worksheet Name", vbRetryCancel)
If sMB = vbRetry Then TTC_SelectWS_SelectR
Exit Sub
End If
On Error GoTo 0
Set R = (Application.InputBox(Prompt:="Select Range to Process: ", _
Title:="Select Range", _
Default:=Selection.Address, _
Type:=8))
Set R = WS.Range(R.Address)
R.TextToColumns DataType:=xlDelimited, textqualifier:=xlTextQualifierDoubleQuote, _
consecutivedelimiter:=False, Tab:=False, semicolon:=False, comma:=True, Space:=False, _
other:=False, fieldinfo:=Array(Array(1, xlYMDFormat))
End Sub

Resources