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
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 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