Using MONTH in If statement in Excel VBA - vba

I'm trying to write an Excel macro that will look at the dates in column A and print each month listed in a column F. I am trying to use a for loop and If/Else statements but I can't seem to get it to work out correctly.
y = 2
Range("F2").Formula = "=MONTH(A3)"
For x = 4 To RowLast - 1
If Range("A" & x).Month = Range("F" & y) Then
Else
y = y + 1
Range("F" & y).Formula = "=MONTH(A" & x & ")"
End If
Next
That is what I have thus far and it should print the first month found in Cell A3 to Cell F2 (which works), then go through every other date until it hits one line above the last. The if statements should check to make sure it's a new month and if it is print the month to the next cell in column F.
Please let me know if you have any questions. Thank you.

I think your if statement is causing the problems. Do you even need an if statement here if you are just printing the month?

RowLast = Cells.Find("*", SearchOrder:=xlByRows, SearchDirection:=xlPrevious).Row
y = 2
Range("F2").Formula = "=MONTH(A3)"
For x = 4 To RowLast - 1
Range("Z2").Formula = "=MONTH(A" & x & ")"
If Range("Z2").Value = Range("F" & y).Value Then
Else
y = y + 1
Range("F" & y).Formula = "=MONTH(A" & x & ")"
End If
Next

To answer your specific question: Month(date) is a function that returns an integer corresponding to the month of the date argument. So Month(Now) would return 3, for example.
.Month is not a property of the .Range object so your code would throw an error ("Object doesn't support this property or method"). The code below shows how to use the Month() function in the way you want.
However, your code poses a wider question. Are you using VBA merely to automate your formula writing? If you are, then all well and good. But is it possible that you are using worksheet functions when, actually, VBA would serve you better? Is there a reason, for example, that you would use VBA to identify target months only to write those target months to your worksheet by way of an Excel formula?
I mention it because quite a few posts recently have limited their scope to how to automate Excel functions (probably as a result of recording macros) whereas VBA can be more capable than their imagination might allow.
Anyhow, here are two very similar versions of the same task: the first that writes the formulae and the second that writes the months. I hope it'll provoke some thought as to which automation type suits your needs:
Code to write the formulae:
Public Sub FormulaGenerator()
Dim ws As Worksheet
Dim firstRow As Long
Dim lastRow As Long
Dim dateRange As Range
Dim cell As Range
Dim hitList As Collection
Dim refMonth As Integer
Dim thisMonth As Integer
Dim r As Long
Dim output() As Variant
Dim item As Variant
'Set these for your own task.
Set ws = ThisWorkbook.Worksheets("Sheet1")
firstRow = 3
lastRow = 20
'Read the values cell by cell
Set dateRange = ws.Range(ws.Cells(firstRow, "A"), ws.Cells(lastRow, "A"))
Set hitList = New Collection
For Each cell In dateRange.Cells
item = cell.Month
thisMonth = Month(cell.Value)
If thisMonth <> refMonth Then
'It's a new month so populate the collection with the cell address
hitList.Add cell.Address(False, False)
refMonth = thisMonth
End If
Next
'Populate the output array values
ReDim output(1 To hitList.Count, 1 To 1)
r = 1
For Each item In hitList
output(r, 1) = "=MONTH(" & item & ")"
r = r + 1
Next
'Write the output array starting at cell "F2"
ws.Cells(2, "F").Resize(UBound(output, 1)).Formula = output
End Sub
Code to write the months as integers:
Public Sub OutputGenerator()
Dim ws As Worksheet
Dim firstRow As Long
Dim lastRow As Long
Dim dates As Variant
Dim hitList As Collection
Dim refMonth As Integer
Dim thisMonth As Integer
Dim r As Long
Dim output() As Integer
Dim item As Variant
'Set these for your own task.
Set ws = ThisWorkbook.Worksheets("Sheet1")
firstRow = 3
lastRow = 23
'Read the dates into an array
dates = ws.Range(ws.Cells(firstRow, "A"), ws.Cells(lastRow, "A")).Value
'Loop through the array to acquire each new date
Set hitList = New Collection
For r = 1 To UBound(dates, 1)
thisMonth = Month(dates(r, 1))
If thisMonth <> refMonth Then
'It's a new date so populate the collection with the month integer
hitList.Add thisMonth
refMonth = thisMonth
End If
Next
'Populate the output array
ReDim output(1 To hitList.Count, 1 To 1)
r = 1
For Each item In hitList
output(r, 1) = item
r = r + 1
Next
'Write the output array starting at cell "F2"
ws.Cells(2, "F").Resize(UBound(output, 1)).Value = output
End Sub

Related

VBA Looping through single row selections and executing concat code

So, I've been scratching my head for a couple of hours now trying to figure this out. No matter where I look and what I do, I can't seem to make it work.
I have an excel document with ~20 columns and a completely variable number of rows. I want to concatenate each adjacent cell within the defined width (columns A:V)into the first cell (A1 for the first row), and then move to the next row and do the same until I get to the bottom. Snippet below:
Example before and after I'm trying to make
I have the code that does the concatenation. To make it work I have to select the cells I want to concatenate (A1:V1), and then execute the code. Even though some cells are blank, I need the code to treat them this way and leave semicolons there. The code works exactly as I need it to, so I've been trying to wrap it in some sort of Range select, offset, loop:
Dim c As Range
Dim txt As String
For Each c In Selection
txt = txt & c.Value & ";"
Next c
Selection.ClearContents
txt = Left(txt, Len(txt) - 2)
Selection(1).Value = txt
What I am struggling with is making the selection A1:V1, running the code, and then looping this down to A2:V1, A3:V3, etc. I think this can be done with a loops and an offset, but I cannot for the life of me work out how.
Any help at all would be much appreciated :)
This uses variant Arrays and will be very quick
Dim rng As Range
With Worksheets("Sheet4") 'change to your sheet
'set the range to the extents of the data
Set rng = .Range(.Cells(1, 1), .Cells(.Rows.Count, 22).End(xlUp))
'Load data into an array
Dim rngArr As Variant
rngArr = rng.Value
'create Out Bound array
Dim OArr() As Variant
ReDim OArr(1 To UBound(rngArr, 1), 1 To 1)
'Loop array
Dim i As Long
For i = LBound(rngArr, 1) To UBound(rngArr, 1)
'Combine Each Line in the array and load result into out bound array
OArr(i, 1) = Join(Application.Index(rngArr, i, 0), ";")
Next i
'clear and load results
rng.Clear
rng.Cells(1, 1).Resize(UBound(OArr, 1)).Value = OArr
End With
Here's a quick little script I made up to do this - the main thing to note is that I don't use selection, I used a defined range instead.
Sub test()
Dim i As Long
Dim target As Range
Dim c As Range
Dim txt As String
For i = 3 To 8
Set target = Range("A" & i & ":C" & i)
For Each c In target
txt = txt & c.Value & ";"
Next c
Cells(i + 8, "A").Value2 = Left$(txt, Len(txt) - 1)
txt = ""
Next i
End Sub
Just change the range on the below to your requirements:
Sub concat_build()
Dim buildline As String
Dim rw As Range, c As Range
With ActiveSheet
For Each rw In .Range("A2:V" & .Cells(.Rows.Count, "B").End(xlUp).Row + 1).Rows
buildline = ""
For Each c In rw.Cells
If buildline <> "" Then buildline = buildline & ";"
buildline = buildline & c.Value2
Next
rw.EntireRow.ClearContents
rw.EntireRow.Cells(1, 1) = buildline
Next
End With
End Sub

VBA iterate through columns and apply a formula if it matches (or doesn't matches)

I am trying to create a dynamic spreadsheet (template), which will be used to fetch data frequently. In this dataset I have to correct the fetched data with slope/intercept values. However, I would like to not have any calculations performed if the data is either 0 or Null. This could also be referrenced through a setpoint in a different column, lets say Sheet1 Column H, with a value of 10.
The K cells in Sheet "Tags" contain the slope & intercept values, column G of "Sheet1" contains data pulled from a server. For simplicity, lets make them all have 10.0 as a value. However, the data in column G may vary due to process conditions.
Currently I am going through the columns with data and once it matches, it will apply a multiplication or addition to the original values in a certain range as such:
' Declare vars
Dim FirstRow As Integer
Dim LastRow As Integer
Dim Number As Integer
Dim fndStr As String
Dim sht As Worksheet
' Init vars
Set sht = ThisWorkbook.Worksheets("Sheet1")
LineText = ""
Number = 0
LastRow = sht.Cells(sht.Rows.Count, "A").End(xlUp).Row
' Loop through colums till fndStr is found
Do While Not fndStr = "lastWord"
fndStr = Sheets("Sheet1").Range("A1").Offset(0, Number)
Select Case fndStr
Case Var1:
Sheets("Tags").Range("K7").Copy
Sheets("Sheet1").Range("G" & FirstRow & ":G" & LastRow).PasteSpecial xlPasteValues, xlPasteSpecialOperationMultiply
Sheets("Tags").Range("K6").Copy
Sheets("Sheet1").Range("G" & FirstRow & ":G" & LastRow).PasteSpecial xlPasteValues, xlPasteSpecialOperationAdd
Number = Number + 1
Case "lastWord": Exit Do
Case Else: Number = Number + 1
End Select
Loop
Some additional info, when new data is pulled, the previous, or already existing data, won't be recalculated for these are the FirstRow and LastRow variables meant to which data is appended.
Thanks in advance!
Another workaround this, in addition to what #arcadeprecinct commented is to put an input box.
For example, get user to key in notations to ignore (example: 0 x X), any combinations just in case there is x, X or 0. Split user input and insert into an array.
Dim userInput as String
Dim allInputs as Variant
userInput= InputBox("Key in notation to ignore.")
allInputs = Split(Split(Trim(userInput), " ")
Adding on to what #arcadeprecinct commented, do a if else based on the array.
for i = 1 to lastRow
for x = 0 to Ubound(allInputs)
'do a check here
next x
next i
Hope it helps.
Thanks to #arcadeprecinct and #tan-stanley I have been able to find a solution that is acceptable for me to work with.
It replicates the original code, but incorporates the looping and the conditional check. However I have done it on the offset column which contains the setpoint data. No calculations are performed on existing values if the setpoint is 0 or Null.
' Declare vars
Dim Slope As Double
Dim Intercept As Double
Dim Number As Integer
Dim cellValue As Range
Dim dataRange As Range
Dim FirstRow As Integer
Dim LastRow As Integer
Dim Number As Integer
Dim fndStr As String
Dim sht As Worksheet
' Init vars
Set sht = ThisWorkbook.Worksheets("Sheet1")
fndStr = ""
Number = 0
LastRow = sht.Cells(sht.Rows.Count, "A").End(xlUp).Row
' Loop through colums till fndStr is found
Do While Not fndStr = "lastWord"
fndStr = Sheets("Sheet1").Range("A1").Offset(0, Number)
Select Case fndStr
Case Var1:
Slope = Sheets("Tags").Range("H7").Value
Intercept = Sheets("Tags").Range("H6").Value
Set dataRange = Sheets("R1").Range("G" & FirstRow & ":G" & LastRow)
For Each cellValue In dataRange
If cellValue.Offset(0, 1).Value <> 0 And cellValue.Offset(0, 1).Value <> "Null" Then
cellValue.Value = cellValue.Value * Slope + Intercept
End If
Next cellValue
Number = Number + 1
Case "lastWord": Exit Do
Case Else: Number = Number + 1
End Select
Loop

Define array to obtain data from range as "double" var type

I'm fairly new to VBA, so please bear with me.
I want to tell VBA to get an array from a range of cells. The user will paste a column of data into cell C2 so cells below C2 will be populated. The number of cells populated is up to the user.
I am also going to need each of the elements in the array to be taken as doubles as I'm going to make operations with them.
Therefore if the list is
1.2222
2.4444
3.5555
Then I need the array to preserve the decimal points.
How do I do this?
This is what I've got this fur, with no luck:
Set ThisWS = Excel.ActiveWorkbook.Worksheets("Hoja1")
Dim InputValues() As Double 'Define Array
Dim LRow As Long 'Define length of array
With Sheets("Hoja1")
LRow = .Range("C" & .Rows.count).End(xlUp).Row
End With
InputValues = ThisWS.Range("C2:C" & LRow).Value 'Error 13: data type doesn't match
End Sub
Thanks!
Excel.ActiveWorkbook. isn't needed in Excel, it is implied. I didn't need to type cast the cell value CDbl(.Cells(x, "C")).
Sub Example()
Dim InputValues() As Double
Dim lastRow As Long, x As Long
With Worksheets("Hoja1")
lastRow = .Range("C" & .Rows.Count).End(xlUp).Row
ReDim InputValues(lastRow - 2)
For x = 2 To .Range("C" & .Rows.Count).End(xlUp).Row
InputValues(x - 2) = CDbl(.Cells(x, "C"))
Next
End With
End Sub
This example is more efficient but won't make a noticeable difference unless you are working with a very large amount of data.
Sub Example2()
Dim InputValues() As Double, vInputValues As Variant
Dim x As Long
With Worksheets("Hoja1")
vInputValues = .Range("C2", .Range("C" & .Rows.Count).End(xlUp)).Value2
ReDim InputValues(UBound(vInputValues) - 1)
For x = 1 To UBound(vInputValues)
InputValues(x - 1) = CDbl(vInputValues(x, 1))
Next
End With
End Sub
Set ThisWS = Excel.ActiveWorkbook.Worksheets("Hoja1")
Dim CurRow As Long
Dim LRow As Long 'Define length of array
LRow = ThisWS.Range("C" & Rows.count).End(xlUp).Row
Dim InputValues(1 to LRow - 1) As Double 'Define Array
For CurRow = 2 to LRow
InputValues(CurRow - 1) = ThisWS.Range("C" & CurRow).Value
Next CurRow
End Sub
you can simply go like follows
Option Explicit
Sub main()
Dim InputValues As Variant 'Define Array
With Excel.ActiveWorkbook.Worksheets("Hoja1") ' refer to wanted worksheet
InputValues = .Range("C2", .Cells(.Rows.Count, 3).End(xlUp)).value 'fill array with values in column "C" cells from row 2 down to last non emtpy one
End With
End Sub
should you ever need to handle array values as of Double type, then you can use CDbl() function
In VBA you can assign .Value and .Value2 arrays only to a Variant
As a side note if the range is formated as table, you can just do something like
Dim InputValues() ' As Variant by default
InputValues = [transpose(Hoja1!Table1[Column3])] ' Variant(1 to number of rows in Table1)

Updating fiscal year with a macro

I'm a relatively new programmer in VBA for Excel and looking for some help on a work-related project.
The issue at hand is with respect to designing an update routine for a budget tracking system that would update the date entries for any upcoming fiscal year, specified by the user in an input box:
Sub Putittogether()
On Error Resume Next
Dim xMonthNumber As Integer
Dim xMonthNumber2 As Integer
Dim xYearInput As Integer
Dim xCell As Range
Dim xCell2 As Range
Dim xMonthYear As Range
Dim xMonthNextYear As Range
Set xMonthYear = Range("B4:B12")
Set xMonthNextYear = Range("B1:B3")
xYearInput = InputBox("Please enter year:")
For Each xCell In xMonthYear
xCell = xYearInput
For xMonthNumber = 4 To 12
Range("B" & xMonthNumber).NumberFormat = "General"
Range("B" & xMonthNumber) = MonthName(xMonthNumber, True) & xCell
For Each xCell2 In xMonthNextYear
xCell2 = xYearInput + 1
For xMonthNumber2 = 1 To 3
Range("B" & xMonthNumber2).NumberFormat = "General"
Range("B" & xMonthNumber2) = MonthName(xMonthNumber2, True) & xCell2
Next xMonthNumber2
Next xCell2
Next xMonthNumber
Next xCell
End Sub
The macro goes from April of the current year to March of the following year, thus the double loops.
I run into issues in two places: using the method I'm using, the ranges in the B column need to be strictly reflected in rows 1-12 and any other rows causes problems with the output, and I'm not sure why.
The second issue is that I've got tracking totals tables for every month and trying to run this macro in a table that has a header row (i.e. shifts the first data row to B2) creates additional output problems - so, how do I make it work in a different range?
Thank you in advance for your help!!
Couldn't you use the Date data type and then the DateAdd() function. This would simplify your loop to just two lines of code.
If you need to stipulate a start row then one way would be to run the row loop from startRow to startRow + 11.
So your code would look something like this:
Const START_ROW As Integer = 1 'set this to the row you want
Dim ws As Worksheet
Dim r As Integer
Dim dat As Date
Dim yyyy As Integer
Set ws = ThisWorkbook.Worksheets("Sheet1")
yyyy = InputBox("Please enter year:")
dat = DateSerial(yyyy, 4, 1)
For r = START_ROW To START_ROW + 11
ws.Cells(r, "B").Value = Format(dat, "mmmyyyy")
dat = DateAdd("m", 1, dat)
Next
May be you can use this code
Sub Putittogether()
On Error Resume Next
Dim xYearInput As Integer
Dim xColumn As Integer
Dim xDate As Date
xYearInput = InputBox("Please enter year:")
xDate = DateSerial(xYear,4,1) 'you might adjust this date to the first month to be entered
For xColumn = 1 to 12
Cells("B" & xColumn ).NumberFormat = "General"
Cells("B" & xColumn ) = Format(xDate,"MMM YY")
xDate = DateAdd("m", 1, xDate)
Next xColumn
End Sub

Excel crashes when comparing two columns VBA macro

I have two columns which I am comparing for identical entries, and pushing the matches to another column through Offset. When I run the macro I've built (off of some Microsoft canned code) it essentially freezes and crashes, since it is a nested for each loop based on cells that are used, I figured it would end upon reaching an empty cell, but I fear I may be in a infinite loop. Any help will be much appreciated.
Dim myRng As Range
Dim lastCell As Long
Dim lastRow As Long
lastRow = ActiveSheet.UsedRange.Rows.Count
Dim c As Range
Dim d As Range
For Each c In Worksheets("Sheet1").Range("AT2:AT" & lastRow).Cells
For Each d In Worksheets("Sheet1").Range("AU2:AU" & lastRow).Cells
If c = d Then c.Offset(0, 1) = c
Next d
Next c
Try this:
Dim lastRow, currentRow, compareRow As Long
Dim found As Boolean
lastRow = Range("AT2").End(xlDown).Row
For currentRow = 2 To lastRow
compareRow = 2
found = False
Do While compareRow <= lastRow And Not found
If Range("AT" & currentRow).Value = Range("AU" & compareRow).Value Then
found = True
Range("AV" & currentRow).Value = Range("AT" & currentRow).Value
End If
compareRow = compareRow + 1
DoEvents
Loop
Next currentRow
Rather than selecting ranges and then cycling through them, this does the same thing without needing to .Select anything. It also breaks out of the inner loop early if it finds a match.
I believe that there are multiple issues here:
Efficiency of the search method
Loss of responsiveness of Excel
You can dramatically improve the efficiency of the code if you can pull all values into arrays. This prevents the time spent by VBA in accessing the Excel Object model and back. Loss of responsiveness can be handled by using DoEvents. Try the code below. It may look longish but should be easy to understand.
'Find last row
Dim lastRow As Variant
lastRow = ActiveSheet.UsedRange.Rows(ActiveSheet.UsedRange.Rows.Count).Row
'Create dynamic arrays
Dim AT() As Variant: Dim AU() As Variant: Dim AV() As Variant
ReDim AT(2 To lastRow): ReDim AU(2 To lastRow): ReDim AV(2 To lastRow)
'Get all contents from Excel
For i = 2 To lastRow
AT(i) = Worksheets("Sheet1").Cells(i, 46)
AU(i) = Worksheets("Sheet1").Cells(i, 47)
Next i
'Do the comparison
For c = 2 To lastRow
For d = 2 To lastRow
If AT(c) = AU(d) Then AV(c) = AT(c)
Next d
'Allow a brief breather to Excel once in a while (don't hang)
If (c / 100) = Int(c / 100) Then DoEvents
Next c
'Place final contents to Excel
For i = 2 To lastRow
Worksheets("Sheet1").Cells(i, 48) = AV(i)
Next i
Try this for your loop:
Dim StartRange As Range, j As Long
Dim CompareRange As Range, i As Range
With Worksheets("Sheet1")
Set StartRange = .Range("AT1", .Range("AT:AT").Find("*", , , , xlByRows, xlPrevious))
Set CompareRange = .Range("AU1", .Range("AU:AU").Find("*", , , , xlByRows, xlPrevious))
For Each i In StartRange
i.Offset(, -8).Value = .Evaluate("IF(COUNTIF(" & CompareRange.Address(0, 0) & "," & i.Address(0, 0) & ")>0," & i.Value & ","""")")
Next i
End With
Dim CompareRange As Variant, To_Be_Compared As Variant, j As Variant, k As Variant
Range("AT2").Select
Selection.End(xlDown).Select
Set To_Be_Compared = Range("AT2:" & Selection.Address)
Range("AU2").Select
Selection.End(xlDown).Select
Set CompareRange = Range("AU2:" & Selection.Address)
To_Be_Compared.Select
For Each j In Selection
DoEvents
For Each k In CompareRange
If j = k Then j.Offset(0, 2) = j
Next k
Next j
I finally got it to work, after taking the suggestions and implementing them into my code, I was able to see where the mistake actually was, I was referencing the wrong column earlier in the code and through this, created no duplicate entries to match, so after fixing this, the matches now appear, I ended up offsetting them, and changing the value to "yes" to reflect the duplication in my chart.
Thank you all for the help.