Looking to select an undetermined number of rows in excel as part of larger VBA macro - vba

I'm working with an excel book containing a large number of sheets; the first sheet is linked to an external program and pulls in data via an external function, and the number of lines imported varies significantly.
This block data is the disseminated over a number of subsequent sheets. The first step has been to populate column A (row name) with the number of rows in sheet 1. From here the data is split over a number of columns (currently B->L). The top row uses an IF() function to populate the first row, and I'm looking to write a clean macro to copy this formula to row x (which varies with each data import refresh) and then paste values for a manageable file size.
Here's what I've got so far; it works, but it's fairly (read: VERY!) clumsy:
Sub Refresh_Data()
Sheets("Sheet2").Select
ActiveWindow.ScrollWorkbookTabs Sheets:=13
Sheets(Array("Sheet2" ... "Sheet25")).Select
Sheets("Sheet2").Activate
Sheets("Sheet25").Select Replace:=False
Range("B1:L1").Select
Selection.Copy
Range("__B2:B1000__").Select
ActiveSheet.Paste
Application.Calculate
ActiveWindow.ScrollWorkbookTabs Position:=xlFirst
Sheets(Array("Sheet2" ... "Sheet25")).Select
Sheets("Sheet2").Activate
Sheets("Sheet25").Select Replace:=False
Sheets("Sheet2").Select
Range("B3").Select
Sheets(Array("Sheet2" ... "Sheet25")).Select
Sheets("Sheet2").Activate
Sheets("Sheet25").Select Replace:=False
Range("B3:L4").Select
Range("__B2:L1000__").Select
Application.CutCopyMode = False
Selection.Copy
Selection.PasteSpecial Paste:=xlPasteValues, Operation:=xlNone, SkipBlanks _
:=False, Transpose:=False
Sheets("Check_sheet").Select
MsgBox "Update complete"
End Sub`
The main thing I'm looking to achieve is to replace the code B2:L1000 with something that can assess the number of rows in column A and select a range in rows B to L accordingly.
Since column L is the last populated column, I don't see why this can't also be done horizontally rather than defining "B:L" incase future columns need to be added.

Although the earlier answer has merits:
1) I would not use COUNTA because if there are empty cells in the row or column, the cells at the bottom or the right will be ignored.
2) I would never rely on the user picking the correct sheet to be used before running a macro; particularly one with so many sheets.
My reaction to the question is that you have set Macro Record, wandered around your workbook and then stopped the record. You select one thing, then another. You scroll through the sheets. To me most of the statements are not clumsy they are pointless.
The following does include an answer to your question about finding the last row of column A but it is more a tutorial about finding the dimensions of a range, getting data out of the range and then putting it somewhere else. This seems to be most of what you are trying to do with the most minimal understanding of VBA. I am sorry if this criticism is unfair but that is the impression your question gives to me.
Sub Test()
Dim RowS01Max As Integer
Dim Sheet1Data() As Variant
' With Sheets("Sheet1") allows you to access data within worksheet Sheet1
' without selecting it.
' Range("A1:C11") refers to a range within the active sheet
' .Range("A1:C11") refers to a range within the sheet identified in the
' With statement.
' ^ Note the dot
With Sheets("Sheet1")
' Rows.Count is the number of rows for the version of Excel you are using.
' .Cells(Rows.Count, "A") address the bottom row of column A of worksheet
' Sheet1.
' .Cells(Rows.Count, 1) refer to column A by number.
' End(xlUp) is the VBA equivalent of Ctrl+Up.
' If you positioned the cursor at the bottom of column A and pressed
' Ctrl+Up, the cursor would jump to the last row in column A with a value.
' The following statement gets that row number without actually moving
' the cursor.
RowS01Max = .Cells(Rows.Count, "A").End(xlUp)
' The following statement loads the contents of range A1:C11 of
' Sheets("Sheet1") into array Sheet1Data.
Sheet1Data = .Range("A1:C11").Value
' This is the same statement but the range is specified in a different way.
' .Cells(Row,Column) identifies a single cell within the sheet specified in
' the With statement. .Cells(1,1) identifies row 1, column 1 which is A1.
'. Cells(11, "C") identifies row 11, column C which is C11.
Sheet1Data = .Range(.Cells(1, 1), .Cells(11, "C")).Value
' This statement uses RowS01Max to specify the last row
Sheet1Data = .Range(.Cells(1, 1), .Cells(RowS01Max, 1)).Value
' In all three examples above, the contents of the specified range will
' be loaded to array Sheet1Data. Whichever range you pick, Sheet1Data
' will always be a two dimensional array with the first dimension being
' the row and the second dimension being the column.
' In the first two examples Sheet1Data(5,3) contains the contents
' of cell C5. In the third example, I have only loaded column A but the
' array will still has two dimensions but the only permitted value for the
' second dimension is 1.
' The following statement writes the contents of Sheet1Data to column "E"
.Range(.Cells(1, 5), .Cells(RowS01Max, 5)).Value = Sheet1Data
End With
With Sheets("Sheet2")
' The following statement writes the contents of Sheet1Data to column "E"
' of worksheet Sheet2.
.Range(.Cells(1, 5), .Cells(RowS01Max, 5)).Value = Sheet1Data
End With
End Sub
Don't despair! Most of us started with the macro recorder and still use it to discover the syntax for an unfamiliar command. Look through other questions. Some ask about exotic functionality but many are about moving data around in, to the experienced programmer, simple ways. Set up some workbooks with the questioner's problem. Copy and paste the solution into a module. Step through it using F8 (see the debugger), switch between Excel and Editor, watch what is happening to the worksheet and move the cursor over a variable to see its current value. Spend half a day playing. You will be amazed at how quickly it starts to make sense. Good luck and good programming.

The following should do the trick:
Sub Refresh_Data()
Dim lastRow As Integer
Dim lastCol As Integer
Dim entireRange As Range
Dim targetRange As Range
lastRow = Excel.Evaluate("COUNTA(A:A)") ''// count the rows in column A
lastCol = Excel.Evaluate("COUNTA(1:1)") ''// count the columns in row 1
Set entireRange = Range(Cells(1, 2), Cells(lastRow, lastCol))
Set targetRange = Range(Cells(2, 2), Cells(lastRow, lastCol))
entireRange.FillDown
Application.Calculate
targetRange.Copy
targetRange.PasteSpecial Paste:=xlPasteValues
End Sub
Notes:
Excel.Evaluate(...) allows you to use the result of worksheet functions in your VBA macros.
COUNTA(range) is a worksheet function that counts the number of non-blank cells in a given range. In this case, it can be used to determine the total number of rows in your data set, as well as the number of columns in row 1 that have a formula in them.

Related

How do I automate copying data from one worksheet in Excel and append it to an existing table in another worksheet?

I have two sheets of data. The first sheet is imported data that will show total users to my site from the day before. The second sheet is a table with all historical data from those daily reports. I'd like to automate a way to copy the data from my first sheet (that data will always be in the same cell) to a new row at the bottom of my existing table. Here's what I have:
Sub Insert_New_Rows()
Dim Lr As Integer
Lr = Range("AF" & Rows.Count).End(xlUp).Row
Rows(Lr + 1).Insert Shift:=xlDown
Cells(Lr + 1, "AF") = Cells(Lr, "AF") + 1
Sheets("Day Before").Range("$A$12:$B$12").Copy
Sheets("Historical").Cells(Lr + 1, "AF").Paste
Application.CutCopyMode = False
End Sub
In this, you'll see that my table is in columns AF and AG. When I run this macro, it only adds a row, it does not copy and paste the information.
I am not really sure where your table starts on the sheet "Day Before". So, I am assuming that it starts in row 1. Based on this assumption here is a little revision to your code:
Option Explicit
Sub Insert_New_Rows()
Dim lngNextEmptyRow As Long
Dim lngLastImportRow As Long
Dim shtYstrdy As Worksheet
Set shtYstrdy = ThisWorkbook.Worksheets("Day Before")
With ThisWorkbook.Worksheets("Historical")
lngNextEmptyRow = .Cells(.Rows.Count, "AF").End(xlUp).Row + 1
.Rows(lngNextEmptyRow).Insert Shift:=xlDown
.Cells(lngNextEmptyRow, "AF").Value2 = _
.Cells(lngNextEmptyRow - 1, "AF").Value2 + 1
lngLastImportRow = shtYstrdy.Cells(shtYstrdy.Rows.Count, "A").End(xlUp).Row
shtYstrdy.Range("A1:B" & lngLastImportRow).Copy _
Destination:=.Cells(lngNextEmptyRow, "AF")
End With
End Sub
Changes:
Explicit coding as suggested by #findwindow stating the workbook and the sheet before each Range, Cells, reference.
Copy and paste in one line of code (before three lines of code).
Using lngNextEmptyRow instead of LastRow so be can skip all these +1.
Determine the size (last row) of the table on the sheet "Day Before", so we know how much we need to copy over.
I hope this is the answer you've been looking for. Let me know if I misunderstood something or if anything requires more explanations.
There is no need to Active or Select Ranges. It is best to work with the Ranges directly. Rarely should you use ActiveCell, ActiveWorkSheet, or Selection.
This is how Copy and Paste work
Here is the shorthand for Copy and Paste
Range(SourceRange).Copy Range(DestinationRange)
Know that this will work for you:
Sheets("Day Before").Range("$A$12:$B$12").Copy Sheets("Historical").Cells(Rows.Count, "AF").End(xlUp).Offset(1)

Copy/paste data into consolidated list

I'm stuck on how to structure a piece of code that:
Loops through all worksheets that begin with the number 673: (e.g. 673:green, 673:blue)
Selects the data in these worksheets from row 5 up until the last row with data - code that works for this (generously provided by another user) is
Dim report As Worksheet
Set report = Excel.ActiveSheet
With report
.Range(.Cells(5, "K"), .Cells(.Rows.Count, "K").End(xlUp)).EntireRow.Select
End With
Select the "Colours" worksheet
Paste the rows at the next available blank row. There could be up to 40/50 worksheets which will have data pasted into the "Colours" worksheet so I need the data added to the next available line.
Thank you in advance.
Loop over the sheets in the workbook and check their names
For Each sheet in ActiveWorkbook.Worksheets
If Instr(sheet.Name,"673")>0 Then
...
End If
Next
Good, but you're going to want to copy.
Selection.Copy
Just select.
Worksheets("Colours").Select
Find the last row then go to the next. The row is found by finding the first populated row from the bottom up. Note I used explicit sheet references, which is unnecessary since you selected the sheet already. This is better form, however, if you will be manipulating data on multiple sheets in your code.
lastRow = Worksheets("Colours").Cells(Worksheets("Colours").rows.count,1).End(xlUp).Row
Worksheets("Colours").Cells(lastRow + 1, 1).Select
Activesheet.Paste

ClearContents for constants but not formulas

I've got a range (N1:N12) on a sheet1 and I've got a code that copy and paste me the values of that range on a secondary sheet2. Everything is working, anyway i didn't consider that i want another button that clear only values in range N1:N12 once i have copied them in sheet2. I don't know how to keep formulas on that range when i want to delete values. Do you have an idea ? I've already tried a normal macro that deletes everything but it is not what i want.
Sub Cancella()
Sheets("Foglio1").select
Range("N1:N12").clearcontents
End Sub
The code i use for copying
Dim lastRow As Long
Sheets("Training Analysis").Range("P1:R13").Copy
lastRow = Sheets("Foglio1").Range("a65536").End(xlUp).Row
Sheets("Foglio1").Range("A" & lastRow + 1).PasteSpecial Paste:=xlPasteValues, Transpose:=True
Replace:
Range("N1:N12").clearcontents
with:
For i = 1 To 12
If Not Cells(i, "N").HasFormula Then Cells(i, "N").ClearContents
Next i
There is a subset of the Range.SpecialCells method that targets xlCellTypeConstants. This can be further broken down to xlNumbers, xlTextValues, xlErrors , xlLogical or a combination of same.
With WorkSheets("Foglio1")
.Range("N1:N12").SpecialCells(xlCellTypeConstants, xlNumbers).ClearContents
End With
Conversely, cells containing formulas can be similarly targeted with the xlCellTypeFormulas subset.

Select a range, avoiding hidden cells, using ActiveCell.Offset()

I am running a macro, that asks for a sheet name and a reference cell, and then selects a range of cells, surrounding the cell of our choice.
After applying a filter to my data, some of the rows become hidden, as they are not needed.
The problem is, that the macro does not take that into consideration and counts the hidden rows too.
Here is the code, that I use in the original version of the macro:
.....after applying some InputBox and a search for the user's value, the following row is executed:
Range(ActiveCell.Offset(90, 0), ActiveCell.Offset(-252, 2)).Select
Selection.Copy
In this way hidden rows are included in the selection.
I tried the following modification
Range(ActiveCell.Offset(90, 0), ActiveCell.Offset(-252, 2)).SpecialCells(xlCellTypeVisible).Select
Selection.Copy
However without success.
I was wondering, can anyone suggest a way to use ActiveCell.Offset in combination with SpecialCells(xlCellTypeVisible) resulting in the above described functionality of the macro -namely to select a range of cells avoiding the hidden rows after filtering?
To select just the visible cells from a range of selected cells, you can use the following line of code:
Selection.SpecialCells(xlCellTypeVisible).Select
Like in this Example:
Range(ActiveCell.Offset(90, 0), ActiveCell.Offset(-252, 2)).Select
Selection.SpecialCells(xlCellTypeVisible).Select
Selection.Copy
The Code Below is a example on how you could count the Rows.
So but there is a Problem where you have to think about.
If You Paste the Selection to your Original Sheet, there is a Chance that there are Hidden Rows in the area where you Copied the Selection.
If this is so, The Text which you copied there will be Hidden too.
So you have to Copied the Data to a new Sheet to avoid that Problem or you have to Copied the Data at the bottom of the Sheet 1.
Option Explicit
'Define a Constant for the Amount of Rows you Need
Private Const ConstAmountRows As Integer = 40
Sub Test()
Dim intCountedRows As Integer
Dim idx As Integer
Dim intDifference As Integer
idx = 0
Do
If Not (intCountedRows = ConstAmountRows) Then
intCountedRows = ConstAmountRows
idx = idx + 1
End If
Sheets("Sheet1").Select
'Select the Range with the Amount of Rows you need ideally
Range("A1:A" & intCountedRows + idx).Select
'Select only the Visible Cells
Selection.SpecialCells(xlCellTypeVisible).Select
Selection.Copy
Sheets("Sheet2").Select 'Select another Sheet
'***-> Her you can select the Place you want to Paste the Text<-***
Range("B1").Select
ActiveSheet.Paste
'*** Count the Rows that you Paste
intCountedRows = Selection.Rows.Count
'if the Counted Rows are not equal to the Amount. Repeat
Loop While Not (intCountedRows >= ConstAmountRows)
End Sub

Excel to CountIF in filtered data

I am trying to count the number of occurrences of a specific string in filtered data. I can do it using a formula in a cell but when I combine that with the other macros in my workbook the whole thing freezes.
So I would like to move the calculation to VBA so that it only calculates when the macro is run. Here is the formula that works in the cell:
=SUMPRODUCT(SUBTOTAL(3,OFFSET('2015 Master'!H:H,ROW('2015 Master'!H:H)-MIN(ROW('2015 Master'!H:H)),,1)),ISNUMBER(SEARCH("*Temp*",'2015 Master'!H:H))+0)
Basically I want to count the number of times "Temp" occurs in column H but only in the filtered data.
Thank you for your help!
ADDITION:
Here is the code I've written for the macro so far. It filters the data on a different sheet then updates the pivot table with the date range. I would like to add the count calculations to the end of this code and return the count to a cell on the 'Reporting' sheet.
Sub Button1_Click()
'Refresh the pivot table and all calculations in the active sheet
ActiveWorkbook.RefreshAll
'Gather the start and end times from the active sheet
dStart = Cells(2, 5).Value
dEnd = Cells(3, 5).Value
'Change the active sheet to the alarms database, clear all filters and then filter for the defined date range and filter for only GMP alarms
Sheets("2015 Master").Select
If ActiveWorkbook.ActiveSheet.FilterMode Or ActiveWorkbook.ActiveSheet.AutoFilterMode Then
ActiveWorkbook.ActiveSheet.ShowAllData
End If
ActiveSheet.ListObjects("Table44").Range.AutoFilter Field _
:=3, Criteria1:=">=" & dStart, Operator:=xlAnd, Criteria2:= _
"<=" & dEnd
Range("Table44[[#Headers],[GMP or non-GMP]]").Select
ActiveSheet.ListObjects("Table44").Range.AutoFilter Field:=2, Criteria1:= _
"GMP"
'Change the active sheet to the Reporting sheet
Sheets("Reporting").Select
'Within the alarms pivot table clear the label filters then filter for the date range and GMP alarms
ActiveSheet.PivotTables("PivotTable1").PivotFields("Active Time"). _
ClearLabelFilters
ActiveSheet.PivotTables("PivotTable1").PivotFields("Active Time").PivotFilters. _
Add Type:=xlDateBetween, Value1:=dStart, Value2:=dEnd
ActiveSheet.PivotTables("PivotTable1").PivotFields("GMP or non-GMP"). _
CurrentPage = "GMP"
End Sub
Pertinent to clarified question topic (i.e. " Basically I want to count the number of times "Temp" occurs in column H..."), the VBA solution can be as shown in the following code snippet. Assuming sample data entered in Column "H":
H
Temp Directory on C: Drive
Temp Directory
Project Directory
Output Temp Directory
Start Directory
Temp obj
apply the VBA Macro:
Sub CountTempDemo()
Dim i As Integer
Dim count As Integer
Dim startRow As Integer
Dim lastRow As Integer
Dim s As String
startRow = 2 'or use your "filtered range"
lastRow = Cells(Rows.count, "H").End(xlUp).Row 'or use your "filtered range"
count = 0
For i = 2 To lastRow
If InStr(Cells(i, 8).Value, "Temp") > 0 Then
count = count + 1
End If
Next
End Sub
where count value of 4 is a number of "Temp" occurrences in specified "H" range.
Hope this may help. Best regards,
To iterate over a column and find only visible (unfiltered) cells, one way is this:
Set h = ... Columns ("H");
Set r = h.SpecialCells(xlCellTypeVisible)
' now r is a composite range of potentially discontiguous cells
' -- it is composed of zero or more areas
'but only the visible cells; all hidden cells are skipped
Set ar = r.Areas
for ac = 1 to ar.Count
Set rSub = ar(ac)
'rSub is a contiguous range
'you can use a standard formula, e.g. Application.WorksheetFunction.CountIf(...)
'or loop over individual elements
'and count what you like
next
caveats: if any rows (or the column) are hidden manually (not from filtering) the count using this method will consider them as filtered (i.e. hidden/not visible).
Update: answer to comment
A Range is really a very general purpose notion of an aggregation of cells into a grouping or collecting object (the Range). Even though we usually think of a Range as being a box or rectangle of cells (i.e. contiguous cells), a Range can actually assemble discontiguous cells.
One example is when the user selects several discontiguous cells, rows, and/or columns. Then, for example, ActiveSheet.Selection will be a single Range reflecting these discontiguous cells. The same can happen with the return value from SpecialCells.
So, the Excel object model says that in general, a Range can be composed of Areas, where each Area itself is also represented by a Range, but this time, it is understood to be a contiguous Range. The only way you can tell if the Range is contiguous or not is if you created it as a box/rectangle, or, if Areas.Count = 1.
One way to investigate a bit more might be to select some discontiguous cells, then enter a macro and use the debugger to observe Selection.