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
Related
I am trying to make an excel file for my parents so that they have it easier than writing all the info in a book X amount of times.
i have A; B; C; D; E; F; G; H; I; J; and L columns and want to automate and duplicate the data in A to G in rows below xn-1 times when
there is a number on Hx cell x amount of times,
where x can be from 1 to 50.
https://preview.redd.it/8p19v7ncjyo91.png?width=1859&format=png&auto=webp&s=5265abb1f6c77b418c409197e19ab836f62bd5ec
before typing 10
https://preview.redd.it/xq9p3m69kyo91.png?width=1384&format=png&auto=webp&s=b06512811b45d8d7c33ff8072d58bc1f8603fa46
example data after inputting 10 or 5 respectively
thus will be inputting all the details in rows 17 and 27
Please, test the next code. It iterates backwards, inserts the necessary number of rows (from "H" cell) and copy on them the values of between columns "A:G" of the row where "H" cell is not empty and numeric:
Sub CopyRowsNTimes()
Dim sh As Worksheet, lastRH As Long, i As Long
Set sh = ActiveSheet 'use here the sheet you need
lastRH = sh.Range("H" & sh.rows.count).End(xlUp).row 'last row on column "H:H")
Application.ScreenUpdating = False: Application.Calculation = xlCalculationManual
For i = lastRH To 2 Step -1
If IsNumeric(sh.Range("H" & i).Value) And sh.Range("H" & i).Value <> "" Then
Application.CutCopyMode = False
sh.rows(i + 1 & ":" & i + sh.Range("H" & i).Value - 1).Insert Shift:=xlShiftDown, CopyOrigin:=xlFormatFromLeftOrAbove
sh.Range("A" & i + 1, "G" & i + 1 + sh.Range("H" & i).Value - 2).Value = _
sh.Range("A" & i, "G" & i).Value
End If
Next i
Application.ScreenUpdating = True: Application.Calculation = xlCalculationAutomatic
MsgBox "Ready..."
End Sub
Please, send some feedback after testing it.
I think, clearing the content of H:H column after processing will be a good idea. For the case you run the code for the second time, by mistake. I let it as it was, only to easily check the inserted rows...
Duplicate Rows
Sheet Module e.g. Sheet1
Option Explicit
Private Sub Worksheet_Change(ByVal Target As Range)
DuplicateRows Target, "H2", 1, 50
End Sub
Standard Module e.g. Module1
Option Explicit
Sub DuplicateRows( _
ByVal TargetCell As Range, _
ByVal CriteriaColumnFirstCellAddress As String, _
Optional ByVal MinTargetValue As Long = 1, _
Optional ByVal MaxTargetValue As Long = 1)
Const ProcName As String = "DuplicateRows"
On Error GoTo ClearError
' Validate 'TargetCell'.
'If TargetCell Is Nothing Then Exit Sub
If TargetCell.Cells.CountLarge > 1 Then Exit Sub ' not a single cell
' Validate 'CriteriaColumnFirstCellAddress'.
Dim ws As Worksheet: Set ws = TargetCell.Worksheet
Dim fCell As Range
On Error Resume Next
Set fCell = ws.Range(CriteriaColumnFirstCellAddress)
On Error GoTo ClearError
If fCell Is Nothing Then Exit Sub ' invalid address
If fCell.Cells.CountLarge > 1 Then Exit Sub ' not a single cell
' Build the Criteria (one-column) range ('crg').
Dim rg As Range: Set rg = ws.UsedRange
Dim crg As Range
With fCell
Set crg = Intersect(rg, .Resize(ws.Rows.Count - .Row + 1))
End With
If crg Is Nothing Then Exit Sub ' not intersecting
If Intersect(TargetCell, crg) Is Nothing Then Exit Sub ' not intersecting
' Validate 'MinTargetValue' and 'MaxTargetValue'.
If MinTargetValue < 1 Then Exit Sub
If MaxTargetValue < 1 Then Exit Sub
Dim MinValue As Long
Dim MaxValue As Long
' Handle if min and max are switched.
If MinTargetValue < MaxTargetValue Then
MinValue = MinTargetValue
MaxValue = MaxTargetValue
Else
MinValue = MaxTargetValue
MaxValue = MinTargetValue
End If
' Validate the Target value.
Dim TargetValue As Variant: TargetValue = TargetCell.Value
If Not VarType(TargetValue) = vbDouble Then Exit Sub ' not a number
If Int(TargetValue) <> TargetValue Then Exit Sub ' not a whole number
Select Case TargetValue
Case MinValue To MaxValue
Case Else: Exit Sub ' exceeds the range of numbers
End Select
Dim rrg As Range: Set rrg = Intersect(rg, TargetCell.EntireRow)
Dim LastRow As Long: LastRow = crg.Cells(crg.Cells.Count).Row
Dim MaxInsertRows As Long: MaxInsertRows = ws.Rows.Count - LastRow
If TargetValue > MaxInsertRows Then Exit Sub ' doesn't fit in the worksheet
' (Insert) Copy the data.
With Application
.ScreenUpdating = False
.EnableEvents = False
End With
With rrg
If .Row < LastRow Then
.Offset(1).Resize(TargetValue).Insert _
Shift:=xlShiftDown, CopyOrigin:=xlFormatFromLeftOrAbove
End If
TargetCell.ClearContents
.Copy Destination:=.Resize(TargetValue + 1)
End With
ProcExit:
On Error Resume Next
With Application
If Not .EnableEvents Then .EnableEvents = True
If Not .ScreenUpdating Then .ScreenUpdating = True
End With
On Error GoTo 0
Exit Sub
ClearError:
Debug.Print "'" & ProcName & "' Run-time error '" _
& Err.Number & "':" & vbLf & " " & Err.Description
Resume ProcExit
End Sub
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
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
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