Automation of selective cell linking across worksheets > Runtime Error 91 - excel

I'm looking to automate the selective copying and stacking of data from multiple sheets into a single sheet. More specifically, I have 4 columns (M, H, A, & F) from which I need to selectively copy cells based on the same-row value of Column I. E.g. with the below case:
Worksheets 2...N
Column A_____Column F_____Column H_____Column I_____Column M
_#####________AAAAAA______AAAAAA_______Rqrmnt_______Date
_#####________AAAAAA______AAAAAA_______Heading_______Blank
For all rows with column I = Rqrmnt across N worksheets, I need to copy the corresponding values in columns A, F, H, and M into worksheet 1, stacking the imports of each sheet top-to-bottom, e.g.:
Worksheet 2 Column A...Worksheet 2 Column M
Worksheet 3 Column A...Worksheet 3 Column M
...
Worksheet N Column A...Worksheet N Column M
I need to be able to perform limited manipulation on the resulting table, specifically sorting the rows by the value of Column M
As I have several hundred such entries, I would prefer to not build this up by linking cells 1-by-1. Additionally, I would prefer to place the copied pseudo-columns individually (i.e. rearrange them in the order M>H>A>F on the master spreadsheet). I Have the following macro, derived from these posts (thanks to urdearboy's comment below for the second linked post). However, I get a Run-time Error 91 fault when I try to run the macro, and the debugger highlights the identified line below. While this post explains the error itself, I has not helped me solve this problem. I have tried initializing the sourceSheetLastRow to an arbitrary number, and slapping the Set keyword in front of the formula, but to no avail.
Option Explicit
Sub Test()
Dim summarySheetTargetRow As Long
Dim sourceSheetTargetRow As Long
Dim sourceSheetLastRow As Long
Dim sourceSheetIndex As Long
Dim numSheets As Long
Dim wb As Workbook
Dim summarySheet As Worksheet
Dim sourceSheet As Worksheet
Set wb = ThisWorkbook
Set summarySheet = wb.Sheets("Summary Sheet")
numSheets = ThisWorkbook.Sheets.Count `My understanding is that this will return the total number of worksheets in the workbook. However, the sheet index seems to skip the number 5, so this may not be getting me the actual number of sheets
sourceSheetIndex = 6 `First sheet from which I want to pull values. Note that the sheets have inconsistent names, so I'm trying to use the sheet index.
summarySheetTargetRow = 38 `Where I want to start plugging in copied cell values
`Make sure receiving area for copied info is clear
Sheets("Summary Sheet").Range("A38:D1415").ClearContents
For sourceSheetIndex = 6 To numSheets
Set sourceSheet = wb.Worksheets(sourceSheetIndex)
DEBUG THORWS FAULT HERE[
sourceSheetLastRow = sourceSheet.Range("M2:M1000").Find("*", SearchDirection:=xlPrevious).Row `I understand this to return the number of cells in the specified range, starting from the last non-empty cell.
]DEBUG THORWS FAULT HERE
For sourceSheetTargetRow = 2 To sourceSheetLastRow `Start at second row because header rows will never have relevant value
If sourceSheet.Range("I" & sourceSheetTargetRow) = "Text" Then
summarySheet.Range("A" & summarySheetTargetRow) = sourceSheet.Range("A" & sourceSheetTargetRow)
summarySheet.Range("B" & summarySheetTargetRow) = sourceSheet.Range("M" & sourceSheetTargetRow)
summarySheet.Range("C" & summarySheetTargetRow) = sourceSheet.Range("H" & sourceSheetTargetRow)
summarySheet.Range("D" & summarySheetTargetRow) = sourceSheet.Range("F" & sourceSheetTargetRow)
summarySheetTargetRow = summarySheetTargetRow + 1
End If
Next sourceSheetTargetRow
Next sourceSheetIndex
End Sub

When do you copy cells, what conditionals in column I need to be true/false? You could use Union to select multiple rows of cells at once.

Courtesy of help I get from my own company's e-forums and a good bit more work, I have finally gotten my desired output. The below macro will copy data from the identified ranges into a semi-dynamic range in a "Summary Sheet". It requires some knowledge of how the source sheet(s) will be formatted.
Option Explicit
Sub Data_Rollup()
'Object variables, which need to be released from memory at the end.
Dim wb As Workbook
Dim summarySheet As Worksheet
Dim sourceSheet As Worksheet
'Non-object variables
Dim summarySheetTargetRow As Long
Dim sourceSheetTargetRow As Long
Dim sourceSheetLastRow As Long
Dim sourceSheetIndex As Long
Dim numSheets As Long
On Error GoTo Error_Test 'Escape clause
'Initialize objects
Set wb = ThisWorkbook
Set summarySheet = wb.Sheets("Summary Sheet")
'Initialize non-object variables
sourceSheetIndex = 5 'Hard-coded starting point for data pull. This should be greater than the number for the Summary sheet.
summarySheetTargetRow = 38 'Hard-coded starting point for data deposition
numSheets = wb.Sheets.Count 'returns the number of sheets in workbook "wb"
Sheets("Summary Sheet").Range("A38:D1415").ClearContents 'Clears out destination cells in summary sheet
'Main loop
For sourceSheetIndex = 5 to numSheets
Set sourceSheet = wb.Worksheets(sourceSheetIndex)
If Not (sourceSheet.Range("A2:A1000").Find("*", SearchDirection:=xlPrevious) Is Nothing) Then 'Using *If Not (* Is Empty) Then* ensures the code just skips over sheets that don't have the range A2:A1000 populated
sourceSheetLastRow = sourceSheet.Range("A2:A1000").Find("*", SearchDirection:=xlPrevious).Row 'searches through the defined range from the bottom up, and returns the number of the first populated row
For sourceSheetTargetRow = 2 To sourceSheetLastRow 'Start at second row to skip headers
If sourceSheet.Range("I" & sourceSheetTargetRow) = "Text" Then
summarySheet.Range("A" & summarySheetTargetRow) = sourceSheet.Range("A" & sourceSheetTargetRow)
summarySheet.Range("B" & summarySheetTargetRow) = sourceSheet.Range("M" & sourceSheetTargetRow)
summarySheet.Range("C" & summarySheetTargetRow) = sourceSheet.Range("H" & sourceSheetTargetRow)
summarySheet.Range("D" & summarySheetTargetRow) = sourceSheet.Range("F" & sourceSheetTargetRow)
summarySheetTargetRow = summarySheetTargetRow + 1
End If 'best practise is to always have an Else clause, but it isn't technically necessary
Next sourceSheetTargetRow 'Cycles loop to next row down
End If 'best practise is to always have an Else clause, but it isn't technically necessary
Next sourceSheetIndex 'Cycles loop to next worksheet
Exit_Test: 'Deallocates memory for object variables
Set sourceSheet = Nothing
Set summarySheet = Nothing
Set wb = Nothing
Exit Sub
Error_Test
MsgBox Err.Number & "-" & Err.Description
GoTo Exit_Test
End Sub

Related

Splitting data in one sheet and sorting it into preexisting sheets based on part number

I have a workbook already made and it is set up specifically to create histograms on data read in from a separate program. When I pull the data into the workbook, it all goes into one sheet in my workbook. From here I need to split the data apart and sort it into specific tabs based on part number. I have 9 part numbers total and around 25,000 rows of data a day that needs to be sorted. Column A is the date, B is the serial number, C is the part number, D is a machine code, E is the static flow data, and F is a detail. I need to sort by Column C 9 potential part numbers which look like this "'111". "'123" etc with an apostrophe before each number. They are already in that format. The only data that needs to go to the corresponding worksheet is numbers from Column E. This is what I have so far but it doesn't work.
'For loop to filter through all the available part times and put the data in the correct tab
For i = 1 To 11
'PartType array is all 9 part types possible
Worksheets("Paste Data Here").AutoFilter Field:=3, Criteria1:=PartType(i) 'This is where it fails
Debug.Print ("Filtered")
Worksheets("Paste Data Here").SpecialCells(xlCellTypeVisible).Select
Selection.Copy
Debug.Print ("Selected")
'InputRanges is where in each worksheet the data needs to go, this is established
'in another sub
'TabList is an array of each worksheet in the same order at the PartType array
ThisWorkbook.Sheets(TabList(i)).InputRanges(daterange).Select
ThisWorkbook.Sheets(TabList(i)).InputRanges(daterange).Paste
Debug.Print ("Pasted")
Application.CutCopyMode = False
Debug.Print ("i: " & i)
Debug.Print ("PartType(i): " & PartType(i))
Next i
Neither AutoFilter nor SpecialCells works like that for a worksheet.
You need to specify some kind of range to apply these methods to.
Dim ws As Worksheet
Set ws = Worksheets("Paste Data Here")
ws.UsedRange.AutoFilter Field:=3, Criteria1:=PartType(i)
ws.UsedRange.SpecialCells(xlCellTypeVisible).Copy ThisWorkbook.Sheets(TabList(i)).InputRanges(DateRange)
For i = 1 To 11
Debug.Print ("Searching Part: " & PartType(i))
Dim ws As Worksheet
Set ws = Worksheets("Paste Data Here")
ws.AutoFilterMode = False
Dim rng1 As Range
Set rng1 = Range("C:C").Find(PartType(i), , xlValues, xlWhole)
If Not rng1 Is Nothing Then
Dim lastrow1 As Long
lastrow1 = ws.Range("A" & ws.Rows.Count).End(xlUp).Row
Dim copyRange As Range
Set copyRange = ws.Range("E2:E" & lastrow1)
ws.UsedRange.AutoFilter Field:=3, Criteria1:=PartType(i)
copyRange.SpecialCells(xlCellTypeVisible).Copy ThisWorkbook.Sheets(TabList(i)).Range(InputRanges(daterange))
End If
Next i

How can I hide the unused columns and rows of an Excel worksheet near cells with comments?

I have some VBA code that queries and creates a table of data in a worksheet with comments in some of the cells.
It's occupies a range of about A1:N200 and then I want to hide the unused space like in the code below.
Worksheet.Columns(Number_of_columns + 1).Resize(, Worksheet.Columns.Count - Number_of_columns).EntireColumn.Hidden = True
Worksheet.Rows(Number_of_rows & ":" & Worksheet.Rows.Count).EntireRow.Hidden = True
Doing so throws the run-time error 1004:
Unable to set the hidden property of the range class
I have checked in a new Excel file, added a comment to the cell A1 and then tried hidding the other columns and rows but had to leave 4 columns (A to D, but could have been for example A and C to E) and 5 rows visible. If I try hidding more I get the message Cannot shift objects off sheet.
Below is an example of a procedure that throws errors when ran in a new Excel file.
Private Sub Procedure()
Dim Worksheet As Excel.Worksheet
Dim Range As Excel.Range
Set Worksheet = Excel.Application.ThisWorkbook.ActiveSheet
Set Range = Worksheet.Cells(1, 1)
If Range.Comment Is Nothing Then
Range.AddComment
End If
Set Range = Worksheet.Columns(2).Resize(, Worksheet.Columns.Count - 1).EntireColumn
Range.Select ' Just to test the range, it works. Columns B to XFD
Range.Hidden = True ' Throws error
Set Range = Worksheet.Rows(2 & ":" & Worksheet.Rows.Count).EntireRow
Range.Select ' Just to test the range, it works. Rows 2 to 1048576
Range.Hidden = True ' Throws error
End Sub
Is there any way to hide them so that only the data is visible? The only workaround (not solution) that I can think of is removing the comments, hidding the columns and rows, and then adding the comments back, which is undesirable.
I am not sure if I understand correctly, would be easier to see Excel, below is the sample code, just tested on Excel 2016:
Sub StackOverflow()
Dim lngLastRow As Long
Dim lngLastColumn As Long
Dim lngLastColLetter As String
Dim shtWorking As Object
Set shtWorking = ActiveSheet
'find last row
lngLastRow = shtWorking.Cells(shtWorking.Rows.Count, 1).End(-4162).Row + 1
'find last column
lngLastColumn = shtWorking.Cells(1, shtWorking.Columns.Count).End(-4159).Column + 1
'convert last column index to last column letter
lngLastColLetter = Split(shtWorking.Cells(1, lngLastColumn).Address, "$")(1)
'hide columns
shtWorking.Columns(lngLastColLetter & ":XFD").EntireColumn.Hidden = True
'hide rows
shtWorking.Rows(lngLastRow & ":" & shtWorking.Rows.Count).EntireRow.Hidden = True
set shtWorking=nothing
End Sub

VB -Copy and Paste Nested Loop in Excel

So I have a problem that this is generating random results with the Qty.
I am trying to make each qty (in their qty's) a new line on a new spreadsheet.
It creates the new sheet, and references the old sheet...
the code copies and pastes the lines...
It just doesn't loop the do while in the correct amount of times. I have tried different operands (>= 0) and altering the variable values to make this work.
It does not seem to be patternized as to why it is happening. Sometimes it does it in the correct amount of loop cycles, others it does not. This occurs on multiple values. Any help is greatly appreciated.
Sub copyPasta()
'
' copyPasta Macro
' This will take the qty, if greater than one in Column C and copy the row
'to a new sheet the amount of time the qty.
'
'
'Set Variable Types
Dim lineItemQty As Integer
Dim newLineItemQty As Integer
Dim LastRow As Integer
Dim strSheetName As String
Dim newSheetName As String
Dim i As Integer
Application.DisplayAlerts = False
'name a variable after the existing active sheet
strSheetName = ActiveSheet.Name
'add a sheet in addition to the current
Sheets.Add After:=ActiveSheet
'set a variable used in loops to the sheet being copied to
newSheetName = ActiveSheet.Name
'Return to first sheet
Sheets(strSheetName).Activate
' Set For Loop to max row
LastRow = Sheets(strSheetName).Range("C:C").Find("*", searchdirection:=xlPrevious).Row
'for loop to run through all rows
For i = 3 To LastRow Step 1
'initializing variable to Qty value in table
lineItemQty = Range("C" & i).Value
'initializing variable within in line of for looping
newLineItemQty = lineItemQty
'do while loop to keep copying/pasting while there are still qty's
Do While newLineItemQty > 0
'do while looped copy and paste
'copy the active row
Sheets(strSheetName).Activate
Rows(i).Select
Selection.Copy
'paste active row into new sheet
Sheets(newSheetName).Select
Rows("3:3").Select
Selection.Insert Shift:=xlDown
newLineItemQty = newLineItemQty - 1
Loop
Next i
Application.DisplayAlerts = True
End Sub
You can consider using (or taking parts from) the below alternative. A couple of note worthy notes are
You should avoid using .Select and .Activate. See here for details
Life is easier when you declare short variables. Here we just have ws for worksheet and ns for newsheet. You then need to actively state what sheet you are refferring to in your code (instead of using .Select or .Activate to do so by prefixing all objects with the appropriate worksheet variable)
You do not need to add Step 1 in your loop. This is the default - you only need to add this when you are deviating from the default!
There are a few ways to add sheets. Nothing wrong with the way you did - here is just an alternative (yay learning) that happens to be my preferred method.
To copy n many times, just create a nested loop and for 1 to n. Notice we never really use the variable n inside the loop which means the exact same operation will execute, we just want it to execute n times.
Sub OliveGarden()
Dim ws As Worksheet: Set ws = ThisWorkbook.ActiveSheet
Dim ns As Worksheet: Set ns = ThisWorkbook.Sheets.Add(After:=Sheets(ThisWorkbook.Sheets.Count))
ns.Name = ws.Name & " New"
Dim i As Long, c As Long
'Application.ScreenUpdating = False
For i = 3 To ws.Range("C" & ws.Rows.Count).End(xlUp).Row
If ws.Range("C" & i) > 0 Then
For c = 1 To ws.Range("C" & i)
LRow = ns.Range("A" & ns.Rows.Count).End(xlUp).Offset(1).Row
ws.Range("C" & i).EntireRow.Copy
ns.Range("A" & LRow).PasteSpecial xlPasteValues
Next c
End If
Next i
'Application.ScreenUpdating = True
End Sub

VBA macro looping through multiple worksheets and returning exact matches

I have run through the relevant topics on the Internet, however I cannot find a solution to the problem I have encountered. I am working on a macro which would copy relevant data from one workbook into a newly created sheet in another workbook and then loop through the remaining worksheets of the latter to find exact matches to the data in this newly created sheet. The part in which I copy and paste the data works fine, however, when it comes to looping through worksheets an error occurs.
I worked up multiple versions of this macro to see whether different solutions would work, however, actually none seems to work. I the destination workbook, the worksheet contain data tickers (sort of an id) in column A, the measure of data relevance in column B and names of the variables in column C.
What I am trying to do is, after copying and pasting the data to a newly created sheet - where the data tickers are contained in column L, loop through all the default sheets in the destination workbook to check whether the tickers in column L of the newly created sheet overlap with the tickers in column A of the remainig worksheets, and, if so, copy the variable name from column C of the relevant worksheet into the newly created worksheet column M. The newly created worksheet is called "Settings" and contains headers in row 1 (it also consists of about 110 rows), the remaining worksheets contain no headers (and have 70 rows maximum).
The macro looks like this:
Sub match1()
Dim listwb As Workbook, mainwb As Workbook
Dim FolderPath As String
Dim fname As String
Dim sht As Worksheet
Dim ws As Worksheet, oput As Worksheet
Dim oldRow As Integer
Dim Rng As Range
Dim ws2Row As Long
Set mainwb = Application.ThisWorkbook
With mainwb
Worksheets.Add(After:=Worksheets(Worksheets.Count)).Name = "Settings"
Set oput = Sheets("Settings")
End With
FolderPath = "C:\VBA\"
fname = Dir(FolderPath & "spr.xlsx")
With Application
Set listwb = .Workbooks.Open(FolderPath & fname)
End With
Set sht = listwb.Worksheets(1)
With sht
.UsedRange.Copy
End With
mainwb.Activate
With oput
.Range("A1").PasteSpecial
End With
For Each ws In ActiveWorkbook.Worksheets
If ws.Name <> "Settings" Then
ws2Row = ws.Range("A" & Rows.Count).End(xlUp).Row
Set Rng = ws.Range("A:C" & ws2Row)
For oldRow = 2 To 110
Worksheets("Settings").Cells(oldRow, 13) = Application.WorksheetFunction.IfError(Application.WorksheetFunction.VLookup(Worksheets("Settings").Cells(oldRow, 12), Rng, 3, False), "")
Next oldRow
End If
Next ws
End Sub
Alternative version looks like this (skipping the copy-paste part):
mainwb.Activate
With oput
.Range("A1").PasteSpecial
End With
For Each ws In ActiveWorkbook.Worksheets
If ws.Name <> "Settings" Then
i = 1
For oldRow = 2 To 110
For newRow = 1 To 70
If StrComp((Worksheets("Settings").Cells(oldRow, 12).Text), (ws.Cells(newRow, 1).Text), vbTextCompare) <> 0 Then
i = oldRow
Worksheets("Settings").Cells(i, 13) = " "
Else
Worksheets("Settings").Cells(i, 13) = ws.Cells(newRow, 3)
i = i + 1
Exit For
End If
Next newRow
Next oldRow
End If
Next ws
When I launch the first version of the macro I get an error:
Run-time error '1004':
Method 'Range' of object '_Worksheet' failed
Debugging highlights the part:
Set Rng = ws.Range("A:C" & ws2Row)
When I run the second version of the macro the error message reads:
Run-time error '9':
Subscript out of range
Debugging highlights the part:
If StrComp((Worksheets("Settings").Cells(oldRow, 12).Text), (ws.Cells(newRow, 1).Text), vbTextCompare) <> 0 Then
I suspect the problem is the definition and use of the ws (Worksheet) object. I am confused now because I use VBA a lot and I've done tasks much harder than this one. And yet I still can't solve the problem. Could you please suggest some solution. I will appreciate your help.
In this line: Set Rng = ws.Range("A:C" & ws2Row) you do not indicate a row value for Column A. Your code basically says Range("A:C110"), which doesn't really mean anything to Excel. Try changing it to Range("A2:C" & ws2Row).
Does that fix the problem?

How to keep a log of usage of a macro

I have a rather silly problem. I have a macro (linked to a button) which copies cells A1:A2 from one worksheet (namedFP) to another worksheet (Log). I intend to copy these 2 cells on the log sheet every time I hit the macro button. The problem I am facing right now is that when I use the button multiple times, these cells are getting copied over each other instead of using the next available row to paste the cells.
This is what I have now, and I tried changing the 'Rowcount+1' to 'RowCount+2' but that did not work. Any help is appreciated.
DHRSheet.Select
Range("A1:A2").Select
Selection.Copy
LogSheet.Select
RowCount = LogSheet.UsedRange.Rows.Count
Dim r As Integer
r = RowCount + 1
Dim infocell As Range
Set infocell = Cells(r, 1)
infocell.Select
ActiveSheet.Paste
infocell.Value = DHRSheet.Name & "$" & infocell.Value
DHRSheet.Select
ActiveWorkbook.Save
Is this what you are trying?
Sub Sample()
Dim LogSheet As Worksheet, DHRSheet As Worksheet
Dim lrow As Long
'~~> Change this as applicable
Set LogSheet = Sheets("Sheet1")
Set DHRSheet = Sheets("Sheet2")
With LogSheet
lrow = LogSheet.Range("A" & .Rows.Count).End(xlUp).Row + 1
DHRSheet.Range("A1:A2").Copy .Range("A" & lrow)
End With
End Sub
Here's a function I use that is very reliable and always returns the last row of a sheet without fail:
(possibly excessive for your simple use, but I always recommend it)
Public Function LastRowOfSheet(ByVal TestSheetNumber As Variant)
'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
' Input: Sheet index # or Sheet name
' Output: Last row of sheet.
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
Dim intNumberOfRowsInWorksheet As Long
intNumberOfRowsInWorksheet = Sheets(TestSheetNumber).UsedRange.Rows.Count
intNumberOfRowsInWorksheet = intNumberOfRowsInWorksheet + Sheets(TestSheetNumber).UsedRange.Row - 1
LastRowOfSheet = intNumberOfRowsInWorksheet
End Function
And I'd clean up your above code and use something like this:
Sub Move2RowsToEnd()
Dim iNextRowOfOutput As Long
Dim iRowNumber As Long
'- use the function to find the last row of the output sheet. we'll be pasting to the first row after.
iNextRowOfOutput = (LastRowOfSheet("Log") + 1)
'- you can adjust this for loop to loop through additional cells if you need to paste more than 2 rows in the future.
For iRowNumber = 1 To 2
'- for each row of input (2 total) set the value of the output sheet equal to it.
Sheets("Log").Range("A" & iNextRowOfOutput).Value = Sheets("namedFP").Range("A" & iRowNumber).Value
iNextRowOfOutput = iNextRowOfOutput + 1
Next iRowNumber
'- not sure which of these you want to save (one or both)
Sheets("namedFP").Save
Sheets("Log").Save
End Sub
Just paste the function above or below the Subroutine and let me know if you have any issues or questions regarding the 'Move2RowsToEnd' code.

Resources