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
Related
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've written the below function intended to take an input array, delete the duplicates and return an array of unique values. I've looked at other functions open source that are similar but could not get them to work either. Watching both input array and the function arrays, Arr and ArrCopy, they have the correct number and value for each index. Any ideas why I'm getting an out of range error?
Public Function getUnique(Arr As Variant) As Variant
Dim ArrCopy As Variant
Dim i As Variant
Dim j As Variant
Dim counter As Integer
'copies input array, loops through copy and clears dupates
ArrCopy = Arr
For i = LBound(Arr) To UBound(Arr)
For j = LBound(ArrCopy) To UBound(ArrCopy)
If Arr(i) = ArrCopy(j) And i <> j Then
ArrCopy(j).Clear
End If
Next j
Next i
'clears array, loops through copy and puts nonzero values back in Arr
Arr.Clear
counter = 0
For i = LBound(ArrCopy) To UBound(ArrCopy)
If ArrCopy(i) <> "" Then
ReDim Preserve Arr(0 To counter)
Arr(counter) = ArrCopy(i)
counter = counter + 1
End If
Next i
'returns unique values
getUnique = Arr
End Function
Update: This is how the array gets loaded. From FaneDuru's comment, I see in the watch table that the input array is actually 2D, so that's why I'm getting an out of range error....
'removes blanks from AO
wks.AutoFilterMode = False
wks.Range("A1:BO" & lastrow).AutoFilter Field:=41, Criteria1:="<>", Operator:=xlFilterValues
Set rng = wks.Range("AO2:AO" & lastrow).SpecialCells(xlCellTypeVisible)
'loads SNs into array
Erase serialNum
serialNum = rng.Value
Update 2:
This has me a lot closer. Using the 2d approach This will set all of the repeats to 0. Then I call a delete element sub I found (Deleting Elements in an Array if Element is a Certain value VBA). I am modifying the original to work with 2D array. I am getting a subscript out of range error on my Redim Preserve line within the DeleteElementAt() sub.
Public Function GetUnique(Arr As Variant) As Variant
Dim i As Variant
Dim j As Variant
Dim counter As Integer
For i = LBound(Arr) To UBound(Arr)
For j = LBound(Arr) To UBound(Arr)
If i <> j And Arr(i, 1) = Arr(j, 1) Then
Arr(j, 1) = "0"
End If
Next j
Next i
counter = 0
For i = LBound(Arr) To UBound(Arr)
If Arr(i, 1) = "0" Then
Call DeleteElementAt(i, Arr)
ReDim Preserve Arr(0 To UBound(Arr))
End If
Next i
GetUnique = Arr
End Function
Public Sub DeleteElementAt(ByVal index As Integer, ByRef Arr As Variant)
Dim i As Integer
' Move all element back one position
For i = index + 1 To UBound(Arr)
Arr(index, 1) = Arr(i, 1)
Next i
' Shrink the array by one, removing the last one
'ERROR HERE
ReDim Preserve Arr(LBound(Arr) To UBound(Arr) - 1, 1)
End Sub
Return the Unique Values From a Range in an Array
Option Explicit
Sub Test()
Dim ws As Worksheet: Set ws = ThisWorkbook.Worksheets("Sheet1")
Dim rg As Range: Set rg = ws.Range("A2:J21")
Dim Data As Variant: Data = GetRange(rg)
Dim Arr As Variant: Arr = ArrUniqueData(Data)
' Continue using 'Arr', e.g.:
If Not IsEmpty(Arr) Then
Debug.Print Join(Arr, vbLf)
Else
Debug.Print "Nope."
End If
' Dim n As Long
' For n = 0 To UBound(Arr)
' Debug.Print Arr(n)
' Next n
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: Writes the unique values from a 2D array
' to a 1D zero-based array, excluding error values and blanks.
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
Function ArrUniqueData( _
Data As Variant, _
Optional ByVal CompareMethod As VbCompareMethod = vbTextCompare) _
As Variant
Const ProcName As String = "ArrUniqueDatae"
On Error GoTo ClearError
Dim cLower As Long: cLower = LBound(Data, 2)
Dim cUpper As Long: cUpper = UBound(Data, 2)
Dim Key As Variant
Dim r As Long
Dim C As Long
With CreateObject("Scripting.Dictionary")
.CompareMode = CompareMethod
For r = LBound(Data, 1) To UBound(Data, 1)
For C = cLower To cUpper
Key = Data(r, C)
If Not IsError(Key) Then ' exclude error values
If Len(Key) > 0 Then ' exclude blanks
.Item(Key) = Empty
End If
End If
Next C
Next r
If .Count = 0 Then Exit Function
ArrUniqueData = .Keys
End With
ProcExit:
Exit Function
ClearError:
Debug.Print "'" & ProcName & "' Run-time error '" _
& Err.Number & "':" & vbLf & " " & Err.Description
Resume ProcExit
End Function
EDIT
This will continue your sub using the (SpecialCells) filtered one-column range. You still need the previous procedures (except the Test procedure) and there is a new function below.
' This is your procedure!
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
' Purpose: ...
' Calls: GetFilteredColumn
' GetRange
' ArrUniqueData
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
Sub YourProcedure()
' ... whatever
Set Rng = wks.Range("AO2:AO" & lastrow).SpecialCells(xlCellTypeVisible)
'Erase serialNum ' you don't need to erase
serialNum = GetFilteredColumn(Rng)
Dim Arr As Variant: Arr = ArrUniqueData(serialNum)
' Continue using 'Arr', e.g.:
If Not IsEmpty(Arr) Then
Debug.Print Join(Arr, vbLf)
Else
Debug.Print "Nope."
End If
End Sub
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
' Purpose: Returns the filtered values of a column range
' in a 2D one-based array.
' Calls: GetRange.
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
Function GetFilteredColumn( _
ByVal FilteredColumnRange As Range) _
As Variant
Const ProcName As String = "GetFilteredColumn"
On Error GoTo ClearError
With FilteredColumnRange
Dim aCount As Long: aCount = .Areas.Count
Dim aData As Variant: ReDim aData(1 To aCount)
Dim arg As Range
Dim a As Long
For Each arg In .Areas
a = a + 1
aData(a) = GetRange(arg)
Next arg
Dim dData As Variant: ReDim dData(1 To .Cells.Count, 1 To 1)
Dim sr As Long
Dim dr As Long
For a = 1 To aCount
For sr = 1 To UBound(aData(a), 1)
dr = dr + 1
dData(dr, 1) = aData(a)(sr, 1)
Next sr
Next a
GetFilteredColumn = dData
End With
ProcExit:
Exit Function
ClearError:
Debug.Print "'" & ProcName & "' Run-time error '" _
& Err.Number & "':" & vbLf & " " & Err.Description
Resume ProcExit
End Function
When you assign the values of an Excel range to a variant in VBA you always get a 2D array even if your range is a single column or row i.e. you get an array that is dimensioned as (1 to X,1 to 1). To get an array with dimensions (1 to X) you need to encapsulate the 'get values' code in a worksheetFunction.Transpose() call.
Assuming you have got your array into a 1D form you can then use either an ArrayList or Scripting.Dictionary to simplify compiling unique values. No need to get messyt with array indeces at all.
This is the ArrayList Version
Public Function getUnique(Arr As Variant) As Variant
Dim myList As Object
Set myList = CreateObject("System.collections.Arraylist")
Dim myItem As Variant
For Each myItem In Arr
If myItem <> 0 Then
If Not myList.Contains(myItem) Then
myList.Add myItem
End If
End If
Next
getUnique = myList.toarray
End Function
This is the Scripting.Dictionary version
Public Function getUnique(Arr As Variant) As Variant
Dim myList As Object
Set myList = CreateObject("Scripting.Dictionary")
Dim myItem As Variant
For Each myItem In Arr
If myItem <> 0 Then
If Not myList.exists(myItem) Then
myList.Add myList.Count, myItem
End If
End If
Next
getUnique = myList.Items
End Function
So I'm very new with working with excel's VBA code, and I'm trying to create a 'Date Modified' column for when a value in the previous column was edited for a checklist at work. I've done this once before for another checklist, but I did it the old-fashioned way since it was not a very long checklist. But for this application, that is not efficient at all since this list will be ongoing. I've cobbled together this code using other examples from people in the community, but I can't figure out where the source of the error is coming from. It's saying that there is a compile error 'Do without Loop'. From my understanding from other posts, it thinks that the 'If' statement is not being closed, but I have used an 'End If' and there is only one 'If' statement in my code. I need it to be alternating columns from the 6th column onward and then repeating every row. Any help is much appreciated!
Sub Worksheet_Change(ByVal Target As Range)
Dim ColCount As Long
ColCount = 6
Dim RowCount As Long
RowCount = 2
Dim iCol As Long
iCol = 7
Dim iRow As Long
iRow = 2
Do While RowCount < 2
Do While ColCount < 6
Do While iCol < 7
Do While iRow < 2
If Target.Column = ColCount And Target.Row = RowCount Then
ActiveSheet.Cells(iRow, iCol).Value = Format(Date, "mm/dd/yyyy")
End If
RowCount = RowCount + 1
ColCount = ColCount + 2
iCol = iCol + 2
iRow = iRow + 1
Loop
End Sub
Simpler approach:
Private Sub Worksheet_Change(ByVal Target As Range)
Dim rng As Range, c As Range
Set rng = Application.Intersect(Target, Me.Range("F:F,H:H,J:J")) 'adjust to suit...
If rng Is Nothing Then Exit Sub 'no updates in monitored range
For Each c In rng.Cells
c.Offset(0, 1).Value = Format(Date, "mm/dd/yyyy")
Next c
End Sub
Add a Datestamp Next to Every Other Column
Option Explicit
Private Sub Worksheet_Change(ByVal Target As Range)
AddDateStamp Target
End Sub
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
' Purpose: Adds a datestamp next to every other column.
' Calls: 'RefWsColumnsFirstRow','RefRangeNthColumns'.
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
Sub AddDateStamp(ByVal Target As Range)
Const ProcName As String = "AddDateStamp"
On Error GoTo ClearError
Const Cols As String = "F:BA"
Const fRow As Long = 2 ' '2' excludes headers in first row
Const cStep As Long = 2
Const dcOffset As Long = 1
Const ddFormat As String = "mm/dd/yy" ' "mm/dd/yy hh:mm:ss" '
Const DoExcludeFirstColumn As Boolean = False ' 'False: F, H, J... AZ'
Dim crg As Range ' 'F2:BA1048576' ('F2:BA65536' for older versions)
Set crg = RefWsColumnsFirstRow(Target.Worksheet, Cols, fRow)
Dim srg As Range ' 'F2:F1048576, H2:H..., J2:J..., ... , AZ2:AZ1048576'
Set srg = RefRangeNthColumns(crg, cStep, DoExcludeFirstColumn)
Dim sirg As Range: Set sirg = Intersect(srg, Target)
If sirg Is Nothing Then Exit Sub
Dim drg As Range: Set drg = sirg.Offset(, dcOffset)
Application.EnableEvents = False
drg.Value = Format(Date, ddFormat) ' 'Now' (instead of 'Date')
SafeExit:
If Not Application.EnableEvents Then Application.EnableEvents = True
Exit Sub
ClearError:
Debug.Print "'" & ProcName & "': Unexpected Error!" & vbLf _
& " " & "Run-time error '" & Err.Number & "':" & vbLf _
& " " & Err.Description
Resume SafeExit
End Sub
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
' Purpose: Creates a reference to a columns ('ColumnsAddress') range
' from the first row ('FirstRow') to the bottom-most
' worksheet's ('ws') row.
' Example: 'If ColumnsAddress = "B:E" and FirstRow = 5 Then "B5:E1048576"'
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
Function RefWsColumnsFirstRow( _
ByVal ws As Worksheet, _
Optional ByVal ColumnsAddress As String = "A:A", _
Optional ByVal FirstRow As Long = 1) _
As Range
Const ProcName As String = "RefWsColumnsFirstRow"
On Error GoTo ClearError
Set RefWsColumnsFirstRow = ws.Columns(ColumnsAddress) _
.Resize(ws.Rows.Count - FirstRow + 1).Offset(FirstRow - 1)
ProcExit:
Exit Function
ClearError:
Debug.Print "'" & ProcName & "': Unexpected Error!" & vbLf _
& " " & "Run-time error '" & Err.Number & "':" & vbLf _
& " " & Err.Description
Resume ProcExit
End Function
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
' Purpose: Creates a reference combined from every n-th ('NthStep') column
' of a range ('rg').
' The default value of 'DoExcludeFirstColumn' is 'False' i.e.
' e.g. if 'NthStep' = 2 then the columns are 1, 3, 5...etc.;
' otherwise, the columns are 2, 4, 6...etc.
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
Function RefRangeNthColumns( _
ByVal rg As Range, _
Optional ByVal NthStep As Long = 1, _
Optional ByVal DoExcludeFirstColumn As Boolean = False) _
As Range
Const ProcName As String = "RefRangeNthColumns"
On Error GoTo ClearError
Dim Col1 As Long, ColGT As Long, Col2 As Long
If DoExcludeFirstColumn Then
Col1 = NthStep: ColGT = 2 * NthStep - 1: Col2 = 2 * NthStep
Else
Col1 = 1: ColGT = NthStep: Col2 = 1 + NthStep
End If
Dim crg As Range: Set crg = rg.Columns(Col1)
Dim scCount As Long: scCount = rg.Columns.Count
Dim c As Long
If scCount > ColGT Then
For c = Col2 To scCount Step NthStep
Set crg = Union(crg, rg.Columns(c))
Next c
End If
Set RefRangeNthColumns = crg
ProcExit:
Exit Function
ClearError:
Debug.Print "'" & ProcName & "': Unexpected Error!" & vbLf _
& " " & "Run-time error '" & Err.Number & "':" & vbLf _
& " " & Err.Description
Resume ProcExit
End Function
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
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