Finding Max Date from "Find" Range - excel

I have a variety of calibration tests. I keep all different types and their dates in one worksheet "wsCAL"
I want to populate the userform with the most recent date of one specific type of test, which is stored in Column C in wsCAL.
In theory, I want VBA to go to wsCAL, look thru column C and find all instances of one test type, find the most recent date(or MAX) in column B of those instances, then populate my userform with that date.
I've tried using the rangeCAL = .Find() function to find all instances of a test type in column C. This part works just fine. However, the application.worksheetfunction.Max(rangeCAL) I try to use fails. I'm guessing it is because that application function only works with worksheet ranges and not Find() ones. I'm struggling with taking my rangeCAL cells, making an array, then finding the most recent date (the MAX) of those.
Private Sub UserForm_Initialize() 'Upon opening the userform
Set wb = ThisWorkbook
Set wsHOME = wb.Worksheets("Home")
Set wsCAL = wb.Worksheets("Bottle Calibrations")
Set wsC1T1 = wb.Worksheets("C1T1")
'Last Calibration Date
Label27.Caption = vbNullString
With wsCAL
Dim Cell As Range
Dim myArray As Date
Dim i As Integer
Dim rangeCAL As Range
Dim rangeDateCAL As Date
i = 0
Set rangeCAL = Range("C:C").Find(What:=tank, LookAt:=xlWhole)
If Not rangeCAL Is Nothing Then
For Each Cell In rangeCAL
myArray(i) = .Range(rangeCAL.Row, "A").Value
i = i + 1
Next
Else
MsgBox "Error: no previous Calibration dates loaded."
End If
rangeDateCAL = Application.WorksheetFunction.Max(myArray)
rangeDateCAL = Format(rangeDateCAL, "yymmdd")
End With
Label27.Caption = rangeDateCAL
I keep getting the error message
"Expected array"
as soon as I get to line:
myArray(i) = .Range(rangeCAL.Row, "B").Value
UPDATE:
Label27.Caption = vbNullString
With wsCAL
Dim Cell As Range
Dim myArray(1 To 5) As Date
Dim i As Long
Dim temp As Date
Dim rangeCAL As Range
Dim rangeDateCAL As Date
i = 1
Set rangeCAL = wsCAL.Range("C1", Range("C1").End(xlDown).Address)
For Each Cell In rangeCAL
If Cell <> "" Then
If Cell.Value = tank Then
temp = wsCAL.Cells(Cell.Row, "B").Value
myArray(i) = temp
i = i + 1
End If
End If
Next
rangeDateCAL = Application.WorksheetFunction.Max(myArray)
rangeDateCAL = Format(rangeDateCAL, "yymmdd")
End With
Label27.Caption = rangeDateCAL
I implemented this change after reading your comments. This code runs, but it fills Label27.Caption with 11/22/4613 instead of the intended 11/7/2019.
I'm assuming the date value is being altered at the MAX function step, but I'm not sure what else I can change.

For Each Cell In rangeCAL
If Cell.Text <> vbNullString Then
If Cell.Text = tank Then 'assuming tank is declared a string
If tempDate < wsCAL.Cells(Cell.Row, "B").Value Then
tempDate = wsCAL.Cells(Cell.Row, "B").Value
End If
End If
End If
Next
Label27.Caption = Format(tempDate, "yymmdd")

This is what I implemented, per SmileyFTW's suggestion. Far simpler than anticipated. Works as intended though. Thank you SmileyFTW, and the others who commented with help.
Label27.Caption = vbNullString
With wsCAL
Dim Cell As Range
Dim i As Date
Dim temp As Date
Dim rangeCAL As Range
temp = 0
Set rangeCAL = wsCAL.Range("C1", Range("C1").End(xlDown).Address)
For Each Cell In rangeCAL
If Cell <> vbNullString Then
If Cell.Value = tank Then
i = wsCAL.Cells(Cell.Row, "B").Text
If i > temp Then
temp = i
End If
End If
End If
Next
End With
Label27.Caption = temp

Related

Loops works, but causes program to freeze

I have this small piece of code.
Sub FillRemainingDates()
Dim SearchRange As Range
Set SearchRange = Sheets(1).UsedRange.Columns(11)
Dim cell As Range
For Each cell In SearchRange
If cell.Value = vbNullString And cell.Offset(0, 9).Value = cell.Offset(1, 9).Value Then
cell = cell.Offset(1, 0).Value
End If
Next
End Sub
Its goal is to attribute a value to a cell in a column depending on the value of a cell a row below:
The macro "works" in the sense that it does what I expect it to do, but whenever I run it it causes the program to become unresponsive and freeze for a long time.
I think I'm missing something in the loop and it's causing the program to fall into an infinite loop or have to deal with more data than necessary.
You should use an array to work on - and then write that back to the sheet.
Sub FillRemainingDates()
'ASSUMPTION: data start in column A
'>>> change the names according to your project
Const colNameOfColumn11 As Long = 11
Const colNameOfColumn20 As Long = 20
Dim arrSearch As Variant
arrSearch = Worksheets(1).UsedRange
Dim i As Long
Dim varValue As Variant '>>>> change type according to your values
For i = 1 To UBound(arrSearch, 1) - 1 ' don't check last row
varValue = arrSearch(i, colNameOfColumn11)
If varValue = vbNullString And _
arrSearch(i, colNameOfColumn20) = arrSearch(i + 1, colNameOfColumn20) Then
arrSearch(i, colNameOfColumn11) = arrSearch(i + 1, colNameOfColumn11)
End If
Next
Worksheets(1).UsedRange.Value = arrSearch
End Sub

VBA Error 91 , after calling Sub within the main Sub

I have a VBA Module that is trying to get all occurrences of a date in column G in one sheet. After finding the row of the occurrence, I'm saving other values from the sheet on the same row from different columns, i.e. bldg, and room, and numPerson.
What I'm trying to do after that is to get the call a function with the date, numPerson, bldg and room as arguments. The function FillDateCapacitiesInOccupancySheet should go to a different sheet and search column B for the bldg, get that row, then look for the room on the same row and place the numPpl on the row under the correct column for the date that was given. In the following code I get an Error 91 on the Loop While statement if I remove
If FoundCell Is Nothing Then
MsgBox "No Found Cell Address", vbInformation
Exit Sub
End If
The click function works fine if I remove the call to the function FillDateCapacitiesInOccupancySheet, but otherwise I get an error. Either the FoundCell or myRange is empty but I'm not sure why or how to fix it.
Sub ConflictButton_Click()
Dim sourceColumn As Range
Dim targetColumn As Range
Dim beginningDate As String, stringDate As String, month As String, day As String, year As String
Dim dates As Date
Dim occWS As Worksheet, excepWS As Worksheet
Dim beginningDateCell As Range, addDatesCells As Range, FindDateRow As Range
Dim datesArray(1 To 7) As Date, stringDatesArray(1 To 7) As String
Dim lLoop As Long, findRowNumber As Long
Dim tempMonth As String, tempDay As String
Dim fnd As String, FirstFound As String
Dim FoundCell As Range, rng As Range
Dim myRange As Range, LastCell As Range
'Copy Building, Room, and Max Capacity Columns from sheet Table to sheet OccupancyByDate
Set sourceColumn = ThisWorkbook.Worksheets("Table").Columns("B:D").Rows("3:500")
Set targetColumn = ThisWorkbook.Worksheets("OccupancyByDate").Columns("B:D").Rows("3:500")
sourceColumn.Copy Destination:=targetColumn
'open FindConflictDates Form
FindConflictDatesForm.Show
'Set OccupancyByDate sheet Column E Row 2 (Column Header) to the Week Starting Date,
'get the week beginning string from what was saved from the FindConflictDatesForm
If WeekBeginningString <> "" Then
beginningDate = WeekBeginningString
WeekBeginningDate = CDate(beginningDate) ' convert String to Date
Set occWS = ThisWorkbook.Sheets("OccupancyByDate")
Set excepWS = ThisWorkbook.Sheets("Telework Exceptions")
' Fill the 7 dates into header in OccupanyByDate sheet Columns E - K row 2
Dim i As Integer
i = 1
For j = 5 To 11 ' From column E(5) to K (11)
dates = WeekBeginningDate - Weekday(WeekBeginningDate, vbUseSystemDayOfWeek) + i
occWS.Cells(2, j).Value = Format(dates, "dd-mmm-yyyy") ' write dates to OccByDate sheet
'Save dates to datesArray
datesArray(i) = dates
stringDate = CStr(dates) ' convert date to string
If Mid(stringDate, 2, 1) <> "/" Then ' 2 digit month
tempMonth = Left(stringDate, 2)
Else
tempMonth = Left(stringDate, 1) ' 1 digit month
month = GetMonthAbbreviation(tempMonth)
End If
If Left(Right(stringDate, 7), 1) <> "/" Then '2 digit day
day = Left(Right(stringDate, 7), 2)
Else
day = Left(Right(stringDate, 6), 1) ' 1 digit day
' day = Mid(stringDate, 4, 2)
End If
year = Right(stringDate, 4)
stringDatesArray(i) = day & "-" & month & "-" & year
i = i + 1 ' Add 1 to increment date of week
Next j
''''''''''''''''''''''''
' Search for dates on the TW Exception Sheet
Dim numPerson As Long, Bldg As String, Room As String, foundDate As String
numPerson = 0
For i = LBound(datesArray) To UBound(datesArray)
'Search for datesArray(i) on TW Exceptions sheet
Set myRange = excepWS.Range("G:G")
Set LastCell = myRange.Cells(myRange.Cells.count)
Set FoundCell = myRange.Find(what:=stringDatesArray(i), after:=LastCell, LookIn:=xlValues)
If Not FoundCell Is Nothing Then ' if value found in column
FirstFound = FoundCell.Address
findRowNumber = FoundCell.Row 'get row number of the found date in the column on TW Excep sheet
foundDate = FoundCell.Text 'get text value of first occurence of new date found in column
Do ' Find additional occurences of date in the sheet column
findRowNumber = FoundCell.Row
If FoundCell.Offset(0, -3).Value = 1 Then
numPerson = 1
End If
If FoundCell.Offset(0, -2).Value = 1 Then
numPerson = 1
End If
Bldg = FoundCell.Offset(0, 3).Text
Room = FoundCell.Offset(0, 4).Text
FillDateCapacitiesInOccupancySheet foundDate, numPerson, Bldg, Room 'if i remove this line I don't get an error
Set FoundCell = myRange.FindNext(FoundCell)
If FoundCell Is Nothing Then
MsgBox "No Found Cell Address", vbInformation
Exit Sub
End If
Loop While (FoundCell.Address <> FirstFound)
End If
Next i 'Get next dateArray value
End If 'End if WeekBeginningString <> ""
End Sub
Sub FillDateCapacitiesInOccupancySheet(fndDate As String, numPpl As Long, Buildg As String, Rm As String)
Dim occWS As Worksheet
Dim FndCell As Range, rng As Range
Dim myNewRange As Range, LastCell As Range
Dim foundBldg As String
Dim findRowNumber As Long, count As Long
Dim dateOffset As Integer
Dim FirstFound As String
count = 0
Set occWS = ThisWorkbook.Sheets("OccupancyByDate")
Set myNewRange = occWS.Range("B:B") ' search in building column
Set LastCell = myNewRange.Cells(myNewRange.Cells.count)
Set FndCell = myNewRange.Find(what:=Buildg, after:=LastCell, LookIn:=xlValues)
If Not FndCell Is Nothing Then ' if value found in column
FirstFound = FndCell.Address
findRowNumber = FndCell.Row 'get row number of the found building in the column on OccByDate sheet
foundBldg = FndCell.Text 'get text value of first occurence of new building found in column
Do ' Find additional occurences of date in the sheet column
findRowNumber = FndCell.Row
If FndCell.Offset(0, 1).Text = Rm Then ' if room passed into function equals room for the building
'Find the date column for the date passed into function
For j = 5 To 11 ' From column E(5) to K (11)
If occWS.Cells(2, j).Text = fndDate Then
dateOffset = j - 2
count = FndCell.Offset(0, dateOffset).Value + numPpl
' write count to cell
FndCell.Offset(0, dateOffset).Value = count
End If
Next j
End If
Set FndCell = myNewRange.FindNext(FndCell)
Loop While (FndCell.Address <> FirstFound)
End If
End Sub
Any help would be greatly appreciated.
I get an Error 91 on the Loop While statement
Here:
Set FndCell = myNewRange.FindNext(FndCell)
Loop While (FndCell.Address <> FirstFound)
If that Range.FindNext call doesn't find anything, FndCell is Nothing when the While condition gets evaluated, and that would be where error 91 is being raised; the If Not FndCell Is Nothing Then parent block means nothing as soon as FndCell is re-assigned.
You need to bail out when FndCell is Nothing. Consider using Exit Do for this:
If FndCell Is Nothing Then Exit Do
Loop While FndCell.Address <> FirstFound
Only exiting the smaller scope conveys intent better than exiting the entire procedure scope here, I find - even if all that's left to execute [for now] is an End Sub statement.
Consider declaring j and having Option Explicit at the top of the module, too!

VBA script to format cells within a column range only formats the first sheet in the workbook

I have successfully scripted VBA code for summarizing and formatting a large set of data within a sheet. The script is successful when the macro is run on the next sheet I select. When tasked to apply the script across all sheets in the workbook, the modified script completes the summarizations for each sheet, but only formats the first. We tried to troubleshoot in my data class, but to no avail. This is an image of what it is supposed to look like.
My script for the whole workbook:
Sub tickerdata_all_ws()
'define variables
dim ws as Worksheet
Dim ticker As String
Dim stock_vol As Long
Dim yrclose As Double
Dim yrchange As Double
Dim yrvar As Double
Dim i As Long
Dim sumrow As Integer
Dim lastrow As Long
lastrow = ActiveSheet.UsedRange.Rows.Count
for each ws in Worksheet
'create the column headers
ws.Range("H1").Value = "Ticker"
ws.Range("J1").Value = "Yearly Change"
ws.Range("K1").Value = "Percent Change"
ws.Range("L1").Value = "Total Stock Volume"
'which row our summaries will be placed for above columns
sumrow = 2
'the loop checks each iteration until the last row
For i = 2 To lastrow
'we need to capture the price of the ticker if it is the first of its year
Dim firstprice As Boolean
If firstprice = False Then 'false is the default boolean value, so this statement is true
Dim yropen As Double
yropen = ws.Cells(i, 3).Value
firstprice = True 'we have captured the opening price of the year for the ticker
End If
'now we can check if we are in the same ticker value
If ws.Cells(i + 1, 1).Value <> ws.Cells(i, 1).Value Then
'this should happen when the cell values are finally different / capture all the values
ticker = ws.Cells(i, 1).Value
stock_vol = ws.Cells(i, 7).Value
yrclose = ws.Cells(i, 6).Value
yrchange = yrclose - yropen
If yropen <> 0 Then 'this prevents dividing by zero which will result in overflow error 6
yrvar = (yrclose - yropen) / yrclose
Else
yrvar = 0
yrchange = 0
End If
'insert values into the summary
ws.Cells(sumrow, 9).Value = ticker
ws.Cells(sumrow, 10).Value = yrchange
ws.Cells(sumrow, 11).Value = yrvar
ws.Cells(sumrow, 12).Value = stock_vol
sumrow = sumrow + 1 'sets the stage for the next set of data into row 3
stock_vol = 0 'resets vol for the next ticker
firstprice = False 'allows the next 'first' open price of the loop to be captured
End If
Next i 'finish i iteration of the loop
ws.Range("K:K").NumberFormat = "0.0%" 'aesthetic preference
'format columns colors
Dim colJ As Range
Dim Cell as Range
Set colJ = Range("J2", Range("J2").End(xlDown)) 'from J2 to the last cell entry
For Each Cell In colJ
If Cell.Value > 0 Then
Cell.Interior.ColorIndex = 50
Cell.Font.ColorIndex = 2
ElseIf Cell.Value < 0 Then
Cell.Interior.ColorIndex = 30
Cell.Font.ColorIndex = 2
Else
Cell.Interior.ColorIndex = xlNone 'this really serves no purpose
End If
Next
next ws
End Sub
I am sure there are other, much better ways to accomplish this, but as a novice, this is my code salad, and I'd appreciate any help as to why it is not formatting the other three sheets.
Excel for Mac user, though I've run it via Parallels as well.
Set colJ = Range("J2", Range("J2").End(xlDown)) 'from J2 to the last cell entry
here you get range for active sheet.
Change to:
Set colJ = ws.Range("J2", ws.Range("J2").End(xlDown))

VBA overlapping networkdays from dates with a condition

First I'm open to do this with an other angle.
I want to count the total hours of work hours estimated, see sheet2. In another sub I've calculated the total work hours (timer tot) with worksheetfunction.sum and timer FRJ/HET with worksheetfunction.sumif. This code doesn't consider overlapping days which means if the dates intersect each other it will calculate 8*2(3,4,5...) (8 hours is average workday in Norway) instead of 8 hours per workday. This will mess up the total amount of time estimated and posibly we will estimate more hours per day than 24 hours :D
I've started this code underneath which I will use to substract the total amount of time and total amout for FRJ and HET.
Code:
Sub Overlapping_WorkDays()
Dim rng_FRJ_HET As Range
Dim cell_name As Range
Dim startDateRng As Range
Dim endDateRng As Range
Set rng_FRJ_HET = Sheet1.Range("A8", Sheet1.Range("A8").End(xlDown))
Set startDateRng = Sheet1.Range("D8", Sheet1.Range("D8").End(xlDown))
Set endDateRng = Sheet1.Range("E8", Sheet1.Range("E8").End(xlDown))
For Each cell_name In rng_FRJ_HET
If cell_name = "FRJ" Then
'Count Overlapping networkdays for FRJ
Elseif cell_name = "HET" Then
'Count Overlapping networkdays for HET
End If
Next cell_name
End Sub
Sheet1 screenshot
Sheet2 screenshot
All you need to do is loop through all the date ranges and count them if they haven't already been counted. A Dictionary from the Microsoft Scripting Runtime is well suited for this (you'll need to add a reference in Tools->References).
Function TotalWorkDays(Optional category As String = vbNullString) As Long
Dim lastRow As Long
With Sheet1
lastRow = .Cells(.Rows.Count, 4).End(xlUp).Row
Dim usedDates As Scripting.Dictionary
Set usedDates = New Scripting.Dictionary
Dim r As Long
'Loop through each row with date ranges.
For r = 8 To lastRow
Dim day As Long
'Loop through each day.
For day = .Cells(r, 4).Value To .Cells(r, 5).Value
'Check to see if the day is already in the Dictionary
'and doesn't fall on a weekend.
If Not usedDates.Exists(day) And Weekday(day, vbMonday) < 6 _
And (.Cells(r, 1).Value = category Or category = vbNullString) Then
'Haven't encountered the day yet, so add it.
usedDates.Add day, vbNull
End If
Next day
Next
End With
'Return the count of unique days.
TotalWorkDays = usedDates.Count
End Function
Note that this will work for any arbitrary category found in column 1, or all categories combined if it isn't passed an argument. Sample usage:
Sub Usage()
Debug.Print TotalWorkDays("HET") 'Sample data prints 55
Debug.Print TotalWorkDays("FRJ") 'Sample data prints 69
Debug.Print TotalWorkDays 'Sample data prints 69
End Sub
You can convert this to late bound (and skip adding the reference) by replacing these two lines...
Dim usedDates As Scripting.Dictionary
Set usedDates = New Scripting.Dictionary
...with:
Dim usedDates As Object
Set usedDates = CreateObject("Scripting.Dictionary")
I think if I were doing this, I'd use the Collection object, as it'd save converting names and dates to index id's.
You could create a main collection of names and, for each name, a sub collection of dates whose key is Excel's date serial number. This would make it easy to store the 'used days' and you could either acquire the total day count by using the .Count property or loop through the collection to aggregate a specific Oppgave.
The code would be straight forward as shown below. You could just put this in a module:
Option Explicit
Private mNames As Collection
Public Sub RunMe()
ReadValues
'Get the total days count
Debug.Print GetDayCount("FRJ")
'Or get the days count for one Oppgave
Debug.Print GetDayCount("FRJ", "Malfil tegning form")
End Sub
Private Sub ReadValues()
Dim v As Variant
Dim r As Long, d As Long
Dim item As Variant
Dim dates As Collection
With Sheet1
v = .Range(.Cells(8, "A"), .Cells(.Rows.Count, "A").End(xlUp)).Resize(, 5).Value2
End With
Set mNames = New Collection
For r = 1 To UBound(v, 1)
'Acquire the dates collection for relevant name
Set dates = Nothing: On Error Resume Next
Set dates = mNames(CStr(v(r, 1))): On Error GoTo 0
'Create a new dates collection if it's a new name
If dates Is Nothing Then
Set dates = New Collection
mNames.Add dates, CStr(v(r, 1))
End If
'Add new dates to the collection
For d = v(r, 4) To v(r, 5)
On Error Resume Next
dates.Add v(r, 2), CStr(d)
On Error GoTo 0
Next
Next
End Sub
Private Function GetDayCount(namv As String, Optional oppgave As String) As Long
Dim dates As Collection
Dim v As Variant
Set dates = mNames(namv)
If oppgave = vbNullString Then
GetDayCount = dates.Count
Else
For Each v In dates
If v = oppgave Then GetDayCount = GetDayCount + 1
Next
End If
End Function
Dictionary approach should be the fastest.
But if your data are not that big you may want to adopt a "string" approach like follows
Function CountWorkingDays(key As String) As Long
Dim cell As Range
Dim iDate As Date
Dim workDates As String
On Error GoTo ExitSub
Application.EnableEvents = False
With Sheet1
With .Range("E7", .Cells(.Rows.Count, "A").End(xlUp))
.AutoFilter field:=1, Criteria1:=key
For Each cell In Intersect(.Offset(1).Resize(.Rows.Count - 1).SpecialCells(xlCellTypeVisible), .Columns(1))
For iDate = cell.Offset(, 3) To cell.Offset(, 4)
If Weekday(iDate, vbMonday) < 6 Then
If InStr(workDates, cell.value & iDate) <= 0 Then workDates = workDates & cell.value & iDate
End If
Next iDate
Next cell
End With
End With
CountWorkingDays = UBound(Split(workDates, key))
ExitSub:
Sheet1.AutoFilterMode = False
Application.EnableEvents = True
End Function
that you can use in your code as follows
sht2.Cells(2, 7) = CountWorkingDays("FRJ")
sht2.Cells(2, 8) = CountWorkingDays("HET")
As far as I know there is no direct formula to get overlap dates. My approach will be different from yours.
For each unique value in rng_FRJ_HET (i.e. only FRJ and HET as per e.g.)
Create an array with first date and last date
Mark array index with 1 for each date in range start and end date
Sum the array to get actual number of days
Next
So if the dates are repeated still they will mark as 1 in the array for that date.
=====================Added the code=== This will do for any number of names.
Option Explicit
Dim NameList() As String
Sub Overlapping_WorkDays()
Dim rng_FRJ_HET As Range
Dim cell_name As Range
Dim startDateRng As Range
Dim endDateRng As Range
Dim uniqueNames As Range
Dim stDate As Variant
Dim edDate As Variant
Dim Dates() As Integer
Set rng_FRJ_HET = Sheet1.Range("A8", Sheet1.Range("A8").End(xlDown))
Set startDateRng = Sheet1.Range("D8", Sheet1.Range("D8").End(xlDown))
Set endDateRng = Sheet1.Range("E8", Sheet1.Range("E8").End(xlDown))
stDate = Application.WorksheetFunction.Min(startDateRng)
edDate = Application.WorksheetFunction.Max(endDateRng)
ReDim NameList(0)
NameList(0) = ""
For Each cell_name In rng_FRJ_HET
If IsNewName(cell_name) Then
ReDim Dates(stDate To edDate + 1)
MsgBox cell_name & " worked for days : " & CStr(GetDays(cell_name, Dates))
End If
Next cell_name
End Sub
Private Function GetDays(ByVal searchName As String, ByRef Dates() As Integer) As Integer
Dim dt As Variant
Dim value As String
Dim rowIndex As Integer
Const COL_NAME = 1
Const COL_STDATE = 4
Const COL_EDDATE = 5
Const ROW_START = 8
Const ROW_END = 19
With Sheet1
For rowIndex = ROW_START To ROW_END
If searchName = .Cells(rowIndex, COL_NAME) Then
For dt = .Cells(rowIndex, COL_STDATE).value To .Cells(rowIndex, COL_EDDATE).value
Dates(CLng(dt)) = 1
Next
End If
Next
End With
GetDays = WorksheetFunction.Sum(Dates)
End Function
Private Function IsNewName(ByVal searchName As String) As Boolean
Dim index As Integer
For index = 0 To UBound(NameList)
If NameList(index) = searchName Then
IsNewName = False
Exit Function
End If
Next
ReDim Preserve NameList(0 To index)
NameList(index) = searchName
IsNewName = True
End Function

Find duplicates in a column and add their corresponding values from another column

I have column A with staff ids and hours worked in column K.
I would like if a staff id appears more than once to add hours worked and put the result in another column corresponding to the first instance of that staff id and the duplicates being 0.
This is for a monthly report and there may be over 2k records at any point.
As everyone else said, a Pivot Table really is the best way. If you're unsure how to use a PivotTable or what it's good for, refer to this SO post where I explain in detail.
Anyway, I put together the below VBA function to help get you started. It's by no means the most efficient approach; it also makes the following assumptions:
Sheet 1 has all the data
A has Staff Id
B has Hours
C is reserved for Total Hours
D will be available for processing status output
This of course can all be changed very easily by altering the code a bit. Review the code, it's commented for you to understand.
The reason a Status column must exist is to avoid processing a Staff Id that was already processed. You could very alter the code to avoid the need for this column, but this is the way I went about things.
CODE
Public Sub HoursForEmployeeById()
Dim currentStaffId As String
Dim totalHours As Double
Dim totalStaffRows As Integer
Dim currentStaffRow As Integer
Dim totalSearchRows As Integer
Dim currentSearchRow As Integer
Dim staffColumn As Integer
Dim hoursColumn As Integer
Dim totalHoursColumn As Integer
Dim statusColumn As Integer
'change these to appropriate columns
staffColumn = 1
hoursColumn = 2
totalHoursColumn = 3
statusColumn = 4
Application.Calculation = xlCalculationManual
Application.ScreenUpdating = False
totalStaffRows = Sheet1.Cells(Rows.Count, staffColumn).End(xlUp).Row
For currentStaffRow = 2 To totalStaffRows
currentStaffId = Cells(currentStaffRow, staffColumn).Value
'if the current staff Id was not already processed (duplicate record)
If Not StrComp("Duplicate", Cells(currentStaffRow, statusColumn).Value, vbTextCompare) = 0 Then
'get this rows total hours
totalHours = CDbl(Cells(currentStaffRow, hoursColumn).Value)
'search all subsequent rows for duplicates
totalSearchRows = totalStaffRows - currentStaffRow + 1
For currentSearchRow = currentStaffRow + 1 To totalSearchRows
If StrComp(currentStaffId, Cells(currentSearchRow, staffColumn), vbTextCompare) = 0 Then
'duplicate found: log the hours worked, set them to 0, then mark as Duplicate
totalHours = totalHours + CDbl(Cells(currentSearchRow, hoursColumn).Value)
Cells(currentSearchRow, hoursColumn).Value = 0
Cells(currentSearchRow, statusColumn).Value = "Duplicate"
End If
Next
'output total hours worked and mark as Processed
Cells(currentStaffRow, totalHoursColumn).Value = totalHours
Cells(currentStaffRow, statusColumn).Value = "Processed"
totalHours = 0 'reset total hours worked
End If
Next
Application.ScreenUpdating = False
Application.Calculation = xlCalculationAutomatic
End Sub
BEFORE
AFTER
Here is the solution for the data table located in range A1:B10 with headers and results written to column C.
Sub Solution()
Range("c2:c10").Clear
Dim i
For i = 2 To 10
If WorksheetFunction.SumIf(Range("A1:a10"), Cells(i, 1), Range("C1:C10")) = 0 Then
Cells(i, "c") = WorksheetFunction.SumIf( _
Range("A1:a10"), Cells(i, 1), Range("B1:B10"))
Else
Cells(i, "c") = 0
End If
Next i
End Sub
Try below code :
Sub sample()
Dim lastRow As Integer, num As Integer, i As Integer
lastRow = Range("A65000").End(xlUp).Row
For i = 2 To lastRow
num = WorksheetFunction.Match(Cells(i, 1), Range("A1:A" & lastRow), 0)
If i = num Then
Cells(i, 3) = WorksheetFunction.SumIf(Range("A1:A" & lastRow), Cells(i, 1), Range("B1:B" & lastRow))
Else
Cells(i, 1).Interior.Color = vbYellow
End If
Next
End Sub
BEFORE
AFTER
Below code identifies duplicate value in a column and highlight with red. Hope this might be of some help.
iLastRow = Cells(chosenExcelSheet.Rows.Count, 1).End(xlUp).Row 'Determine the last row to look at
Set rangeLocation = Range("A1:A" & iLastRow)
'Checking if duplicate values exists in same column
For Each myCell In rangeLocation
If WorksheetFunction.CountIf(rangeLocation, myCell.Value) > 1 Then
myCell.Interior.ColorIndex = 3'Highlight with red Color
Else
myCell.Interior.ColorIndex = 2'Retain white Color
End If
Next
Sub SelectColoredCells()
Dim rCell As Range
Dim lColor As Long
Dim rColored As Range
'Select the color by name (8 possible)
'vbBlack, vbBlue, vbGreen, vbCyan,
'vbRed, vbMagenta, vbYellow, vbWhite
lColor = RGB(156, 0, 6)
'If you prefer, you can use the RGB function
'to specify a color
'Default was lColor = vbBlue
'lColor = RGB(0, 0, 255)
Set rColored = Nothing
For Each rCell In Selection
If rCell.Interior.Color = lColor Then
If rColored Is Nothing Then
Set rColored = rCell
Else
Set rColored = Union(rColored, rCell)
End If
End If
Next
If rColored Is Nothing Then
MsgBox "No cells match the color"
Else
rColored.Select
MsgBox "Selected cells match the color:" & _
vbCrLf & rColored.Address
End If
Set rCell = Nothing
Set rColored = Nothing
End Sub
this highlights the duplicates

Resources