Excel Moving duplicate values to new sheet - vba

I have compiled this code from bit and pieces I have found - I am by no means an expert - more of an eager student - This code works for me but now I need to keep the first occurrence of the duplicate row to stay on the original worksheet and move only the subsequent occurrence(s) to the newly created sheet.
I am willing to redo all the code if needed but would prefer to modify the existing vba for the sake of time
Sub moveduplicates
'***************************************************************
'** This proc expects you to select all the cells in a single **
'** column that you want to check for duplicates in. If dup- **
'** licates are found, the entire row will be copied to the **
'** predetermined sheet. **
'***************************************************************
Set Rng = ActiveCell
'Sticky_Selection()
Dim s As Range
Set s = Selection
Cells.EntireColumn.Hidden = False
Worksheets.Add(After:=Worksheets(Worksheets.Count)).Name = "Duplicate Values"
Sheets("Data").Select
Range("A2").Select
Range(Selection, Selection.End(xlToRight)).Select
Selection.Copy
Sheets("Duplicate Values").Select
Range("A1").Select
ActiveSheet.Paste
s.Parent.Activate
s.Select 'NOT Activate - possibly more than one cell!
Dim ShO As Worksheet
Dim Rng1 As Range
Dim pRow As Integer
Dim c As Range, cTmp As Range
Dim found
Dim Addresses() As String
Dim a() As String
Dim p2 As Integer
Dim tfFlag As Boolean, sTmp As Variant
Set ShO = Worksheets("Duplicate Values") 'You can change this to whatever worksheet name you want the duplicates in Set Rng1 = Application.InputBox("Select a range", "Obtain Range Object", Type:=8)
MsgBox "The cells selected were " & Rng.Address 'Rng1 is all the currently selected cells
pRow = 2 'This is the first row in our output sheet that will be used to record duplicates
ReDim a(0) 'Initialize our array that holds found values
For Each c In Rng1.Cells 'Cycle through each cell in our selected range
ReDim Addresses(0) 'This array holds the cell address for our duplicates.
'We will reset the array each time we move to the next cell
Now check the array of already found duplicates.
If the current value is already there skip to next value
tfFlag = False
For Each sTmp In a
If CStr(c.Value) = sTmp Or CStr(c.Value) = "xXDeleteXx" Then 'We've already done this value, move on
tfFlag = True
Exit For
End If
Next
If Not tfFlag Then 'Remember the flag is true when we have already located the
'duplicates for this value, so skip to next value
With Rng1
Set found = .Find(c.Value, LookIn:=xlValues) 'Search entire selected range for value
If Not found Is Nothing Then 'Found it
Addresses(0) = found.Address 'Record the address we found it
Do 'Now keep finding occurances of it
Set found = .FindNext(found)
If found.Address <> Addresses(0) Then
ReDim Preserve Addresses(UBound(Addresses) + 1)
Addresses(UBound(Addresses)) = found.Address
End If
Loop While Not found Is Nothing And found.Address <> Addresses(0) 'Until we get back to the original address
If UBound(Addresses) > 0 Then 'We Found Duplicates
a(UBound(a)) = c.Value 'Record the value we found a duplicate for in an array
'ReDim Preserve a(UBound(a) + 1) 'add an empty spot to the array for next value
'ShO.Range("A" & pRow).Value = "Duplicate Rows for Value " & c.Value & _
" in Column " & c.Column & " on original sheet" 'Add a label row
'pRow = pRow + 1 'Increment to the next row
For p2 = UBound(Addresses) To 0 Step -1 'Cycle through the duplicate addresses
Set cTmp = Rng1.Worksheet.Range(Addresses(p2)) 'we just want to easily get the correct row to copy
Rng1.Worksheet.Rows(cTmp.Row).Copy ShO.Rows(pRow) 'Copy form orig to duplicates sheet
cTmp.Value = "xXDeleteXx" 'Mark for Delete the original row
pRow = pRow + 1 'Increment row counter
Next p2
'Row = pRow + 1 'This increment will give us a blank row between sets of duplicates
End If
End If
End With
End If
Next
'Now go delete all the marked rows
Do
tfFlag = False
For Each c In Rng1
If c.Value = "xXDeleteXx" Then
Rng1.Worksheet.Rows(c.Row).Delete (xlShiftUp)
tfFlag = True
End If
Next
Loop Until tfFlag = False
'AutoFit Every Worksheet Column in a Workbook
For Each sht In ThisWorkbook.Worksheets
sht.Cells.EntireColumn.AutoFit
Next sht
Application.Goto Rng
End
End Sub
Thank you very much for your time and consideration

You can use a scripting Dictionary object to keep track of duplicates:
Sub RemoveDups()
Dim c As Range, dict, rngDel As Range, rw As Long
Dim wb As Workbook
Dim shtDups As Worksheet
Dim rng1 As Range
Set rng1 = Selection 'assuming you've selected a single column of values
' from which you want to remove dups
Set wb = ActiveWorkbook
Set shtDups = wb.Worksheets.Add( _
after:=wb.Worksheets(wb.Worksheets.Count))
shtDups.Name = "Duplicate Values"
With rng1.Parent
.Range(.Range("A2"), .Range("A2").End(xlToRight)).Copy _
shtDups.Range("A1")
End With
rw = 2
Set dict = CreateObject("scripting.dictionary")
For Each c In rng1.Cells
'already seen this value?
If dict.exists(c.Value) Then
c.EntireRow.Copy shtDups.Cells(rw, 1)
rw = rw + 1
'add row to "delete" range
If rngDel Is Nothing Then
Set rngDel = c
Else
Set rngDel = Application.Union(c, rngDel)
End If
Else
'first time for this value - add to dictionary
dict.Add c.Value, 1
End If
Next c
'delete all duplicate rows (if found)
If Not rngDel Is Nothing Then
rngDel.EntireRow.Delete
End If
End Sub

Another enthusiastic amateur here!
Not really answering your question, but here is a little function I use for removing duplicate rows:
Sub RemoveDupes(TempWB As Workbook, TargetSheet As String, ConcatCols As String, DeleteTF As Boolean)
Dim Counter As Integer
Dim Formula As String
Dim RowCount As Integer
Dim StartingCol As String
Dim CurrentRow As Integer
'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
' Remove duplicate rows on a worksheet '
'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
' Prerequisites:
' - Data needs to start # A1
' - Data has headings in row 1
' determine number of rows to be processed
RowCount = TempWB.Sheets(TargetSheet).Cells(TempWB.Sheets(TargetSheet).Rows.Count, "A").End(xlUp).Row
' insert a column to hold the calculate unique key
TempWB.Sheets(TargetSheet).Columns("A:A").Insert Shift:=xlToRight, CopyOrigin:=xlFormatFromLeftOrAbove
' add a heading
TempWB.Sheets(TargetSheet).Cells(1, 1).Value = "Duplication Check"
' insert the unique key formula
For CurrentRow = 2 To RowCount
' start the formula string
Formula = "="
' construct the formula
For Counter = 1 To Len(ConcatCols)
' if we are on the last element, dont add another '&'
If Counter = Len(ConcatCols) Then
Formula = Formula & AddLetter(Mid(ConcatCols, Counter, 1)) & CurrentRow
Else
Formula = Formula & AddLetter(Mid(ConcatCols, Counter, 1)) & CurrentRow & "&"
End If
' Debug.Print Mid(ConcatCols, Counter, 1)'Next
' next element!
Next
' insert the newly constructed formula
TempWB.Sheets(TargetSheet).Cells(CurrentRow, "A").Formula = Formula
' next row
Next
' unfortunately we need to use explicit selection here *sigh*
TempWB.Sheets(TargetSheet).Activate
' to select the range we are going to test
TempWB.Sheets(TargetSheet).Range("A2:A" & TempWB.Sheets(TargetSheet).Cells(Rows.Count, "A").End(xlUp).Row).Select
' clock down the list flagging each dupe by changing the text color
Dim d As Object, e
Set d = CreateObject("scripting.dictionary")
For Each e In Intersect(Columns(ActiveCell.Column), ActiveSheet.UsedRange)
If e.Value <> vbNullString Then
If Not d.exists(e.Value) Then d(e.Value) = 1 Else _
e.Font.ColorIndex = 4
End If
Next
' if the delete flag is set...
If DeleteTF Then
' then go down the list deleting rows...
For CurrentRow = RowCount To 2 Step -1
' if the row has been highlighted, its time to go...
If TempWB.Sheets(TargetSheet).Cells(CurrentRow, "A").Font.ColorIndex = 4 Then
TempWB.Sheets(TargetSheet).Cells(CurrentRow, "A").EntireRow.Delete
End If
Next
' If we are deleting rows, remove the column just like we were never here
TempWB.Sheets(TargetSheet).Cells(1, "A").EntireColumn.Delete
End If
End Sub
Function AddLetter(Letter As String)
' gives you the next letter
AddLetter = Split(Cells(, Range(Letter & 1).Column + 1).Address, "$")(1)
End Function
When I get a sec I will have a go adapting this to your requirements...

This will search a specified column for duplicates, copying subsequent duplicates entries to Sheet2 and then remove them from Sheet1.
I've used the Scripting Dictionary too but you will need to add a reference to "Microsoft Scripting Runtime" for the code to work as-is. (Adding the reference will help if you want to learn about dictionaries since it adds the Dictionary to Intellitype code completion stuff)
Sub Main()
Dim SearchColumn As Integer: SearchColumn = 2 ' column to search for duplicates
Dim Source As Worksheet: Set Source = ThisWorkbook.Worksheets("Sheet1")
Dim Duplicates As Worksheet: Set Duplicates = ThisWorkbook.Worksheets("Sheet2")
Dim List As Dictionary: Set List = New Dictionary ' used to hold the first instance of unique items
Dim Data As Variant ' holds a copy of the column you want to search
Dim Count As Integer ' hold the size of said column
Dim Index As Integer ' iterator for data
Dim Item As String ' holds the current item
Count = Source.Cells(Source.Rows.Count, SearchColumn).End(xlUp).Row
Set Data = Source.Range(Source.Cells(1, SearchColumn).Address, Source.Cells(Count, SearchColumn).Address)
Application.ScreenUpdating = False
' first loop, find unique items and copy duplicates
For Index = 1 To Count
Item = Data(Index, 1)
If List.Exists(Item) = False Then
' add the item to our dictionary of items
List.Add Item, Index
Else
' add item to duplicates sheet as its a duplicate
Source.Rows(Index).Copy
Duplicates.Rows(1).Insert xlShiftDown
End If
Next Index
' second loop, remove duplicates from original sheet
For Index = Count To 1 Step -1
Item = Data(Index, 1)
If List.Exists(Item) Then
If Not List(Item) = Index Then
' the item is a duplicate and needs to be removed
Source.Rows(Index).Delete
End If
End If
Next Index
Application.ScreenUpdating = True
End Sub

Related

VBA case insensitive sorting within listbox

I have the code below which is used to remove duplicates and sort the values alphabetically into a listbox within a userform but it is prioritizing uppercase over alphabetical and I would like it to ignore the case of the text
Dim Coll As Collection, cell As Range, LastRow As Long
Dim blnUnsorted As Boolean, i As Integer, temp As Variant
Dim SourceSheet As Worksheet
Set SourceSheet = Worksheets("Groups")
'///////////////////////////////////////////////////////
'Populate the ListBox with unique Make items from column A.
LastRow = SourceSheet.Cells(Rows.Count, 1).End(xlUp).Row
On Error Resume Next
Set Coll = New Collection
'Open a With structure for the ListBox control.
With ClientInput
.Clear
For Each cell In SourceSheet.Range("A2:A" & LastRow)
'Only attempt to populate cells containing a text or value.
If Len(cell.Value) <> 0 Then
Err.Clear
Coll.Add cell.Text, cell.Text
If Err.Number = 0 Then .AddItem cell.Text
End If
Next cell
blnUnsorted = True
Do
blnUnsorted = False
For i = 0 To UBound(.List) - 1
If .List(i) > .List(i + 1) Then
temp = .List(i)
.List(i) = .List(i + 1)
.List(i + 1) = temp
blnUnsorted = True
Exit For
End If
Next i
Loop While blnUnsorted = True
'Close the With structure for the ListBox control.
End With
Current
AC
AZ
ab
Desired
ab
AC
AZ
Instead of
If .List(i) > .List(i + 1) Then
use
If LCase(.List(i)) > LCase(.List(i + 1)) Then
You can use the worksheetfunction sort
This is a sample code of how to use it
Sub sortRange()
Dim rg As Range: Set rg = Selection
Dim arrValues As Variant
arrValues = WorksheetFunction.Sort(rg)
rg.Offset(, 2).Resize(3).Value = arrValues
End Sub
If A1:A3 are selected the above code will write the sorted values to C1:C3
You can iterate over the array to add the items to the list
Please, try the next code. It firstly places the existing range (from A:A) in an array, sorts the range in place, places the sorted range in another array, extract unique strings (case sensitive) using a Dictionary and load the list box directly from the array using its List property. Then, places back the initial extracted array before sorting:
Sub UniqueSortLoadListBox()
Dim sh As Worksheet, lastR As Long, arr, arrSort, i As Long, dict As Object
Set sh = Worksheets("Groups")
lastR = sh.Range("A" & sh.rows.count).End(xlUp).row
arr = sh.Range("A2:A" & lastR).Value
With sh.Sort 'the fastest sorting way
.SortFields.Clear
.SortFields.Add2 Key:=sh.Range("A1:A" & lastR), _
SortOn:=xlSortOnValues, Order:=xlAscending, DataOption:=xlSortNormal
.SetRange sh.Range("A1:A" & lastR)
.Header = xlYes
.MatchCase = True
.Orientation = xlTopToBottom
.SortMethod = xlPinYin
.Apply
End With
lastR = sh.Range("A" & sh.rows.count).End(xlUp).row ' recalculate to eliminate the empty cells
arrSort = sh.Range("A2:A" & lastR).Value 'place the sorted range in an array, for faster iteration/processing
'extract unique strings (case sensitive)
Set dict = CreateObject("Scripting.Dictionary")
dict.CompareMode = BinaryCompare 'case sensitive for keys creation
For i = 1 To UBound(arrSort)
dict(arrSort(i, 1)) = 1
Next i
'load the listbox directly from an array (dictionary keys array)
With clientInput
.Clear
.List = dict.Keys
End With
'place back the array as it was before sorting and unique extracting:
sh.Range("A2").Resize(UBound(arr), 1).Value = arr
End Sub
Please, send some feedback after testing it.
Sorted Column Values to a List Box
If You Don't Have 365
In a nutshell, the code will write the values from the criteria column to a helper column, the column adjacent to the right of the used range, sort it, retrieve its values, clear its contents, and populate a list box with the retrieved unique (sorted) values.
Sub PopulateClientInput()
Const ProcTitle As String = "Populate Client Input"
' Define constants.
Const wsName As String = "Groups"
Const cCol As Long = 1
' Reference the workbook ('wb').
Dim wb As Workbook: Set wb = ThisWorkbook ' workbook containing this code
' Reference the worksheet ('ws').
Dim ws As Worksheet: Set ws = wb.Worksheets(wsName)
' It is assumed that the worksheet's used range consists of a valid table
' (not an Excel table) i.e. one row of headers and contiguous data below.
' Reference the expanded data range ('rg') i.e. the data range (no headers)
' and an extra helper column to the right. Retrieve the range's
' number of rows ('rCount') and columns ('cCount').
Dim rg As Range
Dim rCount As Long
Dim cCount As Long
With ws.UsedRange
rCount = .Rows.Count - 1 ' shrink
If rCount = 0 Then ' only headers or empty worksheet
MsgBox "Not enough rows.", vbExclamation, ProcTitle
Exit Sub
End If
cCount = .Columns.Count + 1 ' expand
' Note that the following cannot happen if 'cCol = 1'.
If cCount < cCol + 1 Then ' criteria column not in used range
MsgBox "Not enough columns.", vbExclamation, ProcTitle
Exit Sub
End If
' Reference the range.
Set rg = .Resize(rCount, cCount).Offset(1)
End With
' Reference the criteria column range ('crg').
Dim crg As Range: Set crg = rg.Columns(cCol)
' Store the sorted values from the criteria column range
' in a 2D one-based (one-column) array, the criteria array ('cData').
Application.ScreenUpdating = False
Dim cData() As Variant
If rCount > 1 Then ' multiple cells
' Reference the helper column range ('hrg').
Dim hrg As Range: Set hrg = rg.Columns(cCount)
' Write the values from the criteria column range
' to the helper column range.
hrg.Value = crg.Value
' Sort the helper column range.
hrg.Sort hrg, xlAscending, , , , , , xlNo
' Store the sorted values from the sorted helper column range
' in the criteria array.
cData = hrg.Value
' Clear the contents of the helper column range.
hrg.ClearContents
Else ' one cell
' Store the single value in the single element of the criteria array.
ReDim cData(1 To 1, 1 To 1): cData(1, 1) = crg.Value
End If
' Store the unique values from the criteria array in the 'keys'
' of a dictionary ('dict').
' The 'items' are irrelevant but will hold 'Empty'.
' Error values and blanks are excluded.
' Define the dictionary.
Dim dict As Object: Set dict = CreateObject("Scripting.Dictionary")
dict.CompareMode = vbTextCompare ' case-insensitive i.e. 'A = a'
Dim cKey As Variant ' Current Value in the Criteria Array
Dim r As Long ' Current Row in the Criteria Array
' Store the unique valus in the dictionary.
For r = 1 To rCount
cKey = cData(r, 1) ' retrieve the current value
If Not IsError(cKey) Then ' exclude error values
If Len(CStr(cKey)) > 0 Then ' exclude blanks
' Check if the current value exists in the dictionary.
' This is not necessary but will ensure that the first occuring
' string's case is used. Otherwise, the last would be used.
If Not dict.Exists(cKey) Then
dict(cKey) = Empty ' store the unique value in a 'key'
End If
End If
End If
Next r
' Populate the list box with the sorted unique values
' from the dictionary and inform.
With ClientInput
' Validate the dictionary.
If dict.Count = 0 Then
.Clear ' or not?
Application.ScreenUpdating = True
MsgBox "No valid data.", vbExclamation, ProcTitle
Exit Sub
End If
.List = dict.Keys ' 'dict.Keys' is a zero-based (1D) array
Application.ScreenUpdating = True
MsgBox "Client input populated.", vbInformation, ProcTitle
End With
End Sub

Exel VBA: Run-Time Error 13 Type Mismatch

I have the following list on Sheet1:
COLUMN A COLUMNB COLUMN C
1 ADDRESS Services(s) USED VEHICLE(S) USED
2 Address1 Service1, Service3 Vehicle1, Vehicle3, Vehicle4
3 Address2 Service1, Service4 Vehicle1, Vehicle3, Vehicle4
4 Address3 Service2, Service5 Vehicle1, Vehicle2, Vehicle5
5 Address4 Service2, Service3 Vehicle1, Vehicle6
6 Address1 Service5, Service6 Vehicle2, Vehicle5, Vehicle6
7 Address2 Service2, Service3 Vehicle2, Vehicle3
8 Address4 Service4, Service6 Vehicle1, Vehicle2, Vehicle3, Vehicle4, Vehicle5, Vehicle6
On Sheet2, I would like the following output in Column B when I enter "Address1" in cell B4
COLUMN A COLUMN B
4 Address1
12 Service1
13 Service3
14 Service5
15 Service6
16
17
50 Vehicle1
51 Vehicle2
52 Vehicle3
53 Vehicle4
54 Vehicle5
56 Vehicle6
The following is the code I am using:
Worksheet_Change Code ("Sheet2" module)
Private Sub Worksheet_Change(ByVal Target As Range)
' call Function only if modifed cell is in Column "B"
If Not IsError(Application.Match(Range("B4"), Worksheets("Google Data").Range("E1:E" & LastRow(Worksheets("Google Data"))), 0)) Then
If Not Intersect(Target, Range("B4")) Is Nothing Then
If (Target.Value <> "") Then
Application.EnableEvents = False
Call FilterAddress(Target.Value)
Else
On Error Resume Next
MsgBox Target.Address & "Cell can't be blank, Input a value first."
Err.Clear
Exit Sub
End If
End If
Else
On Error Resume Next
MsgBox "The Appointment # you entered is incorrect or does not exist. Please try again."
Err.Clear
Exit Sub
End If
Application.EnableEvents = True
End Sub
Sub FilterAddress Code (Regular module)
Option Explicit
Sub FilterAddress(FilterVal As String)
Dim LastRow As Long
Dim FilterRng As Range, cell As Range
Dim Dict As Object
'Dim ID
Dim Vehicle As Variant
Dim VehicleArr As Variant
Dim i As Long, j As Long
Dim Service As Variant
Dim ServiceArr As Variant
Dim x As Long, y As Long
Dim My_Range As Range
With Sheets("Sheet1")
' find last row with data in column "A" (Adress)
LastRow = .Cells(.Rows.Count, "A").End(xlUp).Row
Set FilterRng = .Range("A1:C" & LastRow)
.Range("A1").AutoFilter
' AutoFilter "Sheet1" according to value in "Sheet2" in Column B
FilterRng.AutoFilter Field:=1, Criteria1:=FilterVal
Set Dict = CreateObject("Scripting.Dictionary")
' create an array with size up to number of rows >> will resize it later
ReDim ServiceArr(1 To LastRow)
j = 1 ' init array counter
For Each cell In .Range("B2:B" & LastRow).SpecialCells(xlCellTypeVisible)
' read values from cell to array using the Split function
Service = Split(cell.Value, ",")
For i = LBound(Service) To UBound(Service)
Service(i) = Trim(Service(i)) ' remove extra spaces from string
If Not Dict.exists(Service(i)) Then
Dict.Add Service(i), Service(i)
' save Service Name to array >> will use it later for "Bubble-sort" and paste in "Sheet2"
ServiceArr(j) = Service(i)
j = j + 1 ' increment ServiceArr counter
End If
Next i
Next cell
' resize array up to number of actual Service
ReDim Preserve ServiceArr(1 To j - 1)
End With
Dim ServiceTmp As Variant
' Bubble-sort Service Array >> sorts the Service array from smallest to largest
For i = 1 To UBound(ServiceArr) - 1
For j = i + 1 To UBound(ServiceArr)
If ServiceArr(j) < ServiceArr(i) Then
ServiceTmp = ServiceArr(j)
ServiceArr(j) = ServiceArr(i)
ServiceArr(i) = ServiceTmp
End If
Next j
Next i
' now the "fun" part >> paste to "Sheet2"
With Sheets("Sheet2")
.Range("A1").Value = "ADDRESS"
.Range("B4").Value = FilterVal
.Range("C1").Value = "VEHICLE(S) USED"
' clear contents from previous run
.Range("B12:B17").ClearContents
.Range("B12:B" & UBound(ServiceArr) + 11) = WorksheetFunction.Transpose(ServiceArr)
End With
FilterRng.Parent.AutoFilterMode = False
With Sheets("Sheet1")
' find last row with data in column "A" (Adress)
LastRow = .Cells(.Rows.Count, "A").End(xlUp).Row
Set FilterRng = .Range("A1:C" & LastRow)
.Range("A1").AutoFilter
' AutoFilter "Sheet1" according to value in "Sheet2" in Column B
FilterRng.AutoFilter Field:=1, Criteria1:=FilterVal
Set Dict = CreateObject("Scripting.Dictionary")
' create an array with size up to number of rows >> will resize it later
ReDim VehicleArr(1 To LastRow)
y = 1 ' init array counter
For Each cell In .Range("C2:C" & LastRow).SpecialCells(xlCellTypeVisible)
' read values from cell to array using the Split function
Vehicle = Split(cell.Value, ",")
For x = LBound(Vehicle) To UBound(Vehicle)
Vehicle(x) = Trim(Vehicle(x)) ' remove extra spaces from string
If Not Dict.exists(Vehicle(x)) Then
Dict.Add Vehicle(x), Vehicle(x)
' save Vehicle Name to array >> will use it later for "Bubble-sort" and paste in "Sheet2"
VehicleArr(y) = Vehicle(x)
y = y + 1 ' increment VehicleArr counter
End If
Next x
Next cell
' resize array up to number of actual Vehicle
ReDim Preserve VehicleArr(1 To y - 1)
End With
Dim VehicleTmp As Variant
' Bubble-sort Vehicle Array >> sorts the Vehicle array from smallest to largest
For x = 1 To UBound(VehicleArr) - 1
For y = x + 1 To UBound(VehicleArr)
If VehicleArr(y) < VehicleArr(x) Then
VehicleTmp = VehicleArr(y)
VehicleArr(y) = VehicleArr(x)
VehicleArr(x) = VehicleTmp
End If
Next y
Next x
' now the "fun" part >> paste to "Sheet2"
With Sheets("Sheet2")
.Range("A1").Value = "ADDRESS"
.Range("B4").Value = FilterVal
.Range("C1").Value = "VEHICLE(S) USED"
' clear contents from previous run
.Range("B50:B55").ClearContents
.Range("B50:B" & UBound(VehicleArr) + 49) = WorksheetFunction.Transpose(VehicleArr)
End With
FilterRng.Parent.AutoFilterMode = False
End Sub
I have found that if I enter an address it will give me the desired output. If I edit B4 to change the address to another, it also works. However, when I delete cell B4, I get a message that says "Runtime error 13 Type Mismatch.
When I Debug, it brings me to the line
Call FilterAddress(Target.Value)
How can I change the code so that when cell B4 is deleted, no action is taken and a message appears asking the user to enter an address?
Something like this to include an additional check for B4's value should be enough.
If Not Intersect(Target, Range("B4")) Is Nothing Then
If (Target.Value <> "") Then
Application.EnableEvents = False
Call FilterAddress(Target.Value)
Else
MsgBox Target.Address & " can't be blank, Input a value first."
End If
End If
Just in case you like to do things in the detailed way ....
Private Sub Worksheet_Change(ByVal Target As Range)
Dim strErr As String
If Not Intersect(Target, Range("B4")) Is Nothing Then
If IsTargetValid(Target, strErr) Then
Application.EnableEvents = False
Call FilterAddress(Target.Value)
Else
MsgBox strErr
End If
End If
End Sub
Public Function IsTargetValid(rng As Range, ByRef strErr As String) As Boolean
Dim bResult As Boolean
bResult = True
If bResult And IsError(rng) Then
bResult = False
strErr = rng.Address & " contains error value."
End If
If bResult And rng.Cells.Count <> 1 Then
bResult = False
strErr = rng.Address & " contains invalid number of cells."
End If
If bResult And rng <> "" Then
bResult = False
strErr = rng.Address & " can't be blank, input a value first."
End If
'// Keep adding any other condition you want to check.
IsTargetValid = bResult
End Function
actually your Worksheet_Change() event handler works for me: if I delete cell B4, I just get the "The Appointment # you entered is incorrect or does not exist. Please try again" message. Which is fine.
maybe refactoring your code could help you debugging it
for instance you could
demand array ordering to a specific Sub like the following:
Sub OrderArray(arrayToOrder As Variant)
Dim ServiceTmp As Variant
Dim iRow As Long, iRow2 As Long
' Bubble-sort Service Array >> sorts the passed array from smallest to largest
For iRow = LBound(arrayToOrder) To UBound(arrayToOrder) - 1
For iRow2 = iRow + 1 To UBound(arrayToOrder)
If arrayToOrder(iRow2) < arrayToOrder(iRow) Then
ServiceTmp = arrayToOrder(iRow2)
arrayToOrder(iRow2) = arrayToOrder(iRow)
arrayToOrder(iRow) = ServiceTmp
End If
Next
Next
End Sub
demand the getting of unique and ordered values out of a range to a Function like the following
Function GetOrderedUniqueValuesArrayFromRange(filteredRng As Range) As Variant
Dim cell As Range
Dim arr As Variant
Dim iArr As Variant
With CreateObject("Scripting.Dictionary") '<--| create a late binded 'Dictionary' object "on the fly" - no need for adding any library references to the project
For Each cell In filteredRng
' read values from cell to array using the Split function
arr = Split(cell.value, ",")
For iArr = LBound(arr) To UBound(arr)
arr(iArr) = Trim(arr(iArr)) ' remove extra spaces from string
.item(arr(iArr)) = .item(arr(iArr)) + 1
Next
Next cell
GetOrderedUniqueValuesArrayFromRange = .Keys '<--| the dictionary keys is the wanted array, though not ordered
OrderArray GetOrderedUniqueValuesArrayFromRange '<--| order it
End With '<--| release the no more necessary 'Dictionary' object
End Function
then you can collapse your FilterAddress() sub code as follows:
Sub FilterAddress(FilterVal As String)
Dim FilterRng As Range
Dim VehicleArr As Variant
Dim ServiceArr As Variant
With Sheets("Sheet1") '<--| reference your "data" sheet
With .Range("C1", .Cells(.Rows.Count, "A").End(xlUp)) '<--| reference its columns A:C cells from row 1 down to column A last not empty one
.AutoFilter '<--| remove any previuous filter
.AutoFilter Field:=1, Criteria1:=FilterVal 'filter referenced range on its 1st column with 'FilterVal' value
With .Resize(.Rows.Count - 1).Offset(1).SpecialCells(xlCellTypeVisible) '<--| reference filtered cells skipping header row
ServiceArr = GetOrderedUniqueValuesArrayFromRange(Intersect(.Cells, .Columns(2).EntireColumn)) '<--| fill ServiceArr with unique ordered values from 2nd column of referenced range
VehicleArr = GetOrderedUniqueValuesArrayFromRange(Intersect(.Cells, .Columns(3).EntireColumn)) '<--| fill VehicleArr with unique ordered values from 3nd column of referenced range
End With
End With
.AutoFilterMode = False '<--| show all rows back
End With
' now the "fun" part >> paste to "Sheet2"
With Sheets("Sheet2")
.Range("A1").value = "ADDRESS"
.Range("B4").value = FilterVal
.Range("C1").value = "VEHICLE(S) USED"
.Range("B12:B17").ClearContents ' clear service contents from previous run
.Range("B12").Resize(UBound(ServiceArr) - LBound(ServiceArr) + 1) = WorksheetFunction.Transpose(ServiceArr)
.Range("B50:B55").ClearContents ' clear vehicle contents from previous run
.Range("B50").Resize(UBound(VehicleArr) - LBound(VehicleArr) + 1) = WorksheetFunction.Transpose(VehicleArr)
End With
End Sub
hope this could help you
let me know if you will

Removing duplicate data from columns in Excel

I am having an issue with this code:
Sub text()
Dim iListCount As Integer
Dim x As Variant
Dim iCtr As Integer
' Turn off screen updating to speed up macro.
Application.ScreenUpdating = False
' Get count of records to search through (list that will be deleted).
iListCount = Sheets("sheet2").Cells(Rows.Count, "C").End(xlUp).Row
' Loop through the "master" list.
For Each x In Sheets("Sheet2").Range("A1:A" & Sheets("Sheet2").Cells(Rows.Count, "C").End(xlUp).Row)
' Loop through all records in the second list.
For iCtr = iListCount To 1 Step -1
' Do comparison of next record.
' To specify a different column, change 1 to the column number.
If x.Value = Sheets("Sheet2").Cells(iCtr, 3).Value Then
' If match is true then delete row.
Sheets("Sheet2").Cells(iCtr, 1).EntireRow.Delete
End If
Next iCtr
Next
Application.ScreenUpdating = True
MsgBox "Done!"
End Sub
It runs, and kind of works. It removes one duplicate but leaves all of the others. I am testing this so I'm using a small sample size, so I know that there are 5 duplicates, however I can't get this code to remove them all. Any ideas? I think its an issue with the loop but no matter what I change I can't get it to work
By deleting entire rows in the inner loop you are modifying the range that the outer loop is looping through in the middle of the loop. Such code is difficult to debug.
Your nested loop structure is essentially a series of linear searches. This makes the overall behavior quadratic in the number of rows and can slow the application to a crawl. One approach is to use a dictionary which can be used in VBA if your project includes a reference to Microsoft Scripting Runtime (Tools - References in the VBA editor)
The following sub uses a dictionary to delete all cells in column C which have a value that occurs in column A:
Sub text()
Dim MasterList As New Dictionary
Dim iListCount As Integer
Dim x As Variant
Dim iCtr As Integer
Dim v As Variant
Application.ScreenUpdating = False
' Get count of records in master list
iListCount = Sheets("sheet2").Cells(Rows.Count, "A").End(xlUp).Row
'Load Dictionary:
For iCtr = 1 To iListCount
v = Sheets("sheet2").Cells(iCtr, "A").Value
If Not MasterList.Exists(v) Then MasterList.Add v, ""
Next iCtr
'Get count of records in list to be deleted
iListCount = Sheets("sheet2").Cells(Rows.Count, "C").End(xlUp).Row
' Loop through the "delete" list.
For iCtr = iListCount To 1 Step -1
If MasterList.Exists(Sheets("Sheet2").Cells(iCtr, "C").Value) Then
Sheets("Sheet2").Cells(iCtr, "C").Delete shift:=xlUp
End If
Next iCtr
Application.ScreenUpdating = True
MsgBox "Done!"
End Sub
Another option would be to loop through the cells, use Find and FindNext to find the duplicates and add them to a range using Union(). You could then delete that range at the end of the routine. This solves the problem with deleting rows as you iterate over them, and should execute pretty quickly.
Note: This code is untested, you may need to debug it.
Sub text()
Dim cell As Range
Dim lastCell as Range
Dim masterList as Range
Dim matchCell as Range
Dim removeUnion as Range
Dim firstMatch as String
' Turn off screen updating to speed up macro.
Application.ScreenUpdating = False
With Sheets("sheet2").Range("A:A")
' Find the last cell with data in column A
Set lastCell = .Find("*", .Cells(1,1), xlFormulas, xlPart, xlByRows, xlPrevious)
' Set the master list range to the used cells within column A
Set masterList = .Range(.cells(1,1) , lastCell)
End With
' Loop through the "master" list.
For Each cell In masterList
' Look for a match anywhere within column "C"
With cell.Parent.Range("C:C")
Set matchCell = .Find(.Cells(1,1), cell.Value, xlValues, xlWhole, xlByRows)
'If we got a match, add it to the range to be deleted later and look for more matches
If Not matchCell is Nothing then
'Store the address of first match so we know when we are done looping
firstMatch = matchCell.Address
'Look for all duplicates, add them to a range to be deleted at the end
Do
If removeUnion is Nothing Then
Set removeUnion = MatchCell
Else
Set removeUnion = Application.Union(removeUnion, MatchCell)
End If
Set MatchCell = .FindNext
Loop While (Not matchCell Is Nothing) and matchCell.Address <> firstMatch
End If
'Reset the variables used in find before next loop
firstMatch = ""
Set matchCell = Nothing
End With
Next
If Not removeUnion is Nothing then removeUnion.EntireRow.Delete
Application.ScreenUpdating = True
MsgBox "Done!"
End Sub

Excel Macro, read a worksheet, remove lines with no data based off value in a column

I'm trying to read a column, which has a numerical value, to indicate whether or not to search that row to see if there is any data contained within the specified range of that row. If there is no data contained within the range, select that row to be deleted. There will be many rows to be deleted once it has looped through the worksheet.
For example, in column "C" when the value "0" is found, search that row to see if there is any data contained in the cells, the cell range to search for empty cells in that row is D:AM. If the cells in the range are empty, then select that row and delete it. The entire row can be deleted. I need to do this for the entire worksheet, which can contain up to 20,000 rows. The problem I'm having is getting the macro to read the row, once the value 0 is found, to determine if the range of cells(D:AM) are empty. Here is the code I have thus far:
Option Explicit
Sub DeleteBlankRows()
'declare variables
Dim x, curVal, BlankCount As Integer
Dim found, completed As Boolean
Dim rowCount, rangesCount As Long
Dim allRanges(10000) As Range
'set variables
BlankCount = 0
x = 0
rowCount = 2
rangesCount = -1
notFirst = False
'Select the starting Cell
Range("C2").Select
'Loop to go down Row C and search for value
Do Until completed
rowCount = rowCount + 1
curVal = Range("C" & CStr(rowCount)).Value
'If 0 is found then start the range counter
If curVal = x Then
found = True
rangesCount = rangesCount + 1
'reset the blanks counter
BlankCount = 0
'Populate the array with the correct range to be selected
Set allRanges(rangesCount) = Range("D" & CStr(rowCount) & ":AM" & CStr(rowCount))
ElseIf (found) Then
'if the cell is blank, increment the counter
If (IsEmpty(Range("I" & CStr(rowCount)).Value)) Then BlankCount = BlankCount + 1
'if counter is greater then 20, reached end of document, stop selection
If BlankCount > 20 Then Exit Do
End If
'In the safest-side condition to avoid an infinite loop in case of not of finding what is intended.
If (rowCount >= 25000) Then Exit Do
Loop
If (rangesCount > 0) Then
'Declare variables
Dim curRange As Variant
Dim allTogether As Range
'Set variables
Set allTogether = allRanges(0)
For Each curRange In allRanges
If (Not curRange Is Nothing) Then Set allTogether = Union(curRange, allTogether)
Next curRange
'Select the array of data
allTogether.Select
'delete the selection of data
'allTogether.Delete
End If
End Sub
The end of the document is being determined by Column C when it encounters 20 or more blank cells the worksheet has reached its end. Thanks in advance for your input!
This should work for you. I have commented the code to help give it clarity:
Sub DeleteBlankRows()
Dim rngDel As Range
Dim rngFound As Range
Dim strFirst As String
'Searching column C
With Columns("C")
'Find "0" in column C
Set rngFound = .Find(0, .Cells(.Cells.Count), xlValues, xlWhole)
If Not rngFound Is Nothing Then
'Remember first one found
strFirst = rngFound.Address
Do
'Check if there is anything within D:AM on the row of this found cell
If WorksheetFunction.CountA(Intersect(rngFound.EntireRow, .Parent.Range("D:AM"))) = 0 Then
'There is nothing, add this row to rngDel
Select Case (rngDel Is Nothing)
Case True: Set rngDel = rngFound
Case Else: Set rngDel = Union(rngDel, rngFound)
End Select
End If
'Find next "0"
Set rngFound = .Find(0, rngFound, xlValues, xlWhole)
'Advance loop; exit when back to the first one
Loop While rngFound.Address <> strFirst
End If
End With
'Delete all rows added to rngDel (if any)
If Not rngDel Is Nothing Then rngDel.EntireRow.Delete
End Sub

Excel Macro, read a worksheet, select range of data, copy selection

I need to write a macro that reads a worksheet of GeoTechnical data, selects the data based off a value in a particular row, select that row and continue reading until the end of worksheet. Once all rows are selected, I then need to copy those rows into a new worksheet. I haven't done VBA in about 10 years, so just trying to get back into things.
For example, I want the macro to read the worksheet, when column "I" contains the word "Run" on a particular row, I want to then select from that row, A:AM. Continue reading through the worksheet until the end of it. The end of the document is tricky as there are up to 10-15 blank rows sometimes in between groups of data in the worksheet. If there is more then 25 blank rows, then the document would be at the end. Once everything is selected, I then need to copy the selection for pasting into a new worksheet. Here is the code I have thus far, but I'm unable to get a selection:
Option Explicit
Sub GeoTechDB()
Dim x As String
Dim BlankCount As Integer
' Select first line of data.
Range("I2").Select
' Set search variable value and counter.
x = "Run"
BlankCount = 0
' Set Do loop to read cell value, increment or reset counter and stop loop at end 'document when there
' is more then 25 blank cells in column "I", copy final selection
Do Until BlankCount > 25
' Check active cell for search value "Run".
If ActiveCell.Value = x Then
'select the range of data when "Run" is found
ActiveCell.Range("A:AM").Select
'set counter to 0
BlankCount = 0
'Step down 1 row from present location
ActiveCell.Offset(1, 0).Select
Else
'Step down 1 row from present location
ActiveCell.Offset(1, 0).Select
'if cell is empty then increment the counter
BlankCount = BlankCount + 1
End If
Loop
End Sub
I see various things wrong with your code. If I understood properly what you want, this code should deliver it:
' Set Do loop to read cell value, increment or reset counter and stop loop at end 'document when there
' is more then 25 blank cells in column "I", copy final selection
Dim x As String
Dim BlankCount As Integer
Range("I2").Select
x = "Run"
BlankCount = 0
Dim found As Boolean
Dim curVal As String
Dim rowCount As Long
Dim completed As Boolean
rowCount = 2
Dim allRanges(5000) As Range
Dim rangesCount As Long
rangesCount = -1
notFirst = False
Do Until completed
rowCount = rowCount + 1
curVal = Range("I" & CStr(rowCount)).Value
If curVal = x Then
found = True
  BlankCounter = 0
rangesCount = rangesCount + 1
Set allRanges(rangesCount) = Range("A" & CStr(rowCount) & ":AM" & CStr(rowCount))
ElseIf (found) Then
If (IsEmpty(Range("I" & CStr(rowCount)).Value)) Then BlankCount = BlankCount + 1
If BlankCount > 25 Then Exit Do
End If
If (rowCount >= 5000) Then Exit Do 'In the safest-side condition to avoid an infinite loop in case of not of finding what is intended. You can delete this line
Loop
If (rangesCount > 0) Then
Dim curRange As Variant
Dim allTogether As Range
Set allTogether = allRanges(0)
For Each curRange In allRanges
If (Not curRange Is Nothing) Then Set allTogether = Union(curRange, allTogether)
Next curRange
allTogether.Select
End If
It starts iterating through column I from I2, until finding the word "Run". In this moment, it starts to count cells until reaching 25 (when the loop is exited and the corresponding range, as defined by the last row and the one at "Run", is selected). You are talking about blank cells but your code does not check that, also I am not sure what to do in case of finding a non-blank cell (restarting the counter?). Please, elaborate more on this.
Sub GeoTechDB()
Const COLS_TO_COPY As Long = 39
Dim x As String, c As Range, rngCopy As Range
Dim BlankCount As Integer
Set c = Range("I2")
x = "Run"
BlankCount = 0
Do Until BlankCount > 25
If Len(c.Value) = 0 Then
BlankCount = BlankCount + 1
Else
BlankCount = 0
If c.Value = x Then
If rngCopy Is Nothing Then
Set rngCopy = c.EntireRow.Cells(1) _
.Resize(1, COLS_TO_COPY)
Else
Set rngCopy = Application.Union(rngCopy, _
c.EntireRow.Cells(1) _
.Resize(1, COLS_TO_COPY))
End If
End If
End If
Set c = c.Offset(1, 0)
Loop
If Not rngCopy Is Nothing Then rngCopy.Copy Sheet2.Range("A2")
End Sub
i like short codes:
Sub column_I_contains_run()
If ActiveSheet.FilterMode Then Selection.Autofilter 'if an autofilter already exists this is removed
ActiveSheet.Range("$I$1:$I$" & ActiveSheet.Cells(1048576, 9).End(xlUp).Row).Autofilter Field:=1, Criteria1:="*run*"
Range("A1:AM" & ActiveSheet.Cells(1048576, 9).End(xlUp).Row).Select
Selection.SpecialCells(xlCellTypeVisible).Select
Selection.Copy
End Sub
now you just have to paste it into a new sheet, what could be automated also...