I have a column full of data in a format I don't like, and need them in another format. Currently they are formatted like this: "190826_095630_3E_1 (ROI 0)" and I need just the "3E" portion. I have written a string split that uses the "_" and I figure I can then just take the column of data that is produced that I want, however I can only get this to work one cell at a time while I click each one. I tried to write a for loop but I am running into trouble, most likely because I used "active.cell". Does anyone have a better way to loop this split through my column? Alternatively if you also know how to just return the third string split (3E) I would really appreciate it.
'No loop: This works for one cell
Option Explicit
Sub NameTest()
Dim txt As String
Dim i As Integer
Dim FullName As Variant
txt = ActiveCell.Value
FullName = Split(txt, "_")
For i = 0 To UBound(FullName)
Cells(1, i + 1).Value = FullName(i)
Next i
End Sub
'Attempt at a loop:
Option Explicit
Sub NameTest()
Dim txt As String
Dim i As Integer
Dim FullName As Variant
Dim x As Integer
For x = 1 To 1000
txt = ActiveCell.Value
FullName = Split(txt, "_")
For i = 0 To UBound(FullName)
Cells(1, i + 1).Value = FullName(i)
Next i
Next x
End Sub
I would like to get this to run until the last cell with data in a given column.
Related
I'm new to vba so I need some help making my macro more efficient. It does return the desired outcome however I know there must be a much quicker way to do so I just do not have the vba experience to know how.
I have a column which contains names of people assigned to a project. Some are only one name, and others may be multiple, for example:
At the moment, my code goes through this column, separates the names by comma, and enters them individually into a new range like so:
I then use a collection for the unique names and enter them in the final desired list. The names must show up three times, blank row, next three rows are the next name, so on.It should look like this in the end:
Currently my code is the following
Sub FindUniques()
Dim Ws As Worksheet, Ns As Worksheet
Dim SubString() As String, m As Integer, k As Long, NameCount As Integer
Dim allNames As New Collection, tempRng As Range
Set Ns = Worksheets("Sheet2")
Set Ws = Worksheets("Sheet1")
'Loops through the Assigned To column, separates and finds unique names
On Error Resume Next
For i = 1 To Ws.Range("A:A").End(xlDown).Row - Range("Assigned_to").Row
SubString = Split(Range("Assigned_to").Offset(i), ", ")
For j = 0 To UBound(SubString)
allNames.Add (allNames.count), SubString(j)
Next j
Next i
On Error GoTo 0
NameCount = allNames.count
For k = 1 To NameCount
For m = 1 To 4
Ns.Cells((k - 1) * 4 + m + 7, 2) = allNames.Key(k)
Next
Range("Names").Offset((k - 1) * 4).ClearContents
Next
End Sub
It works, however there must be some way that is more efficient than entering the names into a new range and then deleting the range. How can I use a collection or an array or something of the sort to make it quicker? Any ideas would be really appreciated
edit: I have now updated the code and it is using an collection, taking values from the substring. This enters the item (0, 1, 2, ...) in the cells instead of the keys (keys here are the names). How do I get it to return the key instead of the item number?
The slowest part of VBA are worksheet interactions so we should attempt to minimize that as much as possible.
Sub FindUniques()
Dim ws As Worksheet, ns As Worksheet
Dim splitStr() As String, nameStr As Variant
Dim dict As New Dictionary
Dim lastRow As Long, i As Long
Set ns = Worksheets("Sheet2")
Set ws = Worksheets("Sheet1")
lastRow = ws.Cells(ws.Rows.Count, 1).End(xlUp).Row
'Loops through the Assigned To column, separates and finds unique names
For i = 2 To lastRow
splitStr = Split(CStr(ws.Cells(i, 1).Value), ", ")
For Each nameStr In splitStr
If Not dict.Exists(nameStr) Then dict.Add nameStr , 0
Next
Next i
i = 2
For Each nameStr In dict.Keys
ns.Cells(i, 1).Resize(3).Value = nameStr
i = i + 4
Next
End Sub
Edited With #Toddleson & #BigBen 's suggestions
Good Luck!
I'm trying to reduce redundancy in my macros but I'm struggling with getting an element from a jagged array and using it elsewhere.
The premise is a single workbook with many sheets being split by groups of sheet names into new documents that I can then send to the process owners so they only get their own data.
Previously I was selecting sheet names listed explicitly and pasting to a new document that was named explicitly, but I had to run 10 separate almost identical macros to do that and I have heard of select being a bad choice in many instances as well.
Below is my latest attempt, the first issue is at the printOut line I get a Type Mismatch.
Sub CopyOut()
Dim printOut, groupNames, Group1, groupArray() As Variant
Dim n, j As Long
Dim reNamed, fileName As String
Dim ws As Worksheet
Dim wb1, wb2 As Workbook
groupNames = Array("Group 1", "Group 2", "Group 3", "Group 4") 'other arrays left off for length
Group1 = Array("FA_1A Report", "FA_1A", "FA_2ACS Report", "FA_2ACS", "FA_2BCS Report", "FA_2BCS", "FANUCMED Report", "FANUCMED", "FA_RRTP1 Report", "FA_RRPT1")
groupArray = Array(groupNames, Group1)
For n = 1 To UBound(groupArray)
fileName = "CS Data Sheet" & " " & Format(Date, "mmmyy") & "-" & groupArray(n - n)(n - 1) & ".xlsm" 'concat file name string. this is not just tacked on the end of reName because i use it on it's own later
reNamed = "C:\Users\xx\Desktop\" & fileName 'concat save location string
Set wb1 = ThisWorkbook
Set wb2 = Workbooks.Add 'create a new workbook, wb2
wb2.SaveAs fileName:=reNamed, FileFormat:=xlOpenXMLWorkbookMacroEnabled 'save with that name and location
printOut = Join(Application.Index(groupArray, n, 0), ",")
wb1.Sheets(printOut).Copy Before:=Workbooks(fileName).Sheets(1) 'copy the sheets for the group and paste into the newly created document
Next
End Sub
If I nix printOut altogether and put in a specific worksheet name instead it does work for just that one sheet (of course) but I need it to copy multiple to each new document.
I have also tried:
For n = 1 To UBound(groupArray)
...
for j= LBound(groupArray(n)) To UBound(groupArray(n))
wb1.Sheets(groupArray(n)(j)).Copy Before:=Workbooks(fileName).Sheets(1)
next
next
to iterate through the subarray and copy a sheet at a time, but it gives subscript out of range. With this version I tried various methods of making the groupArray(n)(j) value into a string or into a "worksheet" type to set as a variable and use the variable in the sheets().copy, to no avail.
Any idea where I could be going wrong?
thanks so much
EDIT:
I got my above code working by wrapping it in split (was trying to use printOut as an array when it was only a string) and fixing the arguments of Index as below, however the resulting code still needs work, since if a sheet is missing it won't run.
printOut = Split(Join(Application.Index(groupArray(n), 1, 0), ","), ",")
In my experience, if you find yourself hard-coding values like sheet names, group names, and other data directly in your code it tends to become difficult to maintain. Adding more groups, or re-shuffling the sheets in each group becomes problematic. My recommendation is to create a (possibly hidden) worksheet that maps your worksheet names into groups. Then you have a small set of code that operates directly on that.
My example data is set up like this:
Next, in its own code module, I created a few methods to work directly with this group map data. The main idea here is to move the group map data into a memory-based array. While in general I rarely use module-level global variables, I have one in this example to illustrate how to work with the data by only reading it into the array once every time the macro is executed.
(These are Subs and Functions. For my own code, I likely would have created a VBA class to handle the data in an object-oriented way.)
So there is a Private Sub to get the data:
Option Explicit
Private groupData As Variant
Private Sub GetGroupData()
Const GROUP_WS_NAME As String = "GroupMap"
Dim ws As Worksheet
Set ws = ThisWorkbook.Sheets(GROUP_WS_NAME)
Dim lastRow As Long
Dim lastCol As Long
With ws
'--- how many columns of groups?
lastCol = .Cells(1, .Columns.Count).End(xlToLeft).Column
lastRow = .UsedRange.Find("*", , , , xlByRows, xlPrevious).Row
groupData = .Range("A1").Resize(lastRow, lastCol).Value
End With
End Sub
Now it's easy to figure out how many groups there are:
Public Function NumberOfGroups() As Long
If IsEmpty(groupData) Then GetGroupData
NumberOfGroups = UBound(groupData, 2)
End Function
And how many items in a particular group:
Public Function NumberInGroup(ByVal groupNumber As Long)
If IsEmpty(groupData) Then GetGroupData
'--- count the number of array values that have data
Dim i As Long
For i = LBound(groupData, 1) To UBound(groupData, 1)
If groupData(i, groupNumber) = vbNullString Then
'--- we found the first empty cell in this array, we're done
Exit For
Else
NumberInGroup = NumberInGroup + 1
End If
Next i
'--- subtract one to discount the header value
NumberInGroup = NumberInGroup - 1
End Function
The easiest of all is getting the value of any group:
Public Function GetGroupValue(ByVal groupNumber As Long, _
ByVal groupIndex As Long) As Variant
If IsEmpty(groupData) Then GetGroupData
'--- always add one to the index to account for the header value
GetGroupValue = groupData(groupIndex + 1, groupNumber)
End Function
Notice the check for If IsEmpty(groupData) Then GetGroupData at the beginning of each method. This makes sure the groupData array is always loaded if necessary.
This example gives it a quick test (in a different code module):
Option Explicit
Sub test()
Dim totalGroups As Long
totalGroups = NumberOfGroups()
Dim i As Long
Dim j As Long
For i = 1 To totalGroups
Dim totalInGroup As Long
totalInGroup = NumberInGroup(i)
For j = 1 To totalInGroup
Debug.Print "group " & i & " = " & GetGroupValue(i, j)
Next j
Next i
End Sub
Here's the whole group data code module in a single block:
Option Explicit
Private groupData As Variant
Private Sub GetGroupData()
Const GROUP_WS_NAME As String = "GroupMap"
Dim ws As Worksheet
Set ws = ThisWorkbook.Sheets(GROUP_WS_NAME)
Dim lastRow As Long
Dim lastCol As Long
With ws
'--- how many columns of groups?
lastCol = .Cells(1, .Columns.Count).End(xlToLeft).Column
lastRow = .UsedRange.Find("*", , , , xlByRows, xlPrevious).Row
groupData = .Range("A1").Resize(lastRow, lastCol).Value
End With
End Sub
Public Function NumberOfGroups() As Long
If IsEmpty(groupData) Then GetGroupData
NumberOfGroups = UBound(groupData, 2)
End Function
Public Function NumberInGroup(ByVal groupNumber As Long)
If IsEmpty(groupData) Then GetGroupData
'--- count the number of array values that have data
Dim i As Long
For i = LBound(groupData, 1) To UBound(groupData, 1)
If groupData(i, groupNumber) = vbNullString Then
'--- we found the first empty cell in this array, we're done
Exit For
Else
NumberInGroup = NumberInGroup + 1
End If
Next i
'--- subtract one to discount the header value
NumberInGroup = NumberInGroup - 1
End Function
Public Function GetGroupValue(ByVal groupNumber As Long, ByVal groupIndex As Long) As Variant
If IsEmpty(groupData) Then GetGroupData
'--- always add one to the index to account for the header value
GetGroupValue = groupData(groupIndex + 1, groupNumber)
End Function
If I got this right, you have one master workbook with n sheets and you want to group some of them, then create a new workbook for each group and paste in its assigned sheets.
I think an approach where you keep a "config" file in your master workbook for setting up groups and sheets, is more suitable rather than editing into code. Example:
The below code will create a file using the names from column A and copy all the sheets defined on their respective row.
Option Explicit
Sub CopyOut()
Dim groupArr() As Variant
Dim wb2 As Workbook
Dim lastRow As Long, lastCol As Long, highestNumOfSheets As Long, i As Long, j As Long, arrColumns As Long
Dim reNamed As String, fileName As String, configSheet As String
Dim removedSheet1 As Boolean
' Modify the sheet name here
configSheet = "config"
' Build an array from sheet defined groups
With ThisWorkbook.Worksheets(configSheet)
lastRow = .Cells(.Rows.Count, "A").End(xlUp).Row
For i = 2 To lastRow
lastCol = .Cells(i, .Columns.Count).End(xlToLeft).Column
If lastCol > highestNumOfSheets Then highestNumOfSheets = lastCol
Next i
groupArr = .Range(.Cells(2, 1), .Cells(lastRow, highestNumOfSheets)).Value2
End With
Application.ScreenUpdating = False
For i = LBound(groupArr) To UBound(groupArr)
fileName = "CS Data Sheet " & Format(Date, "mmmyy") & "-" & groupArr(i, 1) & ".xlsm"
reNamed = Environ("UserProfile") & "\Desktop\" & fileName
removedSheet1 = False ' Reset this on each new workbook created
Set wb2 = Workbooks.Add
' Pick all the sheet names for the current group
For j = 2 To UBound(groupArr, 2)
' Skip empty values from array (if it's the case) and skip missing sheets
If Trim(groupArr(i, j)) <> vbNullString And SheetExists(groupArr(i, j)) Then
ThisWorkbook.Worksheets(groupArr(i, j)).Copy Before:=wb2.Worksheets(1)
' Remove Sheet1 from the new Workbook
If removedSheet1 = False Then
With Application
.DisplayAlerts = False
wb2.Worksheets("Sheet1").Delete
removedSheet1 = True
.DisplayAlerts = True
End With
End If
End If
Next j
' Here you might need an error handler if you think you're going to run the macro multiple times in the same day
' If the file exists already this will throw an error
' A quick lazy way is to add time (including seconds) when you define the file name above
wb2.SaveAs fileName:=reNamed, FileFormat:=xlOpenXMLWorkbookMacroEnabled
wb2.Close
If Not wb2 Is Nothing Then Set wb2 = Nothing
Next i
Application.ScreenUpdating = True
End Sub
Function SheetExists(ByVal sheetName As String) As Boolean
Dim ws As Worksheet
On Error Resume Next
Set ws = ThisWorkbook.Worksheets(sheetName)
On Error GoTo 0
If Not ws Is Nothing Then
SheetExists = True
Set ws = Nothing
End If
End Function
Of course it can be tweaked around, with error handling and other checks (depending on what you want to achieve entirely) but it should give you an alternative view of your code.
EDIT: Added a function to check if sheet exists.
I've read the post on this VBA problem, but my VBA script is still not working.
Public Sub Test()
Dim arrNames As Variant 'Declare array named "arrNames"
arrNames = Sheet1.Range("F2:F1000") 'Array filled with column F
intN = Application.CountIf(arrNames, "*") 'does not work intent: count cells w/info
'intN = Application.CountA(arrNames) 'works but MsgBox displays value of 999
MsgBox (intN)
End Sub
How do I get the number of cells in my array containing any value?
EDITED version after help
Public Sub Test()
Dim arrNames As Variant 'Declare array named "arrNames"
Dim i As Long
arrNames = Sheet1.Range("F2:F1000") 'Array filled with column F
For i = LBound(arrNames) To UBound(arrNames)
If (arrNames(i,1) = "") Then
EmptyCounter = EmptyCounter + 1
End If
Next i
End Sub
There is no direct way to do it, as far as I understand. But you could run a simple loop to check if the values are equal to "" assuming string data.
For e.g.
For i = LBound(ArrayName) to UBound(ArrayName)
If (ArrayName(i) = "") Then
EmptyCounter = EmptyCounter + 1
End If
Next i
If it's numeric or other type of data, you may try variations of the above loop using functions such as IsEmpty(VariableName) etc.
You can try this:
intN = Worksheets("Sheet1").Range("F2:F1000").Cells.SpecialCells(xlCellTypeConstants).Count
MsgBox intN
100% it works.
In Excel file1, I have very big table, with numbers in each row in same column (let's say col F).
In Excel file2, I have numbers also in one column (let's say col A).
Q: How I can select all rows in file2 that contain numbers from file1 col A.
I found how to select rows in file2 that contain one string from file1... but array of strings is a little bit tricky for me and the array in file1 is very big.
Sub SelectManyRows()
Dim CatchPhrase As String
Dim WholeRange As String
Dim AnyCell As Object
Dim RowsToSelect As String
CatchPhrase = "10044" // <- here should be array from file1 col A
'first undo any current highlighting
Selection.SpecialCells(xlCellTypeLastCell).Select
WholeRange = "A1:" & ActiveCell.Address
Range(WholeRange).Select
On Error Resume Next ' ignore errors
For Each AnyCell In Selection
If InStr(UCase$(AnyCell.Text), UCase$(CatchPhrase)) Then
If RowsToSelect <> "" Then
RowsToSelect = RowsToSelect & "," ' add group separator
End If
RowsToSelect = RowsToSelect & Trim$(Str$(AnyCell.Row)) & ":" & Trim$(Str$(AnyCell.Row))
End If
Next
On Error GoTo 0 ' clear error 'trap'
Range(RowsToSelect).Select
End Sub
The following idea is trying to avoid looping which is usually inefficient. Instead, I used AdvancedFilter assuming its possible with the set of data you have.
The code works fine for the following set of data located in different sheets (File1 and File2). You would need to change it to work with workbooks as you need.
Sub qTest()
Sheets("File1").Activate
Dim sRNG As Range
Dim aRNG As Range
Set sRNG = Sheets("File2").Range("S1", Sheets("File2").Range("S1").End(xlDown))
Set aRNG = Sheets("File1").Range("A1", Sheets("File1").Range("a1").End(xlDown))
aRNG.AdvancedFilter Action:=xlFilterInPlace, CriteriaRange:=sRNG, Unique:=False
Dim aADD As String
aADD = aRNG.SpecialCells(xlCellTypeVisible).Address
aRNG.Parent.ShowAllData
Range(aADD).Select
End Sub
Something akin to this could be used. Select is avoided, except to actually select the rows you're looking for. Also this dynamically adds the same numbers to a range to be selected at the end.
Dim cl As Variant
Dim first_range As Boolean: first_range = True
Dim colF_range As Range, selected_range As Range
'colF_range is the list in File 2
Set colF_range = Workbooks("File2").Worksheets("Your_Worksheet") _
.Range("F:F")
'Go through each cell in the File 2 list
For Each cl In colF_range
'Look if that cell's value matches something
'in File 1 column A
If Not Workbooks("File1").Worksheets("Your_Worksheet") _
.Range("A:A").Find(cl.Value) Is Nothing Then
'If so, select that row in File 2
If first_range Then
Set selected_range = cl.EntireRow
first_range = False
Else
Set selected_range = Application.Union _
(cl.EntireRow, selected_range)
End If
End If
Next
I am not very familiar with VBA but need to use it for a new software program I am using (not Microsoft related)
I have a text file that has columns of data I would like to read into VBA.
Specifically the text file has 4 entries per row. Thus I would like to load in the column vectors (N by 1).
The text file is separated by a space between each entry.
So for example I want to load in column one and save it as array A, then column two and save as array B, then column three and save as array C, and then column four and save as array D.
This code snippet found below from http://www.tek-tips.com/faqs.cfm?fid=482 is something I found that can load in text to an array, but I need to adapt it to be able to save the columns as different arrays as specified above...
Open "MyFile.txt" For Input As #1
ReDim Txt$(0)
Do While Not EOF(1)
ReDim Preserve Txt$(UBound(Txt$) + 1)
Input #1, Txt$(UBound(Txt$))
Loop
Close #1
For this example, you will need a file called schema.ini in the same directory as the text file. It should contain:
[Import.txt]
Format=Delimited( )
Where Import.txt is the name of the file (http://msdn.microsoft.com/en-us/library/ms709353(VS.85).aspx).
You can then use this, which should work in VBScript or VBA with very little tampering:
Set cn = CreateObject("ADODB.Connection")
'Note HDR=Yes, that is, first row contains field names '
'and FMT delimted, ie CSV '
strCon="Provider=Microsoft.Jet.OLEDB.4.0;Data Source=c:\Docs\;" _
& "Extended Properties=""text;HDR=Yes;FMT=Delimited"";"
cn.Open strcon
strSQL="SELECT * FROM Import.txt" _
set rs = createobject("adodb.recordset")
rs.open strSQL,cn
MsgBox rs(2)
MsgBox rs.GetString
The first message box should return the third column of the first row, it is a test that it works.
The second message box should return the whole file, so don't use it with a large set. The recordset can be manipulated, or you can use .GetRows to create an array of values (http://www.w3schools.com/ado/met_rs_getrows.asp)
Seems the remaining problem is to convert from an array of lines to four arrays of columns.
Maybe this snippet helps
Option Explicit
Option Base 0
Sub import()
Dim sTxt() As String
Dim sLine As Variant
Dim iCountLines As Long
Dim iRowIterator As Long
Dim i As Long
Dim sRow() As String
Dim sColumnA() As String
Dim sColumnB() As String
Dim sColumnC() As String
Dim sColumnD() As String
' read in file '
Open "MyFile.txt" For Input As #1
ReDim sTxt(0)
Do While Not EOF(1)
Input #1, sTxt(UBound(sTxt))
ReDim Preserve sTxt(UBound(sTxt) + 1)
Loop
Close #1
' dim array for each columns '
iCountLines = UBound(sTxt)
Debug.Print "working with ", iCountLines, "lines"
ReDim sColumnA(iCountLines)
ReDim sColumnB(iCountLines)
ReDim sColumnC(iCountLines)
ReDim sColumnD(iCountLines)
' "transpose" sTxt '
iRowIterator = 0
For Each sLine In sTxt
sRow = Split(sLine, " ")
If UBound(sRow) = 3 Then
sColumnA(iRowIterator) = sRow(0)
sColumnB(iRowIterator) = sRow(1)
sColumnC(iRowIterator) = sRow(2)
sColumnD(iRowIterator) = sRow(3)
iRowIterator = iRowIterator + 1
End If
Next sLine
' now work with sColumnX '
Debug.Print "Column A"
For i = 0 To iCountLines
Debug.Print sColumnA(i)
Next i
End Sub
There is few detais in your question, but i would suggest using "Text to column"
If you're not very familiar with VBA programming try recording macro with this steps:
Import file to Excel
select column A
select "Text to columns" form tools menu
choose delimited by space
This way you'll get array of data you asked for, now assigning to any variables you want shouldn't be a problem.
EDIT (Without using Excel):
Take a look on that FSO method.
by replacing
MsgBox strLing
with some kind of split function, like
strTemp = Split(strLine, " ")
You'll be able to loop through all the values in your source file, would that work?