I was wondering if I can create a macro that sorts my worksheet with a range that is constantly changing? Instead of the last row constantly changing, the top row count will be changing.
Is it possible to have my .setrange be a variable which gets updated with the new range based on arguments?
For example the current code I have sets my range starting at A5 but if rows 6 - 10 have an interior color of green I want it to leave those at the top and only sort starting from A11.
activesheet.Sort.SortFields.Clear
activesheet.Sort.SortFields.Add Key:=Range("M6"), _
SortOn:=xlSortOnValues, Order:=xlAscending, DataOption:=xlSortNormal
With activesheet.Sort
.SetRange Range("A5:O150") ' I want this range to change constantly without me manually changing, for example it would be from ("A10:0150") now
.Header = xlYes
.MatchCase = False
.Orientation = xlTopToBottom
.SortMethod = xlPinYin
.Apply
End With
This UDF will return a range object representing your sort range as defined by the first row of data in the fifth column containing a "white" interior (not including the first row, unless that is the ONLY row with a white interior):
Option Explicit
Private Function SortRange(baseRange As Range) As Range
Dim firstCell As Range, lastCell As Range, C As Range
With baseRange
Set lastCell = .Worksheet.Cells(.Row + .Rows.Count - 1, .Column + .Columns.Count - 1)
End With
With Application.FindFormat
.Clear
.Interior.Color = vbWhite
End With
'find the first cell in fifth column that is white
'As implemented, this should be column E, but if you move the range, it will adjust to the 5th column
'after the first row in baseRange
With baseRange
Set firstCell = .Columns(5).Find(what:="*", after:=.Cells(1, 5), searchorder:=xlByRows, searchdirection:=xlNext, searchformat:=True)
If Not firstCell Is Nothing Then
With .Worksheet
Set SortRange = .Range(.Cells(firstCell.Row, 1), lastCell)
End With
Else
MsgBox "no cells to sort"
Exit Function
End If
End With
End Function
You could then use it like:
.SetRange SortRange(activesheet.range("A3:O150"))
but I would suggest replacing activesheet with the actual worksheet.
Related
As it's shown in the image, rows in the range with highest number of non-empty cells move to the top position.
Here's code that will sort the way you explain.
Sub SortByCountA()
' 136
Dim Ws As Worksheet
Dim Rng As Range
Dim C As Long ' helper column
Set Ws = Worksheets(1) ' change to suit
With Ws.UsedRange
C = .Columns.Count + 1
Set Rng = .Resize(, C)
End With
Application.ScreenUpdating = False
With Rng
.Cells(.Row, C).FormulaR1C1 = "=COUNTA(RC[" & (1 - C) & "]:RC[-1])"
.Columns(C).FillDown
End With
With Ws.Sort
With .SortFields
.Clear
.Add2 Key:=Ws.Cells(Rng.Row, C), _
SortOn:=xlSortOnValues, _
Order:=xlDescending, _
DataOption:=xlSortNormal
End With
.SetRange Rng
.Header = xlNo
.MatchCase = False
.Orientation = xlTopToBottom
.SortMethod = xlPinYin
.Apply
End With
Ws.Columns(C).EntireColumn.Delete
Application.ScreenUpdating = True
End Sub
The difficulty in this task is to define the range that needs to be sorted. The above code sorts the entire UsedRange. It doesn't expewct to encounter any tables and it expects to start in column A. The Sort.Header property is set to xlNo. An easy modification would be to set it to xlYes. If you have a more difficult range it isn't difficult to adapt the above code to work with such a range once it is defined.
In Output table E2, formula copied across and down :
=IFERROR(1/(1/INDEX(A$2:A$5,MATCH(IFERROR(AGGREGATE(15,6,$A$2:$A$5/($B$2:$B$5<>""),ROW(A1)),AGGREGATE(15,6,$A$2:$A$5/($B$2:$B$5=""),COUNT($A$2:$A$5)-COUNT(E$1:E1))),$A$2:$A$5,0))),"")
=SORT(SORT(SORT(A1:C4;1;1);2;1);3;1)
In office 365 you can use sort over sort over sort. It sorts by column A, than column B, then C.
I am trying to implement a button that, when pressed sorts an array first alphabetically, then based on font color. The column that I am using to sort has 3 possible values (enrolled, waitlisted, and cancelled). The font color for 'cancelled' is grey. I want to get enrolled at the top of the list, then waitlisted, then cancelled at the bottom. Shouldn't be that difficult, but I can't get the code to work. Here's the code I wrote. Many thanks!
Private Sub btnSort_Click()
Dim SortArray As Range
Dim SortColumn As Range
Set SortArray = Range("A3").CurrentRegion
Set SortColumn = Range(Range("A3").End(xlToRight), Range("A3").End(xlToRight).End(xlDown))
SortArray.Sort Key1:=SortColumn, Header:=xlYes
With SortArray.Sort
.SortFields.Clear
.SortFields.Add Key:=SortColumn
.xlSortOnFontColor
.SortOnValue.Color = RGB(192, 192, 192)
.SortOrder = xlAscending
.Header = xlYes
.Apply
End With
Since there are only 3 values, we use a helper column and then assign values to it. We then sort and then finally delete the helper column.
Let's say, your data looks like this
Try this code. I have commneted the code so you should not have a problem in understanding it.
Sub Sample()
Dim ws As Worksheet
Dim lRow As Long, lCol As Long
Dim rng As Range
Dim ColName As String
'~~> Change this to the relevant sheet
Set ws = Sheet1
With ws
'~~> Insert a helper column in Col A
.Columns(1).Insert Shift:=xlToRight
.Cells(1, 1).Value = "TmpHeader"
'~~> Get Last Row and last Column
'~~> I am assuming that headers are in row 1
lRow = .Range("B" & .Rows.Count).End(xlUp).Row
lCol = .Cells(1, Columns.Count).End(xlToLeft).Column
ColName = Split(Cells(, lCol).Address, "$")(1)
'~~> Insert the formula in Col A
.Range("A2:A" & lRow).Formula = "=IF(RC[1]=""enrolled"",1,IF(RC[1]=""waitlisted"",2,3))"
'~~> Set your range
Set rng = .Range("A1:" & ColName & lRow)
'~~> Sort it
rng.Sort Key1:=.Range("A2"), Order1:=xlAscending, Header:=xlYes, _
OrderCustom:=1, MatchCase:=False, Orientation:=xlTopToBottom, _
DataOption1:=xlSortNormal
'~~> Delete the helper column
.Columns(1).Delete
End With
End Sub
When you run the above code, it inserts a helper column and then inserts a formula =IF(B2="enrolled",1,IF(B2="waitlisted",2,3)) What this basically does is assigns a value of 1,2 and 3 based on the value whether it is enrolled, waitlisted or cancelled.
Once the formula is inserted, we sort on Col A in ascending order and then finally delete the helper column.
Figured it out:
ActiveSheet.Range("A3").CurrentRegion.Sort Key1:=Range("I3"), Header:=xlYes
ActiveSheet.Sort.SortFields.Clear
ActiveSheet.Sort.SortFields.Add(Range("I3"), _
xlSortOnFontColor, xlDescending, , _
xlSortNormal).SortOnValue.Color = RGB(192, 192, 192)
With ActiveSheet.Sort
.SetRange Range("A3").CurrentRegion
.Header = xlYes
.MatchCase = False
.Orientation = xlTopToBottom
.SortMethod = xlPinYin
.Apply
End With
There are two date ranges which are set. Once this range is set, I click my refresh data macro which will then extract the data from within these two date ranges. (i.e. Date 1 - 1/1/2017. Date 2 - 31/1/2017) However I have noticed as soon as I go beyond the date range 19/1/2017 it will throw an error indicating the range of the object has failed. I cannot seem to understand the significance of this date.
I have checked that my ranges are not unqualified as I have set the Worksheet to "Outage Schedule ->" and I have not changed the sheet name.
Does anyone have any idea why this is the case?
Sub SortFinalTable(numRows As Long)
Dim sht As Worksheet
Dim rng As Range
Set sht = Worksheets("Outage Schedule ->")
Set rng = sht.Range("A5").Resize(numRows + 1, 52)
sht.AutoFilterMode = False
rng.AutoFilter
On Error Resume Next
sht.AutoFilter.Sort.SortFields.Clear
On Error GoTo 0
sht.AutoFilter.Sort.SortFields.Add Key:=Range("A5:A" & numRows - 4), SortOn:=xlSortOnValues, Order:=xlDescending, _
DataOption:=xlSortNormal
sht.AutoFilter.Sort.SortFields.Add _
Key:=Range("B5:B" & numRows - 4), SortOn:=xlSortOnValues, Order:=xlAscending, _
DataOption:=xlSortNormal
With sht.AutoFilter.Sort
.Header = xlYes
.MatchCase = False
.Orientation = xlTopToBottom
.SortMethod = xlPinYin
.Apply
End With
End Sub
The issue was that the numRows variable was recording a 0 or negative value which meant that when I tried to set the range below:
sht.range("A5")>Resize(numRows +1, 52)
....
....
....
sht.AutoFilter.Sort.SortFields.Add Key=Sht.Range("A5:A" & numRows - 4),...
The range would always fail as the numbers were negative. I addressed this by ensuring the numRows always stays +ve so the lowest number the range could ever drop to is 1.
This seems to have resolved the issue for now.
So I've got an incredibly large contact list, originally an export from Outlook so it follows the same formatting. I've an incredibly large number of duplicates down to records holding the same name, but separate addresses / mobile numbers.
I'm looking for a macro that can help me merge these duplicates so I don't lose things like different addresses under the same name.
http://i.stack.imgur.com/EaI6e.png
In this case I'd love a macro to see that A3 is a duplicate of A2, so take J3 to O3 and paste these in Q2 to V2. Then repeat this process for any duplicate pair found.
This is a bit rudimentary, but it works. Note, since you didn't say what should happen where there are more than 2 duplicates, this macro won't really work if that's the case.
Sub moveDuplicates()
Dim i As Integer, lastRow As Integer
Dim primaryRange As Range, copyToRange As Range, cel As Range, cel2 As Range, rng As Range
Dim ws As Worksheet
Set ws = ActiveSheet
'First, sort by "Key" to get duplicates all in a row
With ws.Sort
.SortFields.Clear
.SortFields.Add Key:=Range( _
"A2:A" & ws.UsedRange.Rows.Count), SortOn:=xlSortOnValues, Order:=xlAscending, DataOption:= _
xlSortNormal
.SetRange Range("A1:P" & ws.UsedRange.Rows.Count)
.Header = xlYes
.MatchCase = False
.Orientation = xlTopToBottom
.SortMethod = xlPinYin
.Apply
End With
lastRow = ws.UsedRange.Rows.Count
Set rng = ws.Range(ws.Cells(2, 1), ws.Cells(lastRow, 1))
Dim lastDupRow As Integer, startDupRow As Integer, copyRow As Integer
For Each cel In rng
'First, see if the cell has a duplicate anywhere, if not, then goto next cel
cel.Select
If WorksheetFunction.CountIf(rng, cel.Value) > 1 Then
Debug.Print "Duplicate exists"
'So, since we know a duplicate exists, we need to copy the duplicate rows' info.
startDupRow = cel.Row + 1
lastDupRow = ws.Columns(1).Find(cel.Value, searchDirection:=xlPrevious).Row
If lastDupRow - startDupRow = 0 Then
copyRow = lastDupRow
Else
End If
For Each cel2 In ws.Range(Cells(startDupRow, 1), ws.Cells(lastDupRow, 1))
' pasteRange(cel.Row).Select
' copyRange(cel2.Row).Select
pasteRange(cel.Row).Value = copyRange(cel2.Row).Value
copyRange(cel2.Row).EntireRow.Delete
Next cel2
End If
Next cel
End Sub
Private Function copyRange(ByVal iRow As Integer) As Range
Set copyRange = Range(Cells(iRow, 10), Cells(iRow, 15))
End Function
Private Function pasteRange(ByVal xRow As Integer) As Range
Set pasteRange = Range(Cells(xRow, 17), Cells(xRow, 22))
End Function
I am a novice at writing vba code and would appreciate some guidance and help writing a basic macro. I am sorting data by three headings, and then separating the data by insering a blank row for matching values in column "B", starting with row B2 and ending at the last row. The pairs are of a variable quantity i.e. may be none, one, two or three matching values, but I just want to group them and insert a single row beneath the last matching value. Can someone help me please?
'Sub BetterFilter()
Dim CDpos As Worksheet
Set CDpos = Worksheets("CD positions with red bars")
AutoFilter = False
FilterMode = False
'the headers are from "A1:CT1"
Range("A1:CT2").Select
CDpos.AutoFilter.Sort. _
SortFields.Clear
CDpos.AutoFilter.Sort. _
'B2 is the first range i want to filter etc'
SortFields.Add Key:=Range("B2:B2").End(xlDown), SortOn:=xlSortOnValues,Order:= _
xlAscending, DataOption:=xlSortNormal
CDpos.AutoFilter.Sort. _
SortFields.Add Key:=Range("M2:M2").End(xlDown), SortOn:=xlSortOnValues, Order:= _
xlAscending, DataOption:=xlSortNormal
CDpos.AutoFilter.Sort. _
SortFields.Add Key:=Range("CE2:CE2").End(xlDown), SortOn:=xlSortOnValues, Order:= _
xlAscending, DataOption:=xlSortNormal
With CDpos.AutoFilter.Sort
.Header = xlYes
.MatchCase = False
.Orientation = xlTopToBottom
.SortMethod = xlPinYin
.Apply
'this is where i have problems and i want to bunch all my matched values in row B and insert a row below the last match value for each grouping'
For i = 1 To 2
If Range("B2" & i).Value = Range("B2" & i + 1).Value Then
Range(B2, & i + 1).EntireRow.Insert
End If
Next
End With
End Sub'
I'd go with something like:
Dim cell As Range
For Each cell In Range("B2", Cells(Rows.Count, "B").End(xlUp).Address)
'loop from B1 to the last used cell in column B
If cell.Offset(-1, 0).Value2 <> cell.Value2 And cell.Offset(-1, 0) <> vbNullString Then cell.EntireRow.Insert
'if the cell above is different and not null then it adds a row above
Next cell
and change "B2" to whatever your first use cell is.