Sorting a varying size table in excel - vba

What I'm trying to do is select a table in a spreadsheet, and then sort according to 2 different columns
I generated this code with the record macro option. The table changes in size which is why I have used the xlDown, unfortunately the code later references the exact cells "B4:B52". Any idea how I might solve this issue?
Range("B4:J4").Select
Range(Selection, Selection.End(xlDown)).Select
ActiveWorkbook.Worksheets("Sheet1").Sort.SortFields.Clear
ActiveWorkbook.Worksheets("Sheet1").Sort.SortFields.Add Key:=Range( _
"B4:B52"), SortOn:=xlSortOnValues, Order:=xlAscending, DataOption:=xlSortNormal
ActiveWorkbook.Worksheets("Sheet1").Sort.SortFields.Add Key:=Range( _
"G4:G52"), SortOn:=xlSortOnValues, Order:=xlDescending, DataOption:=xlSortNormal
With ActiveWorkbook.Worksheets("Sheet1").Sort
.SetRange Range("B4:J52")
.Header = xlGuess
.MatchCase = False
.Orientation = xlTopToBottom
.SortMethod = xlPinYin
.Apply
End With

Since you are sorting a Table (ListObject in VBA), you'll want to refer to it. This will dynamically adjust to encompass entire table columns. In this example the column headers/names to be sorted are "Data1" and "Data3":
Sub SortTable()
Dim lo As Excel.ListObject
'change this assignment to suit your table location and name
Set lo = ActiveWorkbook.Worksheets("Sheet1").ListObjects("Table1")
With lo
.Sort.SortFields.Clear
.Sort.SortFields.Add _
Key:=Range("Table1[data1]"), SortOn:=xlSortOnValues, Order:=xlAscending, _
DataOption:=xlSortNormal
.Sort.SortFields.Add _
Key:=Range("Table1[data3]"), SortOn:=xlSortOnValues, Order:=xlAscending, _
DataOption:=xlSortNormal
With .Sort
.Header = xlYes
.MatchCase = False
.Orientation = xlTopToBottom
.SortMethod = xlPinYin
.Apply
End With
End With
End Sub

I would sort the column in the table in the following way:
Sub SortingTable()
Dim ws As Worksheet
set ws = ActiveSheet
Dim target_table As ListObject
Set target_table = ws.ListObjects(wsName)
Dim sort_column_index As Long
sort_column_index = target_table.ListColumns("ColumnToBeSortedName").Index
Dim sort_column As Range
Set sort_column = target_table.ListColumns(sort_column_index).Range
' Applying the sorting to the table
With target_table.Sort
.SortFields.Clear
.SortFields.Add Key:=sort_column _
, SortOn:=xlSortOnValues, Order:=xlAscending _
, DataOption:=xlSortNormal
.Apply
End With
End Sub
In fact, the only difference is that you declare a Range and assign it to the specific table column that should be sorted. Doing it this way, you're able to apply the macro to tables on different sheets assuming that the tables contain the column with name "ColumnToBeSortedName".

Related

Custom sort dynamic range using VBA

I receive an Excel file every morning which I like to sort into a more logical manner. All of the column headings are always the same, but the number of rows may change.
I'm trying to put together a macro that highlights the entire region (starting in B2). It needs to sort column C (ascending), G (descending), H (ascending) and I (descending).
I started off by using the macro recorder and am now trying to clean up the code it spat out.
So far I've managed to put together code that selects the region from B2 to the right and then down. Then when defining the sorting criteria for each column, I've tried to make sure that the range selected goes from the top of the list in row 3 (row 2 has headers, row 3 is first item in the list) and then dynamically selects down for each relevant column. However, after the With statement I'm struggling to get the range to be dynamic (it's just the macro-recorded static range still).
I'm also getting an 'Run-time error '1004': Application-defined or object-defined error' after .Apply.
Sub Macro1()
Range("B2").Select
Range(Selection, Selection.End(xlToRight)).Select
Range(Selection, Selection.End(xlDown)).Select
ActiveWorkbook.Worksheets("Sheet1").Sort.SortFields.Clear
ActiveWorkbook.Worksheets("Sheet1").Sort.SortFields.Add Key:=Range("G3",
Range("G3").End(xlToRight)) _
, SortOn:=xlSortOnValues, Order:=xlDescending,
DataOption:=xlSortNormal
ActiveWorkbook.Worksheets("Sheet1").Sort.SortFields.Add Key:=Range("C3",
Range("C3").End(xlToRight)) _
, SortOn:=xlSortOnValues, Order:=xlAscending, DataOption:=xlSortNormal
ActiveWorkbook.Worksheets("Sheet1").Sort.SortFields.Add Key:=Range("H3",
Range("H3").End(xlToRight)) _
, SortOn:=xlSortOnValues, Order:=xlAscending, DataOption:=xlSortNormal
ActiveWorkbook.Worksheets("Sheet1").Sort.SortFields.Add Key:=Range("I3",
Range("I3").End(xlToRight)) _
, SortOn:=xlSortOnValues, Order:=xlDescending,
DataOption:=xlSortNormal
With ActiveWorkbook.Worksheets("Sheet1").Sort
.SetRange Range("B2:Q31") ' NOT SURE HOW TO MAKE DYNAMIC HERE
.Header = xlYes
.MatchCase = False
.Orientation = xlTopToBottom
.SortMethod = xlPinYin
.Apply ' GETTING ERROR HERE
End With
End Sub
Just had to change the Range to Selection as you already have dynamically selected the Range in work:
Sub Macro1()
Range("B2").Select
Range(Selection, Selection.End(xlToRight)).Select
Range(Selection, Selection.End(xlDown)).Select
ActiveWorkbook.Worksheets("Sheet1").Sort.SortFields.Clear
ActiveWorkbook.Worksheets("Sheet1").Sort.SortFields.Add2 Key:=Range("G3", Range("G3").End(xlDown)), _
SortOn:=xlSortOnValues, Order:=xlDescending, DataOption:=xlSortNormal
ActiveWorkbook.Worksheets("Sheet1").Sort.SortFields.Add2 Key:=Range("C3", _
Range("C3").End(xlDown)), SortOn:=xlSortOnValues, Order:=xlAscending, DataOption:=xlSortNormal
ActiveWorkbook.Worksheets("Sheet1").Sort.SortFields.Add2 Key:=Range("H3", _
Range("H3").End(xlDown)), SortOn:=xlSortOnValues, Order:=xlAscending, DataOption:=xlSortNormal
ActiveWorkbook.Worksheets("Sheet1").Sort.SortFields.Add2 Key:=Range("I3", _
Range("I3").End(xlDown)), SortOn:=xlSortOnValues, Order:=xlDescending, DataOption:=xlSortNormal
'
With ActiveWorkbook.Worksheets("Sheet1").Sort
.SetRange Range(Selection.Address)
.Header = xlYes
.MatchCase = False
.Orientation = xlTopToBottom
.SortMethod = xlPinYin
.Apply
End With
End Sub
See if it works now.

How to make recorded macro work on any sheet?

I recorded a macro to sort my excel sheet by Number (which is column A), then ID (which is column D), then the newest-oldest Date (which is column M)
I want to be able to copy this macro onto any sheet (which would have all the same columns), but it tells me "subscript out of range" because there are different names.
Here is the macro. The sheet name is "owssvr (1)", but I'd like it to be able to be for any sheet that has the same columns and such:
ActiveWorkbook.Worksheets("owssvr (1)").ListObjects("Table_owssvr__2").Sort. _
SortFields.Clear
ActiveWorkbook.Worksheets("owssvr (1)").ListObjects("Table_owssvr__2").Sort. _
SortFields.Add Key:=Range("Table_owssvr__2[Number]"), SortOn:=xlSortOnValues _
, Order:=xlAscending, DataOption:=xlSortNormal
ActiveWorkbook.Worksheets("owssvr (1)").ListObjects("Table_owssvr__2").Sort. _
SortFields.Add Key:=Range("Table_owssvr__2[ID]"), SortOn:= _
xlSortOnValues, Order:=xlAscending, DataOption:=xlSortTextAsNumbers
ActiveWorkbook.Worksheets("owssvr (1)").ListObjects("Table_owssvr__2").Sort. _
SortFields.Add Key:=Range("Table_owssvr__2[Date]"), SortOn:= _
xlSortOnValues, Order:=xlDescending, DataOption:=xlSortNormal
With ActiveWorkbook.Worksheets("owssvr (1)").ListObjects("Table_owssvr__2"). _
Sort
.Header = xlYes
.MatchCase = False
.Orientation = xlTopToBottom
.SortMethod = xlPinYin
.Apply
End With
I was thinking of using something w/ a With
This should work on any sheet which has only a single listobject which contains the required column names:
Sub Tester()
Dim tName As String
With ActiveSheet.ListObjects(1).Sort
tName = .Parent.Name 'list name
.SortFields.Clear
.SortFields.Add Key:=Range(tName & "[Number]"), SortOn:=xlSortOnValues, _
Order:=xlAscending, DataOption:=xlSortNormal
.SortFields.Add Key:=Range(tName & "[ID]"), SortOn:=xlSortOnValues, _
Order:=xlAscending, DataOption:=xlSortTextAsNumbers
.SortFields.Add Key:=Range(tName & "[Date]"), SortOn:=xlSortOnValues, _
Order:=xlDescending, DataOption:=xlSortNormal
.Header = xlYes
.MatchCase = False
.Orientation = xlTopToBottom
.SortMethod = xlPinYin
.Apply
End With
End Sub
There two things at issue here:
the worksheet name
the Table name
Lets assume that the name of the table is the same on all sheets on which the macro is to be used. First Activate the worksheet and then run:
Sub Macro1()
'
' Macro1 Macro
'
Dim ws As Worksheet
Set ws = ActiveSheet
ws.ListObjects("Table_owssvr__2").Sort. _
SortFields.Clear
ws.ListObjects("Table_owssvr__2").Sort. _
SortFields.Add Key:=Range("Table_owssvr__2[Number]"), SortOn:=xlSortOnValues _
, Order:=xlAscending, DataOption:=xlSortNormal
ws.ListObjects("Table_owssvr__2").Sort. _
SortFields.Add Key:=Range("Table_owssvr__2[ID]"), SortOn:= _
xlSortOnValues, Order:=xlAscending, DataOption:=xlSortTextAsNumbers
ws.ListObjects("Table_owssvr__2").Sort. _
SortFields.Add Key:=Range("Table_owssvr__2[Date]"), SortOn:= _
xlSortOnValues, Order:=xlDescending, DataOption:=xlSortNormal
With ws.ListObjects("Table_owssvr__2"). _
Sort
.Header = xlYes
.MatchCase = False
.Orientation = xlTopToBottom
.SortMethod = xlPinYin
.Apply
End With
End Sub
Reference your worksheet name by Index Number
Option Explicit
Public Sub Example()
ActiveWorkbook.Worksheets(1)
End Sub
Referring to Sheets by Index Number [Excel 2003 VBA Language Reference]
An index number is a sequential number assigned to a sheet, based on the position of its sheet tab (counting from the left) among sheets of the same type. The following procedure uses the Worksheets property to activate worksheet one in the active workbook.

excel Vba error 400 when I added sorting to the end of my macro

I Have a relatively long sub that adds data to another sheet in a bunch of columns based on where its coming from. this part of the code works perfectly, however I wanted to sort all the rows in the table up to the last row that was added. the sorting code that I added on to the end works if I hard code what cells to include in the range, but my range will grow each time the sub is run so I tried to make the range include the variable I named for the next empty row (1MaxRows).
When I do this I get an error that says "400", in the past when I have gotten this error it is because i referenced the sheet or workbook wrong, but this time I didn't change any sheet references. The section of my code that gives me this error is as follows:
Columns("A:Q").Select
ActiveWorkbook.Worksheets("Raw Data").Sort.SortFields.Clear
ActiveWorkbook.Worksheets("Raw Data").Sort.SortFields.Add Key:=range("A2:A & lMaxRows" _
), SortOn:=xlSortOnValues, Order:=xlAscending, DataOption:=xlSortNormal
ActiveWorkbook.Worksheets("Raw Data").Sort.SortFields.Add Key:=range("B2:B & lMaxRows" _
), SortOn:=xlSortOnValues, Order:=xlDescending, DataOption:=xlSortNormal
With ActiveWorkbook.Worksheets("Raw Data").Sort
.SetRange range("A1:Q & lMaxRows")
.Header = xlYes
.MatchCase = False
.Orientation = xlTopToBottom
.SortMethod = xlPinYin
.Apply
End With
I have tried changing the range codes in a handful of ways but I always either get an overflow error or a 400 error.
Thank you for the help.
You should qualify your Range calls with the correct worksheet, and you also have quotes in the wrong place in your addresses. For example:
Dim ws As Worksheet
Set ws = ActiveWorkbook.Worksheets("Raw Data")
With ws.Sort.SortFields
.Clear
.Add Key:=ws.Range("A2:A" & lMaxRows), SortOn:=xlSortOnValues, _
Order:=xlAscending, DataOption:=xlSortNormal
.Add Key:=ws.Range("B2:B" & lMaxRows), SortOn:=xlSortOnValues, _
Order:=xlDescending, DataOption:=xlSortNormal
End With
With ws.Sort
.SetRange ws.Range("A1:Q" & lMaxRows)
.Header = xlYes
.MatchCase = False
.Orientation = xlTopToBottom
.SortMethod = xlPinYin
.Apply
End With
I've assumed you have properly declared and assigned a value to lMaxRows (and note that it is LMAXROWS and not 1MAXROWS with a number at the start.

Looping through multiple worksheets and tables to do a multi-criteria sort

Trying to create a loop that goes through multiple worksheets, selects the table and sorts it based on multiple criteria. Each worksheet only has one table, and this is what I've come up with so far.
Sub sorter()
'
' sorter Macro
'
' Keyboard Shortcut: Ctrl+Shift+F
'
Dim varTblSortName As Variant
varTblSortName = ActiveSheet.Name
Dim Client As Worksheet
Dim rng As Range
For Each Client In ActiveWorkbook.Worksheets
'This tests to make sure the code is only run on Client_ sheets
If InStr(1, Client.Name, "Client_", vbTextCompare) Then
Set rng = ActiveSheet.ListObjects(1)
Worksheets(Client.Name).Activate
'ActiveSheet.ListObjects(1).Range.Select
'Range(, Selection.End(xlDown)).Select
'Range(Selection, Selection.End(xlToRight)).Select
'ActiveWorkbook.Worksheets("Client_Jamie").ListObjects("Client_Jamie").Sort. _
ActiveSheet.ListObjects(1).Sort.SortFields.Clear
ActiveWorkbook.ActiveSheet.ListObjects(1).Sort. _
SortFields.Add Key:=Range("varTblSortName[Asset Class]"), SortOn:= _
xlSortOnValues, Order:=xlAscending, DataOption:=xlSortNormal
ActiveWorkbook.ActiveSheet.ListObjects(1).Sort. _
SortFields.Add Key:=Range("varTblSortName[Sector]"), SortOn:=xlSortOnValues, _
Order:=xlAscending, DataOption:=xlSortNormal
ActiveWorkbook.ActiveSheet.ListObjects("varTblSortName").Sort. _
SortFields.Add Key:=Range("varTblSortName[Ticker]"), SortOn:=xlSortOnValues, _
Order:=xlAscending, DataOption:=xlSortNormal
With ActiveWorkbook.ActiveSheet.ListObjects("varTblSortName").Sort
.Header = xlYes
.MatchCase = False
.Orientation = xlTopToBottom
.SortMethod = xlPinYin
.Apply
End With
End If
Next Client
End Sub
I've extended the use of With/End With and broken your string variables out of the quoted strings.
Sub sorter()
' Keyboard Shortcut: Ctrl+Shift+F
Dim varTblSortName As Variant
Dim Client As Worksheet
For Each Client In ActiveWorkbook.Worksheets
With Client
If CBool(InStr(1, .Name, "Client_", vbTextCompare)) Then
varTblSortName = .Name
With .ListObjects(varTblSortName)
.Sort.SortFields.Clear
.Sort.SortFields.Add Key:=Range(varTblSortName & "[Asset Class]"), SortOn:= _
xlSortOnValues, Order:=xlAscending, DataOption:=xlSortNormal
.Sort.SortFields.Add Key:=Range(varTblSortName & "[Sector]"), SortOn:=xlSortOnValues, _
Order:=xlAscending, DataOption:=xlSortNormal
.Sort.SortFields.Add Key:=Range(varTblSortName & "[Ticker]"), SortOn:=xlSortOnValues, _
Order:=xlAscending, DataOption:=xlSortNormal
With .Sort
.Header = xlYes
.MatchCase = False
.Orientation = xlTopToBottom
.SortMethod = xlPinYin
.Apply
End With
End With
End If
End With
Next Client
End Sub

Sorting multiple sheets - code clean up

Looking for some guidance on sorting columns across multiple sheets.
I have 2 data sets (tab1: ABC and tab2: XYZ).
I'm trying to sort both sheets (range column A to column J) by column A in descending order.
This is what I have so far... it is recorded. I would very much like to clean up my code and also look for better ways to approach sorting by columns. Any help/tips would be appreciated.
Sub sortingcolumns()
Application.Goto Reference:="ABC!A1"
ActiveWorkbook.Worksheets("ABC").sort.SortFields.Clear
ActiveWorkbook.Worksheets("ABC").sort.SortFields.Add Key:=Range("A1"), _
SortOn:=xlSortOnValues, Order:=xlDescending, DataOption:= _
xlSortTextAsNumbers
With ActiveWorkbook.Worksheets("ABC").sort
.SetRange Range("A2:K187")
.Header = xlNo
.MatchCase = False
.Orientation = xlTopToBottom
.SortMethod = xlPinYin
.Apply
End With
Application.Goto Reference:="XYZ!RC"
ActiveWorkbook.Worksheets("XYZ").sort.SortFields.Clear
ActiveWorkbook.Worksheets("XYZ").sort.SortFields.Add Key:=Range("A1"), _
SortOn:=xlSortOnValues, Order:=xlDescending, DataOption:= _
xlSortTextAsNumbers
With ActiveWorkbook.Worksheets("XYZ").sort
.SetRange Range("A2:J179")
.Header = xlNo
.MatchCase = False
.Orientation = xlTopToBottom
.SortMethod = xlPinYin
.Apply
End With
End Sub
Since you are using "with" you can combine those into one bigger with statement:
With ActiveWorkbook.Worksheets("ABC").sort
.SortFields.Clear
.SortFields.Add Key:=Range("A1"), _
SortOn:=xlSortOnValues, Order:=xlDescending, DataOption:= _
xlSortTextAsNumbers
.SetRange Range("A2:K187")
.Header = xlNo
.MatchCase = False
.Orientation = xlTopToBottom
.SortMethod = xlPinYin
.Apply
End With
Also, you can remove the .header, .matchcase, .orientation, .sortmethod if you don't need to sort by those.
call sortingcolumns ActiveWorkbook.worksheets("ABC"), 2, 187
call sortingcolumns ActiveWorkbook.worksheets("XYZ"), 2, 179
Sub sortingcolumns(sht as Worksheet, First as Integer, Last as Integer)
With sht.sort
.sortfields.clear
.sortfields.add Key:=Range("A1"), SortOn:=xlSortOnValues, Order:=xlDescending, _
DataOption:=xlSortTextAsNumbers
.setrange = sht.range("A" & First & ":K" & Last)
.Header = xlNo
.MatchCase = False
.Orientation = xlTopToBottom
.SortMethod = xlPinYin
.Apply
End With
End Sub
You could modify it to provide the first and last columns, as well, then you have a nice utility function that can sort any range for you just by passing the appropriate parameters.
I like to fully qualify all objects e.g. ActiveWorkbook becomes Excel.ActiveWorkbook. This is a personal choice.
Also I like to avoid Active... objects so if this code only needs to act on the workbook where the code is stored then switch to ThisWorkbook
With Excel.ThisWorkbook.Worksheets("ABC").sort
.SortFields.Clear
.SortFields.Add _
Key:=Range("A1"), _
SortOn:=Excel.xlSortOnValues, _
Order:=Excel.xlDescending, _
DataOption:= Excel.xlSortTextAsNumbers
.SetRange Range("A2:K187")
.Header = Excel.xlNo
.MatchCase = False
.Orientation = Excel.xlTopToBottom
.SortMethod = Excel.xlPinYin
.Apply
End With
If it needs to act on a separate workbook then use an object variable. For example if the target columns are in a book called foo.xlsx (that we assume is open)
Dim myFooBk As Excel.workbook
Set myFooBk = Excel.workbooks("foo.xlsx")
With myFooBk.Worksheets("ABC").sort
.SortFields.Clear
.SortFields.Add _
Key:=Range("A1"), _
SortOn:=Excel.xlSortOnValues, _
Order:=Excel.xlDescending, _
DataOption:= Excel.xlSortTextAsNumbers
.SetRange Range("A2:K187")
.Header = Excel.xlNo
.MatchCase = False
.Orientation = Excel.xlTopToBottom
.SortMethod = Excel.xlPinYin
.Apply
End With
Also the with could be altered slightly by moving the .Sort inside the clause:
Dim myFooBk As Excel.workbook
Set myFooBk = Excel.workbooks("foo.xlsx")
With myFooBk.Worksheets("ABC")
.sort.SortFields.Clear
.sort.SortFields.Add _
Key:= .Range("A1"), _ '<<more specific now as `.` infront of Range
SortOn:=Excel.xlSortOnValues, _
Order:=Excel.xlDescending, _
DataOption:= Excel.xlSortTextAsNumbers
.sort.SetRange .Range("A2:K187") '<<more specific now as `.` infront of Range
.sort.Header = Excel.xlNo
.sort.MatchCase = False
.sort.Orientation = Excel.xlTopToBottom
.sort.SortMethod = Excel.xlPinYin
.sort.Apply
End With
Yes, you can shorten it substantially:
Sub sortingcolumns()
Worksheets("ABC").Range("A2:K187").sort key1:=Range("A1"), Order1:=xlDescending
Worksheets("XYZ").Range("A2:J179").sort key1:=Range("A1"), Order1:=xlDescending
End sub
I made the following changes:
I used the (old) range.sort construct instead of the (newer) worksheet.sort. With the old construct, you simply identify the range to sort and then apply the .sort method with the appropriate arguments. The newer one is also more flexible but also more complicated to use.
The only arguments you need are key1 and Order1. If you wanted to sort ascending, you could also eliminate the order1 argument; then you'd have just one argument.
If you don't specify an argument, Excel will use the default. All of the arguments in your snippet are the default arguments for the sort method. Therefore you don't need to specify them.