Im trying to create a module to pull data from a giant spreadsheet that I have on another sheet by year. Every part of the code works except for the part that matches the year to a user entered year.
Here's how I defined the user input and how I tried to write the if statement.
Dim y As variant
y = InputBox("Input year here")
If Year(RptSht.Cells(i, 2)) = y
At this point I get a type mismatch (I've tried setting y as an integer instead). Also just as a note I can use
Year(RptSht.Cells(i, 2))
to get a value, it just mismatches with y. Any help would be appreciated.
If Year(RptSht.Cells(i,2)) = y
That's doing too many things. Split it up.
First you want to get the cell at (i, 2):
Dim yearCellValue As Variant
yearCellValue = RptSht.Cells(i, 2)
Now, we can't just assume that yearCellValue is a valid date. We have to know it, otherwise if anything is wrong with the assumption, we'll likely run into a type mismatch error. Use the IsDate function to make sure you're looking at a Date value:
If IsDate(yearCellValue) Then
End If
Inside that conditional block, Year(yearCellValue) is safe. Outside of it, it isn't.
If IsDate(yearCellValue) Then
If Year(yearCellValue) = y Then
'...
End If
End If
Problem is, we don't know that y is a valid value either.
Dim y As variant
y = InputBox("Input year here")
If Not IsNumeric(y) Then Exit Sub 'bail out, we can't go any further.
Here is one way to handle the issue:
Sub gotimm()
Dim y As Long, RptSht As Worksheet, i As Long
y = Application.InputBox(Prompt:="Input year here", Type:=1)
Set RptSht = ActiveSheet
i = 1
With RptSht
If IsDate(.Cells(i, 2)) Then
If .Cells(i, 2) = y Then
MsgBox "match"
Else
MsgBox "nomatch"
End If
Else
MsgBox "no date in cell " & .Cells(i, 2).Address
End If
End With
End Sub
I guess the problem is that i is not assigned or (i,2) is not a date. Try this:
Sub TestMe()
Dim y As Variant
Dim i As Long
y = InputBox("Input year here")
i = 5
If IsDate(Worksheets(1).Cells(i, 2)) Then
If Year(Worksheets(1).Cells(i, 2)) = y Then
'Logic here
End If
End If
End Sub
Thus, the i is 5 and the reference cell is B5. The IsDate() checks whether the cell is a date.
Related
I've been banging my head on this issue! I have a variable called repDate which today is equal to "5/1/2020" as a string. I've tried this formula to convert it to a Long so I can compare it to a date in the file rDtLng = CLng(repDate). I'm getting error "Type Mismatch" which I am not sure why there would be one. This is where I am doing the comparing the rest works great, just the report date as long doesn't want to work.
'repDate equals "5/1/2020", currently
rDtLng = CLng(repDate)
.
.
.
'Delete charge offs
For w = rwCnt To 3 Step -1
Do While .Cells(w, napbRng.Column).Value2 <= 0 And .Cells(w, apbRng.Column).Value2 <= 0
If .Cells(w, matDtRng.Column).Value2 = "" Then Exit Do
If .Cells(w, matDtRng.Column).Value2 < rDtLng Then
.Rows(w).Delete shift:=xlShiftUp
Else: Exit Do
End If
Loop
Next w
Thanks in advance!
CLng expects a numeric input, which the text-that-looks-like-a-date "5/1/2020" is not.
You can convert that to an actual date using CDate and then perform mathematical operations on it, including the existing < comparison.
Though if I understand what your end goal is, you might consider Range.AutoFilter with a date filter, and then deleting visible rows, instead of your current Do loop approach.
Side note: you could CLng(CDate("5/1/2020")) and the result would be 43952, but that's an unnecessary step, as you can do math with dates directly.
Use DateValue() Function.
Sub test()
Dim myDate As Long
Dim str As String
str = "5/1/2020"
myDate = DateValue(str)
If myDate = DateSerial(2020, 5, 1) Then
MsgBox "OK"
End If
End Sub
I am writing a VBA code to go through a specified range or ranges, look for a keyword provided by the user at run-time, and grab the value in the cell offset from the cell with the keyword by an amount also provided by the user. For instance, if you wanted to look through A1:B10 for the word "Apple" and then grab the value in the cell to the right of every instance of "Apple", it can do that. Two weird things have been occurring for me. First and not so weird, when I run it and click the cancel button on the userform that only contains the single line "Unload Me", it throws an error saying it expected and End Sub statement, but it has one. I don't know why it is doing that. Weird thing number 2. Whenever I click and move the cursor to the end of the file after the Cancel_Click() sub, my excel crashes and closes. Every. Single. Time. And it is weird that it does that just from me clicking. It also sometimes happens when I click around the Cancel_Click() sub or hit enter around there too. Just simply from clicking. I don't get it. Any ideas? Code contained in the userform is below. Fyi, the user can input ranges like "A1:A10,E1:E10" separated by commas for multiple ranges. I don't think it is important for this question, but I thought I would add that since i don't know how to add the userform here, if you even can.
Private Sub Accept_Click()
'Searches for string input into the KeywordBox
'Grabs contents of the cell defined by the OffsetBox
'The range it searches through is defined by the RangeBox
Dim rawRange As String: rawRange = Me.RangeBox.Text
Dim rawOffset As String: rawOffset = Me.OffsetBox.Text
Dim Keyword As String: Keyword = Me.KeywordBox.Text
Dim numOfRanges As Integer: numOfRanges = 1
Dim Ranges() As Range
Dim commaLoc As Integer: commaLoc = -1
Dim tempRange As String: tempRange = rawRange
Dim offset As Integer
Dim values() As Double
Dim valCount As Integer: valCount = 0
'--------------------------------------------------------
'Set ranges
For i = 1 To Len(rawRange)
If (Mid(rawRange, i, 1) = ",") Then
numOfRanges = numOfRanges + 1
End If
Next
ReDim Ranges(numOfRanges) As Range
If (Not numOfRanges = 1) Then
For i = 1 To numOfRanges - 1
commaLoc = InStr(1, tempRange, ",")
Set Ranges(i) = Range(Left(tempRange, commaLoc - 1))
tempRange = Right(tempRange, Len(tempRange) - commaLoc)
Next
End If
Set Ranges(numOfRanges) = Range(tempRange)
'---------------------------------------------------------
'Set offset
If (IsNumeric(rawOffset)) Then
offset = CInt(rawOffset)
Else:
MsgBox ("Offset was not input as a number")
Exit Sub
End If
'----------------------------------------------------------
'Searches for keyword
For i = 1 To numOfRanges
For Each cell In Ranges(i)
If (cell.Value = Keyword) Then
valCount = valCount + 1
End If
Next
Next
ReDim values(valCount) As Double
valCount = 0
For i = 1 To numOfRanges
For Each cell In Ranges(i)
If (cell.Value = Keyword) Then
valCount = valCount + 1
values(valCount) = cell.offset(0, offset).Value
End If
Next
Next
For i = 1 To valCount
Range("I" & i).Value = values(i)
Next
Unload Me
End Sub
I've had similar, weird things happen to me. A good thing to try is to force the VBA project to reset, then save, exit, and restart Excel.
To force a project reset, add an Enum to the general section of one of your code modules. It doesn't matter what the enum is...make it something simple, like
Enum stoplight
Red
Yellow
Green
End Enum
As you do that, you'll get a message saying that it will reset your project. That's fine; let that happen. Then save your Excel workbook, exit excel completely, start it up again, reload your workbook, go into the VBA Editor, and delete the enum you added. Then recompile and see if things work better for you.
You put an "Exit Sub" in the set offset, this is probably causing your problem.
I was able to fix the issue by making a new workbook and copying everything over. It worked fine. I think the original was corrupted somehow. For those having the same issue, I think Rich Holton's answer would be worth a try in case you have more than just a few things to copy. Thanks everyone for you time and input on this!
Hope you're doing well. I'm going to preface this by saying I'm not a programmer and I'm sure the code I have started is riddled with more errors then what I think. Hopefully you can help :D.
I have an Excel sheet that gets generated from another program that comes out like this:
excel sheet
However, the size of this sheet can change with every new generation of this sheet from the other program. (ex, A can have 7 next time, and D could have 9) And the sheet as it is cannot be used easily to do the math required as I only need specific groups of information at a given time, in this example groups B and D only.
What I'm hoping to create is something that will take the sheet as its generated, and turn it into something that looks like this:
result sheet
This is the code I've written so far, but since I don't really know what I'm doing I keep running into numerous problems. Any help would be appreciated.
Option Explicit
Sub Numbers()
Dim matchesFound As Integer
Dim row As Integer
Dim c As Integer
Dim copyRow As Integer
Dim copyLocationColumn As Integer
Dim arr(2) As String
arr(0) = "1"
arr(1) = "2"
arr(2) = "3"
Function arrayContainsValue(array, varValue)
found = false
for each = 0 to array
if array(i) = varValue then
found = true
exit for
arrayContainsValue = found
End Function
row = 1
c = 1
copyLocationColumn = 1
copyRow = 1
matchesFound = 0
Do While matchesFound < 3
if arrayContainsValue(arr, ThisWorkbook.Sheets("Data").Cell(column,row))
matchesFound = matchesFound + 1
Do While ThisWorkbook.Sheets("Data").Cell(column, row)
ThisWorkbook.Sheets("postHere").Cell(copyLocationColumn, copyRow) = _
ThisWorkbook.Sheets("postHere").Cell(c + 1, row)
copyRow = copyRow+1
row = row + 1
Loop
End If
row = row + 1
Loop
End Sub
There are many logic errors to numerate in a comment, Excel highlights them automatically I'll do a summary explaining them:
1. Function can't be "in the middle" of the sub, finish the Sub (take the Function from the sub and paste until it says end sub.
2.array is a forbidden name, try with another variable name
3.For each =0 ? to array? what do you try to mean like that? For Each has to be element in something For each element in Array for example For and To are for something defined in numbers (for counter=1 to 15)
Function arrayContainsValue(***array***, varValue) '2nd problem
found = false
for each = 0 to array '3rd problem
if array(i) = varValue then
found = true
exit for
arrayContainsValue = found
End Function
....
4. you're missing a then at the end
if arrayContainsValue(arr, ThisWorkbook.Sheets("Data").Cell(column,row))
I don't get the coding logic on how relates to the problem stated (?)
My code so far is like this:
Sub FindMatchingValue()
Dim i As Integer, TimeValueToFind As Date
TimeValueToFind = "04:00:00"
Sheets("Vessels").Range("F07").ClearContents
For i = 1 To 25 '
If CDate(Sheets("Vessels").Cells(i, 1).Value) = TimeValueToFind Then
MsgBox ("Found value on row " & i)
Sheets("Vessels").Range("F07").Value = Cells(i, 1).Offset(1, 1).Resize(1).Value
Exit Sub
End If
Next i
MsgBox ("Value not found in the range!")
End Sub
This code checks Column A for the time inputted in the format xx:xx:xx Both where the input is, and where the times are written are set as "Time" format.
Initially the CDate edit was not added. And this caused the code to always return false because, as it had been put, I was trying to "compare apples to oranges".
However adding the CDate addition produces a mismatch error. Similarly changing both to be a double also did not work:
Sub FindMatchingValue()
Dim i As Integer, TimeValueToFind As Date
TimeValueToFind = "04:00:00"
Sheets("Vessels").Range("F07").ClearContents
For i = 1 To 25 '
If Sheets("Vessels").Cells(i, 1).Value = CDbl(TimeValueToFind) Then ' < This was the line changed
MsgBox ("Found value on row " & i)
Sheets("Vessels").Range("F07").Value = Cells(i, 1).Offset(1, 1).Resize(1).Value
Exit Sub
End If
Next i
MsgBox ("Value not found in the range!")
End Sub
However this one is a different reason, since Excel stores the values as floating points, each value is still different. "It is well known that the expression a==b is likely to return False when a and b are both doubles, even though you might think they are the same. This is due to the finite precision with which floating point numbers are stored."
The way around this would be to Set a tolerance. If abs(a-b)<tolerance Then
However i'm not particularly sure which tolerance to use nor how to write it to include without messing up the first loop.
I wonder if anyone could shed some light on this and direct me to which additions I need to make and what sort of tolerances would be acceptable? I think the question is essentially twofold. Thank you in advance!
Use TimeValue() or TimeSerial() like so:
Sub SO()
Dim x As Date
Dim y As Date
Dim z As Date
x = TimeValue("04:00:00")
y = TimeSerial(4, 0, 0)
z = CDate(Range("A1").value) '// A1 has "04:00:00" entered
Debug.Print x = y '// True
Debug.Print y = z '// True
Debug.Print x = z '// True
End Sub
Putting this into the context of your code:
Sub FindMatchingValue()
Dim i As Integer, TimeValueToFind As Date
TimeValueToFind = TimeValue("04:00:00")
Sheets("Vessels").Range("F07").ClearContents
For i = 1 To 25 '
If CDate(Sheets("Vessels").Cells(i, 1).value) = TimeValueToFind Then
MsgBox ("Found value on row " & i)
Sheets("Vessels").Range("F07").value = Cells(i, 1).Offset(1, 1).Resize(1).value
Exit Sub
End If
Next i
MsgBox ("Value not found in the range!")
End Sub
You are correct that the imprecision of floating point numbers is the cause of your problem. Remember that the underlying data in a Date data type is still a Double, formatted to look like a date.
The question of "...what sort of tolerances would be acceptable?" is really up to you. Given that your test value is "hh:mm:ss" then equal to the second may suffice.
There are many ways to achieve this. If your data is formatted as "hh:mm:ss" then this will work
If CDate(Sheets("Vessels").Cells(i,1).Text) = TimeValueToFind Then
This relies on the format applied to the sheet being to the same precision as your test value
For those interested, here is the answer:
Sub FindMatchingValue()
Dim i As Integer, TimeValueToFind As Date, Delta As Double, Tolerance As Double
TimeValueToFind = Sheets("Vessels").Range("F06")
Tolerance = 0.001
Sheets("Vessels").Range("F07").ClearContents
For i = 2 To 25 '
Delta = Sheets("Vessels").Cells(i, 1).Value - CDbl(TimeValueToFind)
If Abs(Delta) <= Tolerance Then
MsgBox ("Found value on row " & i)
Sheets("Vessels").Range("F07").Value = Cells(i, 1).Offset(0, 1).Resize(1).Value
Exit Sub
End If
Next i
MsgBox ("Value not found in the range!")
End Sub
So any time in the box F06 typed in, it now finds. A combination of tolerance was used and also converting to a Double. i = 1-25 was changed to 2-25, because I had a text header and that was producing a mismatch error.
I have a problem. I spent hours designing a form which works just great with all your feedback. Today, everything went wrong. The reason for this is simple. A few new columns got added and, obviously, the data my form is reading in is now wrong.
Thus I was thinking of trying the following...
Rather than using the column number as below
TK = Cells(ActiveCell.Row, "S").Value 'everything in the form refers to the active row
I could possibly use the column headings in Row 1.
Is that possible ? This way the spreadsheet can have columns added up to as many as a user would like and the form would dynamically scan for the right heading and get the column number that way.
My thought is, on opening the form, read in all the headings, pick out the ones I need and assign them to a variable. Then I use my normal code and substitute the variable into the column section.
It sounds easy, but I have no idea how to do this.
Use the versatile Find to give you a quick method of detecting where your header is - or if it is missing
Find details here
In the code below I have specified that the search must return
an exact match (xlWhole)
a case sensitive match (False)
The match can be a partial match (xlPart) if you were looking to match say Game out of Game X
code
Const strFind = "Game"
Sub GetEm()
Dim rng1 As Range
Set rng1 = ActiveSheet.Rows(1).Find(strFind, , xlValues, xlWhole, , , False)
If Not rng1 Is Nothing Then
MsgBox "Your column is " & rng1.Column
Else
MsgBox strFind & " not found", vbCritical
End If
End Sub
Why use a loop? There's no need to.
Dim col as variant
Col = application.match("my header", rows(1), 0)
If iserror(col) then
'not found
Else
TK = cells(activecell.row, col)
End if
For this purpose I usually use a function which runs through the headers (in the first row of a sheet) and returns the number of the column which contains the value I have searched for.
Public Function FindColumn(HeaderName As String, Sht As String) As Long
Dim ColFound As Boolean
Dim StartingPoint As Range
ColFound = False
Set StartingPoint = Sheets(Sht).Range("A1")
Do While StartingPoint.Value <> ""
If UCase(Trim(StartingPoint.Value)) = UCase(Trim(HeaderName)) Then
FindColumn = StartingPoint.Column
ColFound = True
Exit Do
Else
Set StartingPoint = StartingPoint.Offset(0, 1)
End If
Loop
If Not ColFound Then FindColumn = 0
End Function
Example:
If the first row of your sheet named "Timeline" contains headers like e.g. "Date" (A1), "Time" (B1), "Value" (C1) then calling FindColumn("Time", "Timeline") returns 2, since "Time" is the second column in sheet "Timeline"
Hope this may help you a little.
Your thought is a good one. Reading in column headers to calculate addresses is one way to avoid hard coding - e.g.
Sub Test()
Dim R As Range
Set R = ActiveSheet.[A1]
Debug.Print ColNo(R, "Col1Hdr")
End Sub
Function ColNo(HdrRange As Range, ColName As String) As Integer
' 1st column with empty header is returned if string not found
ColNo = 1
Do While HdrRange(1, ColNo) <> ""
If HdrRange(1, ColNo) = ColName Then Exit Do
ColNo = ColNo + 1
Loop
End Function
Another way I frequently use - and I must admit I prefer it over the above, is to define Enum's for all my tables in a seperate "definition" module, e.g.
Public Enum T_VPN ' sheet VPN
NofHRows = 3 ' number of header rows
NofCols = 35 ' number of columns
MaxData = 203 ' last row validated
GroupNo = 1
CtyCode = 2
Country = 3
MRegion = 4
PRegion = 5
City = 6
SiteType = 7
' ....
End Enum
and use it like
Sub Test1()
Debug.Print ActiveSheet(T_VPN.NofHRows, T_VPN.Country)
End Sub
As you can see, the usage is simpler. Allthough this is again "some kind" of hardcoding, having all definition in one place reduces maintenance significantly.