Dynamically storing a cell reference as a variable in VBA to then select (and delete) a range using the variable stored - excel

I currently have a VBA macro that turns a regular data extract into a table. In the macro I have defined a range which is large enough to exceed the number of rows typically extracted.
ActiveSheet.ListObjects.Add(xlSrcRange, Range("$A$1:$AG$20000"), , xlYes).Name _
= "Table1"
My macro then does some other transformation/addition of formulas etc to the table, and the table is then presented via PowerBI.
I want to delete the excess rows in the table - which varies for each extract.
In the example below - which has recorded the desired sequence of steps, there are only 186 rows.
Range("Table1[[#Headers],[Client Id]]").Select
Selection.End(xlDown).Select
Range("A187").Select
Range(Selection, Selection.End(xlDown)).Select
Rows("187:20000").Select
Selection.Delete Shift:=xlUp
I want to store the range ("A187") as a variable
I then want to insert the stored variable in the selection 187:20000
Alternatively, if I could do a variabilised selection of the range I want to turn into a table, that would work too.
Any help would be appreciated.

The following will create a table to fit the data assuming there are no extra data cells:
ActiveSheet.ListObjects.Add(xlSrcRange, Range("A1").CurrentRegion, , xlYes).Name = "Table1"
If you need to force columns to include "A:AG" only use:
ActiveSheet.ListObjects.Add(xlSrcRange, Range("A1").CurrentRegion.Columns("A:AG"), , xlYes).Name = "Table1"
ActiveSheet.ListObjects.Add(...).Name = "Table1" is how a recorded macro would create the table. To avoid naming conflict, I would avoid using the generic Table1 as a name.
If the name isn't important use:
ActiveSheet.ListObjects.Add xlSrcRange, Range("A1").CurrentRegion.Columns("A:AG"), , xlYes
If there is only one Table on the woksheet, you can refer to it as:
ActiveSheet.ListObjects(1)

Delete Empty Bottom Rows (Applied to an Excel Table (ListObject))
Sub DeleteEmptyBottomRowsTEST()
Dim wb As Workbook: Set wb = ThisWorkbook ' workbook containing this code
Dim ws As Worksheet: Set ws = wb.Worksheets("Sheet1")
Dim tbl As ListObject: Set tbl = ws.ListObjects("Table1")
' Remove possible filters.
' The Find method will fail if the table is filtered.
If tbl.ShowAutoFilter Then
If tbl.AutoFilter.FilterMode Then tbl.AutoFilter.ShowAllData
End If
Dim rg As Range: Set rg = tbl.DataBodyRange
If rg Is Nothing Then Exit Sub
DeleteEmptyBottomRows rg
End Sub
Sub DeleteEmptyBottomRows(ByVal rg As Range)
Dim lCell As Range
Set lCell = rg.Find("*", , xlFormulas, , xlByRows, xlPrevious)
If lCell Is Nothing Then rg.Delete xlShiftUp: Exit Sub
Dim rOffset As Long: rOffset = lCell.Row - rg.Row + 1
Dim rCount As Long: rCount = rg.Rows.Count
If rOffset = rCount Then Exit Sub
Dim rResize As Long: rResize = rCount - rOffset
rg.Resize(rResize).Offset(rOffset).Delete xlShiftUp
End Sub

Related

VBA, Autofilter method of Range Class Failed,

I am getting the error Autofilter method range of class failed.
I am thinking it is because there is a column space in my headers but not entirely sure as to how to get around this. Getting the error on this line:
ws.Range("$A:$K").AutoFilter field:=10, Criteria1:="#N/A"
I have my range till K, but when it gets to a blank column, ie "I" , the autofilter is only applied till there.
Also if i manually apply a filter to the whole first row the macro works.
Why is this?
Ive tried using A1:K1 and that doesnt work.
When I debug and manually go into apply the filter to the remaining columns the code works fine.
Reference a Discontinuous Range to Apply AutoFilter
All four solutions could (should) work. Your feedback is most welcome.
A rule of thumb: Don't let Excel decide (if you don't have to)... in this case,
to set the range for you.
Sub FilterData()
Dim ws As Worksheet: Set ws = ActiveSheet
If ws.AutoFilterMode Then ws.AutoFilterMode = False
Dim rg As Range
' If your data is the only thing in the worksheet:
Set rg = ws.UsedRange
' ' If there are more columns:
' Set rg = Intersect(ws.UsedRange, ws.Columns("A:K"))
' ' or:
' Dim rg As Range: Set rg = ws.Columns("A:K")
' Dim lCell As Range
' Set lCell = rg.Find("*", , xlFormulas, , xlByRows, xlPrevious)
' Set rg = rg.Resize(lCell.Row)
' ' If additionally there are other data below your data:
' Set rg = ws.Range("A1").CurrentRegion.Resize(, 11) ' K = 11
rg.AutoFilter Field:=10, Criteria1:="#N/A"
End Sub

Delete cells in column after last row in another

I would like to clear content of cells (not delete rows) in a column after the last row of another column. The code would act as follows to work properly
Go to last cell in column BA,
move to the right to column BB
delete all rows in BB below that last rows
When I try recording the macro the code includes the range of that last cell as a fixed place.
This is the code, I highlighted where I believe the issue is
Sub CopyPaste2()
'
' CopyPaste2 Macro
'
'
Columns("AS:AV").Select
Selection.Copy
Columns("AX:AX").Select
Selection.PasteSpecial Paste:=xlPasteValues, Operation:=xlNone, SkipBlanks _
:=False, Transpose:=False
**Range("BA7").Select
Selection.End(xlDown).Select
Range("BB47").Select
Range(Selection, Selection.End(xlDown)).Select
Application.CutCopyMode = False
Selection.ClearContents**
Range("BB46").Select
Selection.End(xlUp).Select
Range("BB7").Select
Selection.AutoFill Destination:=Range("BB7:BB46")
Range("BB7:BB46").Select
Range("BA6").Select
ActiveWorkbook.Worksheets("KPI - Efficiency - Case Level").Sort.SortFields. _
Clear
ActiveWorkbook.Worksheets("KPI - Efficiency - Case Level").Sort.SortFields.Add _
Key:=Range("BA7:BA46"), SortOn:=xlSortOnValues, Order:=xlAscending, _
DataOption:=xlSortNormal
With ActiveWorkbook.Worksheets("KPI - Efficiency - Case Level").Sort
.SetRange Range("AX6:BB46")
.Header = xlYes
.MatchCase = False
.Orientation = xlTopToBottom
.SortMethod = xlPinYin
.Apply
End With
End Sub
Im pretty new to VBA so really appreciate your help
Try this:
Add the following line near the top of your code - traditionally, we tend to declare our variables at the start of a procedure:
'declare 'lastrow' to store value of row number
Dim lastrow As Long
And then at the end of your code, after the sort etc., add this:
With ActiveWorkbook.Worksheets("KPI - Efficiency - Case Level")
' find last used row of column BA and add 1
lastrow = .Range("BA" & .Rows.Count).End(xlUp).Row + 1
' clear from 'lastrow' to bottom of sheet in column BB
.Range("BB" & lastrow & ":BB" & .Rows.Count).ClearContents
End With
I can see you've recorded this macro, so it's a little messy. If you're interested in learning how to craft better vba that is more portable and easier to read, you will want to read up on avoiding Select etc.:
How to avoid using Select in Excel VBA
Clear the Cells Below a Range
If rg is a range object, to clear all cells below it, you can use the following line:
rg.Resize(rg.Worksheet.Rows.Count - rg.Row - rg.Rows.Count + 1).Offset(rg.Rows.Count).Clear
In the code, some parts of it are replaced with variables:
drg.Resize(ws.Rows.Count - FirstRow - rCount + 1).Offset(rCount).Clear
If rg has only one row, you can simplify with:
rg.Resize(rg.Worksheet.Rows.Count - rg.Row).Offset(1).Clear
Clear Below
Sub ClearBelow()
' Reference the workbook ('wb').
Dim wb As Workbook: Set wb = ThisWorkbook ' workbook containing this code
' Reference the worksheet ('ws').
Dim ws As Worksheet: Set ws = wb.Worksheets(wsName)
Dim lCell As Range
' ("Go to last cell in column BA")
' Reference the last non-empty cell in column 'BA' using 'End'
' (in the code the Find method is used instead of the End property).
Set lCell = ws.Cells(ws.Rows.Count, "BA").End(xlUp)
' ("Move to the right to column BB")
' Reference the cell adjacent to the right using offset.
Set lCell = lCell.Offset(, 1)
' Reference the cell in the same row but in column 'BB' using 'EntireRow'.
' (can be any column).
'Set lCell = lCell.EntireRow.Columns("BB")
' ("Delete all rows in BB below that last rows")
' Clear all cells below the cell using 'Resize' and 'Offset'.
lCell.Resize(ws.Rows.Count - lCell.Row).Offset(1).Clear
End Sub
The Code
Option Explicit
Sub CopyPaste2() ' be more creative e.g. 'CreateEfficiencyReport'!
' Define constants.
Const wsName As String = "KPI - Efficiency - Case Level"
Const sColumnsString As String = "AS:AV" ' Source Copy Columns
Const dFirstColumnString As String = "AX" ' Destination First Copy Column
Const FirstRow As Long = 7
' Reference the workbook ('wb').
Dim wb As Workbook: Set wb = ThisWorkbook ' workbook containing this code
' Reference the worksheet ('ws').
Dim ws As Worksheet: Set ws = wb.Worksheets(wsName)
' To make sure that the worksheet is not filtered, when the remaining
' code would fail, you could use the following:
'If ws.FilterMode Then ws.ShowAllData
' Reference the source columns range ('scrg') ('$AS$7:$AV$1048576').
Dim scrg As Range: Set scrg = ws.Rows(FirstRow).Columns(sColumnsString) _
.Resize(ws.Rows.Count - FirstRow + 1)
'Debug.Print scrg.Address(0, 0)
' Attempt to reference the last cell ('lCell'), the bottom-most
' non-empty cell in the source columns range (for the bottom-most
' non-blank cell, use 'xlValues' instead of 'xlFormulas').
Dim lCell As Range
Set lCell = scrg.Find("*", , xlFormulas, , xlByRows, xlPrevious)
If lCell Is Nothing Then Exit Sub ' no data
'Debug.Print lCell.Address(0, 0)
' Reference the source range ('srg').
Dim srg As Range: Set srg = scrg.Resize(lCell.Row - FirstRow + 1)
'Debug.Print srg.Address(0, 0)
' Write the number of rows and columns of the source range
' to variables ('rCount', 'cCount').
Dim rCount As Long: rCount = srg.Rows.Count
Dim cCount As Long: cCount = srg.Columns.Count
Dim dcrg As Range ' Destination Copy Range
Dim dfcCell As Range ' Destination First Copy Cell
' Reference the destination first copy cell ('dfcCell').
Set dfcCell = ws.Cells(FirstRow, dFirstColumnString)
' Reference the destination copy range ('dcrg').
Set dcrg = dfcCell.Resize(rCount, cCount)
'Debug.Print dcrg.Address(0, 0)
' Copy the values from the source range to the destination copy range.
dcrg.Value = srg.Value
Dim dfrg As Range ' Destination Formula Range
Dim dffCell As Range ' Destination First Formula Cell
' Reference the destination first formula cell ('dffCell')
' in the column adjacent to the right of the copy range.
Set dffCell = dfcCell.Offset(, cCount)
' Reference the destination formula range ('dfrg').
Set dfrg = dffCell.Resize(rCount)
'Debug.Print dfrg.Address(0, 0)
Dim drg As Range ' (Whole) Destination Range
If rCount > 1 Then
' Write the formula from the first formula cell to the remaining cells
' of the destination formula range.
dfrg.Formula = dffCell.Formula
'
' Reference the destination range ('drg').
Set drg = dcrg.Resize(, cCount + 1) ' include the formula column
'Debug.Print drg.Address(0, 0)
' Sort the destination range ('drg') by the last column
' of the copy range.
drg.Sort drg.Columns(cCount), xlAscending, , , , , , xlNo
'Else ' there is only one row of data; do nothing
End If
' Clear the cells below the destination range.
drg.Resize(ws.Rows.Count - FirstRow - rCount + 1).Offset(rCount).Clear
End Sub

Fill in the entire column according to the last data in the table - Does not work

I have a formula in Column A2.
I have a table similar to this:
Formula
Note
Datum
I am very happy because I am
Years
years old
=CONCATENATE(TEXT(C2;"dd-mm-yyyy");$D$1;E2;$F$1)
Any word, TEXT
01.04.2021
21
Autofill
Any word, TEXT 2
01.04.2021
25
I want to transfer it and use it automatically for the whole column. However, I tried possible and impossible ways to do it, but none of them worked. I also looked at forums such as here:
I don't have all the data filled in the table, so I want "excel" to look for the last row in which the record is and try to calculate the formula and return it to the last cell in column A.
Thank you in advance for all the help
(The formula joins the text together) =CONCATENATE(TEXT(C2;"dd-mm-yyyy");$D$1;E2;$F$1)
Sub AutofilCol()
' Apply to the entire column Autofill
Range("A1").Offset(1, 0).Activate
ActiveCell.FormulaR1C1 = _
"=CONCATENATE(TEXT(RC[2],""dd-mm-yyyy""),R1C4,RC[4],R1C6)"
' AutoFill
Selection.AutoFill Destination:=Range("A2:A").End(xlDown).Row
ActiveCell.EntireColumn.AutoFit
End Sub
It looks like this is what you want to do:-
Sub AutofillCol()
Dim Rl As Long ' last used row in column C
Dim Rng As Range
Rl = Cells(Rows.Count, "C").End(xlUp).Row
Set Rng = Range(Cells(2, "A"), Cells(Rl, "A"))
Rng.FormulaR1C1 = "=CONCATENATE(TEXT(RC[2],""dd-mm-yyyy""),R1C4,RC[4],R1C6)"
End Sub
Copy Formulas (Defining a Range)
In this case, there is no need to Activate (or Select) anything neither is the use of AutoFill (FillDown).
Let's say the first solution is the most flexible (reliable) but also the most complex. To better understand it, see the ranges at the various stages of the code printed in the Immediate window (CTRL+G). The flexibility is in the option to use any first cell address e.g. C5, D10, etc. and it will still work.
Depending on your data, you might easily get away with the remaining two solutions.
I didn't include any solution using End since you got that covered by another post.
Option Explicit
Sub copyFormulas()
Const First As String = "A1"
Dim ws As Worksheet: Set ws = ActiveSheet
Dim fCell As Range ' Last Cell in First Row Range
Dim frg As Range ' First Row Range of Table Range
With ws.Range(First)
Set fCell = .Resize(, .Worksheet.Columns.Count - .Column + 1) _
.Find("*", , xlFormulas, , , xlPrevious)
If fCell Is Nothing Then Exit Sub
Set frg = .Resize(, fCell.Column - .Column + 1)
Debug.Print "First", fCell.Address, frg.Address
End With
Dim tCell As Range ' Last Cell in Table Range
Dim trg As Range ' Table Range
With frg
Set tCell = .Resize(.Worksheet.Rows.Count - .Row + 1) _
.Find("*", , xlFormulas, , xlByRows, xlPrevious)
Set trg = .Resize(tCell.Row - .Row + 1)
End With
Debug.Print "Table", tCell.Address, trg.Address
Dim drg As Range ' Destination Range
Set drg = trg.Columns(1).Resize(trg.Rows.Count - 1).Offset(1)
Debug.Print "Destination", drg.Address
drg.FormulaR1C1 = "=CONCATENATE(TEXT(RC[2],""dd-mm-yyyy""),R1C4,RC[4],R1C6)"
' Or.
'drg.Formula = "=CONCATENATE(TEXT(C2,""dd-mm-yyyy""),$D$1,E2,$F$1)"
End Sub
Sub copyFormulasUsedRange()
With ActiveSheet.UsedRange.Columns(1)
.Resize(.Rows.Count - 1).Offset(1).FormulaR1C1 _
= "=CONCATENATE(TEXT(RC[2],""dd-mm-yyyy""),R1C4,RC[4],R1C6)"
End With
End Sub
Sub copyFormulasCurrentRegion()
With ActiveSheet.Range("A1").CurrentRegion.Columns(1)
.Resize(.Rows.Count - 1).Offset(1).FormulaR1C1 _
= "=CONCATENATE(TEXT(RC[2],""dd-mm-yyyy""),R1C4,RC[4],R1C6)"
End With
End Sub

VBA Excel select content after the character occurrence

I want to select the content of my worksheet after the character occurrence in the row.
The code I have been using so far selects all the stuff after the offset.
Sub Selection ()
Dim Target_Start As Range, Target_End As Range
Dim n as long
Set Target_Start = work.Cells.Find("X", SearchOrder:=xlByColumns).Offset(1)
Set Target_End = Target_Start.End(xlDown).End(xlToRight)
work.Range(Target_Start, Target_End).Select
'Selection.EntireRow.Clear
End Sub
What should I alter in my code?
Select Used Range After a Cell
If you want to allow a lower-case "X" then replace True with False (MatchCase).
Note that this solution will include row 15 in your image which may be only formatted (borders, no values), because we are using Used Range. If you don't like that, you will have to use another way to define the range.
Option Explicit
Sub selectAfter()
Const sString As String = "X"
With work.UsedRange
Dim rg As Range
Set rg = .Find(sString, .Cells(.Rows.Count, .Columns.Count), _
xlFormulas, xlWhole, xlByRows, , True)
If Not rg Is Nothing Then
Dim Offs As Long: Offs = rg.Row - .Row + 1
Set rg = .Resize(.Rows.Count - Offs).Offset(Offs)
rg.Select
End If
End With
End Sub

Create a table with a macro

I have this simple range:
I want to create a macro which creates a table from a range. The macro I get is this:
Sub Macro1()
'
' Macro1 Macro
'
'
Range(Selection, Selection.End(xlToRight)).Select
Range(Selection, Selection.End(xlDown)).Select
Application.CutCopyMode = False
ActiveSheet.ListObjects.Add(xlSrcRange, Range("$A$1:$B$3"), , xlYes).Name = _
"Table12"
ActiveCell.Range("Table12[#All]").Select
End Sub
The problem is that I want the macro to not be contingent on a specific size. I for instance want it to be able to work with this table as range aswell:
The problem in the code seems that it uses "$A$1:$B$3" but it should be independent of that. Is there a simple way to fix this? How can I record the macro so that it works for all tables?
The next piece of code will create a table ("MyTable") starting from the active cell to the adjacent most right column and to the down filled row of active cell column:
Sub TableRightDownOfSelection()
Dim rnG As Range
UnlistIt
Set rnG = Range(ActiveCell.Address & ":" & Cells(Cells(Rows.count, _
ActiveCell.Column).End(xlUp).Row, ActiveCell.End(xlToRight).Column).Address)
ActiveSheet.ListObjects.Add(xlSrcRange, rnG, , xlYes).Name = "MyTable"
End Sub
Sub UnlistIt()
On Error Resume Next
ActiveSheet.ListObjects("Table1").Unlist
If Err.Number > 0 Then Err.Clear
On Error GoTo 0
End Sub
In order to avoid an error message, in case you try the code again, the UnlistIt Sub will be called before creating the table...
You could use the currentregion property
Sub CreateTbl()
Dim ws As Worksheet
Set ws = ActiveSheet
Dim rg As Range
Set rg = ws.Range("A1").CurrentRegion
ws.ListObjects.Add(xlSrcRange, rg, , xlYes).Name = "myTable"
End Sub
But be aware this code will create a table named like myTable_1 in case you already have a table namend myTable somewhere else and it will faill if you run it twice for the same range.
Addition: Based on the comments one could try to use the active cell
Sub CreateTbl()
Dim ws As Worksheet
Dim rg As Range
Set rg = ActiveCell.CurrentRegion
Set ws = rg.Parent
ws.ListObjects.Add(xlSrcRange, rg, , xlYes).Name = "myTable"
End Sub

Resources