Excel VBA: Sort Sheets in Alphanumeric Order - excel

I have a workbook of about 30 sheets which I am attempting to put in alphanumeric order. Ex: "New York 9, New York 10, New York 11"
My code fails to order double digit numbers after single digit ones. "10, 11, 9"
Is anyone familiar with the method for accounting for this? Many thanks!
Sub AscendingSortOfWorksheets()
'Sort worksheets in a workbook in ascending order
Dim SCount, i, j As Integer
Application.ScreenUpdating = False
SCount = Worksheets.Count
For i = 1 To SCount - 1
For j = i + 1 To SCount
If Worksheets(j).Name < Worksheets(i).Name Then
Worksheets(j).Move before:=Worksheets(i)
End If
Next j
Next i
End Sub

As mentioned in the comments, you need to pad the numbers with zeros, in your case single digit numbers need to be padded with 1 zero. Use this function
Function PadNumber(sName As String, lNumOfDigits As Long) As String
Dim v As Variant
Dim vPrefixList As Variant
Dim sTemp As String
Dim i As Long
' Add all other possible prefixes in this array
vPrefixList = Array("New York")
sTemp = sName
For Each v In vPrefixList
sTemp = Replace(LCase(sTemp), LCase(v), "")
Next v
sTemp = Trim(sTemp)
PadNumber = sTemp
For i = Len(sTemp) + 1 To lNumOfDigits
PadNumber = "0" & PadNumber
Next i
PadNumber = Replace(sName, sTemp, PadNumber)
End Function
Then change the line If Worksheets(j).Name < Worksheets(i).Name Then to
If PadNumber(LCase(Worksheets(j).Name), 2) < PadNumber(LCase(Worksheets(i).Name), 2) Then
Note I added LCase in the comparison. Case-sensitivity might not matter for you in this particular case but it is something you always need to keep in mind.

Here is one way to achieve it
Logic:
Create a 2D array to store the number after space and sheet name
Sort the array
Arrange the sheets
Code:
Sub Sample()
Dim SheetsArray() As String
'~~> Get sheet counts
Dim sheetsCount As Long: sheetsCount = ThisWorkbook.Sheets.Count
'~~> Prepare our array for input
'~~> One part will store the number and the other will store the name
ReDim SheetsArray(1 To sheetsCount, 1 To 2)
Dim ws As Worksheet
Dim tmpAr As Variant
Dim sheetNo As Long
Dim i As Long: i = 1
Dim j As Long
'~~> Loop though the worksheest
For Each ws In ThisWorkbook.Sheets
tmpAr = Split(ws.Name)
'~~> Extract last number after space
sheetNo = Trim(tmpAr(UBound(tmpAr)))
'~~> Store number and sheet name as planned
SheetsArray(i, 1) = sheetNo
SheetsArray(i, 2) = ws.Name
i = i + 1
Next ws
'~~> Sort the array on numbers
Dim TempA, TempB
For i = LBound(SheetsArray) To UBound(SheetsArray) - 1
For j = i + 1 To UBound(SheetsArray)
If SheetsArray(i, 1) > SheetsArray(j, 1) Then
TempA = SheetsArray(j, 1): TempB = SheetsArray(j, 2)
SheetsArray(j, 1) = SheetsArray(i, 1): SheetsArray(j, 2) = SheetsArray(i, 2)
SheetsArray(i, 1) = TempA: SheetsArray(i, 2) = TempB
End If
Next j
Next i
'~~> Arrange the sheets
For i = UBound(SheetsArray) To LBound(SheetsArray) Step -1
ThisWorkbook.Sheets(SheetsArray(i, 2)).Move After:=ThisWorkbook.Sheets(sheetsCount)
sheetsCount = sheetsCount - 1
Next i
End Sub
Assumptions:
The sheet names have space in their names
The sheet names are in the format New York #

Related

Reading medium large .dat file with Excel VBA

Your support is really appreciated!
I am receiving a .dat file from a measuring tool, which is found hard to get in to excel.
I would like to do it without power query as well.
I do this in steps:
Step 1; convert dat file to "csv/txt" by removing duplicate spaces and replacing spaces with ";", also replacing "." with ",".
I would like to keep this format as several other tools tends to use similar format.
And from this I thought it would be fairly ok to import it, however...
First row of 11000 rows of .dat file:
1 1 -0.4200 -0.0550 0.1420 173 174 181 56.3 55.5 59.3 87 84 95 0.778 0 0 0
first row of the converted file, all rows below looks good as well.
1;1;-0,4260;-0,1500;0,0990;171;168;176;55,5;53,8;57,6;96;83;82;4,794;0;0;0
if I import this file with power query it seems ok.
Step 2:
When importing it with the code below, following occurs on line 660
from txt file
1;660;-1,0210;-0,0340;0,0470;169;164;176;54,6;51,2;57,2;15;96;63;0,782;0;0;0
from excel:
Debuging the shows following:
file:
format of the cell is "Numbers" and not "geeral" as for other numbers.
This seems to occure now and then, and typically when the number goes above -1,xx.
Code is found online, and is fairly quick.
I suspect that something happens when putting the two-dimensional variant array into the sheet
Dim Data As Variant 'Array for the file values
.
.
.
.
With Sheets(parSheetName)
'Delete any old content
.cells.ClearContents
'A range gets the same dimensions as the array
'and the array values are inserted in one operation.
.cells(4, 1).Resize(UBound(Data, 1), UBound(Data, 2)) = Data
End With
End If
Option Explicit
'**************************************************************
' Imports CSV to sheet, following the generated numbers will be placed in a table.
'**************************************************************
Public Sub copyDataFromCsvFileToSheet(parFileName As String, _
parDelimiter As String, parSheetName As String)
Dim Data As Variant 'Array for the file values
Dim I As Long
Dim J As Long
Dim prt As String
'Function call - the file is read into the array
Data = getDataFromFile(parFileName, parDelimiter)
'If the array isn't empty it is inserted into
'the sheet in one swift operation.
If Not isArrayEmpty(Data) Then
'If you want to operate directly on the array,
'you can leave out the following lines.
ActiveWorkbook.Worksheets.Add(After:=Sheets(Sheets.Count)).Name = parSheetName
'For I = 1 To 1000 'UBound(Data, 1)
'For J = 1 To 18 'UBound(Data, 2)
''prt = Data(I, J)
''Debug.Print prt
''ThisWorkbook.Worksheets(parSheetName).cells(I, J) = Data(I, J)
'Next J
'Next I
'Debug.Print "done"
'End If
With Sheets(parSheetName)
'Delete any old content
.cells.ClearContents
'A range gets the same dimensions as the array
'and the array values are inserted in one operation.
.cells(4, 1).Resize(UBound(Data, 1), UBound(Data, 2)) = Data
End With
End If
'Call sbCreatTable(parSheetName)
End Sub
'**************************************************************
Private Function isArrayEmpty(parArray As Variant) As Boolean
'Returns False if not an array or a dynamic array
'that hasn't been initialised (ReDim) or
'deleted (Erase).
If IsArray(parArray) = False Then isArrayEmpty = True
On Error Resume Next
If UBound(parArray) < LBound(parArray) Then
isArrayEmpty = True
Exit Function
Else
isArrayEmpty = False
End If
End Function
Private Function getDataFromFile(parFileName As String, _
parDelimiter As String, _
Optional parExcludeCharacter As String = "") As Variant
'parFileName is the delimited file (csv, txt ...)
'parDelimiter is the separator, e.g. semicolon.
'The function returns an empty array, if the file
'is empty or cannot be opened.
'Number of columns is based on the line with most
'columns and not the first line.
'parExcludeCharacter: Some csv files have strings in
'quotations marks ("ABC"), and if parExcludeCharacter = """"
'quotation marks are removed.
Dim locLinesList() As Variant 'Array
Dim locData As Variant 'Array
Dim I As Long 'Counter
Dim J As Long 'Counter
Dim locNumRows As Long 'Nb of rows
Dim locNumCols As Long 'Nb of columns
Dim fso As Variant 'File system object
Dim ts As Variant 'File variable
Const REDIM_STEP = 10000 'Constant
'If this fails you need to reference Microsoft Scripting Runtime.
'You select this in "Tools" (VBA editor menu).
Set fso = CreateObject("Scripting.FileSystemObject")
On Error GoTo error_open_file
'Sets ts = the file
Set ts = fso.OpenTextFile(parFileName)
On Error GoTo unhandled_error
'Initialise the array
ReDim locLinesList(1 To 1) As Variant
I = 0
'Loops through the file, counts the number of lines (rows)
'and finds the highest number of columns.
Do While Not ts.AtEndOfStream
'If the row number Mod 10000 = 0
'we redimension the array.
If I Mod REDIM_STEP = 0 Then
ReDim Preserve locLinesList _
(1 To UBound(locLinesList, 1) + REDIM_STEP) As Variant
End If
locLinesList(I + 1) = Split(ts.ReadLine, parDelimiter)
J = UBound(locLinesList(I + 1), 1) 'Nb of columns in present row
'If the number of columns is then highest so far.
'the new number is saved.
If locNumCols < J Then locNumCols = J
I = I + 1
Loop
ts.Close 'Close file
locNumRows = I
'If number of rows is zero
If locNumRows = 0 Then Exit Function
ReDim locData(1 To locNumRows, 1 To locNumCols + 1) As Variant
'Copies the file values into an array.
'If parExcludeCharacter has a value,
'the characters are removed.
If parExcludeCharacter <> "" Then
For I = 1 To locNumRows
For J = 0 To UBound(locLinesList(I), 1)
If Left(locLinesList(I)(J), 1) = parExcludeCharacter Then
If Right(locLinesList(I)(J), 1) = parExcludeCharacter Then
locLinesList(I)(J) = _
Mid(locLinesList(I)(J), 2, Len(locLinesList(I)(J)) - 2)
Else
locLinesList(I)(J) = _
Right(locLinesList(I)(J), Len(locLinesList(I)(J)) - 1)
End If
ElseIf Right(locLinesList(I)(J), 1) = parExcludeCharacter Then
locLinesList(I)(J) = _
Left(locLinesList(I)(J), Len(locLinesList(I)(J)) - 1)
End If
locData(I, J + 1) = locLinesList(I)(J)
Next J
Next I
Else
For I = 1 To locNumRows
For J = 0 To UBound(locLinesList(I), 1)
locData(I, J + 1) = locLinesList(I)(J)
Next J
Next I
End If
getDataFromFile = locData
Exit Function
error_open_file: 'Returns empty Variant
unhandled_error: 'Returns empty Variant
End Function
Due to mentioned several measuring tools, the power query is un suited, and the control is better when using the ole way of doing it.
Solution:
Setting the variant to decimal when building the array
CDec(locLinesList(I)(J))
Thanks for your responce!

Out of 10 attendance sheets, how can I find the names that are present in at least 7 of them in Excel

I have 10 attendance sheets in excel with only the names of present people. I want to find out which names are present in at least 7 sheets. How can I do this?
I know it is a terrible way to keep attendance and I wasn't the one who came up with it, I'm trying to salvage it.
Thanks in advance
Alright, this should do the trick. I threw it together so there are likely some efficiency improvements possible :)
Not sure what your structure is so I built this based on the simplest possible sheet, where there's a header row and students' names are shown when they attend. Students not in attendance are not listed. This probably doesn't match exactly but code should be easily tweaked. I can help if needed. You end up with an array called "arrGood" that contains the students and it'll pop up a message box telling you who met attendance or if no one did.
Option Explicit
Sub attendance()
'Set Variables
Dim sht As Integer, rw As Integer, pos As Integer, i As Integer, j As Integer, goodAttendance As Integer
Dim arrClass() As Variant, arrTransfer() As Variant, arrGood() As Variant
Dim strName As String, strStudents As String
Dim inArray As Boolean
goodAttendance = 2 'change this if you want to check a different number
'Initialize empty arrays
ReDim arrClass(0, 1)
ReDim arrGood(0)
For sht = 1 To ThisWorkbook.Worksheets.Count 'loop through worksheets
For rw = 2 To ThisWorkbook.Worksheets(sht).Cells.Find("*", searchorder:=xlByRows, searchdirection:=xlPrevious).Row 'loop through rows starting with 2 (assuming there's a header row)
inArray = False
strName = ThisWorkbook.Sheets(sht).Cells(rw, 1).Value 'change this cell if searching for student names outside the first column
'This section only triggers on the first run and adds the first student with attendance of 1
If arrClass(0, 0) = "" Then
arrClass(0, 0) = strName
arrClass(0, 1) = 1
inArray = True
Else
For pos = 0 To UBound(arrClass()) 'loop through array
'check if student is already in array. Add 1 to their attendance if so
If arrClass(pos, 0) = strName Then
inArray = True
arrClass(pos, 1) = arrClass(pos, 1) + 1
End If
Next pos
End If
If inArray = False Then 'new student not in array
'Workaround to redim multidimensional array
arrTransfer = arrClass
ReDim arrClass(0 To UBound(arrClass) + 1, 1)
For i = 0 To UBound(arrTransfer)
For j = 0 To 1
arrClass(i, j) = arrTransfer(i, j)
Next j
Next i
'Add new student to array and set their attendance to 1
arrClass(UBound(arrClass), 0) = strName
arrClass(UBound(arrClass), 1) = 1
End If
Next rw
Next sht
'Check for students with good attendance
For i = 0 To UBound(arrClass)
If arrClass(i, 1) >= goodAttendance Then
'Add first student
If arrGood(0) = "" Then
arrGood(0) = arrClass(i, 0)
'Add subsequent students
Else
ReDim Preserve arrGood(0 To UBound(arrGood) + 1)
arrGood(UBound(arrGood)) = arrClass(i, 0)
End If
strStudents = strStudents + arrClass(i, 0) + ", "
End If
Next i
'Display students meeting criteria
If Len(strStudents) > 0 Then
strStudents = Left(strStudents, Len(strStudents) - 2)
MsgBox ("Students with more than " & goodAttendance & " attendances: " & strStudents)
Else
MsgBox ("No students had at least " & goodAttendance & " attendances.")
End If
End Sub

How can i use VBA Code to Copy and Paste Specific Cells if Condition is Met in two or more worksheets to different areas of another worksheet

Looking for a little more help please. I was here a month ago a RiskyPenguin gave me a great bit of code. I would like to add to this.
This is the part that works:
So if the "invoice" spreadsheet (sheet 5), if cell G4 (for example is I111) matches any of the data in the first column of the "income" spreadsheet (sheet 1) (starting at row 6) then the corresponding data in columns 2 3, 8 & 9 will copy over to the "invoice" spreadsheet in columns 2, 3, 4 & 5 (starting at row 13).
Sub FindAndCopyData2()
Dim shData As Worksheet, shReport As Worksheet
Set shData = Sheet1
Set shReport = Sheet6
Dim strInvoceNumber As String
strInvoceNumber = shReport.Cells(4, "E").Value
Dim intLastRow As Integer
intLastRow = shData.Cells(Rows.Count, 1).End(xlUp).Row
Dim intReportRow As Integer
intReportRow = 13
shReport.Range("B13:E20").ClearContents
Dim i As Integer
For i = 1 To intLastRow
If shData.Cells(i, 1).Value2 = strInvoceNumber Then
shReport.Cells(intReportRow, 2).Value2 = shData.Cells(i, 3).Value2
shReport.Cells(intReportRow, 3).Value2 = shData.Cells(i, 4).Value2
shReport.Cells(intReportRow, 4).Value2 = shData.Cells(i, 8).Value2
shReport.Cells(intReportRow, 5).Value2 = shData.Cells(i, 9).Value2
intReportRow = intReportRow + 1
End If
Next i
End Sub
I would then like to (hopefully using the same search)
Take the "invoice" spreadsheet (sheet 5), if cell G4 (for example is I111) matches any of the data in the second column of the "expenses" spreadsheet (sheet 2) (starting at row 11) then the corresponding data in columns 3, 5, & 7 will copy over to the "invoice" spreadsheet in columns 2, 4 & 6 (starting at row 13).
Is this possible or does it have to be a separate piece of programming?
Many Thanks for any advise.
Assuming this could be useful for others I made a function out of it and refactored the initial code to handle the copy in memory. I setup your first lookup so you just need to edit the variables to get your second lookup:
Option Explicit
''''''''''''''''''''''''''''''''''''''
''Main Sub
''''''''''''''''''''''''''''''''''''''''''''''''''
Sub main()
'Set some vars
Dim sourceArr, targetArr, sourceCls, targetCls, sourceStartRw As Long, targetStartRw As Long, dict As Object, j As Long, sourceLookupCl As Long, Matchkey As Long
''''''''''''''''''''''''''''''''''''''
''Lookup 1
''''''''''''''''''''''''''''''''''''''''''''''''''
Matchkey = Sheet5.Range("G4").Value2 'lookupKey
sourceCls = Split("2,3,8,9 ", ",") 'Columns to copy from
targetCls = Split("2,3,4,5", ",") 'Columns to copy to
sourceStartRw = 6
targetStartRw = 13
sourceLookupCl = 1 'matching column
'get data in memory = array
sourceArr = Sheet1.Range("A1").CurrentRegion.Value2
'call our function
targetArr = reorder(sourceArr, sourceCls, targetCls, sourceStartRw, sourceLookupCl, Matchkey)
'dump to sheet
With Sheet5
.Range(.Cells(targetStartRw, 1), .Cells(UBound(targetArr) + targetStartRw - 1, UBound(targetArr, 2))).Value2 = targetArr
End With
''''''''''''''''''''''''''''''''''''''
''Lookup 2 => change source and target cols to your need
''''''''''''''''''''''''''''''''''''''''''''''''''
Matchkey = Sheet5.Range("G4").Value2
sourceCls = Split("2,3,8,9 ", ",")
targetCls = Split("2,3,4,5", ",")
sourceStartRw = 6
targetStartRw = 13 'must be the same as previous lookup if you want to keep the targetArr from previous lookups
sourceLookupCl = 1
'get data in memory = array
sourceArr = Sheet1.Range("A1").CurrentRegion.Value2
'call our function keeping the data from the first lookup
targetArr = reorder(sourceArr, sourceCls, targetCls, sourceStartRw, sourceLookupCl, Matchkey, targetArr)
'dump to sheet
With Sheet5
.Range(.Cells(targetStartRw, 1), .Cells(UBound(targetArr) + targetStartRw - 1, UBound(targetArr, 2))).Value2 = targetArr
End With
End Sub
''''''''''''''''''''''''''''''''''''''
''Supporting function
''''''''''''''''''''''''''''''''''''''''''''''''''
Private Function reorder(sourceArr, sourceCls, targetCls, sourceStartRw As Long, sourceLookupCl As Long, Matchkey As Long, Optional targetArr) As Variant
Dim dict As Object, j As Long
'if the target array overlaps the previous lookups pass it to the function
If IsMissing(targetArr) Then
ReDim targetArr(1 To UBound(sourceArr), 1 To UBound(sourceArr, 2))
End If
'build a dict to compare quickly
Set dict = CreateObject("Scripting.Dictionary") 'create dictionary lateB
For j = 1 To UBound(sourceArr) 'traverse source
dict(sourceArr(j, sourceLookupCl)) = Empty
Next j
'check if key exists in dict and copy data
Dim i As Long, ii As Long ': ii = 1
If dict.Exists(Matchkey) Then
For j = sourceStartRw To UBound(sourceArr)
For i = 1 To UBound(sourceArr, 2)
If i = sourceCls(ii) Then
targetArr(j - sourceStartRw + 1, targetCls(ii)) = sourceArr(j, i)
ii = IIf(ii < UBound(sourceCls), ii + 1, ii)
End If
Next i
ii = 0
Next j
End If
reorder = targetArr
End Function

Extracting Text from Cell using Excel VBA

I am looking to extract multiple text values from a column in Excel and populate another column with these text values.
To be more specific, I am looking to extract the STLS ticket numbers.
For example, one row may contain "ABCD-4, STLS-5644, ABBD-33, STLS-421", another row may contain "ABB-567, STLS-56435" and another row may contain no STLS tickets.
What would be the best way to approach this problem?
You could try this code:
Option Explicit
Sub testExtract()
Dim i As Long, j As Long, jUp As Long, lFirstRow As Long, lLastRow As Long
Dim lColFrom As Long, lColTo As Long, nTicks As Long
Dim str1 As String
Dim varArray
'
' define source column number and the destination one:
'
lColFrom = 1
lColTo = 2
'
' initialize range to analyze:
'
lFirstRow = 1
lLastRow = Cells(Rows.Count, 1).End(xlUp).Row
'
' loop over the rows:
'
For i = lFirstRow To lLastRow
'
' split the string in the cell in an array:
'
varArray = Split(Cells(i, lColFrom).Value, ",")
jUp = UBound(varArray)
nTicks = 0
str1 = ""
'
' check the array element by element if we have some ticket:
'
For j = 0 To jUp
'
' trim spaces:
'
varArray(j) = Trim(varArray(j))
'
' check if we have ticks and count them:
'
If (InStr(1, varArray(j), "STLS-") > 0) Then
If (nTicks > 0) Then
str1 = str1 & ", "
End If
str1 = str1 & varArray(j)
nTicks = nTicks + 1
End If
Next
'
' save ticks:
'
If (str1 <> "") Then
Cells(i, lColTo).Value = str1
End If
Next
End Sub
If your Excel has the FILTERXML function (windows Excel 2013+) and the TEXTJOIN function, you don't need VBA.
You can use:
=IFERROR(TEXTJOIN(",",TRUE,FILTERXML("<t><s>" & SUBSTITUTE(A1,",","</s><s>")&"</s></t>","//s[contains(.,'STLS')]")),"")
If you don't have those functions, you can use this VBA UDF:
Option Explicit
Function getTickets(s As String, ticket As String) As String
Dim v, w, x, col As Collection, i As Long
v = Split(s, ",")
Set col = New Collection
For Each w In v
If Trim(w) Like ticket & "*" Then col.Add Trim(w)
Next w
i = 0
If col.Count = 0 Then
getTickets = ""
Else
ReDim x(col.Count - 1)
For Each w In col
x(i) = w
i = i + 1
Next w
getTickets = Join(x, ",")
End If
End Function

Concatenate varying number of rows in one column by one keyword in Excel

Can you help me understand how to Concatenate all rows of a bunch of records in one column, but with varying numbers of rows and delineated by a keyword indicating the first row like 'File*#' ? An example:
1 file# x stuff...
2 more stuff
3 more stuff.
4 file# x stuff...
5 more stuff
6 File # stuff
7 File# stuff
Thank you!
There's probably a better way but I think this will work for you:
Sub Test()
Dim r As Range
Dim i As Integer
Dim lRow As Long
Dim x As Variant
Dim arr() As Variant
Dim aSht As Worksheet
Set aSht = ActiveSheet
Set r = ActiveSheet.Range("A1")
i = 1
Do While r <> ""
If Left(r.Offset(i, 0), 6) = "File #" Or r.Offset(i, 0) = "" Then
x = r.Resize(i, 1).Value2
ReDim arr(1 To UBound(x, 1))
For lRow = 1 To UBound(x, 1)
arr(lRow) = x(lRow, 1)
Next
If aSht.Cells(aSht.Rows.Count, 2).End(xlUp).Value = "" Then
aSht.Cells(aSht.Rows.Count, 2).End(xlUp).Value = Join(arr, ";")
Else
aSht.Cells(aSht.Rows.Count, 2).End(xlUp).Offset(1, 0).Value = Join(arr, ";")
End If
Set r = r.Offset(i, 0)
i = 1
Else
i = i + 1
End If
Loop
End Sub
NOTE: There must be a minimum of 2 rows for a File. In other words, row 1 is File..., and row 2 is stuff. Each File row must begin: File (space) #. I can make it more flexible but it will work just fine.

Resources