Trying to refer to columns in Excel using variables. What function should I use? - excel

Trying to sort data in Excel alphabetically by row, but NOT in all of the columns for each row.
For example: Sort J7 through U7 alphabetically, then J8 through U8, all the way down through J2000 through U2000, while keeping each item within its row (like all cells in row 7 should still be in row 7 by the end of it.)
It seems like I'll need to use a macro/VBA for this, as Excel only lets you sort one row at a time alphabetically. If I select more than one row to sort, it still only sorts the first row.
I have done a bit of research as far as what kind of macros I could use for this, but nothing seems to do the job. The one thing I found that was similar to this sorts by number, but can't sort alphabetically.
Here's what I've got at a moment. I recorded a macro of myself selecting and sorting the row so that I could hopefully get all the code for that right at least:
Sub Macro4()
'
' Macro4 Macro
'
' Keyboard Shortcut: Option+Cmd+j
'
Dim lngIndex As Long
Dim strArray(9 To 11000) As String
Dim intCounter As Integer
Dim x As Integer
intCounter = 1
x = 9
For lngIndex = LBound(strArray) To UBound(strArray)
intCounter = intCounter + 1
strArray(lngIndex) = intCounter
x = x + 1
Range("Jx:UNx").Select
ActiveWorkbook.Worksheets("export_729559 (3).xlsx").Sort.SortFields.Clear
ActiveWorkbook.Worksheets("export_729559 (3).xlsx").Sort.SortFields.Add Key:= _
Range("Jx:UNx"), SortOn:=xlSortOnValues, Order:=xlAscending, DataOption:= _
xlSortNormal
With ActiveWorkbook.Worksheets("export_729559 (3).xlsx").Sort
.SetRange Range("Jx:UNx")
.Header = xlNo
.MatchCase = False
.Orientation = xlLeftToRight
.SortMethod = xlPinYin
.Apply
End With
Next
End Sub
I'm getting: Run-time error ‘9’: Subscript out of range And it's highlighting the line: ActiveWorkbook.Worksheets("export_729559 (3).xlsx").Sort.SortFields.Clear
I think the error I'm getting is because it's thinking I mean Column JX, rather than Column Jx, where x is a variable I was trying to use to cycle through each row. It seems like there are a few different ways to use variables instead of directly naming the columns, which I'll need to do to reference a new row through each iteration. Could someone recommend a certain function?
One of the ones I've come across is =OFFSET(), but I'm not sure if that would be a good fit for what I'm trying to do, and I'm having a hard time knowing how to fill it in with the right things.

Something like this:
Sub Macro4()
Dim lngIndex As Long
Dim strArray(9 To 11000) As String
Dim intCounter As Integer
Dim sht As Worksheet, rng As Range
Set sht = ActiveWorkbook.Worksheets("export_729559 (3).xlsx")
Set rng = sht.Range("J10:UN10")
intCounter = 1
For lngIndex = LBound(strArray) To UBound(strArray)
intCounter = intCounter + 1
strArray(lngIndex) = intCounter
With sht.Sort
.SortFields.Clear
.SortFields.Add Key:=rng, SortOn:=xlSortOnValues, _
Order:=xlAscending, DataOption:=xlSortNormal
.SetRange rng
.Header = xlNo
.MatchCase = False
.Orientation = xlLeftToRight
.SortMethod = xlPinYin
.Apply
End With
Set rng = rng.Offset(1, 0)
Next
End Sub

If you want to use x as a variable:
Range("J" & x & ":UN" & x)
If you put that in what you are using, should work.

Related

Sort groups and keep spaces

I'm looking for help on a code where the end user hits a button and it sorts the data, but keeps the "groups" together (from columns A through AA) as well as the spaces between tasks.
I've done some research online, but wasn't able to get anything to work, so I don't even have a base code to start from.
Here's some pictures to show what I'm trying to accomplish.
The first image shows the tasks as they may have been entered, but then we assign priorities after all tasks are entered and, as you can see, they're out of order.
Then I'd like for them to hit that "sort" button on the top left of the image, and it sort the worksheet base upon priority, with 1 being the first task, and going down to the last, but keep the "groups" and the space between tasks, so it ends up looking like this:
Again, columns affected would be from A through AA (i.e., the data needing to stay together spans between those columns).
I don't know if this is even possible, but any help would be GREATLY appreciated.
EDIT:
I created a thread on ExcelForum so I can post an actual spreadsheet... that post is here: https://www.excelforum.com/excel-programming-vba-macros/1362269-sort-groups-and-keep-spaces.html#post5585545
Use 2 spare columns to hold sort order keys.
Option Explicit
Sub sortit()
Dim wb As Workbook, ws As Worksheet
Dim LastRow As Long, r As Long
Dim p As Integer, n As Long
Set wb = ThisWorkbook
Set ws = wb.Sheets(1)
With ws
LastRow = .UsedRange.Rows.Count + .UsedRange.Row - 1
' use column AB,AC for sort order
For r = 4 To LastRow
If .Cells(r, "A") > 0 Then
p = .Cells(r, "A")
n = 0
End If
n = n + 1
.Cells(r, "AB") = p
.Cells(r, "AC") = n
Next
With .Sort
.SortFields.Clear
.SortFields.Add Key:=ws.Range("AB4"), SortOn:=xlSortOnValues, _
Order:=xlAscending, DataOption:=xlSortNormal
.SortFields.Add Key:=ws.Range("AC4"), SortOn:=xlSortOnValues, _
Order:=xlAscending, DataOption:=xlSortNormal
.SetRange ws.Range("A4:AC" & LastRow)
.Header = xlNo
.MatchCase = False
.Orientation = xlTopToBottom
.SortMethod = xlPinYin
.Apply
End With
' clear columns
.Columns("AB:AC").Clear
End With
MsgBox "Sorted"
End Sub

Value of cell that depends on another dynamic cell within a range

Range A1:A5 is filled with dynamic data, though the data is limited to 5 values, the sequence could differ. It is also possible that only 4 or less is presented. The values are also unique.
Column B's value will depend on Column A.
Example:
A B
1 item2 USD18
2 item1 USD15
3 item3 USD4
4 item5 USD23
5 item4 USD11
How do I accomplish this on VBA?
Quite tricky. Please try this code after adjusting the items marked "change to suit".
Sub SetSequence()
' 156
Const DataClm As Long = 2 ' change to suit (2 = column B)
Const ItemClm As Long = 1 ' change to suit (1 = column A)
Dim Wb As Workbook
Dim Ws As Worksheet
Dim DataRng As Range ' sorted given data (column B in your example)
Dim Results As Variant ' results: sorted 1 to 5
Dim TmpClm As Long ' a column temporarily used by this macro
Dim Tmp As String ' working string
Dim R As Long ' oop counter: rows
Dim i As Long ' index of Results
Results = Array("Item1", "Item2", "Item3", _
"Item4", "Item5") ' modify list items as required (sorted!)
Set Wb = ThisWorkbook ' modify as needed
Set Ws = Wb.Worksheets("Sheet1") ' change to suit
With Ws
With .UsedRange
TmpClm = .Column + .Columns.Count
End With
' create a copy of your data (without header) in an unused column
.Range(.Cells(2, DataClm), .Cells(.Rows.Count, DataClm).End(xlUp)) _
.Copy .Cells(1, TmpClm)
Set DataRng = .Range(.Cells(1, TmpClm), .Cells(.Rows.Count, TmpClm).End(xlUp))
With .Sort.SortFields
.Clear
.Add2 Key:=Ws.Cells(1, TmpClm), _
SortOn:=xlSortOnValues, _
Order:=xlAscending, _
DataOption:=xlSortNormal
End With
With .Sort
.SetRange DataRng
.Header = xlNo
.MatchCase = False
.Orientation = xlTopToBottom
.SortMethod = xlPinYin
.Apply
End With
' blanks are removed, if any
Set DataRng = .Range(.Cells(1, TmpClm), .Cells(.Rows.Count, TmpClm).End(xlUp))
' start in row 2 of DataClm and look at next 5 cells
For R = 2 To 6
' skip over blanks
Tmp = .Cells(R, DataClm).Value
If Len(Trim(Tmp)) Then
i = WorksheetFunction.Match(Tmp, DataRng, 0)
.Cells(R, ItemClm).Value = Results(i - 1)
End If
Next R
.Columns(TmpClm).ClearContents
End With
End Sub
The code creates a sorted copy of the items you have in column B and draws the output in column A from the similarly sorted list of results. Blanks are ignored. But if there is one blank in the input list (column B) there will be only 4 items in the sorted input list and therefore none of the items can be assigned "Item 5" in column A.
I've replaced my answer with the one below from your edit which completely changed things.
See if this is what you're looking for:
Dim ValueArr As Variant
ValueArr = Array("USD15", "USD18", "USD4", "USD11", "USD23")
For i = 1 To 5
If Range("A" & i) <> "" Then
Range("B" & i) = ValueArr(Right(Range("A" & i), 1) - 1)
End If
Next i
This code is based off of using the number on the end of item to know which value to put in place. If the row is blank it will skip over it.

Sorting a Range based on sheet variable with VBA

Scenario: I have a sheet and am trying to sort a part of it. Inside the sheet I have a dropdown list, which allows me to select the values (aaa, bbb, ccc). Each of those values represent a column and when selected, the code should sort the range by that column.
Problem: The process works for one of the values in the dropdown list, but not for the others (it runs, but nothing happens).
Code:
Sub ratecolumnssort()
Dim LastRow As Integer
Dim sortColumn As String, sortAgent As String
shtMonitoring.Activate
LastRow = shtMonitoring.Cells(shtMonitoring.rows.count, "B").End(xlUp).row
sortAgent = shtMonitoring.Cells(8, 9) ' this is where the dropdown with values aaa, bbb and ccc is
If sortAgent = "aaa" Then
sortColumn = "F"
ElseIf sortAgent = "bbb" Then
sortColumn = "H"
ElseIf sortAgent = "ccc" Then
sortColumn = "J"
End If
With ActiveSheet.sort
.SortFields.Add key:=Range(sortColumn & "11"), Order:=xlAscending
.SetRange Range("B11", "N" & LastRow )
.Header = xlYes
.Apply
End With
End Sub
Question: What am I doing wrong here?
You need to clear the sort settings first.
With ActiveSheet.Sort
.SortFields.Clear 'add this line
'as before

How to delete rows in an Excel ListObject based on criteria using VBA?

I have a table in Excel called tblFruits with 10 columns and I want to delete any rows where the Fruit column contains Apple. How can I do this?
The following sub works:
Private Sub deleteTableRowsBasedOnCriteria(tbl As ListObject, columnName As String, criteria As String)
Dim x As Long, lastrow As Long, lr As ListRow
lastrow = tbl.ListRows.Count
For x = lastrow To 1 Step -1
Set lr = tbl.ListRows(x)
If Intersect(lr.Range, tbl.ListColumns(columnName).Range).Value = criteria Then
'lr.Range.Select
lr.Delete
End If
Next x
End Sub
The sub can be executed like this:
Dim tbl As ListObject
Set tbl = ThisWorkbook.Worksheets("Sheet1").ListObjects("tblFruits")
Call deleteTableRowsBasedOnCriteria(tbl, "Fruit", "Apple")
Well, it seems the .listrows property is limited to either ONE list row or ALL list rows.
Easiest way I found to get around this was by:
Setting up a column with a formula that would point out to me all rows I would like to eliminate (you may not need the formula, in this case)
Sorting the listobject on that specific column (preferably making it so that my value to be deleted would be at the end of the sorting)
Retrieving the address of the range of listrows I will delete
Finally, deleting the range retrieved, moving cells up.
In this specific piece of code:
Sub Delete_LO_Rows
Const ctRemove as string = "Remove" 'value to be removed
Dim myLO as listobject, r as long
Dim N as integer 'number of the listcolumn with the formula
Set myLo = Sheet1.ListObjects("Table1") 'listobject goes here
With myLO
With .Sort
With .SortFields
.Clear
.Add Key:=.HeaderRowRange(myLO.ListColumns(N)), SortOn:= _
xlSortOnValues, Order:=xlAscending, DataOption:=xlSortNormal
End With
.Header = xlYes
.MatchCase = False
.Orientation = xlTopToBottom
.SortMethod = xlPinYin
.Apply
End With
On Error GoTo NoRemoveFound
r = Application.WorksheetFunction.Match(ctRemove, .ListColumns(.ListColumns.Count).DataBodyRange, 0)
Range(.parent.name & "!" & .DataBodyRange(r, 1).Address & ":" & .DataBodyRange(.ListRows.Count, .ListColumns.Count).Address).Delete xlShiftUp
'Added the .parent.name to make sure the address is on the correct sure, but it will fail if there are any spaces or characters on the sheet name that will make it need a pair of '.
'The error is just to skip these two lines in case the match returns an error. There's likely a better/cleaner way to do that.
NoRemoveFound:
End With
End sub
Hope it helps...

How to eliminate a row with a "file directory" for a parameter of hour

I have rows in excel with a file structure for example.
Row 1 c:\User\Folder100\13-25\File100.log
Row 2 c:\User\Folder200\11-16\File200.log
Row 3 c:\User\Folder300\21-20\File300.log
Row 4 c:\User\Folder400\13-25\File400.log
Row 5 c:\User\Folder400\9-10\File400.log
Row 6 c:\User\Folder500\8-16\File500.log
Row 7 c:\User\Folder600\8-16\File600.log
Row 8 c:\User\Folder700\11-16\File700.log
Row 9 c:\User\Folder700\9-40\File700.log
With the first rows there aren't any problem because the file logs are different but with the rows (4 and 5) a There are the same log in two different folders "c:\User\Folder400\13-25\" and c:\User\Folder400\9-10\ I would like to keep just 13-25(eliminate Row 5) because has more recent time.
Also with the lines 8 and 9 I just want to keep row 8 (11-16)
Row 1 c:\User\Folder100\13-25\File100.log
Row 2 c:\User\Folder200\11-16\File200.log
Row 3 c:\User\Folder300\21-20\File300.log
Row 4 c:\User\Folder400\13-25\File400.log
Row 6 c:\User\Folder500\8-16\File500.log
Row 7 c:\User\Folder600\8-16\File600.log
Row 8 c:\User\Folder700\11-16\File700.log
(eliminated row 5 and 9)
Do you know any Idea how to made it in VBA¿?
The code below
uses a RegEx to extract the folder name and file number into two new columns (see pic below)
sorts the columns by column B and then by column C descending
delete the entire row where duplicate exists in column B using Excels Remove Duplicates functionality (the latest time comes first in column CV so it is preserved)
Removes the two working columns
Update: The code below assumes that both the 1st folder after "User" and the file name much match for it to be a duplicate - the initial guidelines are still ambigious. This code does solve the example shown in the question
Sub Sliced()
Dim lngRow As Long
Dim lngCalc As Long
Dim objReg As Object
Dim objDic As Object
Dim rng1 As Range
Dim X()
Dim Y()
Set rng1 = Range([a1], Cells(Rows.Count, "A").End(xlUp))
'See Patrick Matthews excellent article on using Regular Expressions with VBA
Set objReg = CreateObject("vbscript.regexp")
objReg.Pattern = "(.+\\){2}(.+\\)(\d+)\-\d+\\(.+)"
'Speed up the code by turning off screenupdating and setting calculation to manual
'Disable any code events that may occur when writing to cells
With Application
lngCalc = .Calculation
.ScreenUpdating = False
.Calculation = xlCalculationManual
.EnableEvents = False
End With
'Test each area in the user selected range
X = rng1.Value2
Y = X
For lngRow = 1 To UBound(X)
'replace the leading zeroes
X(lngRow, 1) = objReg.Replace(X(lngRow, 1), "$2$4")
Y(lngRow, 1) = objReg.Replace(Y(lngRow, 1), "$3")
Next
Columns("B:C").Insert
rng1.Offset(0, 1) = X
rng1.Offset(0, 2) = Y
With ActiveSheet.Sort
.SortFields.Clear
.SortFields.Add Key:=rng1.Offset(0, 1), _
SortOn:=xlSortOnValues, Order:=xlAscending, DataOption:=xlSortNormal
.SortFields.Add Key:=rng1.Offset(0, 2), _
SortOn:=xlSortOnValues, Order:=xlDescending, DataOption:=xlSortNormal
.SetRange rng1.Cells(1).Offset(0, 1).Resize(rng1.Rows.Count, 2)
.Header = xlGuess
.MatchCase = False
.Orientation = xlTopToBottom
.SortMethod = xlPinYin
.Apply
End With
ActiveSheet.UsedRange.RemoveDuplicates Columns:=2, Header:=xlNo
Columns("B:C").Delete
'cleanup the Application settings
With Application
.ScreenUpdating = True
.Calculation = lngCalc
.EnableEvents = True
End With
Set objReg = Nothing
End Sub
This does not exactly serve the purpose, but serves to illustrate a way by which you could go about problems like this.
It takes into account the filename and the time string preceding it only. The folder can be added if necessary.
Main Module:
Option Explicit
Private dict As dictionary
'Prints the rows you need (time criterion applied)
Private Sub FindDuplicates()
Dim lastRow As Long, row As Long
Dim x As Variant, v As Variant
Dim fileName As String, timeString As String
Set dict = CreateObject("Scripting.Dictionary")
'Determine last row
lastRow = Worksheets(1).Cells(Rows.Count, 1).End(xlUp).row
'Iterate and store in dictionary
For row = 1 To lastRow
x = Split(Cells(row, 1), Application.PathSeparator)
fileName = x(UBound(x))
timeString = x(UBound(x) - 1)
AddDictEntry fileName, row, timeString
Next row
'Print results
For Each v In dict.Keys
Debug.Print "FileName: " & v & ", Recent Version: " & dict.Item(v)
Next
End Sub
To add/remove dictionary entries:
Private Sub AddDictEntry(fileName As String, rowNo As Long, timeString As String)
Dim timeParts As Variant, timeLong As Long
'converts time string to long, for comparison
timeParts = Split(timeString, "-")
timeLong = CInt(timeParts(0)) * 100 + CInt(timeParts(1))
'Adds entry to dictionary if time is more recent
If (dict.Exists(fileName)) Then
If CInt(dict.Item(fileName)) < timeLong Then
dict(fileName) = timeLong
End If
Else
dict.Add fileName, timeLong
End If
End Sub
Input:
c:\User\Folder100\13-25\File100.log
c:\User\Folder200\11-16\File200.log
c:\User\Folder300\21-20\File300.log
c:\User\Folder400\13-25\File400.log
c:\User\Folder400\9-10\File400.log
c:\User\Folder300\22-20\File300.log
Output:
FileName: File100.log, Recent Version: 1325
FileName: File200.log, Recent Version: 1116
FileName: File300.log, Recent Version: 2220
FileName: File400.log, Recent Version: 1325

Resources