Countering circular reference with SUM and OFFSET (VLOOKUP & VBA involved) - excel

To give you a breakdown of my spreadsheet:
I have a master spreadsheet that pulls data from another spreadsheet (generated daily), placing it into the next empty column and converting the column that previously held the formula to values. This is achieved with a combination of the following formula and VBA code:
=IF(ISNA(VLOOKUP("Row 1",'N:\Reports\[data.xls]Sheet1'!$A$2:$B$40,2,FALSE)),0,(VLOOKUP("Row 1",'N:\Reports\[data.xls]Sheet1'!$A$2:$B$40,2,FALSE)))
Sub Test()
Dim ws As Worksheet
Set ws = ActiveSheet
Dim rLastCell As Range
Dim LastCol As Integer
Set rLastCell = ws.Cells.Find(what:="*", After:=ws.Cells(1, 1), LookIn:=xlFormulas, LookAt:= _
xlPart, SearchOrder:=xlByColumns, SearchDirection:=xlPrevious, MatchCase:=False)
LastCol = rLastCell.Column
ws.Columns(LastCol).Copy ws.Columns(LastCol + 1)
With ws.Columns(LastCol)
.Copy .Offset(0, 1)
.Value = .Value
End With
End Sub
The intention is for Column B to be a 'totals' column, that dynamically sums all of the values in the relevant row as new entries are pulled by the formula/VBA combo and added to the first blank column. Unfortunately though, I also need to subtract that row's total from the value that the formula returns--however, doing so creates a circular reference.
My solution was to just exclude the last cell in the row (that has the formula) from the total, with this:
=SUM(C2:OFFSET(I$2,0,-1))
However, the dynamic range doesn't appear all that dynamic. It doesn't expand to include the next column when a new record is added, and I'm really not enough of a hand at this stuff to figure out why or how to rectify it.
Thanks in advance for any assistance with this and please don't hesitate to ask for any clarification!

It may be simplest to use a named range. If you name the column before the monthly total say LastDay, you can use:
=SUM(C2:INDEX(LastDay,ROW())
as your formula, and your code then becomes:
Sub Test()
Dim ws As Worksheet
Set ws = ActiveSheet
Dim rLastCell As Range
Dim LastCol As Integer
Set rLastCell = ws.Cells.Find(what:="*", After:=ws.Cells(1, 1), LookIn:=xlFormulas, LookAt:= _
xlPart, SearchOrder:=xlByColumns, SearchDirection:=xlPrevious, MatchCase:=False)
LastCol = rLastCell.Column
With ws.Columns(LastCol)
.Copy .Offset(0, 1)
.Value = .Value
.Name = "LastDay"
End With
End Sub
Assuming I have understood your layout correctly.

Related

Copy paste data from one sheet to another and only pick filtered data and maintain target column sequence

I have a requirement to automate a step to copy data from one sheet to another using excel macro.
But below are the problem I am facing with this requirement:
Need to copy paste in scope data i.e. filter on 'Data Scope' = Yes
Column sequence of source and target are different and since there are around 127 columns so could not hardcode this part.
Please help if you have a handy code or logic to implement the same.
Found a simple way to implement this, posting it here for others to use.
Sub Reorganize_columns()
Dim v As Variant, x As Variant, findfield As Variant
Dim oCell As Range
Dim rng As Range
Dim iNum As Long
Dim sht_source As Worksheet, sht_target As Worksheet
Set sht_source = ActiveWorkbook.Sheets("Data")
Set sht_target = ActiveWorkbook.Sheets("Macro")
sht_source.Range("A1").AutoFilter Field:=1, Criteria1:="Yes"
Set rng = sht_target.Range("A1:HS1")
For Each cell In rng
iNum = iNum + 1
findfield = cell.Value
Set oCell = sht_source.Rows(1).Find(What:=findfield, LookIn:=xlValues, LookAt:=xlWhole, SearchOrder:=xlByRows, SearchDirection:=xlNext, MatchCase:=False, SearchFormat:=False)
sht_source.Columns(oCell.Column).Copy
sht_target.Columns(iNum).PasteSpecial Paste:=xlPasteValuesAndNumberFormats
Next cell
ActiveWorkbook.Save
MsgBox "Completed"
End Sub

How to Select All or (Ctrl + A) dynamic table doesn't matter how big it is

This is my sample data.
And this is the VBA code produced with macro.
Sub Macro1()
'
' Macro1 Macro
'
'
Range("A1:C3").Select
Application.CutCopyMode = False
ActiveSheet.ListObjects.Add(xlSrcRange, Range("$A$1:$C$3"), , xlYes).Name = _
"Table1"
Range("Table1[#All]").Select
ActiveSheet.ListObjects("Table1").TableStyle = "TableStyleMedium9"
End Sub
How do I get the macro code?
Developer > Record Macro
Select all (ctrl + A) inside any cells within A1:C3
Home > Format as Table
Output
The problem is my data is dynamic and not necessarily stay at A1:C3.
It could be bigger or smaller.
E.g., let say I've bigger within range A1:C4 in different Ms Excel file.
The code above won't select all, instead it selects only A1:C3.
Last row A4:C4 won't be affected with this code.
How do I change this doesn't matter how big the table is?
Solution 1
You can use Range("A1").CurrentRegion to get the area of continous data (which is the same as Ctrl + A):
Option Explicit
Public Sub SelectCurrentRegion()
Dim MyData As Range
Set MyData = Worksheets("Sheet1").Range("A1").CurrentRegion
'don't use .select this is just for illustrating
MyData.Select
End Sub
Note that the number in cell D6 is not vertically nor horizontally connected with the other data. Therefore it is not selected by Ctrl + A or .CurrentRegion.
Solution 2
Or Worksheets("Sheet1").UsedRange to get the area of all data:
Option Explicit
Public Sub SelectCurrentRegion()
Dim MyData As Range
Set MyData = Worksheets("Sheet1").UsedRange
'don't use .select this is just for illustrating
MyData.Select
End Sub
You might benefit from reading
How to avoid using Select in Excel VBA.
#Pᴇʜ has already given you 2 solutions. Here is Solution 3. I would prefer finding last row and column over UsedRange and then construct the range. I have explained here why I do not prefer using UsedRange
Solution 3
Find the last row and last column and then create your range
Option Explicit
Sub Sample()
Dim ws As Worksheet
Dim LastRow As Long
Dim LastColumn As Long
Dim rng As Range
'~~> Change this to relevant sheet
Set ws = Sheet1
With ws
LastRow = .Cells.Find(What:="*", _
After:=.Range("A1"), _
Lookat:=xlPart, _
LookIn:=xlFormulas, _
SearchOrder:=xlByRows, _
SearchDirection:=xlPrevious, _
MatchCase:=False).Row
LastColumn = .Cells.Find(What:="*", _
After:=.Range("A1"), _
Lookat:=xlPart, _
LookIn:=xlFormulas, _
SearchOrder:=xlByColumns, _
SearchDirection:=xlPrevious, _
MatchCase:=False).Column
Set rng = .Range(.Cells(1, 1), .Cells(LastRow, LastColumn))
Debug.Print rng.Address
End With
End Sub
If you are using Excel tables then you can use DataBodyRange or Range Properties to select what you need to select.
Dim lstObj As ListObject
For Each lstObj In ActiveSheet.ListObjects
lstObj.DataBodyRange.Select ' Will select only data without headers
lstObj.Range.Select ' Will select complete table
Next lstObj

Is it possible to specify dynamic range in auto filter syntax?

I have written below code to apply auto filter. Code is working fine. However I have specified static range, here: *Range("A1", **"M"** & lastRow).AutoFilter* .
Is there a way to replace "M" with last existing column number in sheet2.
I have calculated last existing column *myCol = rngFound.Column - 1*. But not sure how to use it.
Please help !!!
My Code:
Sub testfilter1()
Dim lastRow As Long
Dim myCol As Long
Dim rngFound As Range
ThisWorkbook.Sheets("sheet2").Activate
lastRow = ActiveSheet.Range("A" & Rows.Count).End(xlUp).Row
Set rngFound = ActiveSheet.Rows(1).Find(What:="", LookIn:=xlValues, LookAt:=xlWhole, _
SearchOrder:=xlByColumns, SearchDirection:=xlNext, MatchCase:=False)
myCol = rngFound.Column - 1 ' this will give last used column
Rows("1:1").Select
Selection.AutoFilter
ActiveSheet.Range("A1", "M" & lastRow).AutoFilter Field:=4, Criteria1:="*somename*"
End Sub
You could use Cells() within Range():
...Range(Cells(1,1),Cells(lastRow,myCol))...
Cells(1,1) is the cell A1. The format is Cells([row],[column])
Edit: Just realized you could also use Range("A1",Cells(lastRow,myCol)). Personally, if I use Cells() I do it in both places in Range(), but that's personal preference and this other way should work for you too.

VBA Excel: Paste into Matching Row between Workbooks

I am trying to develop code to do the following:
1)Copy cells K4: M4 in Workbook 1, Sheet 1 <- I can do this step;
2)Find a cell in Workbook2, Sheet1, column C that matches cell B4 in
Workbook1, Sheet1;
3)Paste the copied values in columns P:R of the matching row in
Workbook 2, Sheet 1 as determined in Step 2.
My apologies in advance for being unable to advance my own work beyond step 1. I am, as I said, completely new to this and have scoured the web for an answer/learnings up until this point, without turning up a solution.
I tested this and it worked. Does this help you get started?
Sub CopyToMatchedRow()
Dim copyRng As Range, matchVal As Variant, matchRng As Range, matchRow As Integer
Set copyRng = Worksheets("Sheet1").Range("K4:M4")
Set matchRng = Worksheets("Sheet2").Range("C:C")
matchVal = Worksheets("Sheet1").Range("B4")
matchRow = matchRng.Find(What:=matchVal, After:=ActiveCell, LookIn:=xlFormulas, LookAt:=xlPart, SearchOrder:=xlByRows, SearchDirection:=xlNext, MatchCase:=False, SearchFormat:=False)
copyRng.Copy Destination:=Worksheets("Sheet2").Range("P" & matchRow & ":R" & matchRow)
End Sub

How do I find the last column with data?

I've found this method for finding the last data containing row in a sheet:
ws.Range("A65536").End(xlUp).row
Is there a similar method for finding the last data containing column in a sheet?
Lots of ways to do this. The most reliable is find.
Dim rLastCell As Range
Set rLastCell = ws.Cells.Find(What:="*", After:=ws.Cells(1, 1), LookIn:=xlFormulas, LookAt:= _
xlPart, SearchOrder:=xlByColumns, SearchDirection:=xlPrevious, MatchCase:=False)
MsgBox ("The last used column is: " & rLastCell.Column)
If you want to find the last column used in a particular row you can use:
Dim lColumn As Long
lColumn = ws.Cells(1, Columns.Count).End(xlToLeft).Column
Using used range (less reliable):
Dim lColumn As Long
lColumn = ws.UsedRange.Columns.Count
Using used range wont work if you have no data in column A. See here for another issue with used range:
See Here regarding resetting used range.
I know this is old, but I've tested this in many ways and it hasn't let me down yet, unless someone can tell me otherwise.
Row number
Row = ws.Cells.Find(What:="*", After:=[A1] , SearchOrder:=xlByRows, SearchDirection:=xlPrevious).Row
Column Letter
ColumnLetter = Split(ws.Cells.Find(What:="*", After:=[A1], SearchOrder:=xlByColumns, SearchDirection:=xlPrevious).Cells.Address(1, 0), "$")(0)
Column Number
ColumnNumber = ws.Cells.Find(What:="*", After:=[A1], SearchOrder:=xlByColumns, SearchDirection:=xlPrevious).Column
Try using the code after you active the sheet:
Dim J as integer
J = ActiveSheet.UsedRange.SpecialCells(xlCellTypeLastCell).Row
If you use Cells.SpecialCells(xlCellTypeLastCell).Row only, the problem will be that the xlCellTypeLastCell information will not be updated unless one do a "Save file" action. But use UsedRange will always update the information in realtime.
I think we can modify the UsedRange code from #Readify's answer above to get the last used column even if the starting columns are blank or not.
So this lColumn = ws.UsedRange.Columns.Count modified to
this lColumn = ws.UsedRange.Column + ws.UsedRange.Columns.Count - 1 will give reliable results always
?Sheet1.UsedRange.Column + Sheet1.UsedRange.Columns.Count - 1
Above line Yields 9 in the immediate window.
Here's something which might be useful. Selecting the entire column based on a row containing data, in this case i am using 5th row:
Dim lColumn As Long
lColumn = ActiveSheet.Cells(5, Columns.Count).End(xlToLeft).Column
MsgBox ("The last used column is: " & lColumn)
I have been using #Reafidy method/answer for a long time, but today I ran into an issue with the top row being merged cell from A1-->N1 and my function returning the "Last Column" as 1 not 14.
Here is my modified function now account for possibly merged cells:
Public Function Get_lRow(WS As Worksheet) As Integer
On Error Resume Next
If Not IsWorksheetEmpty(WS) Then
Get_lRow = WS.Cells.Find("*", SearchOrder:=xlByRows, SearchDirection:=xlPrevious).Row
Dim Cell As Range
For Each Cell In WS.UsedRange
If Cell.MergeCells Then
With Cell.MergeArea
If .Cells(.Cells.Count).Row > Get_lRow Then Get_lRow = .Cells(.Cells.Count).Row
End With
End If
Next Cell
Else
Get_lRow = 1
End If
End Function
Public Function Get_lCol(WS As Worksheet) As Integer
On Error Resume Next
If Not IsWorksheetEmpty(WS) Then
Get_lCol = WS.Cells.Find(What:="*", after:=[A1], SearchOrder:=xlByColumns, SearchDirection:=xlPrevious).Column
Dim Cell As Range
For Each Cell In WS.UsedRange
If Cell.MergeCells Then
With Cell.MergeArea
If .Cells(.Cells.Count).Column > Get_lCol Then Get_lCol = .Cells(.Cells.Count).Column
End With
End If
Next Cell
Else
Get_lCol = 1
End If
End Function
Here's a simple option if your data starts in the first row.
MsgBox "Last Row: " + CStr(Application.WorksheetFunction.CountA(ActiveSheet.Cells(1).EntireRow))
It just uses CountA to count the number of columns with data in the entire row.
This has all sorts of scenarios where it won't work, such as if you have multiple tables sharing the top row, but for a few quick & easy things it works perfect.

Resources