I have two "combo boxes" and one "txtbox" in my userform, In workbook "sheet1" i have names on column A and Month on column B and columns C to N are Jan. to Dec. which contain production hours for each name/specific month
-cboName
-cboMonth
-txtHours
I use below code to populate txtHours
Private Sub cboName_Change()
Dim EName As String
Dim Row, Col As Integer
EName = Me.cboName.Text
If EName <> "" Then
With Application.WorksheetFunction
Row = .Match(EName, Sheets("sheet1").Range("A2:A100"), 0)
GetMonthNum (Me.cboMonth.Text)
txtShiftHours.Value = Sheets("sheet1").Cells(Row + 1, Col + 3)
End With
End If
End Sub
Private Sub GetMonthNum(Month As String)
Select Case Month
Case Jan
Col = 3
Case Feb
Col = 4
Case Mar
Col = 5
Case Apr
Col = 6
Case May
Col = 7
Case June
Col = 8
Case July
Col = 9
Case Aug
Col = 10
Case Sept
Col = 11
Case Oct
Col = 12
Case Nov
Col = 13
Case Dec
Col = 14
End Select
End Sub
but regardless of month selection on cboMonth,txtProduct is populated with column 3 cuz this line
txtShiftHours.Value = Sheets("sheet1").Cells(Row + 1, Col + 3)
Please help me
thanks
You had several issues:
Your Case statements were checking the value of the String variable Month against undefined variables such as Jan, Feb, etc. This should have been checking against String literals such as "Jan", "Feb", etc.
In your GetMonthNum subroutine, you were assigning a value to an undefined variable Col.
In your cboName_Change subroutine you were using a variable Col which had never been assigned a value, so it would have had the default value of zero.
You also had some minor issues, which wouldn't have stopped your code from working, but could lead to problems down the track:
You used several variable names (Row, Month) which are the same as built in Functions / Properties within VBA. This is usually a very bad idea.
You declared Row as a Variant, despite declaring Col as an Integer.
It's a good idea to define row and column variables to be Long rather than Integer - the maximum number of rows in Excel is now 1048576, but an Integer can only hold numbers up to 65536.
It is also a good idea to always include the Option Explicit statement as the first line of each of your code modules. This tells the compiler to check that all your variables have been declared, and thus prevents many typos and attempts to use variables in one subroutine which are local to another subroutine.
I have refactored your code and hopefully it should now work.
Option Explicit
Private Sub cboName_Change()
Dim EName As String
Dim RowNum As Long, ColNum As Long
EName = Me.cboName.Text
If EName <> "" Then
With Application.WorksheetFunction
RowNum = .Match(EName, Sheets("sheet1").Range("A2:A100"), 0)
ColNum = GetMonthNum(Me.cboMonth.Text) + 2
txtShiftHours.Value = Sheets("sheet1").Cells(RowNum + 1, ColNum)
End With
End If
End Sub
Private Function GetMonthNum(Mth As String) As Long
Select Case Mth
Case "Jan": GetMonthNum = 1
Case "Feb": GetMonthNum = 2
Case "Mar": GetMonthNum = 3
Case "Apr": GetMonthNum = 4
Case "May": GetMonthNum = 5
Case "June": GetMonthNum = 6
Case "July": GetMonthNum = 7
Case "Aug": GetMonthNum = 8
Case "Sept": GetMonthNum = 9
Case "Oct": GetMonthNum = 10
Case "Nov": GetMonthNum = 11
Case "Dec": GetMonthNum = 12
End Select
End Function
You could use some of Excel's Date & Time built-in functions, to replace your entire Private Sub GetMonthNum(Month As String) with the 1 line of code below:
ColNum = Month(DateValue("1/" & Me.cboMonth.Text & "/2017")) + 2
Explanation: since your cboMonth Combo-Box has month strings in the mmm month format. If you select "Feb", then when you get to this section ("1/" & Me.cboMonth.Text & "/2017") you are getting "1/Feb/2017".
When adding DateValue before, you get 1/Feb/2017, and when adding the Month before, the result is 2.
Related
I'm pretty new to VBA coding and I want to find the column index for the first numeric / short date / date column in a worksheet.
An example of what the data looks like this:
id sex 2015 2016
2 M 1 3
4 F 7 5
3 F 8 9
In this example, the answer should be 3. I'd like to set this as a variable so I can use this number later.
This is my code so far:
Sub find_value()
Dim c As Range
Dim firstAddress As String
With Worksheets(1).Range("A1:A500")
Set c = .Find(2015, lookin:=xlValues)
End Sub
As you can see, this is just a static solution.
Any ideas would be great! Thanks so much
Very rough, but here is a function that would get you the result you are after. You can call on result for later use or call the function as you need.
This is purposefully broken out so you can see how find_value becomes defined. You could amalgamate the code if you choose.
Sub test()
Dim result As String
result = find_value(2015, 1) 'find year 2015, entry 1
Debug.Print result
End Sub
Function find_value(year As Integer, entry As Integer)
Dim yearVal, entryVal, foundVal As Integer
yearVal = Application.WorksheetFunction.Match(year, Worksheets(1).Range("1:1"), 0)
entryVal = Application.WorksheetFunction.Match(entry, Worksheets(1).Range(Worksheets(1).Cells(1, yearVal), Worksheets(1).Cells(500, yearVal)), 0)
foundVal = Cells(entryVal, yearVal + 1)
find_value = foundVal
End Function
I made the below Public Function to choose a quarter based on the month number in my data.
Public Function quarter()
Dim Number As Integer
Dim quarterChosen As String
Number = Worksheets("Sheet1").Cells(Application.ActiveCell.Row, 2).Value
Select Case Number
Case 1 To 3
quarterChosen = "Q1"
Case 4 To 6
quarterChosen = "Q2"
Case 7 To 9
quarterChosen = "Q3"
Case 10 To 12
quarterChosen = "Q4"
End Select
quarter = quarterChosen
End Function
It works but when I drag it down to copy it down it stills considers the active cell the original cell started at. I need to rewrite the formula to get the result I want. How do I get that active cell to change as I drag or copy it down?
Pass your input cell as an argument of your function (and add an output type), along the line of this.
So you enter the formula in B1 =quarter(A1).
Public Function quarter(r As Range) As String
Dim quarterChosen As String
Select Case r.Value
Case 1 To 3: quarter = "Q1"
Case 4 To 6: quarter = "Q2"
Case 7 To 9: quarter = "Q3"
Case 10 To 12: quarter = "Q4"
Case Else: quarter = "n/a"
End Select
End Function
You should use Application.Volatile in the upper part of your function. I've just wrote this one for you and all works my end :)
Public Function Quarter(arg1 As Long)
Dim quarterChosen As String
Application.Volatile
Select Case arg1
Case 1 To 3
quarterChosen = "Q1"
Case 4 To 6
quarterChosen = "Q2"
Case 7 To 9
quarterChosen = "Q3"
Case 10 To 12
quarterChosen = "Q4"
End Select
Quarter = quarterChosen
End Function
You should also pass the function the cell you want to evaluate.
I have two excel sheets, one cumulative (year-to-date) and one periodic (quarterly). I am trying to check for potential entry errors.
Simplified ytd table:
ID Q1/18 Q2/18 Q3/18 Q4/18 Q1/19 Q2/19 ...
1 6 12 20 28 10 20
2 5 11 18 26 10 20
3 5 11 18 26 10 20
Simplified quarterly table:
ID Q1/18 Q2/18 Q3/18 Q4/18 Q1/19 Q2/19 ...
1 6 6 8 8 10 10
2 5 6 7 8 10 10
3 5 6 7 8 10 10
In the above example there are no entry errors.
I am trying to create a third sheet that would look something like this
ID Q1/18 Q2/18 Q3/18 Q4/18 Q1/19 Q2/19 ...
1 T T T T T
2 T T T T T
3 T T T T T
I initially tried using a formula like this:
=IF('YTD'!C2-'YTD LC'!B2-'QTR'!B2=0,T,F)
I don't particularly like this because the formula will not apply in the first quarter. This also assumes that my data in both sheets are ordered in the same way. Whilst I believe it to be true in all cases, I would rather have something like an index-match to confirm.
I tried working on a VBA solution based on other solutions I found here but made less progress than via the formulas:
Sub Compare()
lrow = Cells (Rows.Count, 1).End(xlUp).Row
lcol = Cells(1, Columns.Count).End(xltoLeft).Column
Sheets.Add
ActiveSheet.Name = "Temp Sheet"
For i = 2 To lrow
For j = 3 To lcol
valytd = Worksheets("YTD").Cells(i,j).Value
valytd = Worksheets("YTD").Cells(i,j).Value
If valytd = valytd Then
Worksheets("Temp").Cells(i,j).Value = "T"
Else:
Worksheets("Temp").Cells(i,j).Value = "F"
Worksheets("Temp").Cells(i,j).Interior.Color Index = 40
End If
Next j
Next i
End Sub
In my opinion the easiest way is to:
Create a sheet & copy paste row 1 + Column 1 like image below (Title & IDs)
Use Sum Product to get your answers
Formula:
=IF(SUMPRODUCT((Sheet1!$B$1:$G$1=Sheet3!$B$1)*(Sheet1!$A$2:$A$4=Sheet3!A2)*(Sheet1!$B$2:$G$4))=SUMPRODUCT((Sheet2!$B$1:$G$1=Sheet3!$B$1)*(Sheet2!$A$2:$A$4=Sheet3!A2)*(Sheet2!$B$2:$G$4)),"T","F")
Formula Notes:
Keep fix the range with Quarters using double $$ -> Sheet1!$B$1:$G$1
keep fix the range with IDs using double $$ -> Sheet1!$A$2:$A$4
Keep fix the range with values -> Sheet1!$B$2:$G$
Keep fix column header -> =Sheet3!$B$1
Leave variable rows number -> =Sheet3!A2
Images:
This should do the trick, the code is all commented:
Option Explicit
Sub Compare()
Dim arrYTD As Variant, arrQuarterly As Variant, arrResult As Variant
Dim Compare As Scripting.Dictionary 'You need Microsoft Scripting Runtime for this to work
Dim i As Long, j As Integer, x As Integer
With Application
.EnableEvents = False
.Calculation = xlCalculationManual
.DisplayAlerts = False
.ScreenUpdating = False
End With
With ThisWorkbook
arrYTD = .Sheets("Name of YTD sheet").UsedRange.Value 'this will get everything on that sheet
arrQuarterly = .Sheets("Name of Quarterly sheet").UsedRange.Value 'this will get everything on that sheet
End With
ReDim arrResult(1 To UBound(arrYTD), 1 To UBound(arrYTD, 2)) 'resize the final array with the same size of YTD
Set Compare = New Scripting.Dictionary
'Here we fill the dictionary with the ID's position on the arrQuarterly array
For i = 2 To UBound(arrQuarterly) '2 because 1 is headers
If Not Compare.Exists(arrQuarterly(i, 1)) Then 'this is an error handle if you have duplicated ID's
Compare.Add arrQuarterly(i, 1), i 'now we know the position of that ID on the table
Else
'Your handle if there was a duplicated ID
End If
Next i
'Let's fill the headers on the result array
For i = 1 To UBound(arrYTD, 2)
arrResult(1, i) = arrYTD(1, i)
Next i
'Now let's compare both tables assuming the columns are the same on both tables (same position)
For i = 1 To UBound(arrYTD)
arrResult(i, 1) = arrYTD(i, 1) 'This is the ID
For j = 2 To UBound(arrYTD, 2)
x = Compare(arrYTD(i, 1)) 'this way we get the position on the quarterly array for that ID
If arrYTD(i, j) = arrQuarterly(x, j) Then 'compare if they have the same value on both sides
arrResult(i, j) = "T"
Else
arrResult(i, j) = "F"
End If
Next j
Next i
With ThisWorkbook.Sheets("Name of the result sheet") 'paste the array to it's sheet
.Range("A1", .Cells(UBound(arrResult), UBound(arrResult, 2))).Value = arrResult
End With
End Sub
I have column with comments in it (more then 5000 cases).
Those comments have text, numbers, date, everything.
I need to get phone number out of those comments.
Phone numbers are in random places for every comment, so LEFT,MID or RIGHT will not work
The closest result that I have reached is with Kutools =EXTRAXTNUMBERS() ...... but I get a line of numbers which includes date, ID`s, etc.
Would prefer a formula. :)
Two sample comments below, required phone numbers are in bold
Thursday, February 2, 2017 2:37 PM Coordinated Universal Time .3868 67076939 ,pers .pārv.Tatjana Call outcome chosen: Noruna citā laikā - 2017-02-03 07:15 Wednesday, February 8, 2017 8:18 AM Coordinated Universal Time .3868 nr.67074071-neeksistē,personāla daļas vad.Tatjana neatbild,arī nr.67076600 Call outcome chosen: Neceļ Friday, February 10, 2017 7:15 AM Coordinated Universal Time * .3868 *** piezv ap 13 Call outcome chosen: Noruna citā laikā - 2017-02-10 11:15
Thursday, February 2, 2017 11:15 AM Coordinated Universal Time 4213 zvanīt 66119908 Call outcome chosen: Noruna citā laikā - 2017-02-07 09:00 Tuesday, February 14, 2017 12:59 PM Coordinated Universal Time .4532 * anita#dzintarniece#rtp.lv Call outcome chosen: Turpināt internetā
This small UDF() will return all the 8 digit numeric sub-strings in a string:
Public Function PHNum(s As String) As String
Dim L As Long, i As Long, temp As String
Dim CH As String
L = Len(s)
temp = ""
PHNum = ""
For i = 1 To L
CH = Mid(s, i, 1)
If IsNumeric(CH) Then
temp = temp & CH
If Len(temp) = 8 Then
PHNum = PHNum & vbCrLf & temp
End If
Else
temp = ""
End If
Next i
End Function
Note:
To get the stacked format in the output cell, format it to wrap on.
Regexp Solution
This UDF extracts to you the phone numbers from a Text, as an array. You can eventually use Join to transform it into a csv string, or you can paste the array into a range of cells.
Function extractPhones(s As String) As String()
Dim i As Long, matches, match, ret
With CreateObject("VBScript.Regexp")
.Global = True
.Pattern = "\W[26]\d{7}\W"
Set matches = .Execute(s)
End With
ReDim ret(1 To matches.Count) As String
For Each match In matches
i = i + 1
ret(i) = Mid(match, 2, Len(match) - 2)
Next
extractPhones = ret
End Function
It uses a regular expression that matches phone number with these specs:
are exactly 8 digits
start by 6 or 2
are not preceded or followed by an alphanumeric letter, but by blanks or punctuation characters.
Using an UDF you can accomplish this by using the following code:
To use it:
Press ALT + F11
Insert Module
Paste Code
In Excel Sheet, use this formula =get_phone("CELL_WITH_NUMBER_HERE") to get the first sequence of 8 digits in your cell.
Code:
Public Function get_phone(cell As Range)
Dim s As String
Dim i As Integer
Dim num
Dim counter As Integer
'get cell value
s = cell.Value
'set the counter
counter = 0
'loop through the entire string
For i = 1 To Len(s)
'check to see if the character is a numeric one
If IsNumeric(Mid(s, i, 1)) = True Then
'add it to the number
num = num + Mid(s, i, 1)
counter = counter + 1
'check if we've reached 8 digits
If counter = 8 Then
get_phone = num
Exit Function
End If
Else
'was not numeric so reset counter and answer
counter = 0
num = ""
End If
Next i
End Function
Example Image:
Another regexp option that returns all matches to a single cell
See https://regex101.com/r/Hdv65h/1
Function StrPhone(strIn As String) As String
Dim objRegexp As Object
Set objRegexp = CreateObject("VBScript.Regexp")
With objRegexp
.Global = True
.Pattern = ".*?(\d{8})|.*$"
StrPhone = Trim(.Replace(strIn, "$1 "))
End With
End Function
There is add-on in Excel that I used in the past for regular expressions (http://seotoolsforexcel.com/regexpfind/). In your case, it could be complicated as you don't know how many times a phone number will appear in your cell. For these cases I suggest you use the VBA scripts that have been provided by other users.
I currently have a "For Next" loop that iterates through various years and I want to modify it to loop through dates, specifically the end of each month. My generic code for the year loop is below. Clearly looping through years is relatively easy since you have a start year, which is an integer, and the iteration is 1. Now I want to modify the loop to iterate though various end of month dates. For example, 1/31/2003, 2/28/2003, ......, 12/31/2007. Also, note that for each iteration I create a new worksheet with the name of the current iteration as the name of the worksheet. Again, this is relatively easy for a year but using a date with a "/" complicates things. Does anyone have any ideas for creating a loop using end of month dates as well as creating sheets using dates? I do have an array of the dates so the code could refer to the array within a sheet. And the name of the sheet could be in any format. For example, "mm-dd-yyyy".
Sub YearLoop()
Dim FirstYr As Integer
Dim LastYr As Integer
Dim Sheetname As String
Dim Counter1 As Single
FirstYr = Sheets("Model").Range("ax15").Value
LastYr = Sheets("Model").Range("ax16").Value
Counter1 = 0
For J = FirstYr To LastYr
Worksheets.Add(After:=Worksheets(Worksheets.Count)).Name = J
Sheetname = J
'do stuff
Counter1 = Counter1+1
Next
End Sub
The DateSerial function produces the end-of-month date of the previous month when you give any month a day of zero.
dim m as integer
for m = 2 to 13
debug.print dateserial(2016, m, 0)
next m
The characters that can't be used in sheet names are ASCII \/[]*:?, but you can use Unicode characters like ⁄∕/
d = #1/31/2003#
While d <= #12/31/2007#
Sheets.Add(, ActiveSheet).Name = Replace(d, "/", ChrW(8260))
d = d + 32
d = d - Day(d)
Wend
Update
Or you can use Jeeped's answer like this:
For m = FirstYr * 12 + 2 To LastYr * 12 + 13
Sheets.Add(, ActiveSheet).Name = Replace(DateSerial(0, m, 0), "/", ChrW(8260))
Next
Public Sub ReadAndDisplay()
' Get Range
Dim rg As Range
Set rg = ThisWorkbook.Worksheets("Returns Calc").Range("C118:C319")
' Create dynamic array
Dim Arr() As Variant
' Read values into array from sheet1
Arr = rg
For Each mark In Arr
Dim CurrentDate1 As Date, DimCurrentDate2 As String
CurrentDate1 = mark
CurrentDate2 = Replace(CurrentDate1, "/", ".")
Worksheets.Add(After:=Worksheets(Worksheets.Count)).Name = CurrentDate2 & " Rtns"
'do Stuff
Next mark
End Sub