How to find duplicates in a column, looping through multiple sheets - vba

I have been trying to write a piece of vba code, so that I can find all of the duplicates in a column, highlight them in red and bring up a message box listing all those duplicated;
and I want the code to do this for column C across multiple sheets. This is essentially to replace conditional formatting, as it was slowing down the workbook about 8 seconds.
This is what I have so far, but it isn't really working.
Sub FindDuplicates()
Sheetcounter = 0
Set MyData = Worksheets("Sheet1").Range("C1:C" & Cells(Rows.Count, "C").End(xlUp).Row)
Do Until Sheetcounter = 3
Set MyUniqueList = CreateObject("Scripting.Dictionary")
MyUniqueList.RemoveAll
Range(Cells(1, 1), Cells(5000, 1)).Interior.Color = xlNone
Application.ScreenUpdating = False
MyDupList = "": MyCounter = 0
For Each Cell In MyData
If Evaluate("COUNTIF(" & MyData.Address & "," & Cell.Address & ")") > 1 Then
If Cell.Value <> "" Then
Cell.Interior.Color = RGB(255, 80, 80)
If MyUniqueList.exists(CStr(Cell)) = False Then
MyCounter = MyCounter + 1
MyUniqueList.Add CStr(Cell), MyCounter
If MyDupList = "" Then
MyDupList = Cell
Else
MyDupList = MyDupList & vbNewLine & Cell
End If
End If
End If
Else
Cell.Interior.ColorIndex = xlNone
End If
Next Cell
Application.ScreenUpdating = True
If MyDupList <> "" Then
MsgBox "The following entries have been used more than once:" & vbNewLine & MyDupList
Else
MsgBox "There were no duplicates found in " & MyData.Address
End If
Sheetcounter = Sheetcounter + 1
If Sheetcounter = 1 Then
Set MyData = Worksheets("Sheet2").Range("C1:C" & Cells(Rows.Count, "C").End(xlUp).Row)
End If
If Sheetcounter = 2 Then
Set MyData = Worksheets("Sheet3").Range("C1:C" & Cells(Rows.Count, "C").End(xlUp).Row)
End If
Loop
End Sub

you can simplify your sub like follows:
Option Explicit
Sub FindDuplicates()
Dim sheetCounter As Long
Dim myData As Range, cell As Range
Dim myUniqueList As Scripting.Dictionary
Set myUniqueList = CreateObject("Scripting.Dictionary")
For sheetCounter = 1 To 3
myUniqueList.RemoveAll
With Worksheets("Sheet00" & sheetCounter)
Set myData = .Range("C1", .Cells(.Rows.Count, "C").End(xlUp))
End With
myData.Interior.Color = xlNone
For Each cell In myData.SpecialCells(xlCellTypeConstants)
If WorksheetFunction.CountIf(myData, cell) > 1 Then
cell.Interior.Color = RGB(255, 80, 80)
If Not myUniqueList.Exists(CStr(cell)) Then myUniqueList.Add CStr(cell), myUniqueList.Count + 1
End If
Next cell
If myUniqueList.Count > 0 Then
MsgBox "The following entries have been used more than once:" & vbNewLine & Join(myUniqueList.Keys, vbNewLine)
Else
MsgBox "There were no duplicates found in " & myData.Address
End If
Next sheetCounter
End Sub

Related

How do I search through a sheet of data and return MULTIPLE matching results on another sheet?

This is my desired flow:
On "Sheet2" you can select a macro "Search by first name"
You see a popup to enter a name, you enter a name (X) and select ok
It will search the next sheet, "Master", and look for results where first name = X
and finally return these results back on "Sheet2"
Here's a screenshot of the two sheets:
Sheet 2
and
Master
The following VB code means that it only returns 1 result when there should be multiple sometimes:
Sub Searchbyfirstname()
Dim wks As Excel.Worksheet
Dim rCell As Excel.Range
Dim fFirst As String
Dim i As Long
Dim MyVal As String
MyVal = InputBox("Enter the first name of the employees record you need", "Search By First Name", "")
If MyVal = "" Then Exit Sub
Application.ScreenUpdating = False
Application.DisplayAlerts = False
With Cells(5, 1)
.Value = "The below data has been found for " & MyVal & ":"
.EntireColumn.AutoFit
.HorizontalAlignment = xlCenter
End With
i = 2
For Each wks In ActiveWorkbook.Worksheets
If wks.Name <> "List" Then
With wks.Range("B:B")
Set rCell = .Find(MyVal, , , xlWhole, xlByColumns, xlNext, False)
If Not rCell Is Nothing Then
fFirst = rCell.Address
Do
rCell.Hyperlinks.Add Cells(6, 1), "", "'" & wks.Name & "'!" & rCell.Address
wks.Range("A" & rCell.Row & ":Z" & rCell.Row).Copy Destination:=Cells(6, 1)
Set rCell = .FindNext(rCell)
i = i + 3
Loop While Not rCell Is Nothing And rCell.Address <> fFirst
End If
End With
End If
Next wks
Set rCell = Nothing
If i = 2 Then
MsgBox "No record for " & MyVal & " has been found", 64, "No Matches"
Cells(1, 1).Value = ""
End If
Application.ScreenUpdating = True
Application.DisplayAlerts = True
End Sub
Any help would be very much appreciated, thanks!
Ok so I am pretty sure I have the answer now that Maertin and chris neilsen pointed out the errors with hardcoding.
I have posted my code again but the points where I have added or changed are not code (didn't know the best way to format this):
Sub Searchbyfirstname()
Dim wks As Excel.Worksheet
Dim rCell As Excel.Range
Dim fFirst As String
Dim i As Long
Dim MyVal As String
MyVal = InputBox("Enter the first name of the employees record you need", "Search By First Name", "")
If MyVal = "" Then Exit Sub
Application.ScreenUpdating = False
Application.DisplayAlerts = False
With Cells(5, 1)
.Value = "The below data has been found for " & MyVal & ":"
.EntireColumn.AutoFit
.HorizontalAlignment = xlCenter
End With
i = 2
For Each wks In ActiveWorkbook.Worksheets
If wks.Name <> "List" Then
With wks.Range("B:B")
Set rCell = .Find(MyVal, , , xlWhole, xlByColumns, xlNext, False)
If Not rCell Is Nothing Then
fFirst = rCell.Address
Dim x As Integer
x = 6
With Sheets("Sheet2")
.Rows(6 & ":" & .Rows.Count).Delete
End With
' for this part I have created the variable x, then I'm assigning this 6 because that's the first row I want to put the data in, then I am saying if there's anything in row 6 or below, delete it
Do
rCell.Hyperlinks.Add Cells(x, 1), "", "'" & wks.Name & "'!" & rCell.Address
'see this and row below, instead of being Cells(6, 1), it is now x and this means it will paste to 6, then if there's another 7 and so on
wks.Range("A" & rCell.Row & ":Z" & rCell.Row).Copy Destination:=Cells(x, 1)
Set rCell = .FindNext(rCell)
i = i + 3
x = x + 1
' Here I am incrementing x by 1 so that if there's another piece of data to paste it will paste in the next row - on first go this would be row 7
Loop While Not rCell Is Nothing And rCell.Address <> fFirst
End If
End With
End If
Next wks
Set rCell = Nothing
If i = 2 Then
MsgBox "No record for " & MyVal & " has been found", 64, "No Matches"
Cells(1, 1).Value = ""
With Sheets("Sheet2")
.Rows(5 & ":" & .Rows.Count).Delete
End With
End If
Application.ScreenUpdating = True
Application.DisplayAlerts = True
End Sub

Empty rows not showing as empty rows using CountA

Why does the following VBA script not show any message boxes when row 4, 5 and 6 are all empty...
Sub Test()
LastRow = 40
For i = LastRow To 3 Step -1
Set myRange = Range("B" & i & ":T" & i)
If WorksheetFunction.CountA(myRange) = 0 Then
MsgBox "Empty " & Cells(i, 1).Row
Else
x = x
End If
Next
End Sub
Just test both column ranges:
Sub Test()
LastRow = 40
For i = LastRow To 3 Step -1
count = WorksheetFunction.CountA(Range("B"&i & ":D"&i))
count = count + WorksheetFunction.CountA(Range("F"&i & ":T"&i))
If count = 0 Then
MsgBox "Empty " & i
End If
Next
End Sub
edit: or build a range object which contains the two column ranges, intersect that with the last row, and move this range object in the loop. This way, you don't build the range object anew in each iteration:
Sub Test()
Dim rng As Range, colrng As Range
Dim LastRow As Long
Dim i As Long
LastRow = 40
Set colrng = Application.Union(Range("B:D"), Range("F:T"))
Set rng = Application.Intersect(colrng, Rows(LastRow))
For i = LastRow To 3 Step -1
If WorksheetFunction.CountA(rng) = 0 Then
MsgBox "Empty row: " & i
End If
Set rng = rng.Offset(-1, 0)
Next
End Sub
As good practice, always declare your variables, and use long integers for row or column indices.
Sub Test()
LastRow = 40
For i = LastRow To 3 Step -1
Set myRange = Range("B" & i & ":T" & i)
If WorksheetFunction.CountIf(myRange,"<>") = 0 Then 'count where it's not a null or empty string
MsgBox "Empty " & Cells(i, 1).Row
Else
x = x
End If
Next
End Sub
The only way I can seem to do it is a slow way:
LastRow = Range("B:Z").Find(What:="*", LookIn:=xlValues, SearchOrder:=xlByRows, SearchDirection:=xlPrevious).Row
LastColumn = Cells.Find(What:="*", LookIn:=xlFormulas, SearchOrder:=xlByColumns, SearchDirection:=xlPrevious).Column
For i = LastRow To 3 Step -1
BlankRow = False
For j = 2 To LastColumn
If Cells(i, j).Value <> "" Then
Blank = False
Exit For
End If
BlankRow = True
Next j
If BlankRow = True Then
x = x
End If
Next i

add value if loop doesn't show value within range

With the following code I loop through a range of about 200 cells [Range("R" & i)] to check if the Temp_Sheet.Range("B2") value is within the range. This code below works great.
Here's the problem: If the loop completes and doesn't find the value within the range, I need the B2 value added to the next blank row. How would I go about doing this? Thanks in advance for the help. Can I add formatting within the loop that will trigger another sub/function if no value is found?
For i = 6 To ColarLastRow
If ChangeSheet.Range("R" & i).Value = Temp_Sheet.Range("B2") Then
ChangeSheet.Range("G" & i) = Sum_Range
ChangeSheet.Range("G" & i).Interior.Color = RGB(0, 100, 0)
Else: End If
Next i
You need to set some flag inside a loop:
Dim AddNew As Boolean
AddNew = True
For i = 6 To ColarLastRow
If ChangeSheet.Range("R" & i).Value = Temp_Sheet.Range("B2") Then
ChangeSheet.Range("G" & i) = Sum_Range
ChangeSheet.Range("G" & i).Interior.Color = RGB(0, 100, 0)
AddNew = False
Else: End If
Next i
If AddNew Then
'place your code here
End If
Looks like there are already some good answers, but this is what I came up with....
Dim ChangeSheet As Worksheet
Dim Temp_Sheet As Worksheet
Dim ValueFound As Boolean
Set ChangeSheet = Sheets("ChangeSheet")
Set Temp_Sheet = Sheets("Temp_Sheet")
ColarLastRow = ChangeSheet.Cells(Rows.Count, 18).End(xlUp).Row
ValueFound = False
For i = 6 To ColarLastRow
If ChangeSheet.Range("R" & i).Value = Temp_Sheet.Range("B2") Then
ChangeSheet.Range("G" & i) = Sum_Range
ChangeSheet.Range("G" & i).Interior.Color = RGB(0, 100, 0)
ValueFound = True
Else: End If
Next i
If ValueFound = False Then
ChangeSheet.Range("R" & ColarLastRow + 1).Value = Temp_Sheet.Range("B2").Value
End If
Dim counter as Integer
counter = 0
For i = 6 To ColarLastRow
If ChangeSheet.Range("R" & i).Value = Temp_Sheet.Range("B2") Then
ChangeSheet.Range("G" & i) = Sum_Range
ChangeSheet.Range("G" & i).Interior.Color = RGB(0, 100, 0)
Counter = counter + 1
Else: End If
Next i
If counter = 0 then
ChangeSheet.Range("R" & ChangeSheet.Range("R" & ChangeSheet.Rows.Count).End(xlUp).Offset(1,0) = Temp_Sheet.Range("B2")
End If
dim Rng as Range
Set rng = ChangeSheet.Range("R6:R" & ColarLastRow).Find _
(What:=Temp_Sheet.Range("B2"), LookIn:=xlValues)
'set LookIn:=xlValues, xlFormulas, as appropriate
'set MatchCase:=True or False, as appropriate
If Rng is Nothing then
ChangeSheet.Range("R" & ColarLastRow+1).Value = 'something
'set color here
Else
ChangeSheet.Range("G" & rng.row) = Sum_Range
End if
Using the .Find will be much faster than looping through. If you need to find multiple occurrences, then after Set Rng = add this:
Set rng = ChangeSheet.Range("R6:R" & ColarLastRow).Find _
(What:=Temp_Sheet.Range("B2"), LookIn:=xlValues)
While Not Rng is Nothing
'do your stuff here
Set rng = ChangeSheet.Range("R" & Rng.Row & ":R" & ColarLastRow).Find _
(What:=Temp_Sheet.Range("B2"), LookIn:=xlValues)
Wend

Do while loop doesnt work on big data file

I have big data file on excel, the file has 6930 rows and 8 columns,
the 8 column has percents (0%, 4%, 16%, 18%, 19% and etc..)
I tried to do a macro that paint all the rows that the percent in them are bigger then 18%, and it doesn't work.
The file start from row 3, so rows 1 and 2 are empty
The macro:
Sub Test_4
Dim i As Long
Dim countErr As Long
countErr = 0
i = 2
Do While Cells(i, 1) = ""
If Cells(i, 8).Value > 0.18 And IsNumeric(Cells(i, 8)) Then
Range(Cells(i, 1), Cells(i, 8)).Interior.ColorIndex = 3
countErr = countErr + 1
End If
i = i + 1
Loop
If countErr > 0 Then
Sheets("test").Select
Range("E8").Select
Selection.Interior.ColorIndex = 3
Range("D8").Select
Selection.FormulaR1C1 = countErr
Else
Sheets("test").Select
Range("E8").Select
Selection.Interior.ColorIndex = 4
Sheets("test").Range("d8") = "0"
End If
End Sub
A Do While loop might be a bad idea if Column H ever has a blank value part way down, instead you could do this (This will add conditional formatting to each line):
Given this input:
Sub testit()
Dim LastRow As Long, CurRow As Long, countErr As Long
LastRow = Range("H" & Rows.Count).End(xlUp).Row
Cells.FormatConditions.Delete
With Range("A3:H" & LastRow)
.FormatConditions.Add Type:=xlExpression, Formula1:="=$H3>0.18"
.FormatConditions(.FormatConditions.Count).SetFirstPriority
.FormatConditions(1).Interior.ColorIndex = 3
.FormatConditions(1).StopIfTrue = False
End With
countErr = 0
Dim cel As Range
For Each cel In Sheets("NAME OF SHEET").Range("H3:H" & LastRow)
If cel.Value > 0.18 Then
countErr = countErr + 1
End If
Next cel
MsgBox "There are " & countErr & " rows greater than 18%"
End Sub
Running the code gives:
Error Testing:
Sub ErrorTesting()
Dim cel As Range, countErr As Long
countErr = 0
LastRow = Range("H" & Rows.Count).End(xlUp).Row
For Each cel In Range("H3:H" & LastRow)
On Error GoTo ErrHandle
If Not IsNumeric(cel.Value) Then
MsgBox cel.Address & " is the address of the non-numeric Cell"
End If
If cel.Value > 0.18 And IsNumeric(cel.Value) Then
countErr = countErr + 1
End If
Next cel
ErrHandle:
If Not cel Is Nothing Then
MsgBox cel.Address & " is the address and " & cel.Value & " is the value of the Error Cell"
End If
MsgBox countErr
End Sub
Try this (updated for error count):
Sub test()
Count = 0
i = 2
While Not IsEmpty(Cells(i, 8))
If Cells(i, 8).Value > 0.18 Then
Range(Cells(i, 1), Cells(i, 8)).Interior.ColorIndex = 3
Count = Count + 1
End If
i = i + 1
Wend
//rows count bigger than 18% in worksheet "test"
Worksheets("test").Cells(1, 1).Value = "Rows count bigger than 18%"
Worksheets("test").Cells(1, 2).Value = Count
End Sub

Copying Selective Rows from Sheet1 to Sheet2

Hi all I need to selectively copy entire rows from sheet1 to other sheet. As of now I am using checkboxes to select the rows and then copy the selected rows to sheet of user's choice. But I am facing a bizarre error. For sometime the code runs fine, copying exact data to sheets but after some time it copies erroneous values from nowhere. Can you please help me with this? Pasting the code I am using.
Sub Addcheckboxes()
Dim cell, LRow As Single
Dim chkbx As CheckBox
Dim MyLeft, MyTop, MyHeight, MyWidth As Double
Application.ScreenUpdating = False
LRow = ActiveSheet.Range("A" & Rows.Count).End(xlUp).Row
For cell = 2 To LRow
If Cells(cell, "A").Value <> "" Then
MyLeft = Cells(cell, "E").Left
MyTop = Cells(cell, "E").Top
MyHeight = Cells(cell, "E").Height
MyWidth = Cells(cell, "E").Width
ActiveSheet.CheckBoxes.Add(MyLeft, MyTop, MyWidth, MyHeight).Select
With Selection
.Caption = ""
.Value = xlOff
.Display3DShading = False
End With
End If
Next cell
Application.ScreenUpdating = True
End Sub
Sub RemoveCheckboxes()
Dim chkbx As CheckBox
For Each chkbx In ActiveSheet.CheckBoxes
chkbx.Delete
Next
End Sub
Sub CopyRows()
Dim Val As String
Val = InputBox(Prompt:="Sheet name please.", _
Title:="ENTER SHEET NAME", Default:="Sheet Name here")
For Each chkbx In ActiveSheet.CheckBoxes
If chkbx.Value = 1 Then
For r = 1 To Rows.Count
If Cells(r, 1).Top = chkbx.Top Then
With Worksheets(Val)
LRow = .Range("A" & Rows.Count).End(xlUp).Row + 1
.Range("A" & LRow & ":AF" & LRow) = _
Worksheets("Usable_Inv_Data").Range("A" & r & ":AF" & r).Value
End With
Exit For
End If
Next r
End If
Next
End Sub
Normal Copy Output:
Erroneous Copy Output for same values:
Doing a quick comparison of the normal and the erroneous outputs, it looks like some of your cells/columns are not formatted correctly in your destination sheet (where you are "pasting" the values).
For example, your Base Change column in the Normal copy (the value 582.16) is formatted as a General or Number. The same column in the destination sheet is formatted as a date (582.16 converted to a date value in Excel will be 8/4/1901, or 8/4/01, as shown in your screen.
Just make sure the columns are formatted to display the data type you expect. On your destination sheet, select the column, right-click "Format Cells", and then select the appropriate data type.
---EDIT---
To automate the formatting, you would have to copy and paste the values, inclusive of the formats. Your code would change from this:
With Worksheets(Val)
LRow = .Range("A" & Rows.Count).End(xlUp).Row + 1
.Range("A" & LRow & ":AF" & LRow) = _
Worksheets("Usable_Inv_Data").Range("A" & r & ":AF" & r).Value
End With
TO
With Worksheets(Val)
LRow = .Range("A" & Rows.Count).End(xlUp).Row + 1
Worksheets("Usable_Inv_Data").Range("A" & r & ":AF" & r).Copy
.Range("A" & LRow).PasteSpecial (xlPasteValuesAndNumberFormats)
End With
I have added the checkbox with LinkedCell property. This helps to identify the rows when checkbox is checked.
Also i have added a function check_worksheet_exists which will check if the workbook exist.
Sub Addcheckboxes()
Dim cell, LRow As Single
Dim chkbx As CheckBox
Dim MyLeft, MyTop, MyHeight, MyWidth As Double
Application.ScreenUpdating = False
LRow = ActiveSheet.Range("A" & Rows.Count).End(xlUp).row
For cell = 2 To LRow
If Cells(cell, "A").Value <> "" Then
MyLeft = Cells(cell, "E").Left
MyTop = Cells(cell, "E").Top
MyHeight = Cells(cell, "E").Height
MyWidth = Cells(cell, "E").Width
ActiveSheet.CheckBoxes.Add(MyLeft, MyTop, MyWidth, MyHeight).Select
With Selection
.Caption = ""
.Value = xlOff
.Display3DShading = False
.LinkedCell = Cells(cell, "AZ").Address
End With
End If
Next cell
Application.ScreenUpdating = True
End Sub
Sub RemoveCheckboxes()
Dim chkbx As CheckBox
For Each chkbx In ActiveSheet.CheckBoxes
chkbx.Delete
Next
End Sub
Sub CopyRows()
Dim Val As String
Dim row As Long
Val = InputBox(Prompt:="Sheet name please.", Title:="ENTER SHEET NAME", Default:="Sheet Name here")
If check_worksheet_exists(ThisWorkbook, Val, False) = False Then
Exit Sub
End If
For Each chkbx In ActiveSheet.CheckBoxes
If chkbx.Value = 1 Then
row = Range(chkbx.LinkedCell).row
With Worksheets(Val)
LRow = .Range("A" & Rows.Count).End(xlUp).row + 1
.Range("A" & LRow & ":AF" & LRow) = ActiveSheet.Range("A" & row & ":AF" & row).Value
End With
End If
Next
End Sub
Function check_worksheet_exists(tBook As Workbook, ByVal check_sheet As String, Optional no_warning As Boolean = False) As Boolean
On Error Resume Next
Dim wkSht As Worksheet
Set wkSht = tBook.Sheets(check_sheet)
If Not wkSht Is Nothing Then
check_worksheet_exists = True
ElseIf wkSht Is Nothing And no_warning = False Then
MsgBox "'" & check_sheet & "' sheet does not exist", vbCritical, "Error"
End If
On Error GoTo 0
End Function
i cannot immediately see the errors you refer to, unless you are referring to the sequences of hash-signs ###? These just indicate that the columns aren't wide enough.
Worksheets(Val).Range("A1").CurrentRegion.EntireColumn.AutoFit
BTW I don't think Val is a sensible variable name ;)