I'm trying to architect a macro to do the following steps:
Compare two lists of data (in this case Column A against Column C)
Output in B any cell that exists in both A and C. Line up the match next to its match in Column A.
Sort both column A and B by their values so that the corresponding cells in A and B are still next to each other after the sort.
Desired result. Notice how the matches in column A and B are still together. This enables users of this macro to quickly eliminate data that only belongs to one of the respective columns and it allows us to retain any information that may be tied to column A, e.g., Column A contains email addresses, and there is a corresponding column next to it that contains phone #'s. We don't want to split that information up. This macro would enable that:
Pastebin of excel data I used: http://pastebin.com/mYuQRMjj
This is the macro I've written, which uses a second macro:
Sub Macro()
Range(Selection, Selection.End(xlDown)).Select
Application.Run "macro4.xlsm!Find_Matches"
Range("B1:B284").Select
ActiveWorkbook.Worksheets("Sheet1").Sort.SortFields.Clear
ActiveWorkbook.Worksheets("Sheet1").Sort.SortFields.Add Key:=Range("B1:B284") _
, SortOn:=xlSortOnValues, Order:=xlAscending, DataOption:=xlSortNormal
With ActiveWorkbook.Worksheets("Sheet1").Sort
.SetRange Range("A1:B284")
.Header = xlGuess
.MatchCase = False
.Orientation = xlTopToBottom
.SortMethod = xlPinYin
.Apply
End With
End Sub
The second macro that does the comparison is literally ripped straight from Microsoft, with a little extra.
Sub Find_Matches()
Application.ScreenUpdating = False
Dim CompareRange As Variant, x As Variant, y As Variant
' Set CompareRange equal to the range to which you will
' compare the selection.
Set CompareRange = Range("C1:C500")
' NOTE: If the compare range is located on another workbook
' or worksheet, use the following syntax.
' Set CompareRange = Workbooks("Book2"). _
' Worksheets("Sheet2").Range("C1:C5")
'
' Loop through each cell in the selection and compare it to
' each cell in CompareRange.
For Each x In Selection
For Each y In CompareRange
If x = y Then x.Offset(0, 1) = x
Next y
Next x
Application.ScreenUpdating = True
End Sub
Using these two macros, I get exactly what I want, but I don't like using limited ranges. I want the macro to be smart enough to determine exactly what the range is, because the people who will be using this macro sometimes will be using a list of 200, sometimes a list of 2,000,000. I want this macro to be a "one size fits all" for range.
I looked into this and the command:
Range(Range("B1"),Range("A1").End(xlDown)).Select
gets exactly the selection I want after Find_Matches runs (I also realize that Find_Matches is using a finite compare range . . . solving my issue for this first Macro will solve that too).
The problem is that I am unsure how to plug that into my Macro. I've tried several implementations and I'm flat out stuck. I can't find an answer for something this specific, but I know I'm very close. Thank you for any help!
edit: This whole method is realllly slow on larger lists (20+ minutes on a list of 100k). If you can suggest some ways to speed it up that would be super helpful!
Sub MatchNSort()
Dim lastrow As Long
'Tell Excel to skip the calculation of all cells and the screen
Application.Calculation = xlCalculationManual
Application.ScreenUpdating = False
'Find the last row in the data
With ActiveSheet
If Application.WorksheetFunction.CountA(.Cells) <> 0 Then
lastrow = .Cells.Find(What:="*", _
After:=.Range("A1"), _
Lookat:=xlPart, _
LookIn:=xlFormulas, _
SearchOrder:=xlByRows, _
SearchDirection:=xlPrevious, _
MatchCase:=False).Row
Else
lastrow = 1
End If
End With
'Force a formula in column B to match a from c
ActiveSheet.Range("B1:B" & lastrow).Formula = _
"=IFERROR(IF(MATCH(C[-1],C[1]:C[1],0)>0,C[-1],""""),"""")"
'Force a recalculate
Application.Calculate
'Sort columns B and A
With ActiveSheet
.Range("A1:B" & lastrow).Select
.Sort.SortFields.Clear
'First key sorts column B
.Sort.SortFields.Add Key:=Range("B1:B" & lastrow) _
, SortOn:=xlSortOnValues, Order:=xlAscending _
, DataOption:=xlSortNormal
'Second key (optional) sort column A, after defering to column B
.Sort.SortFields.Add Key:=Range("A1:A" & lastrow) _
, SortOn:=xlSortOnValues, Order:=xlAscending _
, DataOption:=xlSortNormal
.Sort.SetRange Range("A1:B" & lastrow)
.Sort.Header = xlGuess
.Sort.MatchCase = False
.Sort.Orientation = xlTopToBottom
.Sort.SortMethod = xlPinYin
.Sort.Apply
End With
'Return autocalulation and screen updates
Application.Calculation = xlCalculationAutomatic
Application.ScreenUpdating = True
Application.Calculate
End Sub
See Error in finding last used cell in VBA for the best way to find the last row of data.
Find the last row and then change your range selection to:
Range("C1:C"&Trim(CStr(lastrow)))
To speed up your macro execution start your macro with:
Application.Calculation = xlCalculationManual
Application.ScreenUpdating = False
and to restore autocalc and screen updates, end your macro with:
Application.Calculation = xlCalculationAutomatic
Application.ScreenUpdating = True
Application.Calculate
Related
For reasons beyond my control, I must save and upload my data as a csv and i must manually select and clear the cells below my data set, the range changes daily.
Question: How can I edit my vba script to create a range based on the cell below my data set to :J10 and clear/delete it?
EDIT: Are there any changes you would implement?
Here's what I currently have, after exporting and saving as a csv, I reopen the new data and sort and attempt to clear. I have already tried Selection.SpecialCells(CellTypeBlanks).EntireRow.ClearContents & Selection.SpecialCells(CellTypeBlanks).ClearContents
I have 5 separate csv workbooks this macro currently opens and operates on but here is the final portion of my script that is giving me errors
Sub openCSV()
Dim lRow As Long
lRow = Cells(Rows.Count, 1).End(xlUp).Row
For i = lRow To 2 Step -1
If ActiveSheet.Range("H" & i).Value = 0 Then
ActiveSheet.Rows(i).ClearContents
End If
Next i
Range("A2:J10").Select
ActiveWorkbook.Worksheets("BA").Sort.SortFields.Clear
ActiveWorkbook.Worksheets("BA").Sort.SortFields.Add Key:=Range("H2") _
, SortOn:=xlSortOnValues, Order:=xlDescending, DataOption:=xlSortNormal
With ActiveWorkbook.Worksheets("BA").Sort
.SetRange Range("A2:J10")
.Header = xlGuess
.MatchCase = False
.Orientation = xlTopToBottom
.SortMethod = xlPinYin
.Apply
End With
On Error Resume Next
Range("A2:H10").Select
Selection.SpecialCells(CellTypeBlanks).ClearContents
Range("A2:J10").Select
Selection.SpecialCells(CellTypeBlanks).EntireRow.ClearContents
ActiveWorkbook.Save
ActiveWorkbook.Close
SaveChanges = True
Thank you for your time and assistance
I need Excel to detect the last column I have and sort on that column. I have a macro that generates a new column every time it is used so I cannot use a constant.
Sub sortyness()
Dim sortdata(A1 & ":", Cells(LastRow, LastColumn)) As Range
ActiveWorkbook.Worksheets("Compiled").Sort.SortFields.Clear
ActiveWorkbook.Worksheets("Compiled").Sort.SortFields.Add _
Key:=Range(Sorton), Sorton:=xlSortOnValues, Order:=xlAscending, _
DataOption:=xlSortNormal
With ActiveWorkbook.Worksheets("Compiled").Sort
.SetRange Range(sortdata)
.Header = xlYes
.MatchCase = False
.Orientation = xlTopToBottom
.SortMethod = xlPinYin
.Apply
End With
End Sub
Here's a screenshot of the sheet:
I am having trouble with getting it to sort by the last column. Can I define the column by looking for the first cell in row 1 that has no data and then use that as a basis to sort? How should I modify my VBA?
Thank you.
I don't know how to edit this thing to get it to not appear as a duplicate, but it's obviously not a duplicate. Mine is more concerned with running a macro on the last column than it is finding the last column.
An vba sort operation actually requires much less code than you get from a recording.
Dim sortdata As Range, LastRow as long, LastColumn as long
With ActiveWorkbook.Worksheets("Compiled")
LastRow = .cells(.rows.count, "A").end(xlup).row
LastColumn = .cells(1, .columns.count).end(xltoleft).column
with .range(.cells(1, 1), .Cells(LastRow, LastColumn))
.Cells.Sort Key1:=.Columns(.columns.count), Order1:=xlAscending, _
Orientation:=xlTopToBottom, Header:=xlyes
end with
end with
Concerning the second line here:
Dim sortdata(A1 & ":", Cells(LastRow, LastColumn)) As Range
This is not how you assign range. If you want to assign a range, starting on A1 and ending on lastRow, lastColumn, consider this:
Public Sub TestMe()
Dim lastRow As Long: lastRow = 5
Dim lastCol As Long: lastCol = 10
Dim sortData As Range
Set sortData = Range("A1:" & Cells(lastRow, lastCol).Address)
Debug.Print sortData.Address
End Sub
In the above case the range is assigned to the ActiveSheet, which is not always what you may need. If you want to avoid assigning to the ActiveSheet, you should specify the worksheet as well:
With Worksheets("Compiled")
Set sortData = .Range("A1:" & .Cells(lastRow, lastCol).Address)
End With
The two dots in the code above .Range and .Cells will make sure that you refer to the Worksheets("Compiled") and thus will save some problems in the future.
Sort the "last" column with a single line of VBA:
Columns(ActiveSheet.UsedRange.Columns(ActiveSheet.UsedRange.Columns.Count)._
Column).Sort key1:=Columns(ActiveSheet._
UsedRange.Columns(ActiveSheet.UsedRange.Columns.Count).Column)
...which is exactly the same as:
Sub SortLastColumn()
With ActiveSheet.UsedRange
Columns(.Columns(.Columns.Count).Column).Sort key1:=Columns(.Columns(.Columns.Count).Column)
End With
End Sub
This challenge came about following an answer to this question:
Excel VBA Sorting
I used the code provided as one of the answers there and attempted to edit it to my situation
I need to sort on column A, header in A1, A to Z - whilst maintaining row integrity, i.e. the row values follow the sorted cells
Here is my attempted code edit, but I've clearly not edited it properly because the column A values are not sorted alphabetically as I'd hoped - can someone please assist?
With ws_catalogue
'''''''''''''''''''''''''''''''''''''''''''
'Get range from B1 to last cell on sheet. '
'''''''''''''''''''''''''''''''''''''''''''
Set myRange = .Range(.Cells(1, 1), .Cells(.Cells.Find("*", , , , 1, 2).Row, .Cells.Find("*", , , , 2, 2).Column))
myRange.Select
With .Sort
.SortFields.Clear
'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
'As using late binding Access won't understand Excel values so: '
'xlSortOnValues = 0 xlYes = 1 '
'xlAscending = 1 xlTopToBottom = 1 '
'xlSortNormal = 0 xlPinYin = 1 '
'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
.SortFields.Add _
Key:=myRange.Offset(, 6).Resize(, 1), _
SortOn:=0, _
Order:=1, _
DataOption:=0
.SetRange myRange
.Header = 1
.MatchCase = False
.Orientation = 1
.SortMethod = 1
.Apply
End With
End With
If you want column A to be sorted, you need to include it on myRange. Currently you have
ws_catalogue.Sort.SetRange = myRange
Where myRange is from B1 to the bottom of the data (it does not include column A).
Additionally, if you want to order column A, you need to add something like this
.SortFields.Add _
Key:=myRange.Offset(0,0) _
SortOn:=0, _
Order:=1, _
DataOption:=0
#Xabier has you taken care of but a few other considerations, sorry this code is from another similar answer but it defines the worksheet, and the column names for easier reading code. I will skip the Dims and go to the meat of it, this was for a 3 column sort of unknown or changing row quantity. The example simply shows setting up for the range definition using the rows and columns and Cells to define the range. I stopped the code at the sort, the rest of the code generates a single email for each person with the dept names listed in the body of the email, not relevant to this question. Maybe another way to think about it. You can see the use of the sort key.
'set worksheet to work on as active (selected sheet)
Set emailWS = ThisWorkbook.ActiveSheet
startRow = 2 ' starting row
nameCol = 1 'col of names, can also do nameCol = emailWS.Range("A1").Column
deptCol = 3 'col of depts, can also do deptCol = emailWS.Range("A3").Column
'** Advantage of the optional way is if you have many columns and you don't want to count them
'find the last row with a name in it from the name column
lastRow = emailWS.Cells(emailWS.Rows.Count, nameCol).End(xlUp).Row
'sort the data first before going through the email process using Range sort and a key
'assumes these are the only columns 1 (nameCol) thru 3 (deptCol) to sort
'assumes you are sorting based on col 1 (nameCol)
emailWS.Range(Cells(startRow, nameCol), Cells(lastRow, deptCol)).Sort _
key1:=emailWS.Range(Cells(startRow, nameCol), Cells(lastRow, nameCol))
Cheers,
WWC
If you want to sort by values on Column A, in ascending order, then the following will do that taking the full sheet into consideration:
Sub sortColumnA()
Dim ws As Worksheet: Set ws = Sheets("Sheet1")
'declare and set your worksheet, amend as required
If ws.AutoFilterMode = False Then ws.Cells.AutoFilter
ws.AutoFilter.Sort.SortFields.Clear
ws.AutoFilter.Sort.SortFields.Add Key:=Range("A1"), SortOn:=xlSortOnValues, Order:=xlAscending, DataOption:=xlSortNormal
With ws.AutoFilter.Sort
.Header = xlYes
.MatchCase = False
.Orientation = xlTopToBottom
.SortMethod = xlPinYin
.Apply
End With
ws.Cells.AutoFilter
End Sub
UPDATE:
If you only want to sort a specific range then something like below should work too:
Sub sortColumnA()
Dim ws As Worksheet: Set ws = Sheets("Sheet1")
'declare and set your worksheet, amend as required
If ws.AutoFilterMode = False Then ws.Range("A1:Z1").AutoFilter
ws.AutoFilter.Sort.SortFields.Clear
ws.AutoFilter.Sort.SortFields.Add Key:=Range("A1"), SortOn:=xlSortOnValues, Order:=xlAscending, DataOption:=xlSortNormal
With ws.AutoFilter.Sort
.Header = xlYes
.MatchCase = False
.Orientation = xlTopToBottom
.SortMethod = xlPinYin
.Apply
End With
ws.Range("A1:Z1").AutoFilter
End Sub
I want to sort complete sheet data by column header alphabetically.
Below code works fine but i have to manually enter data range in variables(keyrange and datarange) every time, Since number of columns/rows varies in every file. I tried different ways in below code. Can you advise Is there a way that the last column automatically selected ??like in below W is last column with data in file and code should pick up last column.
Similarly last row of columns should pick up into range (like 485 is last row of file in below code), IS it possible ?
Sub sortfile2()
Dim keyrange As String
Dim DataRange As String
keyrange = "A1:W1"
DataRange = "A1:W485"
Range("A1").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(keyrange), _
SortOn:=xlSortOnValues, Order:=xlAscending, DataOption:=xlSortNormal
With ActiveWorkbook.Worksheets("Sheet1").Sort
.SetRange Range(DataRange)
.Header = xlYes
.MatchCase = False
.Orientation = xlLeftToRight
.SortMethod = xlPinYin
.Apply
End With
End Sub
If the source rane is dynamic, you can go with
bottom= Range("A1").End(xlDown).Row
Set DataRange = Range("A1").CurrentRegion.Resize(bottom - 1).Offset(1)
Note that CurrentRegion itself is not enough. you should combine it with Resize and Offset. İf you try with only CurrentRegion and go with F8, you can see why.
Yes, determining last column and last row is possible.
If you want to determine last column in first (1) row, use the code:
Cells(1, Columns.Count).End(xlToLeft).Column
If you want to get last row in first column, use following:
Cells(Rows.Count, 1).End(xlUp).Row
This is for the first column / row, so you can change it as you want.
This is range of data.
Sub test()
Dim rngDB As Range
Dim Ws As Worksheet
Dim r As Long, c As Long
Set Ws = ActiveSheet
With Ws
r = .Cells.Find("*", SearchOrder:=xlByRows, SearchDirection:=xlPrevious).Row
c = .Cells.Find("*", SearchOrder:=xlByColumns, SearchDirection:=xlPrevious).Column
Set rngDB = .Range("a1", .Cells(r, c))
rngDB.Select
End With
End Sub
Or
range("a1").CurrentRegion
yes, Michal answer and other source helped to find exact required output
'Find the last non-blank cell in row 1
lCol = Cells(1, Columns.Count).End(xlToLeft).Column
d = Replace(Cells(1, lCol).Address(True, False), "$1", "")
'Find the last non-blank cell in column 1
lRow = Cells(rows.Count, 1).End(xlUp).row
keyrange = "A1:" & d & 1
DataRange = "A1:" & d & lRow
'below line is to print (for debugging) the calculated range
MsgBox (keyrange)
MsgBox (DataRange)
I would like to implement an Excel macro that sorts all columns from column "C" to the last column containing data (columns A and B shall not be affected).
The columns shall be sorted from A->Z based on the cell value of their first row (which is a string).
So far, I came up with the following code which I do not like that much because it contains hardcoded numbers for the Sort range making the code not really robust.
Sub SortAllColumns()
Application.ScreenUpdating = False
'Sort columns
With ActiveWorkbook.Worksheets("mySheet").Sort
.SetRange Range("C1:ZZ1000")
.Header = xlYes
.MatchCase = False
.Orientation = xlLeftToRight
.Apply
End With
Application.ScreenUpdating = True
End Sub
Searching the internet, one may find tons of suggestions getting the last used column or row. However most of them will blow up the code more than I expected.
I am not a VBA expert and it would be great if someone could make a suggestion how this problem can be solved in an elegant and efficient way.
If this is important: We will definitely not have more that 1000 rows and 1000 columns.
Any suggestion is highly appreciated.
edited:
changed temporary sheet adding statement to have it always as the last one
revised its deletion statement accordingly
should your need be to sort columns by moving them so as to have their headers sorted from left to right, then try this code
Option Explicit
Sub main()
Dim lastCol As Long
With Sheets("mySheet")
lastCol = .cells(1, .Columns.Count).End(xlToLeft).Column
Call OrderColumns(Range(.Columns(3), Columns(lastCol)))
End With
End Sub
Sub OrderColumns(columnsRng As Range)
Dim LastRow As Long
With columnsRng
LastRow = GetColumnsLastRow(columnsRng)
With .Resize(LastRow)
.Copy
With Worksheets.Add(after:=Worksheets(Worksheets.Count)).cells(1, 1).Resize(.Columns.Count, .Rows.Count) 'this will add a "helper" sheet: it'll be removed
.PasteSpecial Paste:=xlPasteAll, Transpose:=True
.Sort key1:=.Columns(1), Order1:=xlAscending, Orientation:=xlTopToBottom, Header:=xlNo
.Copy
End With
.PasteSpecial Paste:=xlPasteAll, Transpose:=True
Application.DisplayAlerts = False: Worksheets(Worksheets.Count).Delete: Application.DisplayAlerts = True 'remove the "helper" sheet (it's the (n-1)th sheet)
End With
End With
End Sub
Function GetColumnsLastRow(rng As Range) As Long
Dim i As Long
'gets last row of the given columns range
GetColumnsLastRow = -1
With rng
For i = 1 To .Columns.Count
GetColumnsLastRow = WorksheetFunction.Max(GetColumnsLastRow, .Parent.cells(.Parent.Rows.Count, .Columns(i).Column).End(xlUp).row)
Next i
End With
End Function
it makes use of a "helper" temporary (it gets deleted by the end) sheet.
Thanks to the suggestions and revisions of #SiddharthRout I got this:
Sub SortAllColumns()
Application.ScreenUpdating = False
Dim ws As Worksheet
Dim LastRow As Long
Dim LastColumn As Long
Dim LastColumnLetter As String
Set ws = ThisWorkbook.Sheets("mySheet")
'Get range
With ws
LastRow = .Range("A" & .Rows.Count).End(xlUp).Row
LastColumn = .Cells(1, .Columns.Count).End(xlToLeft).Column
LastColumnLetter = Split(.Cells(, LastColumn).Address, "$")(1)
'Sort columns
Range("C1:" & LastColumnLetter & LastRow).Select
.Sort.SortFields.Clear
.Sort.SortFields.Add Key:=Range("C1:" & LastColumnLetter & 1), SortOn:=xlSortOnValues, Order:=xlAscending, DataOption:=xlSortNormal
With .Sort
.SetRange ws.Range("C1:" & LastColumnLetter & LastRow)
.Header = xlYes
.MatchCase = False
.Orientation = xlLeftToRight
.Apply
End With
End With
Application.ScreenUpdating = True
End Sub