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
Related
i have two workbooks named "main" and "temp" . in workbook "temp" i have 2 sheets. i wanna write a macro that in a loop from A1 TO A1000, search cell A(x,1) VALUE from workbook "main" in workbook"temp" sheet"1" and if find it , then copy and paste entire row in workbook"temp" sheet"2". i write below code. but two problem exsits:
1- i wanna copy the the entire row found in workbook "temp" sheet1 in sheet 2 according to workbook "main" row number not workbook"temp" row number. i mean if text:book is in A(1,1) cell in workbook "main" and found it in A(9,1) in workbook"temp".sheet1 copy its entire row and paste it in sheet2 in row 1 not row 9.
2-i write macro in workbook"temp" and have a button to run this macro- but when i am in sheet2 macro don't work well but when i am in sheet1 its works well.
please help me find problems...thanks
Sub sorting()
Dim coname As String
Dim counter As Integer
Dim cell As Range
For counter = 1 To 1000
coname = Workbooks("main").Worksheets("statics").Cells(counter, 1)
With Workbooks("temp").Worksheets(1)
For Each cell In Range("a1", Range("a1").End(xlDown))
If cell.Value = coname Then
Rows(cell.Row).Copy Destination:=Workbooks("temp").Sheets(2).Rows(cell.Row)
End If
Next cell
End With
Next counter
End Sub
1.
I would change coname to be a Range data type (Dim coname As Range) and then slightly change your code like so:
If cell.Value = coname.Value Then
coname.EntireRow.Copy Destination:=Workbooks("temp").Sheets(2).cell
End If
By changing the datatype, we can now refer to the correct row (on the correct sheet) using the EntireRow property of the coname Range object.
Previously you were getting the wrong row because your source data was using the Cell.Row property to get the row to copy from, but that is your destination reference, so changing it to coname now points the source data to the right range.
2.
Use explicit qualification to your workbook/worksheets! Currently the issue of where you call the code from is due to this line: For Each cell In Range("a1", Range("a1").End(xlDown)).
Because you haven't lead the Range() reference with a ., it's not making use of the With statement it's within! So it translates to ActiveSheet.Range("A1"...). Put a . in front of Range to use your With statement and it will be Workbooks("temp").Worksheets(1).Range("A1"...).
After that it won't matter where/how you call the code, it will always refer to the correct sheet!
Three Sheets in Play
Carefully adjust the constants to fit your needs.
Especially take care of srcLastColumn which wasn't mentioned in
your question. You don't want to copy the whole range, just the range
containing values.
The complete code goes into a standard module (e.g. Module1).
What the code emulates would be something like the following:
In the Main Worksheet loops through a column and reads the values row
by row.
It compares each of the value with each of the values in a column in
the Source Worksheet.
If it finds a match it writes the complete row containing values from
Source Worksheet to the same row as the row of Main Worksheet,
but to the row in Target Worksheet.
Then it stops searching and goes to the next row in Main Worksheet.
The Code
Option Explicit
Sub Sorting()
Const mFirst As String = "A1" ' First Cell in Main or Target
Const mWbName As String = "main.xlsx" ' The workbook has to be open.
Const mWsName As String = "statics"
Const srcNameOrIndex As Variant = 1 ' It is safer to use the Sheet Name.
Const srcFirst As String = "A1" ' First Cell in Source
Const srcLastColumn As Long = 5 ' !!! Source Last Column !!!
Const tgtNameOrIndex As Variant = 2 ' It is safer to use the Sheet Name.
' Write values from Main and Source Worksheets to Main and Source Arrays.
Dim mn As Worksheet: Set mn = Workbooks(mWbName).Worksheets(mWsName)
Dim Main As Variant ' Main Array
Main = getColumn(mn, mn.Range(mFirst).Column, mn.Range(mFirst).Row)
If IsEmpty(Main) Then Exit Sub
Dim src As Worksheet: Set src = ThisWorkbook.Worksheets(srcNameOrIndex)
Dim Source As Variant ' Source Array
Source = getColumn(src, src.Range(srcFirst).Column, src.Range(srcFirst).Row)
If IsEmpty(Source) Then Exit Sub
Dim rng As Range
Set rng = src.Range(srcFirst).Resize(UBound(Source), _
srcLastColumn - src.Range(srcFirst).Column + 1)
Source = rng: Set rng = Nothing
' Write values from Source Array to Target Array.
Dim ubM As Long: ubM = UBound(Main)
Dim ubS1 As Long: ubS1 = UBound(Source)
Dim ubS2 As Long: ubS2 = UBound(Source, 2)
Dim Target As Variant ' Target Array
ReDim Target(1 To ubM, 1 To ubS2)
Dim i As Long, k As Long, l As Long, Criteria As String
For i = 1 To ubM
Criteria = Main(i, 1)
For k = 1 To ubS1
If Source(k, 1) = Criteria Then
For l = 1 To ubS2
Target(i, l) = Source(k, l)
Next l
Exit For
End If
Next k
Next i
' Write values from Target Array to Target Worksheet.
Dim tgt As Worksheet: Set tgt = ThisWorkbook.Worksheets(tgtNameOrIndex)
tgt.Range(mFirst).Resize(ubM, ubS2) = Target
' Inform user.
MsgBox "Data successfully transfered.", vbInformation, "Success"
' If you don't see this message, nothing has happened.
End Sub
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
' Purpose: Writes the values of a one-column range to a 2D one-based '
' one-column or one-row array. '
' Returns: A 2D one-based one-column or one-row array. '
' Remarks: The cells below the column range have to be empty. '
' If an error occurs the function will return an empty variant. '
' Therefore its result can be tested with "IsEmpty". '
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
Function getColumn(Sheet As Worksheet, ByVal AnyColumn As Variant, _
Optional ByVal FirstRow As Long = 1, _
Optional ByVal transposeResult As Boolean = False, _
Optional ByVal showMessages As Boolean = False) As Variant
Const Proc As String = "getColumn"
On Error GoTo cleanError
Dim rng As Range
Set rng = Sheet.Columns(AnyColumn).Find("*", , xlFormulas, , , xlPrevious)
If rng Is Nothing Then Exit Function
If rng.Row < FirstRow Then Exit Function
Set rng = Sheet.Range(Sheet.Cells(FirstRow, AnyColumn), rng)
If Not rng Is Nothing Then
If Not transposeResult Then
getColumn = rng
Else
getColumn = Application.Transpose(rng)
End If
End If
Exit Function
cleanError:
If showMessages Then
MsgBox "An unexpected error occurred in '" & Proc & "'." & vbCr _
& "Run-time error '" & Err.Number & "':" & vbCr & Err.Description _
, vbCritical, Proc & " Error"
End If
On Error GoTo 0
End Function
I'm creating a new process in Excel that ties in several spreadsheets. One of my current problems is in porting notes over to the new spreadsheet. The issue arises with the system that the data is stored in. Every time someone edits a note in the system it generates a Unique LineRefNo. This creates an issue as I will have an address that has 20 lines of data. Each line has the same note but several unique LineRefNo scattered throughout. This makes it impossible to port over clean notes on an aggregate level.
I've tried some base code and different variations just to remove the current LineRefNum currently. I have been overdeleting with that code however.
' This Macro is to remove excess information from the Comment Field
ActiveWorkbook.ActiveSheet.Range("A1").Select
ActiveWorkbook.ActiveSheet.Cells.Replace What:="{[LineRefNum", Replacement:="", LookAt:=xlPart, _
SearchOrder:=xlByRows, MatchCase:=False, SearchFormat:=False, _
ReplaceFormat:=False
I've got two issues. One is the base code I've started with is deleting all data from almost every cell. I just want it to remove the LineRefNo and leave the actual note.
The second issue is I need it to delete the LineRefNo plus the 16 characters following this phrase. ({[LineRefNum: 532351517A000010]).
The end result would be just the actual comment that follows.
{[LineRefNum: 532354632A000010][Comment: Cleared and approved on PV 2.13.19 File ][User: \*****][Date: Feb 27 2019 11:08AM]}
If I can get that to work I would edit and expand upon the Macro to do more cleanup functions on the text.
Thanks for any help. If this is impossible in VBA currently just let me know and I will stop wasting my time.
EDIT: Realized you can still use the Range.Replace method to do this in one fell swoop since your match condition is pretty simple:
Sub tgr()
ActiveWorkbook.ActiveSheet.Cells.Replace "[LineRefNum*]", vbNullString
End Sub
(Original post, leaving for posterity and anybody interested in learning regex) Here's an example of how to accomplish this using a regular expression:
Sub tgr()
Dim ws As Worksheet
Dim rData As Range
Dim oRegEx As Object
Dim oMatches As Object
Dim vMatch As Variant
Dim aData As Variant
Dim sCommentsCol As String
Dim sPattern As String
Dim lHeaderRow As Long
Dim i As Long
Set ws = ActiveWorkbook.ActiveSheet
sPattern = "\[LineRefNum:\s[(a-z|A-Z|0-9)]+\]"
sCommentsCol = "A"
lHeaderRow = 1 'If you don't have a header row, set this to 0
Set oRegEx = CreateObject("VBScript.RegExp")
With oRegEx
.MultiLine = True
.Global = True
.IgnoreCase = True
.Pattern = sPattern
End With
With ws.Range(ws.Cells(lHeaderRow + 1, sCommentsCol), ws.Cells(ws.Rows.Count, sCommentsCol).End(xlUp))
If .Row <= lHeaderRow Then Exit Sub 'No data
Set rData = .Cells
If .Cells.Count = 1 Then
ReDim aData(1 To 1, 1 To 1)
aData(1, 1) = .Value
Else
aData = .Value
End If
End With
For i = 1 To UBound(aData, 1)
Set oMatches = oRegEx.Execute(aData(i, 1))
If oMatches.Count > 0 Then
For Each vMatch In oMatches
aData(i, 1) = Replace(aData(i, 1), vMatch, vbNullString)
Next vMatch
End If
Next i
rData.Value = aData
End Sub
This did what you wanted in testing, could always use more information but it seems you know what column this text edit needs to take place in:
Option Explicit
Public Sub parserLineRef()
Dim lastrow As Long
Dim startrow As Long
Dim searchcol As Long ' col number definition
Dim i As Long 'loop iterator
Dim newstring As String 'the newly modified string
Dim oldstring As String 'the original string
Dim retPos As Long 'the position of the substring in the serached string, zero is not found
Dim ws As Worksheet
'Change this to suit your needs
searchcol = 1
startrow = 1
'Change this to suit your needs
Set ws = ThisWorkbook.Worksheets("Sheet1") 'I simply copied into a new workbook default name is Sheet1
'get the last row
lastrow = ws.Cells(ws.Rows.Count, 1).End(xlUp).Row
For i = startrow To lastrow
'convert cell contents to string, just in case
oldstring = CStr(ws.Cells(i, searchcol))
'Find the position of the string in the cell
'zero means your search string is not found
retPos = InStr(oldstring, "[LineRefNum")
If retPos > 0 Then
'the substring was found, make a new string taking the LineRefNum off
newstring = Mid(oldstring, retPos + 30, Len(oldstring) - (retPos + 30))
'put the new string back into the cell
ws.Cells(i, searchcol) = newstring
Else
'Do Nothing, move along to the next row
End If
Next i
End Sub
Give it a spin and see if it meets your needs.
Happy Coding! - WWC
I am looking to sort data after a dynamic row is created with specific text in one cell in the A column. I am able to set up the condition where the data is only sorted once the cell is located, but I am struggling with then specifying to only apply the sort to the rows beyond that location. Here is what I have tried, attempting to sort only the data one row below where Cell 8 does not equal Cell 9 and beyond for column C:
Dim intl As Range
Dim rSortRangez As Range
Dim iRowz As Integer, iColz As Integer
Dim cus As Range
Set intl = shtDest.Range("C8")
iRowz = intl.Row
iColz = intl.Column
Set rSortRangez = sheets("Sheet1").Range("A8", "P99")
Set cus = intl.Offset(1, 0)
Do
For Each intl In rSortRangez
If intl <> intl.Offset(1, 0) Then
rSortRangez.Sort _
Key1:=sheets("Sheet1").Range("cus"), Order1:=xlDescending, _
Header:=xlNo, OrderCustom:=1, MatchCase:=False, _
Orientation:=xlTopToBottom, DataOption1:=xlSortNormal, _
DataOption2:=xlSortNormal, DataOption3:=xlSortNormal
You can use the vba Range.Sort method.
It can be used as follows:
Dim customSortRangeString As String
customSortRangeString = "C1:E5" 'enter in whatever you want
'key1 is the column that you will be sorting based on the first time.
'order1 is the order that it will sort the first time.
'header tells excel that the first row contains headers.
'Note: there are also key2 and key3 (as well as an order for each) allowing you to have multiple
'search criteria.
ActiveWorkbook.Worksheet("YOUR WORKSHEET HERE").Range(customSortRangeString).Sort key1:=Range("C2"), _
order1:=xlAccending, header:=xlYes
More information on the sort method can be found here
UPDATE - 5/9/19: Added information for a dynamic sort range based on criteria provided in this answer's comments.
Private Sub test()
Dim wb As Workbook
Dim ws As Worksheet
Dim totalRow As Integer
Dim startSortRow As Integer
Dim sortRange As String
Dim sortKey As String
'Setting both the workbook and sheet to variables to make them easier to use.
Set wb = ActiveWorkbook
Set ws = wb.Worksheets("YOUR WORK SHEET")
'getting the total number of rows we may have to loop through to find the appropreate cell.
totalRow = ws.Range("C" & Rows.Count).End(xlUp).Row
'loops through the rows
For a = 1 To totalRow
'checks if the previous cell does not equal the current cell
If ws.Range("C" & a).Value <> ws.Range("C" & a - 1) Then
'if above statement is true, set the startSortRow to the value of a and exit the loop
startSortRow = a
Exit For
End If
Next
'Create the sort range string
sortRange = "A" & startSortRow & ":P100"
'Create the sort key
sortKey = "C" & startSortRow
'Sort based on the values found above
ws.Range(sortRange).Sort key1:=Range(sortKey), order1:=xlAccending, Header:=xlNo
End Sub
This should work as expected with the dynamic sort range. (Have not tested)
I am working on a code that will automate a process. I want it to copy from various files to other files with formulas, calculate, and then back again.
I have encountered, a message 'Run-time error '1004', the pastespecial method of range class failed' , when tried to paste. The message appears ONLY when I am using variables to declare the first cell, in order to copy a range of values.
When I use a direct cell description everything works fine.
I'm also using a custom function for obtaining the column letter, of a given field name.
Function ActiveColumnName(fieldname As String, fieldnames_line As Integer) As String
Range("A" & fieldnames_line & ":AB" & fieldnames_line).NumberFormat = "#"
Cells.find(What:=fieldname, After:=ActiveCell, LookIn:=xlFormulas, LookAt _
:=xlPart, SearchOrder:=xlByRows, SearchDirection:=xlNext, MatchCase:= _
False, SearchFormat:=False).Activate
ActiveColumnNumber = ActiveCell.Column
Dim m As Integer
Dim ActiveColumnName As String
ActiveColumnName = ""
Do While (ActiveColumnNumber > 0)
m = (ActiveColumnNumber - 1) Mod 26
ActiveColumnName = Chr(65 + m) + ActiveColumnName
ActiveColumnNumber = Int((ActiveColumnNumber - m) / 26)
Loop
End Function
sub main ()
Dim firstrow_data_main As Integer
Dim firstrow_fieldnames_main As Integer
firstrow_data_main = 16
firstrow_fieldnames_main = 15
Range(ActiveColumnName("<FIELDNAME>", firstrow_fieldnames_main) & firstrow_data_main, Range(ActiveColumnName("ÄÅÔÅ", firstrow_fieldnames_main) & Rows.Count).End(xlUp).Offset(-1)).Select
Application.CutCopyMode = False
Selection.Copy
Workbooks.Open help_file '"help_file" is any given .xls path with formulas
Dim firstrow_data_help As Integer
Dim firstrow_fieldnames_help As Integer
firstrow_data_help = 7
firstrow_fieldnames_help = 4
'NOW WHEN I USE THIS, DOESN'T WORK:
-> Range(ActiveColumnName("<FIELDNAME>", firstrow_fieldnames_help) & firstrow_data_help).Select
'WHEN I USE THIS, WORKS FINE:
-> Range("L7").Select
Selection.PasteSpecial Paste:=xlPasteValues, Operation:=xlNone, SkipBlanks:=False, Transpose:=False
Application.CutCopyMode = False
End Sub
When it doesn't work, it opens the .xls, and the desirable cell is indeed selected, but no pate. I understand that has something to do with the clipboard, but I cannot figure it out. Any suggestions?
Remove all the Select and Activate by referring to the cells directly, See HERE for more information.
Look into Cells() instead of Range and avoid the whole need to convert column numbers to letters, as Cells() uses numbers.
Avoid the Clipboard when values are the only thing you want and simply assign the value to the new cells (This will require that both ranges are the same size, so use Resize())
Always denote the parent sheet of the range, it will cut down on the errors.
Code refactored
Sub main()
Dim firstrow_data_main As Integer
Dim firstrow_fieldnames_main As Integer
Dim rng As Range
Dim tWb As Workbook
Dim ws As Worksheet
Dim tWs As Worksheet
Dim firstrow_data_help As Integer
Dim firstrow_fieldnames_help As Integer
Set ws = ThisWorkbook.ActiveSheet
Set tWb = Workbooks.Open(help_file)
Set tWs = tWb.ActiveSheet
firstrow_data_main = 16
firstrow_fieldnames_main = 15
firstrow_data_help = 7
firstrow_fieldnames_help = 4
With ws
Set rng = .Range(.Cells(firstrow_data_main, firstrow_fieldnames_main), .Cells(.Rows.Count, firstrow_fieldnames_main).End(xlUp).Offset(-1))
tWs.Cells(firstrow_data_help, firstrow_fieldnames_help).Resize(rng.Rows.Count, rng.Columns.Count).Value = rng.Value
End With
End Sub
I think the problem may be here:
ActiveColumnName("<FIELDNAME>", firstrow_fieldnames_main)
This implies that ActiveColumnName is a matrix with n x (1 to 2) dimension. If you want to concatenate a name to a variable, you have to use (example):
"YourStringHere" & YourVariableHere & "AnotherString"
Which in your case would be:
ActiveColumnName("<FIELDNAME>" & firstrow_fieldnames_main)
So if I correctly understood (<FIELDNAME> is a bit obscure), the whole command should be:
Range(ActiveColumnName("<FIELDNAME>" & firstrow_fieldnames_help) & "," & firstrow_data_help).Select
First of all, you should compile your VBA before running. The VBA compiler caught this off the bat:
Dim ActiveColumnName As String
is unnecessary because you assigned ActiveColumnName As String when you defined the function in line 1.
You use a lot of references to active cells and selecting cells. This is known to cause runtime errors. See this post: "How to avoid using Select in Excel Vba Macros".
I suspect the fieldname is not where you think it should be in your help_file, i.e. it isn't in Row 4. This would mean the code wouldn't know where to paste the data. In general, the best way to debug is to piece the code into the smallest action possible to see what's causing the error (see SpreadSheet Guru Strategies). Can you run the following code to see what the output is?
Function ActiveColumnName(fieldname As String, fieldnames_line As Integer) As String
Range("A" & fieldnames_line & ":AB" & fieldnames_line).NumberFormat = "#"
Cells.Find(What:=fieldname, After:=ActiveCell, LookIn:=xlFormulas, LookAt _
:=xlWhole, SearchOrder:=xlByRows, SearchDirection:=xlNext, MatchCase:= _
False, SearchFormat:=False).Activate
ActiveColumnNumber = ActiveCell.Column
Dim m As Integer
ActiveColumnName = ""
Do While (ActiveColumnNumber > 0)
m = (ActiveColumnNumber - 1) Mod 26
ActiveColumnName = Chr(65 + m) + ActiveColumnName
ActiveColumnNumber = Int((ActiveColumnNumber - m) / 26)
Loop
End Function
Sub main()
Workbooks.Open "help_file" '"help_file" is any given .xls path with formulas
Dim firstrow_data_help As Integer
Dim firstrow_fieldnames_help As Integer
firstrow_data_help = 7
firstrow_fieldnames_help = 4
MessageBox = ActiveColumnName("FIELDNAME", firstrow_fieldnames_help) & firstrow_data_help
End Sub
thank you all for your response! I've tried all of your suggestions one by one but encountered various issues along the way. Nevertheless all of your suggestions helped me to grow a different perspective on the subject. The solution I've end up with, derives from your suggestion to discard ".select" as a way of reference and to use "rng" variables and of course to get rid of the double reference "ActiveColumnName".I know i have a long way to go but for the moment the this thing works!!thanks!!
Sub main()
Dim firstrow_data_main As Integer
Dim firstrow_fieldnames_main As Integer
Dim firstrow_data_help As Integer
Dim firstrow_fieldnames_help As Integer
firstrow_data_main = 16
firstrow_fieldnames_main = 15
firstrow_data_help = 7
firstrow_fieldnames_help = 4
Dim rng1 As Range
Dim rng2 As Range
Set rng1 = Range(ActiveColumnName("<FIELDNAME>", firstrow_fieldnames_main) & firstrow_data_main, Range(ActiveColumnName("<FIELDNAME>", firstrow_fieldnames_main) & Rows.Count).End(xlUp).Offset(-1))
cells_selected = rng1.Rows.Count
Workbooks.Open <help_file>
Set rng2 = Range(ActiveColumnName("<FIELDNAME>", firstrow_fieldnames_help) & firstrow_data_help, Range(ActiveColumnName("<FIELDNAME>", firstrow_fieldnames_help) & cells_selected + firstrow_data_help - 1))
rng1.Copy rng2
End Sub
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: