I really have no idea if this is possible or not, but I'm trying to write an Excel program that uses the built-in date/time function to do a calculation and output it to a cell. It will use the downloaded date (located in column A), the last time of data entry (bottom row of B), and each independent data entry time (column B).
I want the formula in each cell to look like this::
A2 - Time(0,0,(B_Last - B_Current)/1000)
I'm assuming I will need it in some sort of loop.
My current code looks like this ::
Sub Bottom()
Dim Count As Integer
Dim i As Integer
Dim Last As Double
Set myRange = Columns("B:B")
Last = Cells(Rows.Count, "B").End(xlUp)
Set r1 = ThisWorkbook.Sheets("Sheet1").Range("A2")
Count = Application.WorksheetFunction.CountA(myRange)
For i = 1 To Count
Set r2 = ThisWorkbook.Sheets("Sheet1").Range("B" & i)
r2 = r1 - Time(0, 0, (Last - r2) / 1000)
Next
End Sub
Any help with this would be greatly appreciated!
Thanks!!
VBA has much more "in-built power" than Excel and thus you don't really need to rely on Excel formulae when using VBA. But if you want to do it anyway, you have to do something like this:
Range("B" & i).Value = "=Time(0, 0, (" & Last - r2 & ") / 1000)"
To get the value from the cell:
Dim cellVal As Date
cellVal = Range("B" & i).Value
You just have to make sure that the VBA variables and the strings written to the cells are treated separatedly (with " and &, as shown above) and that you store the value of the given cell in a variable of the right type (if you rely on string type it would work with any content).
Some correction in your code:
Sub Bottom()
Dim Count As Integer
Dim i As Integer
Dim Last As Double
Set myRange = Columns("B:B")
Last = 10 'What you want here??
Set r1 = ThisWorkbook.Sheets("Sheet1").Range("A2")
Dim r1Val As Long
r1Val = r1.Value
Count = Application.WorksheetFunction.CountA(myRange)
For i = 1 To Count
Set r2 = ThisWorkbook.Sheets("Sheet1").Range("B" & i)
r2.Value = "=" & r1Val & " - Time(0, 0," & CStr(Last) & "/ 1000)" 'r2 = r1 - Time(0, 0, (Last - r2) / 1000) -> r2 = ... Last - r2 does not make any sense; r2 is a whole date and Last - r2 refers to a part of a date
Next
End Sub
This is just a simple version of your code showing you some of things you shouldn't be doing: you are mixing up ranges with values, not considering different types, accounting for variables which I cannot even understand, etc.
Just take this code, analyse it carefully, understand what you have done wrong and keep adding functionalities step by step, by doing some research every time. The ideas are very simple:
You can set as many cells as you wish as a range.
A range can have different contents (got through range.Value); these contents are associated to a variable in VBA which has to have
the same type (string works for any content).
When you write to a cell, you write the whole text (if you put "=" ahead, Excel understand it as a formula).
When you read from a cell, you get the final value (the one from the formula, if applicable).
Etc.
I recommend you to do some research and get much more used to VBA than what you are right now.
Related
So, I've run into the typical date formatting issue.
Which is causing me a headache.
I can't share the data for security reasons. But, I can describe the format.
The VBA is simple enough, and she be able to be worked out.
In short,
My macro obtains its data from Col C, where the regex extracts the department and date.
I then call these in a loop to paste the data into Col A, B and finally C to replace the original data.
My issue is, Col B has a date in it.
It is extracted as "02/04/2018", but when it pastes it into the col, it appears as 04/02/2018. Which is a massive issue.
I cannot get the VBA macro to hold, or set the format. So far.
I will attach my script below and you can let me know.
Thanks!
Ps. This is the line I suspect is the issue.
ws.Range("B" & x).Value = match.SubMatches(3)
I cannot figure out how to have it set the value as extracted from the string. sigh.
Sub Link()
Dim ws As Worksheet
Dim lastRow As Long, x As Long
Dim matches As Variant, match As Variant
Dim Reg_Exp As Object
Dim rValue As String
Set Reg_Exp = CreateObject("vbscript.regexp")
Reg_Exp.Pattern = "([\s\S]+?):\s*([\s\S]+?)\s*-\s*([A-z]+)\s*,\s*([0-9]{2}\/[0-9]{2}\/[0-9]{4})\b"
Set ws = Sheet2
lastRow = ws.Range("C" & Rows.Count).End(xlUp).Row
For x = 1 To lastRow
Set matches = Reg_Exp.Execute(CStr(ws.Range("C" & x).Value))
If matches.Count > 0 Then
For Each match In matches
ws.Range("A" & x).Value = match.SubMatches(3) & match.SubMatches(1)
ws.Range("B" & x).Value = match.SubMatches(3)
ws.Range("C" & x).Value = match.SubMatches(1)
Next match
End If
Next x
End Sub
You have provided an ambiguous date. 02/04/2018, depending on your locale, could be either 04-Feb-2018 or 02-Apr-2018.
Exactly how to fix your problem depends on your locale settings.
However, what you can do is create an unambiguous date by extracting separately the month, day and year, and creating a date from that.
Change your regex pattern to split out date parts separately.
([\s\S]+?):\s*([\s\S]+?)\s*-\s*([A-z]+)\s*,\s*([0-9]{2})\/([0-9]{2})\/([0-9]{4})\b
Then add into your code lines like this:
Dim DT as Date
... your regex stuff ...
With mc(0)
'DT = DateSerial(.SubMatches(5), .SubMatches(3), .SubMatches(4))
' or
'DT = DateSerial(.SubMatches(5), .SubMatches(4), .SubMatches(3))
End With
Choose the appropriate line depending on whether the date in your original data is MDY or DMY format.
You can then write DT to the appropriate part of your worksheet, and format the cell as you wish.
Well I have done a lot of research and found a lot of relevant questions and answers but couldn't quite figure out how to cater that information to my specific need.
I am working on a project to create a macro that will correct mistakes and fill in information commonly found in product catalogs that I work with.
One thing I am trying to accomplish is to give the value "unassigned" to each blank cell in a row that is marked "Y" in column B.
I've found out how to change every cell in those particular rows and have it adjust dynamically to the number of rows. What I can't figure out is how to do the same for the number of columns. In my code below everything between columns B and S is included. Column B will always be in the same spot but column S will not always be the last column.
Dim tracked As String
Dim endCell As Range
Dim endRow As Long
Dim endColumn As Long
Dim start As Long
endRow = ActiveSheet.Range("D2").End(xlDown).Row
endColumn = ActiveSheet.Range("A1").End(xlToRight).Column
Let tracked = "B2:" & "B" & endRow
Set trackItem = ActiveSheet.Range(tracked)
For Each y In trackItem
If Left(y.Value, 1) = "Y" Then
'start = y.Row
'Set endCell = ActiveSheet.Cells(endColumn, start)
ActiveSheet.Range("B" & y.Row & ":" & "S" & endColumn).Value = "Unassigned"
End If
Next y
I included some code that I've left commented out so you can see what I've tried.
So, I can successfully change the value of all cells within that range but I need to know how to do it with a range where the number of columns will not always be the same. In addition, I want to select the blank cells only within this range and assign them a value. I imagine this will need to be done row by row as the correct criteria will not always be together.
I'm surprised more people don't use 'UsedRange' when there is a need to loop through all the cells that have data on a sheet. (Just yesterday someone was complaining that it takes too long to loop through all 17,179,869,184 cells on a worksheet...)
This example lists & counts the "used" range, and will easily adapt to your needs.
Sub List_Used_Cells()
Dim c As Range, x As Long
For Each c In ActiveSheet.UsedRange.Cells
Debug.Print c.Address & " ";
x = x + 1
Next c
Debug.Print
Debug.Print " -> " & x & " Cells in Range '" & ActiveSheet.UsedRange.Address & "' are considered 'used'."
End Sub
I'm using INDEX and MATCH functions to pull data which is concatenated string of G2 and H2 from column D (sorry I don't have enough points to attach pic). Column D has INDEX(column A and column B) and columns A and B have values till 12th row. MATCH is working fine giving me the position as 6 on the worksheet. But when I use this in VBA code as shown below,INDEX is working in the VBA code (can be seen through MsgBox) but MATCH function which would allot value to the variable 'check' isn't working. I have been breaking my head for really long. Need help from experts here. Somebody please tell me where am I going wrong?
Sub testindex()
Dim check As Long
Set sh = Sheets("Sheet1")
For j = 1 To 11
'Index value is correctly shown
MsgBox "Index Value=" & Application.WorksheetFunction.Index(sh.Range("A2:B12"), j, 1) & Application.WorksheetFunction.Index(sh.Range("A2:B12"), j, 2)
'Cells(7, 4)=ISA737775 same as G2&H2
MsgBox "Cells(7,4)=" & Cells(7, 4)
check = Application.WorksheetFunction.Match(Cells(7, 4), Application.WorksheetFunction.Index(sh.Range("A2:B12"), j, 1) & Application.WorksheetFunction.Index(sh.Range("A2:B12"), j, 2), 0)
Next j
End Sub
Thanks
Match expects the second paramater to be in the form of a range. When you call match through VBA that range actually needs to be a range object, not just some string like "A1:A12" or whatever it is that your concatenated Index formulas output.
At any rate, you are iterating already, so why not just call those values directly instead of pulling their values through Index?
check = Application.WorksheetFunction.Match(Cells(7, 4), sh.Range("A" & 2 + j).value & sh.Range("B" & 2 + j), 0)
Which is writing the same exact thing but without having to use a taxing INDEX function in VBA to do it. Note that this still won't work because the second parameter of match is still just a string which is a concatenated value from Column A and Column B. You could convert to a range by sticking them in the range object with:
check = Application.WorksheetFunction.Match(Cells(7, 4), sh.Range(sh.Range("A" & 2 + j).value & sh.Range("B" & 2 + j)), 0)
I'm assuming that the values in A and B are actual cell names that when concatenated will make a range. Like when j=1 then the it would be like check=Match(Cells(7,4), sh.Range("G2:H50"), 0) or something...
I'm new to excel VBA and I've been searching for a few hours and I can't figure this out. The R1C1 method is confusing me (I have been researching it), but this is how the record macro started the formula.
I have names in Column 1 and I need to average all the values in Column 4, but only for each Name, then repeat for the other names. I want the output in Column 5, Offset(0,1) from "Buy Day". I have been searching online and putting together some code from other examples I found, but I still can't get it. Any help would be appreciated! Thanks!
Example Sheet (note: is there any way to insert a table?)
Desired Outcome
Code (so far)
Dim LastRow2 As Long
Dim rng As Range
Dim r1 As Long, r2 As Long
Dim lNoRows As Long
LastRow2 = Sheets("Sheet2").Cells.Find("*", SearchOrder:=xlByRows, SearchDirection:=xlPrevious).Row
Set rng = Sheets("Sheet2").Range("A2:D" & LastRow2)
lNoRows = rng.Rows.Count
For r1 = 1 To lNoRows
If rng(r1, 4) = "Buy Day" Then
For r2 = 1 To lNoRows
If rng(r1, 1) = rng(r2, 1) And r1 = r2 Then _
rng(r2, 5).FormulaR1C1 = "=AVERAGE(R" & r2 & "C[-1]:R" & r2 & "C[-1])"
Next r2
End If
Next r1
Range("K9").FormulaR1C1 = "=Average(R[-7]C[-7]:R[1]C[-7])"
Basically what I'm trying to recreate in the top Average formula is what's in the bottom one, except dynamically, not specific to one cell. When it runs right now it just pulls in one value, it doesn't do any averaging. In cell E6 it shows =AVERAGE(D$5:D$5).
I see you've understood that you've mixed up rows and columns in your question description. So I'll just show you the idiomatic Excel way of doing what you want:
You'll find the Subtotal tool in Data > Outline > Subtotal.
I haven't answered a question twice before, but I feel that even though my first answer is still valid, this one is better. You will want this formula in your cell E2, and then copy down for the entire column:
=IF(D2 = "Buy Day", AVERAGEIF($A:$A, A2, $D:$D), "")
It takes the average of only those numbers in column D whose corresponding symbol in column A is the same as the symbol in the 'Buy Day' row. It will give you your desired outcome.
I have a loop wherein I take the mean of several columns of numbers with the same number of rows each.
The point of the loop is to capture these means in a new vector.
So for each loop I need to indicate "all rows". In matlab this would be easy, just use ":" But I can't figure out what the analogy is in VB. Please help! Thanks.
(Please advise me as to what I put in the code below where I have ALLROWS).
My attempt so far:
For i = 1 To CA
mrCA11(i) = Application.WorksheetFunction.Average(revCA11(**ALLROWS**,i))
Next i
In matlab this would be:
For i = 1:CA
mrCA11(i) = mean(revCA11(:,i));
Next i
EDIT: I've also tried this trick to no avail:
For j = 1 To CA
For i = 1 To s11
temp11(i) = revCA11(i, j)
Next i
mrCA11(j) = Application.WorksheetFunction.Average(temp11)
Next j
I get the error message: "Unable to get the Average property of the Worksheet Function class"
As everybody (Tim and shahkalpesh at least) pointed out, we need to understand what is revCall or more specifically, we need to understand how you want to give them ALL ROWS in argument.
Finding the last row (or column or cell)
A common Excel issue is to find the last used row / column / cell.
This will give you the end of your vector.
Excel give you several methods to deal with this:
xlTypeLastCell
Last cell used in the entire sheet (regardless if it's used in column A or not)
lastRow = ActiveSheet.Cells.SpecialCells(xlCellTypeLastCell).Row
End(xlUp)
Last cell used (including blanks in-between) in Column A is as simple as this:
lastRow = Range("A" & Rows.Count).End(xlUp).Row
End(xlToLeft)
Last cell used (including blanks in-between) in Row 1 is as simple as this:
lastRow = ActiveSheet.Cells(1, Columns.Count).End(xlToLeft).Row
UsedRange
Last cell used in the WorkSheet (according to Excel interpretation):
Set rangeLastCell = ActiveSheet.UsedRange
Using an array as argument
The methods above told you how to find the last row (if this is what you need). You can then easily create your vector and use it in your procedure revCA11.
You can either give an array as argument as Tim pointed out in his answer with this kind of statement:
myArray = ActiveSheet.Range("A1", Cells(lastRow, lastColumn).Value
Or you can use the integer (or long) to build your vector inside your procedure as simple as declaring a range:
Range("A1:A" & lastRow)
You might clarify exactly how revCA11 is declared/created, but maybe something along these lines might work for you:
Sub Tester()
Dim arr, x
arr = ActiveSheet.Range("A1:D5").Value '2-D array
'average each column
Debug.Print "Columns:"
For x = 1 To UBound(arr, 2)
Debug.Print x, Application.Average(Application.Index(arr, 0, x))
Next x
'average each row
Debug.Print "Rows:"
For x = 1 To UBound(arr, 1)
Debug.Print x, Application.Average(Application.Index(arr, x, 0))
Next x
End Sub