Trouble with an Excel VBScript, Referencing A Relational Cell Address - excel

Okay, I'm learning scripting for Excel and I've come across something that I just don't know how to do.
I've been asked to help automate the import of a sheet into a sheet and then add some columns off to the side and do some calculations and autofill them to the last row of the imported info. That is no problem. I recently found out however that the sheet that I would import for my office has X number of columns and other offices have Y and Z number of columns in the sheets that they would import. So I'm trying to do this to where it builds the calculation columns at the end of the imported columns. With that bit of background here's where I need some assistance:
Script as written for my office and works:
Range("O1").Select
ActiveCell.FormulaR1C1 = "Remainder"
Range("O2").Select
ActiveCell.FormulaR1C1 = "=MOD(RC[-14],RC[-5])"
Range("O2").Select
Range("O2").AutoFill Destination:=Range("O2:O" & Cells(Rows.Count, "B").End(xlUp).Row)
So now I need to make this relational not Cell Address Specific So I came up with this.
Range("A1").select
Selection.End(xlToRight).Select
ActiveCell.Offset(0, 1).Select
ActiveCell.FormulaR1C1 = "Threshold"
ActiveCell.Offset(1, 0).Select
ActiveCell.FormulaR1C1 = "=IF(RC[-13]>(RC[-5]/3),""Over"",""Under"")"
Range(ActiveCell.Address).AutoFill Destination:=Range(*****How to return the Current Cell Address** : **How to Return the Current Column Letter***** & Cells(Rows.Count, "B").End(xlUp).Row)
I've Tried "ActiveCell.Address":"CHAR(COLUMN()+64))" but That doesn't work and I Just don't know how to get it to set that value to Be the equivalent ?2:? my office running this should autofill from O2:O. But would return P2:P in another.

I'm assuming you want the formula to go in the 1st unused column, no matter how many columns there are. (This will allow for future column use, as well.)
Dim LastCol as integer
LastCol = Cells(1, Columns.Count).End(xlToLeft).Column + 1
Cells(1, LastCol).FormulaR1C1 = "Remainder"
Cells(2, LastCol).FormulaR1C1 = "=MOD(RC[-14],RC[-5])"
Cells(2, LastCol).AutoFill Destination:=Range(Cells(2, LastCol), _
Cells(Cells(Rows.Count, "B").End(xlUp).Row, LastCol))

How's this:
Sub test()
Dim lastCol As Integer, lastRow As Integer, formulaCol As Integer
Dim ws As Worksheet
Set ws = ActiveSheet
' First, let's find the last column (#) and last row (#)
With ws.UsedRange
lastCol = .Columns.Count
lastRow = .Rows.Count
End With
' Now, create the "header, two columns offset from last column"
formulaCol = lastCol + 2
Cells(1, formulaCol).Value = "Remainder"
'Now, let's create a range, that we will fill with the MOD() formula. You can do this _
through resizing a range, instead of selecting and autofill
With ws
.Range(.Cells(2, formulaCol), .Cells(2, formulaCol)).Resize(lastRow - 1, 1).FormulaR1C1 = _
"=IF(RC[-" & formulaCol - 1 & "]>(RC[-2]/3),""Over"",""Under"")"
End With
End Sub
I am assuming a few things: Your raw sheets come with the data in one block. In other words, you don't have a gap of columns between data you need. This will get the last used column and row, and use those to enter your formula.
Also, I assume that you are checking that the first part of your mod() formula refers to the info in Col. A, and the second part is the right most column's data. I think I'm not understanding something, so any more info. about how the data is laid out would be helpful. But also, this avoids using "Select", which is good VBA practice from what I've gathered.

Related

Selecting cell by adjacent column name

I am very new to VBA and am trying to create a macro that selects the cell next to a specifically named column, names it "UniqueID", has it apply a concatenate formula to the whole column, and then selects the next column over, names it "VerifyID", and has it apply a VLOOKUP to the whole column. What I'm having issues with is having the specific cell selection work. Here is what I have:
Application.CutCopyMode = False
Sheets("PowerBI Data Dump").Select
Selection.AutoFilter
Dim i As Long
Dim LastSamplePrepColumn As Range
Dim rngHeaders As Range
Set rngHeaders = Range("1:1")
Set LastSamplePrepColumn = rngHeaders.Find("UniqueID")
i = LastSamplePrepColumn.Column
j = LastSamplePrepColumn.Column + 1
ActiveSheet.Cells(2, i).Select
ActiveCell.FormulaR1C1 = "=CONCATENATE(RC[-2],RC[-1])"
Selection.AutoFill Destination:=Range("RC2:RC157")
ActiveSheet.Cells(1, j).Select
ActiveCell.FormulaR1C1 = "VerifyID"
ActiveSheet.Cells(2, j).Select
ActiveCell.FormulaR1C1 = "=VLOOKUP(RC[-1],UniqueID!C[-26],1,FALSE)"
When debugging, it errors at the last line. My main issue, however, is with having the Range().Select choosing columns by letter instead of by the name of the column. I get data dumps in a variety of formats, so I need it to be able to select a column by the name of the one next to it.
Thanks in advance for any advice.
Edit: I have changed my code to reflect where I am now. I'm still stuck on how to make the formula apply to the whole column without selecting the column by letter.
So you want to find the 'UniqueID' column and then add formulas in the two columns to the right and copy then down?
This code will do that but I think you might need to rethink the VLOOKUP formula.
For a start you can probably replace it with MATCH and if the column UniqueID is going to be in you might want to consider changing the relative column reference, i.e. -26, to an absolute reference.
Dim wsData As Worksheet
Dim LastSamplePrepColumn As Range
Dim rngHeaders As Range
Dim colID As Long
Application.CutCopyMode = False
Set wsData = Sheets("PowerBI Data Dump")
Set rngHeaders = ws.Range("1:1")
colID = Application.Match("UniqueID", rngHeaders, 0)
If Not IsError(colID) Then
With wsData
.Range(.Cells(2, colID + 1), .Cells(.Rows.Count, colID).End(xlUp).Offset(, 1)) _
.FormulaR1C1 = "=CONCATENATE(RC[-2],RC[-1])"
.Range(.Cells(2, colID + 2), .Cells(.Rows.Count, colID).End(xlUp).Offset(, 2)) _
.FormulaR1C1 = "=VLOOKUP(RC[-1],UniqueID!C[-26],1,FALSE)"
End With
End If

Find end of variable range

I am new to VBA and have been struggling with finding a solution to copying & pasting some formulas into a range with a variable end row. I managed to cobble together the below code, which works, but it is inefficient because it pastes the formulas one row at a time. I would like to copy the formulas and then paste them into the entire range at once (instead of row by row). I have to do this function in a few different sheets and ranges so ideally I'd like to create a sub routine to find the last row. What I don't know is 1) how to find the last row 2) how to reference it when I'm selecting the range to paste the formulas into.
The sheet is setup with data in the first column, starting in cell C9, and the formulas are in D8:I8. I need to copy the formulas into the range of D9.I? (with the last row being the last row of data in column C).
I've been working on this for about 5 hours and am going out of my mind. Any help would be appreciated!
Sample of the code I have managed to write that works but isn't efficient:
Range("D8").Select
Range(Selection, Selection.End(xlToRight)).Select
Selection.Copy
ActiveCell.Offset(1, -1).Select
Do Until ActiveCell.Value = Empty
ActiveCell.Offset(0, 1).Select
Selection.PasteSpecial Paste:=xlPasteFormulas, Operation:=xlNone, _
SkipBlanks:=False, Transpose:=False
ActiveCell.Offset(0, -1).Select
ActiveCell.Offset(1, 0).Select
Loop
The classic way to find the last used row is shown below. Call the function like Debug.Print LastRow or, directly in the Immediate Window, with ? LastRow
Function LastRow() As Long
With ActiveSheet
LastRow = .Cells(.Rows.Count, "A").End(XlUp).Row
End with
End Function
Observe that both, the .Rows.Count and the result are taken from the ActiveSheet and that the measure is taken in column "A". (You can replace the name "A" with the number 1 in the above formula). If you want to develop the function, pass both the sheet and the column to it as arguments.
.Cells(.Rows.Count, "A") defines the cell A1400000 (or thereabouts), the last cell in the column. Then the function looks for the first occupied cell above that, meaning that if A1 and A10 are in use and A2:A9 are blank, the function will return 10. It's important to understand that .Cells(.Rows.Count, "A").End(XlUp) is a range object, a cell, of which the .Row property holds the number of the row where that range is located.
Now, if you want to define a range D9:I? you might do it like this, setting the range by defining its first and last cell. Observe the 4 leading periods. Each one stands for the object in the With statement, in this case ActiveSheet.
With ActiveSheet
Set MyRange = .Range(.Cells(9, "D"), .Cells(.Rows.Count, "I").End(xlUp))
End With
But that would take the measure for the last used cell in column I. Often it's the first column on the left that is longer than the last column in the required range. In that case you might use code as shown below.
With ActiveSheet
Set MyRange = .Range(.Cells(9, "D"), .Cells(.Rows.Count, "D").End(xlUp))
End With
With MyRange
Set MyRange = .Resize(.Rows.Count, 9)
End With
The code first sets the range for column D only, presuming that column D is the longest one, and then expands it to include 9 columns. Observe the .RowsCount refers to the ActiveSheet in the first With block and to MyRange in the second.
Of course, you could achieve a similar result with this code which calls the LastRow function (which measures the last row in column A):-
With ActiveSheet
Set MyRange = .Range(.Cells(9, "D"), .Cells(LastRow, "I"))
End With
This solution is a Subroutine to fill a range with values (in this case, formulas) and find the LastRow in a separate Function. There are many ways to do this so feel free to modify it how you please.
First this Subroutine receives the relevant Worksheet, range the formulas are in and the Column letter for the start and end of our destination range.
The Sub uses the Range.AutoFill method to fill the destination range, much the same as if you click the bottom right of a cell with a value and drag up/down/left/right to fill the cells in that direction.
Public Sub AutoFillVariableSizedRangeByRow _
(ByRef TargetWorkSheet As Worksheet, _
ByVal TargetValueCellAddress As String, _
ByVal StartColumn As String, _
ByVal EndColumn As String)
Dim RangeValuesArray As Variant
Dim TargetValueCell As Range
Dim TargetRange As Range
Set TargetValueCell = TargetWorkSheet.Range(TargetValueCellAddress)
Set TargetRange = TargetWorkSheet.Range(StartColumn & Right(Mid(TargetValueCellAddress, 4), 1) & ":" & _
EndColumn & LastRow(TargetWorkSheet, "C"))
TargetValueCell.AutoFill TargetRange
End Sub
The LastRow is found by a separate function, which is well explained already in many places on the net, including another answer to this question.
Public Function LastRow(ByRef TargetSheet As Worksheet, ByVal TargetColumnLetter As String) As Long
LastRow = TargetSheet.Cells(Rows.Count, TargetColumnLetter).End(xlUp).Row
End Function
To write the LastRow function with excel references (not user defined variables), it would look like:
Sheet1.Cells(Rows.Count, "C").End(xlUp).Row
To call the sub it could look something like:
Private Sub myProcedure()
AutoFillVariableSizedRangeByRow ThisWorkbook.Sheets("Sheet1"), "D1:I1", "D", "I"
End Sub
In the above, ThisWorkbook.Sheets("Sheet1") is TargetWorkSheet and "D1:I1" is TargetValueCellAddress, "D" and "I" are the start and end columns of our destination range respectively.
In this example, I've put values 1 to 20 down column C and the formula =$C1*$C1 in row 1 of columns D to I, all on Sheet1.
And here is the output after running AutoFillVariableSizedRangeByRow Sheet1, "D1:I1", "D", "I":
As example, the formula across row 8 is =$C8*$C8 and row 20 is =$C20*$C20.

VBA - Copy and paste the data from the 4th column

I tried to use my other similar VBA code but I don't think I understand what I'm trying to replace for the range. In this code, I am trying to copy the data in the Repeating Items sheet in the fourth column with the cell value of 12, then paste it to the last worksheet.
' Repeating items worksheet
Worksheets("Repeating Items").Select
ActiveSheet.ShowAllData
b = Worksheets("Repeating Items").Cells(Rows.Count, 1).End(xlUp).Row
' Filters the data where column 2 equals to 12 to x. ** this is where the error starts
ActiveSheet.Range(Cells(1, 1), Cells(b, 4)).Autofilter Field:=4, Criteria1:="12", Operator:=xlFilterValues
' Selects only the filtered cells and copy
Range(Cells(2, 1), Cells(b, 4)).SpecialCells(xlCellTypeVisible).Select
Selection.Copy
ThisWorkbook.Worksheets(ThisWorkbook.Sheets.Count).Select
ActiveSheet.Paste Destination:=Cells(Cells(Rows.Count, 1).End(xlUp).Row + 1, 1)
Per the Microsoft Documentation, you can just apply the filter to the first row much how you would manually do in excel and it will automatically filter your range. You don't need to quote your number filter FYI (unless the column is Text).
Also, no need to Select anything here. It is just a middle man operator that only slows your code down. Instead, explicitly define your objects (sheets and ranges) and skip right to the action statements (copy/paste).
Sub Test()
Dim ws As Worksheet: Set ws = ThisWorkbook.Sheets("Repeating Items")
Dim ls As Worksheet: Set ls = ThisWorkbook.Sheets(ThisWorkbook.Sheets.Count)
Dim lr As Long
lr = ws.Range("A" & ws.Rows.Count).End(xlUp).Row
ws.Range("A1:D1").AutoFilter Field:=4, Criteria1:=12
ws.Range("A2:D" & lr).SpecialCells(xlCellTypeVisible).Copy
ls.Range("A" & ls.Range("A" & ls.Rows.Count).End(xlUp).Row).PasteSpecial xlPasteValues
End Sub

How do I AutoFill down in one column, using the row count of another column?

I am trying to produce some code that will use the AutoFill Down function. I would like to fill Column B with the typical 1,2,3,4, etc. as long as there is text/values in the respective rows of Column A. Right now, my code is hardwired to fill down to cell B50 no matter what, but I don't want it to paste that far down if Column A only has data through cell A7, for example. Ideally, I would like to use the following variable -- rownum = Range("A1").End(xlDown).Row -- to count the number of cells in Column A that have text/values, and use that to replace "B50" in the row designation below. Just not sure of the appropriate syntax to make that happen. Here is the code I have so far.
ActiveCell.FormulaR1C1 = "1"
Range("B2").Select
ActiveCell.FormulaR1C1 = "2"
Range("A1").Select
Range("B1:B2").Select
Selection.AutoFill Destination:=Range("B1:B50"), Type:=xlFillDefault
Thanks in advance to anyone who helps me out! I am a new user of both Macros and VBA Code, and the amount of knowledge that so many of you have amazes me!
Econ
You're much better off if you don't select anything in your code. If you just want to number the rows in column B based on the contents of column A, this will do the trick:
Sub NumberColumn()
Dim ws As Worksheet
Dim lastRow As Long
Set ws = ThisWorkbook.Sheets("Sheet1")
' get the last row from column A that has a value
lastRow = ws.Range("A" & ws.Rows.Count).End(xlUp).Row
' use the last row to determine how far down to extend the formula
ws.Range("B1:B" & lastRow).Formula = "=row()"
End Sub
Pretty simple option to use without the need for a string:
Selection.AutoFill Destination:=Range("A2:A" & Cells(Rows.Count, "B").End(xlUp).Row)
Same goes for copying the number of rows in a column:
Range("A2:A" & Cells(Rows.Count, "B").End(xlUp).Row).Select
Selection.Copy

Vlookup in Visual Basic: Trying to Set for the reference column that has a variable range

I had a question regarding the code below. If anyone could provide some insight, I would be greatly appreciative. Essentially this is part of a report I'm building to be run on a weekly basis. The issue I'm having is that the reference column K is variable in size of inputs and I hate putting in a constant that results in a bunch of N/A#s (K rows should never exceed 300). I'm sure it's something simple I'm overlooking, but I tried to write something to count K2:Kn and then input it in the Ranges (q2:r2:s2:t2:qrstn). Please share your wisdom =)
Sub Vlookup()
Sheets(2).Select
Range("q2").Select
ActiveCell.FormulaR1C1 = "=VLOOKUP(RC[-6],table,15,FALSE)"
Range("q2").Select
Selection.AutoFill Destination:=Range("q2:q300"), Type:=xlFillDefault
Range("r2").Select
ActiveCell.FormulaR1C1 = "=VLOOKUP(RC[-7],table,16,FALSE)"
Range("r2").Select
Selection.AutoFill Destination:=Range("r2:r300"), Type:=xlFillDefault
Range("s2").Select
ActiveCell.FormulaR1C1 = "=VLOOKUP(RC[-8],table,17,FALSE)"
Range("s2").Select
Selection.AutoFill Destination:=Range("s2:s300"), Type:=xlFillDefault
Range("t2").Select
ActiveCell.FormulaR1C1 = "=VLOOKUP(RC[-9],table,18,FALSE)"
Range("t2").Select
Selection.AutoFill Destination:=Range("t2:t300"), Type:=xlFillDefault
Hope I've understood your question properly
The following will autofill range Q2:QN where N is the last used cell in column Q
Selection.AutoFill Destination:=Range(Range("K2"),Range("K2").End(xlDown)).Offset(ColumnOffset:=6), Type:=xlFillDefault
You could also do it the other way round as you have a fixed maximum.
Selection.AutoFill Destination:=Range(Range("K2"),Range("K301").End(xlUp)).Offset(ColumnOffset:=6), Type:=xlFillDefault
The first looks down the sheet for the last row, and the 2nd looks up the sheet from the maximum to the last row.
You can replace Q with whatever column you need, as you appear do do the same for lots of columns!
Update
I've updated the example, it now finds the used range in the K column and offsets it to your desired column. The value of ColumnOffset will be the same (although positive) as the value used in your R1C1 formulas.
Also, as the K Range should remain constant, and your formulas are fairly predictable you could shorten it a little. like the following (i've not testing it, just playing around)
Sub Vlookup() ' might want to re-name, to avoid confusion with the worksheet function
Dim R As Range
Dim A As Range
Dim I As Integer
Set R = Range(Range("K2"), Range("K301").End(xlUp))
Set A = Range("K2")
For I = 6 To 9
A.Offset(ColumnOffset:=I).FormulaR1C1 = "=VLOOKUP(RC[-" & CStr(I) & "], table, " & CStr(I+9) & ", FALSE)"
A.Offset(ColumnOffset:=I).AutoFill Destination:=R.Offset(I), Type:=xlFillDefault
Next I
End Sub
You don't need to fill down when you use FormulaR1C1. You can apply the formula to the whole range. Here's an example.
Sub MakeVlookup()
Dim lRows As Long, lCols As Long
Dim rCell As Range
Dim rRef As Range
'Set the first cell of the reference column
Set rRef = Sheet1.Range("K2")
'Count the rows of the reference column
lRows = Sheet1.Range(rRef, rRef.End(xlDown)).Rows.Count
'Loop through the row 2 cells where you want the formula
For Each rCell In Sheet1.Range("Q2:T2").Cells
'Compute the offset back to the reference column
lCols = rCell.Column - rRef.Column
'Write the formula to the whole range
rCell.Resize(lRows, 1).FormulaR1C1 = _
"=VLOOKUP(RC[-" & lCols & "],Table1," & rCell.Column - 2 & ",FALSE)"
Next rCell
End Sub
No need to loop or address each column with a different formula!
Instead of these formulas:
Column Q: =VLOOKUP(K2,table,15,FALSE)
Column R: =VLOOKUP(K2,table,16,FALSE)
...
you can simply use =VLOOKUP($K2,table,COLUMN()-2,0) as one formula across all columns! $K2 (in R1C1: RC11) instead of K2 (in R1C1 RC[-6], RC[-7], ...) will keep column K. And COLUMN() will return you the column number of the actual cell, i.e. column Q = 17, R = 18, etc.. (I also replaced FALSE with 0 as this reduces typing for lazy blokes like me but results in the same. :-) ).
Therefore, you can reduce your macro to
Sub MakeVLOOKUP
Sheets(2).Range("Q2:T300").FormulaR1C1 = "=VLOOKUP(RC11,table,COLUMN()-2,0)"
End Sub
and achieve the same result.
If you want to flexibilize the range as in Dick's solution, you can do it like this:
Sub MakeVLOOKUP
Sheets(2).Range("Q2").Resize(Sheet1.Range("K2").End(xlDown)).Rows.Count-1,4).FormulaR1C1 = _
"=VLOOKUP(RC11,table,COLUMN()-2,0)"
End Sub

Resources