Loop through date range with a monthly step - vba

I have an issue with VBA, I am trying to loop into a date range with a monthly step but i'm struggling at the "monthly step" part.
Actually i am able to get this output:
13/08/2021
14/08/2021
15/08/2021
16/08/2021
17/08/2021
18/08/2021
And what I am trying to get is more like:
08/2021
09/2021
10/2021
11/2021
12/2021
01/2022
Here is my code:
aIndex = 1
For Each Cell In Range("F2", Range("F2").End(xlDown))
aIndex = aIndex + 1
For J = Range("D" & aIndex) To Cell
Debug.Print J
Next J
Next Cell
The "F" and "D" column simply contains dates with format "DD/MM/YYYY" and I'm looping between "D" and "F" date.
Thanks in advance,
Nicolas.

There are lots of way to do it, but trying to keep it similar to what you have, removing things that are not needed, and using a Dictionary instead:
Sub test()
Dim sDate As String
Dim dict As Object
Dim Cell As Range
Set dict = CreateObject("Scripting.Dictionary")
For Each Cell In Range("F2", Range("F2").End(xlDown))
sDate = Format$(Range("D" & Cell.Row), "mm/yyyy")
If Not dict.Exists(sDate) Then
dict.Add sDate, 1
Debug.Print sDate
End If
Next
Debug.Print "Total: " & dict.Count
End Sub
There are things you can do with the Dictionary afterwards if your Debug.Print is not exactly what you needed.

Related

Excel VBA - Get a Cells value by using Offset, based on the data from another column

Column K can contain the string 'Item Cost'. When Column K contains 'Item Cost' I would like to offset to Column U and copy the value from that cell within the same ROW as the string 'Item Cost'.
I can get the code to read and find the value in Column K, but am having a problem with the coping portion of the code for Column U.
Dim range1 As Range
Dim Answer4 As Variant
LstRw = Cells(Rows.Count, "K").End(xlUp).Row
Set List = CreateObject("Scripting.Dictionary")
For Each range1 In wbFrom.Sheets("Sheet0").Range("K9:K" & LstRw)
If range1.Offset(0, 0) = "Item Cost " Then
'MsgBox "found"
Answer4 = range1.Offset(0, 10).Value '<---- PROBLEM
End If
Next
'Msgbox Answer4 'returns nothing
wbTo.Sheets("Sheet1").Range("D10").Value = Answer4 'returns nothing
Seems like you will want to make your destination range dynamic (Range("D10")). The code as is will continousely re-write over your value in D10. Do you maybe want the value to be in Col D on the same row as the target range? If so, swap
wbTo.Sheets("Sheet1").Range("D10") = range1.Offset(0, 10)
for
wbTo.Sheets("Sheet1").Range("D" & range1.Row) = range1.Offset(0, 10)
For Each range1 In wbFrom.Sheets("Sheet0").Range("K9:K" & LstRw)
If range1 = "Item Cost " Then
'MsgBox "found"
wbTo.Sheets("Sheet1").Range("D10") = range1.Offset(0, 10)
End If
Next

Iterating through row range group data and take an action

I am getting to know Excel VBA. I have a working program that uses an action button on one sheet opens a source workbook and data worksheet, selects data and puts that into a second workbook and destination sheet. I then sort the data as needed and it looks like this
Destination sheet, sorted and annotated duplicates
I am now trying to select the data based on col 2 "B" where the items are duplicated and/or not duplicated then perform an action (send an email to the manager about the staff under their control). I can get an email to work but its selecting the data that I'm having trouble with.
the output data would be col 1 & col 3 to 5 e.g.
Dear Manager1,
you staff member/s listed below have achieved xyz
Person1 22/06/2017 11/08/2017 22/08/2017
Person11 22/06/2017 11/08/2017 22/08/2017
Person15 22/06/2017 11/08/2017 22/08/2017
congratulations....
So what I hope somebody can help me with is a clue how I get to look at the data in col 2
add the Row data required to an array or something then to check the next Row add it to the same something until it is different to the next Row Pause do the action then do the next iteration. Resulting in:
Manager1 .....Person 1,11,15action
Manager10 ..... Person 10action
Manager2 ..... Person 12,16,2,25,28action
Manager3 ..... Person 13,17,26,29,3action
until last line is reached.
I am so confused with arrays / lookups and loops I have lost the plot somewhere along the way.
I have a variable lastTmp which tells me the last line of data in the set, this will vary each month.
The Range is:
Set rng1 = Range("B5:B" & Cells(Rows.Count, "B").End(xlUp).row)
The last piece of my working code is:
Dim lp As Integer
lp = 1
For Each cell In rng1
If 1 < Application.CountIf(rng1, cell.Value) Then
With cell
.Offset(0, 4) = "duplicate : "
.Offset(0, 5) = lp
End With
Else
With cell
.Offset(0, 4) = "NOT duplicate : "
.Offset(0, 5) = 0
End With
End If
Next cell
You will be better placed to confront confusion if you do your indenting more logically. Related For / Next, If / Else / End If and With / End With should always be on the same indent level for easier reading. I rearranged your original code like this:-
For Each Cell In Rng1
If 1 < Application.CountIf(Rng1, Cell.Value) Then
With Cell
.Offset(0, 4) = "duplicate : "
.Offset(0, 5) = lp
End With
Else
With Cell
.Offset(0, 4) = "NOT duplicate : "
.Offset(0, 5) = 0
End With
End If
Next Cell
It now becomes apparent that the With Cell / End With need not be duplicated. I have further presumed that your variable lp actually was intended to hold the count. That made me arrive at the following compression of your code.
Dim Rng1 As Range
Dim Cell As Range
Dim lp As Integer
' the sheet isn't specified: uses the ActiveSheet
Set Rng1 = Range("B5:B" & Cells(Rows.Count, "B").End(xlUp).Row)
For Each Cell In Rng1
With Cell
lp = Application.CountIf(Rng1, .Value)
.Offset(0, 4) = IIf(lp, "", "NOT ") & "duplicate : "
.Offset(0, 5) = lp
End With
Next Cell
Consider using a Dictionary or Collection, whenever, checking for duplicates.
Here I use a Dictionary of Dictionaries to compile lists of Persons by Manager.
Sub ListManagerList1()
Dim cell As Range
Dim manager As String, person As String
Dim key As Variant
Dim dictManagers As Object
Set dictManagers = CreateObject("Scripting.Dictionary")
For Each cell In Range("B5:B" & Cells(Rows.Count, "B").End(xlUp).Row)
manager = cell.Value
person = cell.Offset(0, -1).Value
If Not dictManagers.Exists(manager) Then
dictManagers.Add manager, CreateObject("Scripting.Dictionary")
End If
If Not dictManagers(manager).Exists(person) Then
dictManagers(manager).Add person, vbNullString
End If
Next
For Each key In dictManagers
Debug.Print key & " -> "; Join(dictManagers(key).Keys(), ",")
Next
End Sub
I recommend you wanting Excel VBA Introduction Part 39 - Dictionaries
Assuming your data is as in the image
Then following code will give you result as in the image below.
Sub Demo()
Dim srcSht As Worksheet, destSht As Worksheet
Dim lastRow As Long, i As Long
Dim arr1(), arr2()
Dim dict As Object
Set dict = CreateObject("scripting.dictionary")
Set srcSht = ThisWorkbook.Sheets("Sheet2") 'change Sheet2 to your data sheet
Set destSht = ThisWorkbook.Sheets("Sheet1") 'change Sheet1 to your output sheet
arr1 = Application.Index(srcSht.Cells, [row(1:7000)], Array(2, 1)) 'See note below
arr2 = arr1
For i = 1 To UBound(arr1, 1)
If Not dict.exists(LCase$(arr1(i, 1))) Then
dict.Add LCase$(arr1(i, 1)), i
Else
arr2(i, 1) = vbNullString
arr2(dict.Item(LCase$(arr1(i, 1))), 2) = arr2(dict.Item(LCase$(arr1(i, 1))), 2) & "," & arr1(i, 2)
End If
Next
destSht.Range("A1").Resize(UBound(arr1, 1), UBound(arr1, 2)) = arr2 'display result
destSht.Columns("a").SpecialCells(xlBlanks).EntireRow.Delete
End Sub
Note : For details on assigning range to array see this.

Converting a long nested if and or formula into vba case

I have a nestled If And Or formula that I am trying to convert into probably a Case formula using VBA (or any other suggestion would be great), but I am a beginner and not sure how. The reason is that this formula currently is in every cell AG12:ACG500 and takes up so much memory that the spreadsheet is extremely slow.
Basically, I am trying to match the date in column Z12:Z500 (Outage Month Start) to the date in Row AG6:ACG6 (DATES), then looking in column C12:C500 (ACTV_NAME)... which provides the output of either R, S, L, MR, MS, ML, ?R, ?S or ?L in the cell where the two dates (column Z and row 6) intersect .. this should coincide with the where the Gantt Chart bar starts .. I do not need any help with the Gantt chart bars/color coding .. I just need help basically labeling them with the fore-mentioned.
=IF(OR($Z12="",$AA12=""),"",IF(AND(AG$6=$Z12,$Z12<>"",$C12="Relay",$L12="No"),"R",IF(AND(AG$6=$Z12,$Z12<>"",$C12="Substation",$L12="No"),"S",IF(AND(AG$6=$Z12,$Z12<>"",$C12="Line",$L12="No"),"L",IF(AND(AG$6=$Z12,$Z12<>"",$C12="Relay",$L12="YES"),"MR",IF(AND(AG$6=$Z12,$Z12<>"",$C12="Substation",$L12="YES"),"MS",IF(AND($AF$6=$Z12,$Z12<>"",$C12="Line",$L12="YES"),"ML",IF(AND(AG$6=$Z12,$Z12<>"",$C12="Relay",$L12="Maybe"),"?R",IF(AND(AG$6=$Z12,$Z12<>"",$C12="Substation",$L12="Maybe"),"?S",IF(AND($AF$6=$Z12,$Z12<>"",$C12="Line",$L12="Maybe"),"?L",""))))))))))
Your formula can be simplified to:
=IF(OR($Z12="",$AA12=""),"",IF(AND(AG$6=$Z12,$Z12<>""),IF($L12 = "YES","M" & LEFT($C12,1),IF($L12 = "Maybe","?" & LEFT($C12,1),LEFT($C12,1))),""))
Converting single formula into a routine that loops
This is Scott Craner's simplified version of your formula, converted into a routine that will loop through all the cells in AG12:ACG500 and check each cell for their intersecting criteria.
I am not able to test this code because I have no data set to base it off of. That being said, I'm not sure that it will perform in the way you desire. Let me know if it works for you.
Sub compareDates()
Dim ws As Worksheet: Set ws = ThisWorkbook.Sheets(1)
Dim dataRange As Range: Set dataRange = ws.Range("AG12:ACG500")
Dim oMS As Range, aN As Range, idk As Range, d As Range
Dim yNM As Range, myCell As Range, myRow As Long, myCol As Long
For Each myCell In dataRange
myRow = myCell.Row
myCol = myCell.Column
Set oMS = ws.Range("Z" & myRow) 'Outage Month Start
Set aN = ws.Range("C" & myRow) 'ACTV_NAME
Set idk = ws.Range("AA" & myRow) 'not sure what AA is for
Set d = ws.Cells(6, myCol) 'DATES
Set yNM = ws.Range("L" & myRow) 'yes no maybe
If oMS.Value = "" Or idk.Value = "" Then
myCell.Value = ""
ElseIf d.Value = oMS.Value And oMS.Value <> "" Then
If UCase(yNM.Value) = UCase("Yes") Then
myCell.Value = "M" & Left(aN.Value, 1)
ElseIf UCase(yNM.Value) = UCase("Maybe") Then
myCell.Value = "?" & Left(aN.Value, 1)
Else: myCell.Value = Left(aN.Value, 1)
End If
Else: myCell.Value = ""
End If
Next myCell
End Sub

VBA: adding up irregular ranges

I need some help to create a macro which adds all the values on the column E between the rows with the "avg" word. the result should be displayed on the cells where the "Sum here" label is displayed. Both texts "avg" and "sum here" is just for illustrate the example, "avg" could be replaced by any other word and "sum here" should actually be the aggregation of the values above it.
The real challenge is that the number of ranges on column E is variable, so i would like to find a macro which is able to deal with "n" number of ranges on column E.
Finally, the values on column D are only the example of the expected value on the "sum here" cells.
This is what I have tried to far:
Sub Macro1()
'
' Macro1 Macro
'
Dim sumhere As Range
Dim startingpoint As Range
Dim endingpoint As Range
'
Range("C17").Select
Selection.End(xlDown).Select
If ActiveCell = "avg" Then
ActiveCell.Offset(rowoffset:=0, columnoffset:=2).Select
Set sumhere = ActiveCell
Set startingpoint = ActiveCell.Offset(rowoffset:=-1, columnoffset:=0)
Selection.End(xlUp).Select
If (ActiveCell.Value) = "Sum here" Then
Set endingpoint = ActiveCell.Offset(rowoffset:=1, columnoffset:=0)
sumhere.Formula = "=sum(range(startingpoint:endingpoint)"
Else
Selection.End(xlUp).Select
If (ActiveCell.Value) = "Sum here" Then
Set endingpoint = ActiveCell.Offset(rowoffset:=1, columnoffset:=0)
sumhere.Formula = "=Sum(Range(startingpoint.adress:endingpoint.adress))"
Else: End If
End If
End If
End Sub
Additionally, as you can see, I do not know, how to define a range using variables. My original idea was to combine this code with some kind of "do while" or/and "for i= 1 to x" and "next i". But I can't see how to combine it.
Using formula only, and providing that column A only has avg (or any text) on each subtotal row.
I've given two versions of the formula - the volatile version (updates everytime you change anything on the spreadsheet), and the non-volatile version (only updates if it needs to).
The formula should be entered on row 6 - change the $E6 to which ever row you need.
(volatile)
=SUM(OFFSET($E6,IFERROR(LOOKUP(2,1/($A$1:INDEX($A:$A,ROW()-1)<>""),ROW($A$1:INDEX($A:$A,ROW()-1))),0)-ROW()+1,,ROW()-1-IFERROR(LOOKUP(2,1/($A$1:INDEX($A:$A,ROW()-1)<>""),ROW($A$1:INDEX($A:$A,ROW()-1))),0)))
(non volatile):
=SUM(INDEX($E:$E,IFERROR(LOOKUP(2,1/($A$1:INDEX($A:$A,ROW()-1)<>""),ROW($A$1:INDEX($A:$A,ROW()-1))),0)+1):INDEX($E:$E,ROW()-1))
or if you don't mind using a helper column:
In cell B6:
=IFERROR(LOOKUP(2,1/($A$1:INDEX($A:$A,ROW()-1)<>""),ROW($A$1:INDEX($A:$A,ROW()-1))),0)
In E6: (volatile)
=SUM(OFFSET($E6,$B6-ROW()+1,,ROW()-1-$B6))
or (non volatile):
=SUM(INDEX($E:$E,$B6):INDEX($E:$E,ROW()-1))
Edit:
Thought I'd add a UDF to calculate it to if you're after VBA.
Use the function =AddSubTotal() in the rows you want the sub total to be shown in, or use =AddSubTotal("pop",6) to sum everything in column F (col 6) using "pop" rather than "avg".
Public Function AddSubTotal(Optional Delim As String = "avg", Optional ColNumber = 5) As Double
Dim rCaller As Range
Dim rPrevious As Range
Dim rSumRange As Range
Set rCaller = Application.Caller
With rCaller.Parent
Set rPrevious = .Range(.Cells(1, 1), .Cells(rCaller.Row - 1, 1)).Find(Delim, , , , , xlPrevious)
If Not rPrevious Is Nothing Then
Set rSumRange = rPrevious.Offset(1, ColNumber - 1).Resize(rCaller.Row - rPrevious.Row - 1)
Else
Set rSumRange = .Range(.Cells(1, ColNumber), .Cells(rCaller.Row - 1, ColNumber))
End If
End With
AddSubTotal = WorksheetFunction.Sum(rSumRange)
End Function
The following VBA routine assumes that
your data is in Columns C:E
Nothing else relevant (nothing numeric) in that range
Your "key word" where you want to show the sum is avg
avg (the key word) is hard-coded in the macro
You could easily modify this routine to also perform an average of those values, and put those results, for example, in Column D
Any of the above are easily modified
Option Explicit
Sub TotalSubRanges()
Dim vSrc As Variant, rSrc As Range
Dim dAdd As Double
Dim I As Long
Const sKey As String = "avg"
Set rSrc = Range(Cells(1, "C"), Cells(Rows.Count, "C").End(xlUp)).Resize(columnsize:=3)
vSrc = rSrc
'Do the "work" in a VBA array, as this will
' execute much faster than working directly
' on the worksheet
For I = 1 To UBound(vSrc, 1)
If vSrc(I, 1) = sKey Then
vSrc(I, 3) = dAdd
dAdd = 0
Else
If IsNumeric(vSrc(I, 3)) Then dAdd = dAdd + vSrc(I, 3)
End If
Next I
'write the results back to the worksheet
' and conditionally format the "sum" cells
With rSrc
.EntireColumn.Clear
.Value = vSrc
.Columns(3).AutoFit
.EntireColumn.ColumnWidth = .Columns(3).ColumnWidth
.FormatConditions.Delete
.FormatConditions.Add _
Type:=xlExpression, _
Formula1:="=" & .Item(1, 1).Address(False, True) & "=""" & sKey & """"
With .FormatConditions(1)
.Interior.ColorIndex = 6
End With
End With
End Sub
Surely you just need something like:
Sub sums()
Dim i As Integer, j As Integer, k As Integer
j = Range("C1048576").End(xlUp).Row
k = 1
For i = 1 To j
If Range("C" & i).Value <> "" Then
Range("E" & i).Value = "=Sum(E" & k & ":E" & i - 1 & ")"
k = i + 1
End If
Next i
End Sub
Change:
Dim startingpoint As Range
Dim endingpoint As Range
To:
Dim startingpoint As Variant
Dim endingpoint As Variant
As the startingpoint and endingpoint is used in a formula, you cant define them as a Range.

COUNTIF() in 'For' loop

I have a column with nearly 100k and am trying to determine how many times a value occurs repeatedly in that column. I can do it row by row currently, but this is menial as a programmer, through something like =COUNTIF(D:D,D2). Yet that only returns D2 matches in column D.
I need to iterate through all values of D returning countif, therefore revealing all of the values repetitions in the column. I can remove duplicates later! So I have a dev. button a basic sub, or function (man this is new to me) and something along the lines of the most basic for loop ever. Just getting caught up on how to implement the COUNTIF() to to the loop properly.
Right now I'm looking at:
Sub doloop()
Dim i As Integer
i = 1
Do While i < D.Length
Cells(i, 8).Value =CountIf(D:D,D[i])
i = i + 1
Loop
End Sub
That code is incorrect obviously but it is where I'm at and may help for anyone more familiar with other languages.
Use Application.WorksheetFunction.CountIf() in your loop.
Private Sub doloop()
Dim lastRow As Long
Dim d As Double
Dim r As Range
Dim WS As Excel.Worksheet
Dim strValue As String
Dim lRow As Long
'Build your worksheet object
Set WS = ActiveWorkbook.Sheets("sheet1")
'Get the last used row in column A
lastRow = WS.Cells(WS.Rows.count, "D").End(xlUp).Row
'Build your range object to be searched
Set r = WS.Range("D1:D" & lastRow)
lRow = 1
WS.Activate
'Loop through the rows and do the search
Do While lRow <= lastRow
'First, get the value we will search for from the current row
strValue = WS.Range("D" & lRow).Value
'Return the count from the CountIf() worksheet function
d = Application.worksheetFunction.CountIf(r, strValue)
'Write that value to the current row
WS.Range("H" & lRow).Value = d
lRow = lRow + 1
Loop
End Sub
I believe you are trying to write the value to the cell, that is what the above does. FYI, if you want to put a formula into the cell, here is how that is done. Use this in place of WS.Range("H" & lRow).Value = d
WS.Range("H" & lRow).Formula = "=CountIf(D:D, D" & lRow & ")"
Sounds like you may want to look into using tables in Excel and capitalizing on their features like filtering and equation autofill. You may also be interested in using a PivotTable to do something very similar to what you're describing.
If you really want to go about this the programmatic way, I think the solution Matt gives answers your question about how to do this using CountIf. There's a big detriment to using CountIf though, in that it's not very computationally efficient. I don't think the code Matt posted will really be practical for processing the 100K rows mentioned in the OP (Application.ScreenUpdating = false would help some). Here's an alternative method that's a lot more efficient, but less intuitive, so you'll have to decide what suites your needs and what you feel conformable with.
Sub CountOccurances()
'Define Input and Output Ranges
'The best way to do this may very from case to case,
'So it should be addressed seperately
'Right now we'll assume current sheet rows 1-100K as OP specifies
Dim RInput, ROutput As Range
Set RInput = Range("D1:D100000")
Set ROutput = Range("E1:E100000")
'Define array for housing and processing range values
Dim A() As Variant
ReDim A(1 To RInput.Rows.Count, 0)
'Use Value2 as quicker more accurate value
A = RInput.Value2
'Create dictionary object
Set d = CreateObject("Scripting.Dictionary")
'Loop through array, adding new values and counting values as you go
For i = 1 To UBound(A)
If d.Exists(A(i, 1)) Then
d(A(i, 1)) = d(A(i, 1)) + 1
Else
d.Add A(i, 1), 1
End If
Next
'Overwrite original array values with count of that value
For i = 1 To UBound(A)
A(i, 1) = d(A(i, 1))
Next
'Write resulting array to output range
ROutput = A
End Sub
You can also modify this to include the removal of replicates you mentioned.
Sub CountOccurances_PrintOnce()
'Define Input and Output Ranges
'The best way to do this may very from case to case,
'So it should be addressed seperately
'Right now we'll assume current sheet rows 1-100K as OP specifies
Dim RInput, ROutput As Range
Set RInput = Range("D1:D100000")
Set ROutput = Range("F1:F9")
'Define array for housing and processing range values
Dim A() As Variant
ReDim A(1 To RInput.Rows.Count, 0)
'Use Value2 as quicker more accurate value
A = RInput.Value2
'Create dictionary object
Set d = CreateObject("Scripting.Dictionary")
'Loop through array, adding new values and counting values as you go
For i = 1 To UBound(A)
If d.Exists(A(i, 1)) Then
d(A(i, 1)) = d(A(i, 1)) + 1
Else
d.Add A(i, 1), 1
End If
Next
'Print results to VBA's immediate window
Dim sum As Double
For Each K In d.Keys
Debug.Print K & ": " & d(K)
sum = sum + d(K)
Next
Debug.Print "Total: " & sum
End Sub