I am trying to copy a list, remove duplicates, and paste it elsewhere but for some reason it is keeping two out of the three copies of 1--see attached. Not sure why its doing that, any help would be greatly appreciated.
Advanced filter assumes the top cell is a header and doesn't count it as one of the duplicates.
To fix this, you'll want to add in another row at the top as a header and then run your code. You can delete this header cell afterwards if you prefer.
If adding a header is not an option, you could use remove duplicates as a separate step. Unlike advanced filter, you can tell remove duplicates that you don't have a header. Just change your code to this:
Sub VBARemoveDuplicate()
Range("A1", Range("A1").End(xlDown)).Select
Selection.Copy Range("B1")
Range("B1", Range("B1").End(xlDown)).RemoveDuplicates Columns:=1, Header:=xlNo
End Sub
Edit:
Another alternative would be to delete the missed duplicate on the backend as I did below.
Sub VBARemoveDuplicate()
Range("A1", Range("A1").End(xlDown)).Select
Selection.AdvancedFilter Action:=xlFilterCopy, CopyToRange:=Range("B1"), Unique:=True
For Each cell In Range("A2", Range("A2").End(xlDown))
If cell.Value = Range("A1").Value Then
Range("B1").Delete xlShiftUp
End If
Next cell
End Sub
Beyond this, you would need to load everything into an array and, loop through and remove duplicates, and then place them back into the sheet. This could be slow if you have a large dataset.
Copy Unique Values to Another Column (Dictionary)
Adjust (play with) the values in the constants section.
Option Explicit
Sub VBARemoveDuplicates()
Const ProcName As String = "VBARemoveDuplicates"
On Error GoTo ClearError
Const sFirst As String = "A1"
Const dFirst As String = "B1"
Const doClearContentsBelow As Boolean = True
Const doAutoFitColumn As Boolean = True
' Create a reference to the worksheet.
Dim ws As Worksheet: Set ws = ActiveSheet
' Create a reference to the Source Column Range ('srg').
Dim sfCell As Range: Set sfCell = ws.Range(sFirst)
Dim srg As Range: Set srg = RefColumn(sfCell)
If srg Is Nothing Then Exit Sub
' Write the unique values from the Source Column Range
' to the Data Array ('Data').
Dim Data As Variant: Data = GetUniqueColumnRange(srg)
If IsEmpty(Data) Then Exit Sub
' Write the values from the Data Array
' to the Destination Column Range ('drg').
Dim dfCell As Range: Set dfCell = ws.Range(dFirst)
Dim rCount As Long: rCount = UBound(Data, 1)
Dim drg As Range: Set drg = dfCell.Resize(rCount)
drg.Value = Data
' Clear the contents in the cells of the Clear Range ('crg'),
' the range from the first cell below the Destination Column Range
' through the last cell in the column.
If doClearContentsBelow Then
Dim crg As Range
Set crg = dfCell.Resize(ws.Rows.Count - dfCell.Row - rCount + 1) _
.Offset(rCount)
crg.ClearContents
End If
' Autofit the Destination Column.
If doAutoFitColumn Then
dfCell.EntireColumn.AutoFit
End If
ProcExit:
Exit Sub
ClearError:
Debug.Print "'" & ProcName & "': Unexpected Error!" & vbLf _
& " " & "Run-time error '" & Err.Number & "':" & vbLf _
& " " & Err.Description
Resume ProcExit
End Sub
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
' Purpose: Creates a reference to the range from the first cell
' in a column ('ColumnIndex') of a range ('rg') through
' the last non-empty cell in the column.
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
Function RefColumn( _
ByVal rg As Range, _
Optional ByVal ColumnIndex As Long = 1) _
As Range
Const ProcName As String = "RefColumn"
On Error GoTo ClearError
' Validate the parameters.
If rg Is Nothing Then Exit Function
' Also, prevent referencing columns outside of the range.
If ColumnIndex < 1 Then Exit Function
If ColumnIndex > rg.Columns.Count Then Exit Function
' Create a reference to the range.
With rg.Rows(1).Columns(ColumnIndex)
Dim lCell As Range
Set lCell = .Resize(.Worksheet.Rows.Count - .Row + 1) _
.Find("*", , xlFormulas, , , xlPrevious)
If lCell Is Nothing Then Exit Function
Set RefColumn = .Resize(lCell.Row - .Row + 1)
End With
ProcExit:
Exit Function
ClearError:
Debug.Print "'" & ProcName & "': Unexpected Error!" & vbLf _
& " " & "Run-time error '" & Err.Number & "':" & vbLf _
& " " & Err.Description
Resume ProcExit
End Function
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
' Purpose: Returns the unique values from a column ('ColumnIndex')
' of a range ('rg') in a 2D one-based one-column array.
' Remarks: Error and blank values are excluded.
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
Function GetUniqueColumnRange( _
ByVal rg As Range, _
Optional ByVal ColumnIndex As Long = 1) _
As Variant
Const ProcName As String = "GetUniqueColumnRange"
On Error GoTo ClearError
' Validate the parameters.
If rg Is Nothing Then Exit Function
' Also, prevent referencing columns outside of the range.
If ColumnIndex < 1 Then Exit Function
If ColumnIndex > rg.Columns.Count Then Exit Function
' Return the values of the column of the range
' in a 2D one-based one-column array.
Dim Data As Variant
Dim rCount As Long
With rg.Columns(ColumnIndex)
rCount = .Rows.Count
If rCount = 1 Then
ReDim Data(1 To 1, 1 To 1): Data(1, 1) = .Value
Else
Data = .Value
End If
End With
' Return the unique values of the array
' in the keys of a dictionary.
Dim dict As Object: Set dict = CreateObject("Scripting.Dictionary")
dict.CompareMode = vbTextCompare
Dim Key As Variant
Dim r As Long
For r = 1 To rCount
Key = Data(r, 1)
If Not IsError(Key) Then
If Len(Key) > 0 Then
dict(Key) = Empty
End If
End If
Next r
' If all values of the column of the range are not unique,
' return the keys of the dictionary
' in another 2D one-based one-column array.
r = dict.Count
Select Case r
Case 0 ' only error and blank values
Exit Function
Case Is < rCount ' fewer unique values than values
ReDim Data(1 To r, 1 To 1)
r = 0
For Each Key In dict.Keys
r = r + 1
Data(r, 1) = Key
Next Key
'Case rCount ' all values are unique - no duplicates
End Select
' Return the array.
GetUniqueColumnRange = Data
ProcExit:
Exit Function
ClearError:
Debug.Print "'" & ProcName & "': Unexpected Error!" & vbLf _
& " " & "Run-time error '" & Err.Number & "':" & vbLf _
& " " & Err.Description
Resume ProcExit
End Function
Related
This question already has answers here:
VBA: How to delete filtered rows in Excel?
(2 answers)
Closed 7 months ago.
This post was edited and submitted for review 7 months ago and failed to reopen the post:
Original close reason(s) were not resolved
I have example data here that I need to filter a column, do a check of another column and then based on that check it'll decide to delete rows or not.
Example: filter Customer Number column for 1035 and then check if Months Pay has any values between 0-4, if yes then delete all rows of 1035. If no then check Own column, if it has any values between 1-5 then delete all rows of 1035.
And I need this to keep looping for all non-blank Customer Number.
Is this possible to create?
Delete Combined Filtered Rows
Option Explicit
Sub FilterCheckDeleteUniques()
Application.ScreenUpdating = False
' Reference the worksheet ('ws').
Dim ws As Worksheet: Set ws = ActiveSheet ' improve!
' Turn off AutoFilter.
If ws.AutoFilterMode Then ws.AutoFilterMode = False
' Reference the table range ('rg') (has headers).
Dim rg As Range: Set rg = ws.Range("A1").CurrentRegion
' Reference the data range ('drg') (no headers).
Dim drg As Range: Set drg = rg.Resize(rg.Rows.Count - 1).Offset(1)
' Write the data for the 3rd and 4th columns to an array.
' (Column, Greater Than or Equal, Less Than or Equal)
Dim InRangeArr() As Variant
InRangeArr = Array(VBA.Array(3, 0, 4), VBA.Array(4, 1, 5))
' Write the values from the 2nd column to an array ('Data').
Dim Data As Variant: Data = GetRange(drg.Columns(2))
' Write the unique values from the array to the 'keys'
' of a dictionary ('dict').
Dim dict As Object: Set dict = DictColumn(Data)
Erase Data
' Declare additional variables.
Dim frg As Range
Dim cfrg As Range
Dim iKey As Variant
' Loop through the 'keys' of the dictionary.
For Each iKey In dict.Keys
' Reference the current criteria filtered rows if conditions
' are met.
Set cfrg = RefFilteredRangeSpecial(rg, drg, CStr(iKey), InRangeArr)
' Combine the current criteria visible rows into a range.
If Not cfrg Is Nothing Then
If frg Is Nothing Then
Set frg = cfrg
Else
Set frg = Union(frg, cfrg)
End If
End If
Next iKey
' Delete all combined rows in one go.
If Not frg Is Nothing Then frg.Delete xlShiftUp
Application.ScreenUpdating = True
' Inform.
MsgBox "Operation finished.", vbInformation
End Sub
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
' Purpose: Returns the values of a range ('rg') in a 2D one-based array.
' Remarks: If ˙rg` refers to a multi-range, only its first area
' is considered.
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
Function GetRange( _
ByVal rg As Range) _
As Variant
Const ProcName As String = "GetRange"
On Error GoTo ClearError
If rg.Rows.Count + rg.Columns.Count = 2 Then ' one cell
Dim Data As Variant: ReDim Data(1 To 1, 1 To 1): Data(1, 1) = rg.Value
GetRange = Data
Else ' multiple cells
GetRange = rg.Value
End If
ProcExit:
Exit Function
ClearError:
Debug.Print "'" & ProcName & "' Run-time error '" _
& Err.Number & "':" & vbLf & " " & Err.Description
Resume ProcExit
End Function
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
' Purpose: Returns the unique values from a column ('ColumnIndex')
' of a 2D array ('Data') in the keys of a dictionary.
' Remarks: Error values and blanks are excluded.
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
Function DictColumn( _
ByVal Data As Variant, _
Optional ByVal ColumnIndex As Variant) _
As Object
Const ProcName As String = "DictColumn"
On Error GoTo ClearError
Dim dict As Object: Set dict = CreateObject("Scripting.Dictionary")
dict.CompareMode = vbTextCompare ' case-insensitive
Dim c As Long
If IsMissing(ColumnIndex) Then
c = LBound(Data, 2) ' use first column index
Else
c = CLng(ColumnIndex)
End If
Dim Key As Variant
Dim r As Long
For r = LBound(Data, 1) To UBound(Data, 1)
Key = Data(r, c)
If Not IsError(Key) Then ' exclude error values
If Len(CStr(Key)) > 0 Then ' exclude blanks
dict(Key) = Empty
End If
End If
Next r
If dict.Count = 0 Then Exit Function ' only error values and blanks
Set DictColumn = dict
ProcExit:
Exit Function
ClearError:
Debug.Print "'" & ProcName & "' Run-time error '" _
& Err.Number & "':" & vbLf & " " & Err.Description
Resume ProcExit
End Function
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
' Purpose: References a filtered range if conditions are met...
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
Function RefFilteredRangeSpecial( _
ByVal rg As Range, _
ByVal drg As Range, _
ByVal Criteria As String, _
InRangeArr() As Variant) _
As Range
' Reference the worksheet.
Dim ws As Worksheet: Set ws = rg.Worksheet
' Filter the table range.
rg.AutoFilter 2, Criteria
' Reference the visible data range ('vdrg'), the filtered rows.
Dim vdrg As Range: Set vdrg = drg.SpecialCells(xlCellTypeVisible)
' Remove the autofilter.
ws.AutoFilterMode = False
' Declare additional variables.
Dim irg As Range
Dim iCell As Range
Dim iValue As Variant
Dim n As Long
Dim IsInRange As Boolean
' Loop.
For n = LBound(InRangeArr) To UBound(InRangeArr)
Set irg = Intersect( _
vdrg, ws.Columns(rg.Columns(InRangeArr(n)(0)).Column))
For Each iCell In irg.Cells
iValue = iCell.Value
If VarType(iValue) = vbDouble Then ' is a number
If iValue >= InRangeArr(n)(1) _
And iValue <= InRangeArr(n)(2) Then ' in range
IsInRange = True
Exit For
'Else ' not in range; do nothing
End If
End If
Next iCell
If IsInRange Then ' in range found
Set RefFilteredRangeSpecial = vdrg
Exit For
'Else ' in range not found; do nothing
End If
Next n
End Function
I would like to create an array that has the actual cell locations of the first column of a selection. For example if I've selected cells E26:I31, I would like to produce an array with the following: ar = Array("E26", "E27", "E28", "E29", "E30", "E31"). I imagine there may be a quick way to do this but I haven't quite figured it out yet. Thanks! Here is an example of the array that would work for my code vs what using columns(1) would store:
In order to do this you need to use the ReDim statement. Try this:
Dim selected As Range
Dim myArray As Variant
Set selected = Selection.Columns(1)
ReDim myArray(selected.Rows.Count)
Dim i As Integer
For i = 1 To selected.Rows.Count
myArray(i) = selected.Cells(i).Address
Next i
Cell Addresses in an Array
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
' Purpose: Writes the cell addresses of the first column
' (of the first area) of a range to an array.
' Calls: ArrFirstColumnAddresses
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
Sub ArrFirstColumnAddressesTEST()
If Not TypeOf Selection Is Range Then
MsgBox "The selection is not a range.", vbCritical
Exit Sub
End If
Dim sArr() As String: sArr = ArrFirstColumnAddresses(Selection)
' Do something, e.g.:
Debug.Print Join(sArr, " ") ' row
Debug.Print Join(sArr, vbLf) ' column
Debug.Print "[LB=" & LBound(sArr) & ",UB=" & UBound(sArr) & "]"
End Sub
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
' Purpose: Returns the cell addresses of the first column
' (of the first area) of a range in an array.
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
Function ArrFirstColumnAddresses( _
ByVal SourceRange As Range) _
As String()
Const ProcName As String = "ArrFirstColumnAddresses"
Dim AnErrorHasOccurred As Boolean
On Error GoTo ClearError
Dim rg As Range: Set rg = SourceRange.Areas(1).Columns(1)
Dim ColString As String: ColString = Split(rg.Cells(1).Address, "$")(1)
Dim FirstRow As Long: FirstRow = rg.Row
Dim rCount As Long: rCount = rg.Rows.Count
Dim sArr() As String: ReDim sArr(0 To rCount - 1)
Dim r As Long
For r = FirstRow To FirstRow + rCount - 1
sArr(r - FirstRow) = ColString & CStr(r)
Next r
ProcExit:
If AnErrorHasOccurred Then
ArrFirstColumnAddresses = Split("")
Else
ArrFirstColumnAddresses = sArr
End If
Exit Function
ClearError:
Debug.Print "'" & ProcName & "' Run-time error '" _
& Err.Number & "':" & vbLf & " " & Err.Description
AnErrorHasOccurred = True
Resume ProcExit
End Function
I'm trying to loop through cells of a specific column, find new values in those cells, count how many times a specific value is found and return the values along with the number of times it appeared.
The values I am looking at are all text.
Something like this:
This is the code I found. I get a compile error
ByRef arguement type mismatch
Function findValues() As Scripting.Dictionary
Dim cellValue
Dim dict As New Scripting.Dictionary
For iRow = 2 To g_totalRow
cellValue = ActiveWorkbook.Sheets(sheetName).Cells(iRow, iCol).Text
If dict.Exists(cellValue) Then
dict.Item(cellValue) = dict.Item(cellValue) + 1
Else
dict.Item(cellValue) = 1
End If
Next iRow
Set findValues = dict
End Function
Sub displayValues(dict As Scripting.Dictionary)
Dim i
Dim value
Dim valueCount
For i = 1 To dict.count
valueCount = dict.Items(i)
value = dict.Keys(i)
ActiveWorkbook.Sheets(sheetName).Cells(i, 3).Text = value
ActiveWorkbook.Sheets(sheetName).Cells(i, 4).Text = valueCount
Next i
End Sub
Sub RunAndDisplay()
Dim dict
Set dict = findValues
displayValues dict
End Sub
Write Unique Column Values With Count
Here's a version more customized to your actual case.
Adjust the values in the constants section.
You only need to replace the worksheet names (sName and dName) with your actual worksheet (tab) name(s).
You can easily write the result to another worksheet by changing the dName constant.
There is no need for a reference to the Microsoft Scripting Runtime so remove it from the workbook (VBE>Tools>References).
Option Explicit
Sub WriteUniqueWithCount()
Const ProcName As String = "WriteUniqueWithCount"
On Error GoTo ClearError
' Source
Const sName As String = "Sheet1"
Const sfCellAddress As String = "A2"
' Destination
Const dName As String = "Sheet1"
Const dfCellAddress As String = "C2"
' Workbook
Dim wb As Workbook: Set wb = ThisWorkbook ' workbook containing this code
' Source
Dim sws As Worksheet: Set sws = wb.Worksheets(sName)
If sws.AutoFilterMode Then sws.AutoFilterMode = False
Dim sfCell As Range: Set sfCell = sws.Range(sfCellAddress)
Dim scrg As Range: Set scrg = RefColumn(sfCell)
If scrg Is Nothing Then Exit Sub ' no data in column
Dim Data As Variant: Data = GetRange(scrg)
Dim dict As Object: Set dict = DictColumnCount(Data)
If dict Is Nothing Then Exit Sub ' only error values and blanks
Data = GetDict(dict) ' 2 columns: keys (values) and items (count)
Set dict = Nothing
Dim rCount As Long: rCount = UBound(Data, 1)
Dim dws As Worksheet: Set dws = wb.Worksheets(dName)
If dws.AutoFilterMode Then dws.AutoFilterMode = False
Dim dfCell As Range: Set dfCell = dws.Range(dfCellAddress)
With dfCell.Resize(, 2) ' first row
.Resize(rCount).Value = Data
.Resize(.Worksheet.Rows.Count - .Row - rCount + 1).Offset(rCount).Clear
End With
MsgBox "Unique values and their count are written.", vbInformation, ProcName
ProcExit:
Exit Sub
ClearError:
Debug.Print "'" & ProcName & "' Run-time error '" _
& Err.Number & "':" & vbLf & " " & Err.Description
Resume ProcExit
End Sub
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
' Purpose: Creates a reference to the one-column range from the first cell
' of a range ('FirstCell') to the bottom-most non-empty cell
' of the first cell's worksheet column.
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
Function RefColumn( _
ByVal FirstCell As Range) _
As Range
Const ProcName As String = "RefColumn"
On Error GoTo ClearError
With FirstCell.Cells(1)
Dim lCell As Range
Set lCell = .Resize(.Worksheet.Rows.Count - .Row + 1) _
.Find("*", , xlFormulas, , , xlPrevious)
If lCell Is Nothing Then Exit Function
Set RefColumn = .Resize(lCell.Row - .Row + 1)
End With
ProcExit:
Exit Function
ClearError:
Debug.Print "'" & ProcName & "' Run-time error '" _
& Err.Number & "':" & vbLf & " " & Err.Description
Resume ProcExit
End Function
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
' Purpose: Returns the values of a range ('rg') in a 2D one-based array.
' Remarks: If ˙rg` refers to a multi-range, only its first area
' is considered.
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
Function GetRange( _
ByVal rg As Range) _
As Variant
Const ProcName As String = "GetRange"
On Error GoTo ClearError
If rg.Rows.Count + rg.Columns.Count = 2 Then ' one cell
Dim Data As Variant: ReDim Data(1 To 1, 1 To 1): Data(1, 1) = rg.Value
GetRange = Data
Else ' multiple cells
GetRange = rg.Value
End If
ProcExit:
Exit Function
ClearError:
Debug.Print "'" & ProcName & "' Run-time error '" _
& Err.Number & "':" & vbLf & " " & Err.Description
Resume ProcExit
End Function
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
' Purpose: Returns the unique values and their count from a column
' ('ColumnIndex') of a 2D array ('Data') in the keys and items
' of a dictionary.
' Remarks: Error values and blanks are excluded.
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
Function DictColumnCount( _
ByVal Data As Variant, _
Optional ByVal ColumnIndex As Variant) _
As Object
Const ProcName As String = "DictColumnCount"
On Error GoTo ClearError
Dim dict As Object: Set dict = CreateObject("Scripting.Dictionary")
dict.CompareMode = vbTextCompare
Dim c As Long
If IsMissing(ColumnIndex) Then
c = LBound(Data, 2)
Else
c = CLng(ColumnIndex)
End If
Dim Key As Variant
Dim r As Long
For r = LBound(Data, 1) To UBound(Data, 1)
Key = Data(r, c)
If Not IsError(Key) Then
If Len(CStr(Key)) > 0 Then
dict(Key) = dict(Key) + 1
End If
End If
Next r
If dict.Count = 0 Then Exit Function
Set DictColumnCount = dict
ProcExit:
Exit Function
ClearError:
Debug.Print "'" & ProcName & "' Run-time error '" _
& Err.Number & "':" & vbLf & " " & Err.Description
Resume ProcExit
End Function
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
' Purpose: Returns the values from a dictionary in a 2D one-based array.
' Remarks: F, F, F - returns the keys and items in two columns.
' F, F, T - returns the items and keys in two columns.
' F, T, F - returns the keys in a column.
' F, T, T - returns the items in a column.
' T, F, F - returns the keys and items in two rows.
' T, F, T - returns the items and keys in two rows.
' T, T, F - returns the keys in a row.
' T, T, T - returns the items in a row.
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
Function GetDict( _
ByVal sDict As Object, _
Optional ByVal Horizontal As Boolean = False, _
Optional ByVal FirstOnly As Boolean = False, _
Optional ByVal Flip As Boolean = False) _
As Variant
Const ProcName As String = "GetDict"
On Error GoTo ClearError
Dim sCount As Long: sCount = sDict.Count
If sCount = 0 Then Exit Function
Dim Data As Variant
Dim Key As Variant
Dim i As Long
If Not Horizontal Then
If Not FirstOnly Then
ReDim Data(1 To sCount, 1 To 2)
If Not Flip Then
For Each Key In sDict.Keys
i = i + 1
Data(i, 1) = Key
Data(i, 2) = sDict(Key)
Next Key
Else
For Each Key In sDict.Keys
i = i + 1
Data(i, 1) = sDict(Key)
Data(i, 2) = Key
Next Key
End If
Else
ReDim Data(1 To sCount, 1 To 1)
If Not Flip Then
For Each Key In sDict.Keys
i = i + 1
Data(i, 1) = Key
Next Key
Else
For Each Key In sDict.Keys
i = i + 1
Data(i, 1) = sDict(Key)
Next Key
End If
End If
Else
If Not FirstOnly Then
ReDim Data(1 To 2, 1 To sCount)
If Not Flip Then
For Each Key In sDict.Keys
i = i + 1
Data(1, i) = Key
Data(2, i) = sDict(Key)
Next Key
Else
For Each Key In sDict.Keys
i = i + 1
Data(1, i) = sDict(Key)
Data(2, i) = Key
Next Key
End If
Else
ReDim Data(1 To 1, 1 To sCount)
If Not Flip Then
For Each Key In sDict.Keys
i = i + 1
Data(1, i) = Key
Next Key
Else
For Each Key In sDict.Keys
i = i + 1
Data(1, i) = sDict(Key)
Next Key
End If
End If
End If
GetDict = Data
ProcExit:
Exit Function
ClearError:
Debug.Print "'" & ProcName & "' Run-time error '" _
& Err.Number & "':" & vbLf & " " & Err.Description
Resume ProcExit
End Function
Closed. This question needs details or clarity. It is not currently accepting answers.
Want to improve this question? Add details and clarify the problem by editing this post.
Closed 2 years ago.
Improve this question
I have an excel sheet that contained roster of employees.Data such as their shifts example is 0700_1500 and their OFf days.
The roster is exported to excel from oracle.At the end of each column i want to know the unique data(what shifts are their in specific column excluding OFf days.
Below is extract of my code.I want to add one line that ignore cell that contains OFf days and arrange in ascending order to display.
Dim lastrow As Long
Application.ScreenUpdating = False
Dim rng, lastcell As Range
Set rng = Application.InputBox("Select a range", "Obtain Range Object", Type:=8)
lastrow = Cells(Rows.Count, "B").End(xlUp).Row
ActiveSheet.Range(rng.Address & lastrow).AdvancedFilter _
Action:=xlFilterCopy, _
CopyToRange:=ActiveSheet.Range(rng.Cells(rng.Rows.Count + 1, rng.Columns.Count).Address), _
Unique:=True
Application.ScreenUpdating = True
Unique Sort With Exceptions (Dictionary)
My setup including the result (in white at the bottom) for the select range A2:J2.
Adjust the values in the constants section as needed.
The Code
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
' Remarks: All current filters in the worksheet will be turned off.
' Error and empty values are excluded.
' Errors occurring if the columns are greater than the number
' of columns in the Source Range are only handled by the basic
' error handler (clearError).
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
Sub createUniqueList()
' Error
' Initialize error handling.
Const ProcName As String = "createUniqueList"
Dim Success As Boolean
On Error GoTo clearError ' Turn on error trapping.
' Constants
Const uniColumn As Long = 1 ' This is also the 'last row column'.
Const excColumn As Long = 1
Const srtColumn As Long = 1
Const EmptyRows As Long = 0 ' Between the source and the result.
Dim Exceptions As Variant
Exceptions = Array("OFF", "LEAVE", "CTC") ' add more...
' Let user select First Data Row Range.
On Error Resume Next
Dim rng As Range
Set rng = Application.InputBox(Prompt:="Select First Data Row", _
Title:="Obtain Range Object", _
Type:=8)
If Err.Number = 424 Then
GoTo UserCanceled
End If
If Err.Number <> 0 Then
GoTo clearError ' Unexpected.
End If
On Error GoTo clearError
' If you're interested in where an error is occurring after this line,
' uncomment the following line.
'On Error GoTo 0
Application.ScreenUpdating = False
' Define Source Range.
' Remove all filters.
Dim ws As Worksheet
Set ws = rng.Worksheet
If ws.FilterMode Then
ws.ShowAllData
End If
If ws.AutoFilterMode Then
ws.AutoFilterMode = False
End If
' Define First Data Row Range (remove possible areas and rows).
Set rng = rng.Areas(1).Rows(1)
' Define First Cell in Unique Column.
Dim cel As Range
Set cel = rng.Cells(uniColumn)
' Define Unique Processing Range.
Dim pRng As Range
Set pRng = cel.Resize(ws.Rows.Count - cel.Row + 1)
' Define Last Cell in Unique Column.
Set cel = pRng.Find(What:="*", _
LookIn:=xlFormulas, _
SearchDirection:=xlPrevious)
If cel Is Nothing Then
GoTo ProcExit ' No data in Unique Processing Range.
End If
' Define Source Range.
Set rng = rng.Resize(cel.Row - rng.Row + 1)
' Write values from Source Range to Data Array.
Dim Data As Variant
If rng.Rows.Count < 1 Or rng.Columns.Count > 1 Then
Data = rng.Value
Else
ReDim Data(1 To 1, 1 To 1)
Data(1, 1) = rng.Value
End If
' Modify values in Data Array to get unique values.
Dim ColumnsCount As Long
ColumnsCount = UBound(Data, 2)
Dim dict As Object
Set dict = CreateObject("Scripting.Dictionary")
Dim uniCurrent As Variant
Dim excCurrent As Variant
Dim i As Long
Dim j As Long
Dim k As Long
If UBound(Exceptions) >= LBound(Exceptions) Then
' There are exceptions.
For i = 1 To UBound(Data, 1)
uniCurrent = Data(i, uniColumn)
If Not IsError(uniCurrent) And Not IsEmpty(uniCurrent) Then
excCurrent = Data(i, excColumn)
If IsError(Application.Match(excCurrent, Exceptions, 0)) Then
If Not dict.Exists(uniCurrent) Then
dict(uniCurrent) = Empty
k = k + 1
For j = 1 To ColumnsCount
Data(k, j) = Data(i, j)
Next j
End If
End If
End If
Next i
Else
' There are no exceptions: 'Exceptions = Array()'.
For i = 1 To UBound(Data, 1)
uniCurrent = Data(i, uniColumn)
If Not IsError(uniCurrent) And Not IsEmpty(uniCurrent) Then
If Not dict.Exists(uniCurrent) Then
dict(uniCurrent) = Empty
k = k + 1
For j = 1 To ColumnsCount
Data(k, j) = Data(i, j)
Next j
End If
End If
Next i
End If
' Write unique values from Data Array to Target Range.
' Define Target First Cell Range.
Set cel = rng.Cells(1).Offset(rng.Rows.Count + EmptyRows)
Set rng = cel.Resize(k, ColumnsCount)
rng.Value = Data
' Sort Target Range.
rng.Sort Key1:=rng.Cells(1, srtColumn), _
Order1:=xlAscending, _
Header:=xlNo
' Confirm success.
Success = True
GoTo ProcSuccess
ProcExit:
Application.ScreenUpdating = True
If Success Then
MsgBox "Data transferred.", vbInformation, "Success"
Else
MsgBox "Data not transferred.", vbCritical, "Fail"
End If
Exit Sub
ProcSuccess:
Debug.Print "'" & ProcName & "': Success."
GoTo ProcExit
ProcFail:
Debug.Print "'" & ProcName & "': Fail."
GoTo ProcExit
UserCanceled:
Debug.Print "'" & ProcName & "': User canceled."
GoTo ProcExit
clearError:
Debug.Print "'" & ProcName & "': " & vbLf _
& " " & "Run-time error '" & Err.Number & "':" & vbLf _
& " " & Err.Description
On Error GoTo 0 ' Turn off error trapping.
GoTo ProcFail
End Sub
I need a count of unique items from a range to know how many lines to add to make room for a pivot table. Being that I know excel better then VBA I put together the following code:
With ActiveSheet
LR = .Cells(.Rows.Count, "A").End(xlUp).Row
End With
Range("F" & LR).Formula2R1C1 = "=UNIQUE(R3C:R[-1]C)"
With ActiveSheet
CT = .Cells(.Rows.Count, "F").End(xlUp).Row
End With
Range("F" & LR).ClearContents
R = "1:" & CT - LR + 3
Rows(R).Insert Shift:=xlDown
I would like to know how I can have VBA do the calulations on its own so I can avoid adding and deleting formulas from the sheet.
You can count Unique in VBA by adding to a collection or taken your post as an example by using the UNIQUE function in combination with evaluate:
With ActiveSheet
LR = .Cells(.Rows.Count, "A").End(xlUp).Row
End With
x = UBound(Application.Evaluate("UNIQUE(F3:F" & LR & ")"))
Count Unique (Dictionary)
If you don't have 365 i.e. you don't have UNIQUE you can use the following function.
The Function and OP's Test
Option Explicit
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
' Purpose: Returns the number of unique values in a range.
' Remarks: Error and empty values are excluded.
' The range can be non-contiguous.
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
Function countUnique(SourceRange As Range) _
As Long
' Initialize error handling.
Const ProcName As String = "countUnique"
On Error GoTo clearError ' Turn on error trapping.
' Check Source Range.
If SourceRange Is Nothing Then
GoTo ProcExit
End If
' Write values from Source Range to arrays of Data Array ('Data').
Dim AreasCount As Long
AreasCount = SourceRange.Areas.Count
Dim Help As Variant
ReDim Help(1 To 1, 1 To 1)
Dim Data As Variant
ReDim Data(1 To AreasCount)
Dim rng As Range
Dim n As Long
For Each rng In SourceRange.Areas
n = n + 1
If rng.Rows.Count > 1 Or rng.Columns.Count > 1 Then
Data(n) = rng.Value
Else
Data(n) = Help
Data(1, 1) = rng.Value
End If
Next rng
' Write (unique) values from arrays of Data Array to a Dictionary ('dict').
Dim dict As Object
Set dict = CreateObject("Scripting.Dictionary")
Dim CurrentValue As Variant
Dim i As Long
Dim j As Long
For n = 1 To AreasCount
For i = 1 To UBound(Data(n), 1)
For j = 1 To UBound(Data(n), 2)
CurrentValue = Data(n)(i, j)
If Not IsError(CurrentValue) And Not IsEmpty(CurrentValue) Then
dict(CurrentValue) = Empty
End If
Next j
Next i
Next n
' Write result (number of elements in the Dictionary).
countUnique = dict.Count
ProcExit:
Exit Function
clearError:
Debug.Print "'" & ProcName & "': " & vbLf _
& " " & "Run-time error '" & Err.Number & "':" & vbLf _
& " " & Err.Description
On Error GoTo 0 ' Turn off error trapping.
GoTo ProcExit
End Function
Sub testOP()
Dim LR As Long ' Last Row
Dim UC As Long ' Unique Count
With ActiveSheet
LR = .Cells(.Rows.Count, "A").End(xlUp).Row
UC = countUnique(.Range(.Cells(3, "F"), .Cells(LR, "F")))
Debug.Print UC
End With
End Sub
Non-Contiguous Test
' Select a range. Then press CTRL and select another range, etc.
' Then run the following procedure.
Sub testNonContiguous()
Dim rng As Range
If TypeName(Selection) = "Range" Then
Set rng = Selection
MsgBox "Range '" & rng.Address(0, 0) & "' contains " _
& countUnique(rng) & " unique item(s)."
End If
End Sub
Performance Tests
' Copy the following formula to A1 and copy down to the bottom of the worksheet.
' =RANDBETWEEN(1,1000000)
' Select the whole column and do a 'Copy/Paste Values'.
' Running this test took about 21 seconds on my machine.
Sub testCountUnique()
Dim rng As Range
Set rng = Range("A:A")
Debug.Print "Range '" & rng.Address(0, 0) & "' contains " _
& countUnique(rng) & " unique item(s)."
End Sub
' This is the same test using UNIQUE which I don't have. I would appreciate
' the feedback, if someone could measure the time this takes to finish.
Sub testUnique()
Dim rng As Range
Set rng = Range("A:A")
Debug.Print "Range '" & rng.Address(0, 0) & "' contains " _
& UBound(Application.Evaluate("UNIQUE(" _
& rng.Address(0, 0) & ")")) & " unique item(s)."
End Sub
You can try so:
Function getCountUnique(rSource As Range) As Long
With Application.WorksheetFunction
getCountUnique = .Count(.Unique(rSource, False, False))
End With
End Function
Call it from your subroutine like as:
With ActiveSheet
LR = ActiveSheet.Cells(.Rows.Count, "A").End(xlUp).Row
uniCount = getCountUnique(.Range("A3:A" & LR))
End With