I inherited a report that acts as a master tracker for changes made in several other reports. The previous owner was using VLOOKUP formulas to pull data, however I decided to try my hand at a VBA macro and was able to patchwork something together (see below) despite being new to this. It works fine, however:
Problem 1: Right now, the macro returns values one column at a time. I have four nearly identical sub procedures that run back-to-back, one per column: Updated_LName (N), New_Doc (O), New_ID (P), Comments (Q).
Desired Solution: I'd like to increase efficiency by searching for the Student_ID_Nbr (K) in the previous worksheet and then returning values for all four columns at once: Updated_LName, New_Doc, New_ID, Comments (N:Q).
Problem 2: I couldn't figure out a better way than clearing formatting to skip to the next Student_ID_Nbr if it's not found on the previous page. I don't want to overwrite any existing values, which is why I didn't assign an empty string value in the If/Else statement.
Desired Solution: Find a better way to skip to the next iteration if Student_ID_Nbr is not found in the previous worksheet without overwriting existing data.
Problem 3: The macro currently ignores records where there is no Student_ID_Nbr match between the master and the previous worksheet. All changes must be extracted from the other reports and reflected in the master worksheet.
Desired Solution: I'd like to paste in the entire row (A:Q) for each record where the Updated_LName (N), New_Doc (O), New_ID (P), AND/OR Comments (Q) are not blank, AND the Student_ID_Nbr is not present in the master worksheet.
Note: Assume Student_ID_Nbr is a primary key.
Screenshot of example worksheets/data, after running macro successfully
K
L
M
N
O
P
Q
1
Imported
Imported
Imported
Imported
2
Student_ID_Nbr
Qty
LName
Updated_LName
New_Doc
New_ID
Comments
3
123456789
1
Doe
Smith
Transcript
987654321
Marriage cert submitted
Public Sub PullUpdated_LName()
'Declarations
Dim varID As Variant
Dim wsCurrent As Worksheet
Dim wsPrevious As Worksheet
Dim rngSelection As Range
Dim i As Integer
For i = 3 To 30000
'Initialization
Set wsCurrent = ActiveSheet
Set wsPrevious = wsCurrent.Previous
Set rngSelection = ActiveCell
'Error checking--do nothing if not in the correct column
If Not rngSelection.Column = 14 Then
MsgBox "Please select a cell in column N.", vbCritical + vbOKOnly, "Error"
Exit Sub
End If
'Search for ID on the previous ws
Set varID = wsPrevious.Columns(11).Find(What:=wsCurrent.Cells(rngSelection.Row, 11).Value)
'If ID not found, leave existing values alone (including empty cells) on current sheet
If varID Is Nothing Then
rngSelection.ClearFormats
Else
'Return the value in the appropriate row and column from the previous sheet
rngSelection.Value = wsPrevious.Cells(varID.Row, 14).Value
End If
'Regardless, move to the next cell
wsCurrent.Cells(rngSelection.Row + 1, rngSelection.Column).Select
Next i
'Move to next column (to prepare for the next sub)
wsCurrent.Cells(3, rngSelection.Column + 1).Select
'Run next sub automatically
PullNew_Doc
End Sub
Thank you to ShawnPCooke for the great starting point.
You seem to be re-initializing the worksheets each time around the loop, and the limit of 30000 rows to search seems arbitrary and will likely be slow while that searches empty space. Any time you "Select" a cell, you will potentially have the view updating; best to keep everything in Range variables.
This code probably addresses your first two points:
Public Sub Pull_All()
'Declarations
Dim varID As Variant
Dim wsCurrent As Worksheet
Dim wsPrevious As Worksheet
Dim rngSelection As Range
Dim i As Integer
Dim search_zone As Range
Dim last_row As Long
Dim update_cols As Variant
Dim ref_row As Long
Dim ref_col As Long
Dim col As Variant
update_cols = Array(14, 15, 16, 17)
ref_row = 3
ref_col = 11
'Initialize sheets
Set wsCurrent = ActiveSheet
Set wsPrevious = wsCurrent.Previous
Set search_zone = wsPrevious.Columns(11)
' start on a defined cell in the worksheet; this may need update
Set rngSelection = wsCurrent.Cells(ref_row, ref_col)
last_row = rngSelection.SpecialCells(xlCellTypeLastCell).Row
For i = ref_row To last_row
' Search for ID on the previous ws
Set varID = search_zone.Find(What:=rngSelection.Value)
If Not varID Is Nothing Then
' Return the values in the appropriate row and update columns from the previous sheet
For Each col In update_cols
rngSelection.Offset(0, col - ref_col).Value = wsPrevious.Cells(varID.Row, col).Value
Next col
End If
' move to the next cell
Set rngSelection = rngSelection.Offset(1, 0)
Next i
End Sub
Related
Background: I have a table in excel that data will get added to over time, and to give my coworkers (who, while lovely, do not like tinkering with things in excel in fear of messing something up) an easy option for expanding the table if it fills when I'm not around, I wanted to add a macro button to add more lines to the table and fill in the formatting (some cells have IF functions in & most have conditional formatting). The idea is they can fill up to but not including the last line of the table, then hit the button and it will add 20 or so new lines before the last line of table and copy the formatting of the last line into them.
So far this is my code:
Sub Add_Rows()
Dim ws As Worksheet
Set ws = ActiveSheet
Dim tbl As ListObject
Set tbl = ws.ListObjects("Table1")
x = tbl.Range.Rows.Count
Range(x - 1, x + 19).Insert Shift:=xlShiftDown, CopyOrigin:=xlFormatFromRightOrBelow
End Sub
I am getting a "Run time error '1004'" "Method range of object _global failed" message when I try clicking the button, and it highlights the "insert" line as being the issue. I am new to vba so any advice is welcome. If my code is utter nonsense then an alternative direction would be appreciated.
Also this is the second version, my first looped Rows.Add which worked, but was taking a few seconds so my hope was inserting 20 would be faster than adding 1 20 times!
Try this.
Sub Add_Rows()
Dim ws As Worksheet
Set ws = ActiveSheet
Dim tbl As ListObject
Dim lastRow As Range, newRng As Range
Dim newRows As Integer: newRows = 20
Set tbl = ws.ListObjects("Table1")
' Last row
On Error GoTo resizeOnly ' Listrows = 0
Set lastRow = tbl.ListRows(tbl.ListRows.Count).Range
On Error GoTo 0
' range of new rows
Set newRng = tbl.ListRows(tbl.ListRows.Count).Range.Resize(newRows).Offset(1)
' resize table
tbl.Resize tbl.Range.Resize(tbl.Range.Rows.Count + newRows, tbl.Range.Columns.Count)
' copy last format to new rows
lastRow.Copy
newRng.PasteSpecial xlPasteFormulasAndNumberFormats
Application.CutCopyMode = False
Exit Sub
resizeOnly:
' resize table
tbl.Resize tbl.Range.Resize(tbl.Range.Rows.Count + newRows, tbl.Range.Columns.Count)
End Sub
If you have no data below the table, you can just assign values to the rows immediately after the table. The table will automatically expand to encompass the new rows, as long as at least one cell in each row, has well defined data.
' Insert 3 new rows into the listoject
' We assume the ListObject already contains data
Public Sub Test(Lob As ListObject)
Dim Sht As Worksheet
Dim StartRow As Long, StartCol As Long, NumCols As Long
Dim Lst As Variant
Dim Rng As Range
' Allocate 3 new rows
NumCols = Lob.ListColumns.Count
ReDim Lst(1 to 3, 1 to NumCols)
' Get the first column of and the first row following the list table
StartCol = Lob.Range.Column
StartRow = Lob.Range.row + Lob.Range.Rows.Count
' Create a range big enough to hold the data, immediately under the last row of the table.
Set Sht = Lob.Parent
Set Rng = Sht.Cells(StartRow, StartCol).Resize(UBound(Lst), UBound(Lst, 2))
' Add some data to the new rows
Lst(1, 1) = "Test1"
Lst(2, 1) = "Test2"
Lst(3, 1) = "Test3"
' Copy data to the destination
Rng = Lst
End Sub
If the list object does not contain data, ie Lob.ListRows.Count = 0, then write data after the header otherwise write it after the last rows.
There are some mistakes in your code:
"Range(x , y)" will cause an error, when x and y are integers. If you want to refer to a cell. Try Cells(x, y). Or Range(Cells(x1, y1), Cells(x2, y2)) to refer to more cells.
And Resize() takes two arguments, and returns a range - it does not affect anything on the sheet.
See also how to insert rows if you want:
Excel Range.Insert:
Example from the doc:
With Range("B2:E5")
.Insert xlShiftDown
' Optionally clear formats, which you do not want, if you add to
' a table with well defined data and formats.
.ClearFormats
End With
The number of the rows inserted, will equal the number of rows in the range we call Insert on.
I have 2 sheets setup: Exclusions and Issues
Issues has a list of CASE ID's and Columns that list the "Issue"
Exclusions will be populated with CASE ID's that are to be excluded (and removed) from the Issues sheet.
My question is 2 fold:
Is my current code handling this correctly? Are there any ways to improve this?
Is there a way to have the code cycle through all columns dynamically? Or is it just easier to copy the FOR/NEXT loop for each column on the "Issues" sheet?
Code below:
Sub Exclusions()
'find exclusions and remove from issues sheet. once done delete any completely blank row
Dim i As Long
Dim k As Long
Dim lastrow As Long
Dim lastrowex As Long
Dim DeleteRow As Long
Dim rng As Range
On Error Resume Next
Sheets("Issues").ShowAllData
Sheets("Exclusions").ShowAllData
On Error GoTo 0
Application.ScreenUpdating = False
lastrowex = Sheets("Exclusions").Cells(Rows.Count, "J").End(xlUp).Row
With ThisWorkbook
lastrow = Sheets("Issues").Cells(Rows.Count, "A").End(xlUp).Row
For k = 2 To lastrowex
For i = 2 To lastrow
If Sheets("Exclusions").Cells(k, 10).Value <> "" Then
If Sheets("Exclusions").Cells(k, 10).Value = Sheets("Issues").Cells(i, 1).Value Then
Sheets("Issues").Cells(i, 11).ClearContents
End If
End If
Next i
Next k
End With
On Error Resume Next
For Each rng In Range("B2:P" & lastrow).Columns
rng.SpecialCells(xlCellTypeBlanks).EntireRow.Delete
Next rng
Application.ScreenUpdating = True
End Sub
Data Format:
"Issues" sheet
CASE ID Issue 1 Issue 2 Issue 3
ABC123 No address No Name No Number
"Exclusions" sheet
Issue 1 Issue 2 Issue 3
ABC123 DEF123 ABC123
Data Example:
Issues sheet may have several CASE ID's present for one or multiple issues.
CASE ID Issue 1 Issue 2 Issue 3
DEF123 No add No num
PLZ No name
Exclusions sheet is basically a method for someone to "exclude" a particular issue for whatever reason. So if it is determined that PLZ CASE ID's not having a name is OK, then it is to be excluded from showing up on the Issues sheet.
Issue 1 Issue 2 Issue 3
DEF123 DEF123
PLZ would not show up in the above example because it is in the "EXCLUSIONS" sheet.
VBAWARD Make a copy of your data before trying this code:
You need to adapt it to your needs. I didn't quite understood when is the row gonna be empty. Any way, working with ranges may be faster and easier to debug.
Option Explicit
Sub Exclusions()
'find exclusions and remove from issues sheet. once done delete any completely blank row
' Declare objects
Dim issuesRange As Range
Dim exclusionsRange As Range
Dim issuesCell As Range
Dim exclusionsCell As Range
' Declare other variables
Dim lastRowIssues As Long
Dim lastRowExclusions As Long
' This is not recommended
On Error Resume Next
Sheets("Issues").ShowAllData
Sheets("Exclusions").ShowAllData
On Error GoTo 0
Application.ScreenUpdating = False
' Get the last row in the exclusions sheet - In this case I'd prefer to work with structured tables
lastRowExclusions = ThisWorkbook.Worksheets("Exclusions").Cells(Rows.Count, "J").End(xlUp).Row ' use full identifier with ThisWorkbook. and also use Worksheets collection as you don't need to look for graphics sheets
' Get the last row in the issues sheet - In this case I'd prefer to work with structured tables
lastRowIssues = ThisWorkbook.Worksheets("Issues").Cells(Rows.Count, "A").End(xlUp).Row
' Store Exclusions in a range
Set exclusionsRange = ThisWorkbook.Worksheets("Exclusions").Range("J2:L" & lastRowExclusions)
' Store Issues in a range
Set issuesRange = ThisWorkbook.Worksheets("Issues").Range("A2:C" & lastRowIssues)
' Loop through each of the exclusions
For Each exclusionsCell In exclusionsRange
' Loop through each of the Issues Cells
For Each issuesCell In issuesRange
' Compare if ex is equal to iss
If exclusionsCell.Value = issuesCell Then
' Color the cell or clear its contents
'issuesCell.Interior.Color = 255
' Clear the cell contents
issuesCell.ClearContents
' Delete the whole row?
'issuesCell.Rows.EntireRow.Delete
' Delete the row if it's empty
If WorksheetFunction.CountA(ThisWorkbook.Worksheets("Issues").Range("B" & issuesCell.Row & ":D" & issuesCell.Row).Value) = 0 Then
issuesCell.Rows.EntireRow.Delete
End If
End If
Next issuesCell
Next exclusionsCell
' Restore settings
Application.ScreenUpdating = True
End Sub
I have several sheets with different structure that i need to merge using some of the columns headers that are common
I gathered in the one sheet ("Combine") the common headers and tried to write a macro to find the same column and to its data to the combine sheet, the macro is only getting the first column and not proceeding.
Any guidance with this issue will be appreciated
Dim II%, XX%, ZZ%, I% ' Dim as long
Dim Sht As Worksheet ' Every Sheet on This Workbook
Dim Comb As Worksheet ' Combine Sheet
Set Comb = ThisWorkbook.Worksheets("Combine")
II = 2 ' Start on row 2 - Sheet1 & Sheet2
XX = 2 ' Start on row 2 - Combine sheet
'Looping through the worksheets in the workbook
For Each Sht In ThisWorkbook.Worksheets
' ignore Sheet "Combine" and "Val"
If Sht.Name <> "Combine" And Sht.Name <> "Val" Then
For ZZ = 1 To 100
For I = 1 To 100
If Sheets(Sht.Name).Cells(1, I).Value = Comb.Cells(1, ZZ).Value Then
Do Until IsEmpty(Sht.Columns(1).Cells(II))
Comb.Cells(XX, ZZ).Value = Sheets(Sht.Name).Cells(II, I).Value
II = II + 1
XX = XX + 1
Loop
End If
Next I
I = 1
Next ZZ
End If
II = 2 ' Reset 1st Loop to capture the new sheet data
Next
If I understand your question correctly, you have multiple sheets that have a heading row of some number of columns. You then have data rows below this in corresponding columns.
You’ve looked at the headings in each sheet and added those names that are common to a sheet you’ve called Combine. Not all columns on all sheets are found on the Combine sheet. The Combine sheet is a subset of the total column names in the workbook.
The sheets might contain data from several test runs or whatever. The output might contain common columns as well as some additional data. For example, sheet 1 could contain date, time, location, and result. Sheet 2 could contain date, time, and tester.
You want a combined sheet that shows the common fields, in this case Date, Time, Result, and Tester. You’ve already determined the common headings.
I think your problem might be in Do Until IsEmpty(Sht.Columns(1).Cells(II)). You may be encountering an empty cell.
Also, it is much faster to use Excel's built-in functions to perform moving large blocks of data between sheets.
Given you seem to be learning about VBA and have made a pretty good attempt, I took the liberty to provide you with an example that uses a more advanced way for solving the problem
The code below in effect concatenates the data from each sheet and common column on to the Combine sheet. It leaves blanks where a column does not have a data sheet have data that would be copied into the Combine column. This means that there will be blank cells under the columns Result and Test – based on the source data sheet.
I hope you find this helpful and that it answers your question. I have learned a lot from other's example on this site and am trying to pay it forward.
Option Explicit
Public Sub Tester()
'Not needed
'Dim II%, XX%, ZZ%, I% ' Dim as long
Dim Comb As Worksheet ' Combine Sheet
Set Comb = ThisWorkbook.Worksheets("Combine")
'Declare a range object and assign it to contain column names
'from Combine. This range, converted to a list
'below will compare the combined heading names with
'each column heading on each sheet.
Dim rngCombineHeadings As Range
'set combine headings into the range using the function
'EndOfRangeColumn, which is decribed below
Set rngCombineHeadings = EndOfRangeColumn(Comb.Range("A1"))
'Declare a collection to be used in the for loop to compare
'Combine column headings with each source sheets headings
'Only copy those columns that match
Dim colCombinedHeadings As Collection
'Get a collection (aka list of strings) of the column headings
Set colCombinedHeadings = GetCommonHeadings(rngCombineHeadings)
'Declare two ranges to be used as the index inside
'for loops below.
Dim combineColTargetRng As Range
Dim colRng As Range
'Declare a variant to used use the index for looing
'through the Combine sheet headings
Dim vHeading As Variant
'Declare tblRng. It will be set to contain the entire data table
'on each sheet. Row 1 contains the headings, rows 2 - n contain
'the data that may be moved.
Dim tblRng As Range
'This is the range that will be manipulated and copied
'to the Combine sheet
Dim copyRng As Range
'Looping through the worksheets in the workbook
'Index variable used in for each loop below best practice is
'declare you variables near where they are used.
Dim Sht As Worksheet ' Every Sheet on This Workbook
For Each Sht In ThisWorkbook.Worksheets
' ignore Sheet "Combine" and "Val"
If Sht.Name <> "Combine" And Sht.Name <> "Val" Then
'Set the data table to the tblRng object.
Set tblRng = EndOfRangeRow(Sht.Range("A1"))
Set tblRng = EndOfRangeColumn(tblRng)
'For each sheet, loop through each headings on
'the Combined sheet and compare those to the
'headings on the data table on the current sheet
For Each vHeading In colCombinedHeadings
For Each colRng In tblRng.Columns
'if the heading on Combined = the current
'columns heading then, copy the data
'to the combined sheet.
If vHeading = colRng.Value2(1, 1) Then
'Resize the copy range to exclude the heading row
'and to reduce the size by one row, reflecting removal
'of the header row from the range
Set copyRng = ResizeTheRange(colRng.Offset(1, 0))
'Find the column on the Combine sheet that
'matches the current value in vHeading
Set combineColTargetRng = rngCombineHeadings.Find(colRng.Value2(1, 1))
'Copy the current sheet-current column to the clipboard
copyRng.Copy
'The if statement below determines if this is the first
'column of data being copied to the Combine sheet
'if it is, the row 2 current column is empty
'otherwise it has a value and we need to move the paste point
'to the end of the current Combine sheet column
If combineColTargetRng.Offset(1, 0).Value2 = "" Then
Set combineColTargetRng = combineColTargetRng.Offset(1, 0)
Else
Set combineColTargetRng = EndOfRangeRow(combineColTargetRng)
Set combineColTargetRng = _
combineColTargetRng.Offset( _
combineColTargetRng.Rows.Count, 0)
End If
'Paste the values copied from the current sheet
'that are under the same column heading as on the combined sheet
'There are a number of options for pasteSpecial
'See https://learn.microsoft.com/en-us/office/vba/api/excel.range.pastespecial
combineColTargetRng.PasteSpecial Paste:=xlPasteAll
End If
Next
Next
End If
Next
End Sub
'*****************************************************************************
'**
'** This function demonstrates use of the ParamArray. It enables the
'** calling routine, to provide the range as an Excel Range, a Collection
'** an Array, or a list of strings.
'**
'** Calling the Function:
'** Dim aCol as Collection
'** Set aCol = GetCommonHeadings(aCol)
'** Dim rngExcelRange as Range
'** set rngExcelRange = Range("A1:X1")
'** Set aCol = GetCommonHeadings(rngExcelRange)
'** Dim vArr() as Variant
'** vArr = Array("H1", "H2", "H3", "H4")
'** Set aCol = GetCommonHeadings(vArr)
'** Set aCol = GetCommonHeadings("Title1", "Title2", "Title3", "Title4")
Public Function GetCommonHeadings(ParamArray mRange() As Variant) As Collection
'Instantiate the return collection
Dim retVal As New Collection
Dim nDx As Long
If UBound(mRange) < 0 Then
'Cannot do anything without the heading range
Set retVal = Nothing
ElseIf TypeOf mRange(0) Is Range Then
'Heading Range is an Excel Range
Dim rngMaster As Range
Dim colRng As Range
Set rngMaster = mRange(0)
For Each colRng In rngMaster.Columns
retVal.Add colRng.Value2
Next
ElseIf TypeOf mRange(0) Is Collection Then
'Heading Range is a collection of text strings
Set retVal = mRange(0)
ElseIf VarType(mRange(0)) = vbArray + vbVariant Then
'Heading Range passed is an array of strings
Dim varArr() As Variant
varArr = mRange(0)
For nDx = 0 To UBound(varArr)
retVal.Add varArr(nDx)
Next
ElseIf VarType(mRange(0)) = vbString Then
'mRange contains an array of strings
For nDx = 0 To UBound(mRange)
retVal.Add mRange(nDx)
Next
Else
Set retVal = Nothing
End If
Set GetCommonHeadings = retVal
End Function
'****************************************************************************
'**
'** The Functions EndOfRangeColumn, EndOfRangeRow, StartOfRangeColumn, and
'** StartOfRangeRow take one parameter which is an Excel Range. Based on
'** the funtions name it will return the cell that is at the other end.
'** These are just wrappers to make the code more readable. The real work
'** is done by the Private Function GetRangeAtEnd. The private function
'** takes an Excel Range and the direction you want to move.
Public Function EndOfRangeColumn(ByRef mStartOfRange As Range) As Range
Set EndOfRangeColumn = GetRangeAtEnd(mStartOfRange, xlToRight)
End Function
Public Function EndOfRangeRow(ByRef mStartOfRange As Range) As Range
Set EndOfRangeRow = GetRangeAtEnd(mStartOfRange, xlDown)
End Function
Public Function StartOfRangeColumn(ByRef mEndOfRange As Range) As Range
Set StartOfRangeColumn = GetRangeAtEnd(mStartOfRange, xlToLeft)
End Function
Public Function StartOfRangeRow(ByRef mEndOfRange As Range) As Range
Set StartOfRangeRow = GetRangeAtEnd(mStartOfRange, xlUp)
End Function
Private Function GetRangeAtEnd(ByRef mRange As Range, ByVal mDirection As XlDirection) As Range
Set GetRangeAtEnd = Range(mRange, mRange.End(mDirection))
End Function
'***************************************************************
'**
'** The Private Function ResizeTheRange takes an Excel range
'** provide in the parameter. In effect it removes the first
'** row from the provided range, and reduces the size by one.
Private Function ResizeTheRange(ByRef mRange As Range) As Range
Dim retVal As Range
Set retVal = mRange.Offset(1, 0)
Set retVal = retVal.Resize(retVal.Rows.Count - 1, 1)
Set retVal = EndOfRangeRow(retVal)
Set ResizeTheRange = retVal
End Function
I'm trying to take a rough list of data, then copy it into a pre-formatted, organized form. To do this, I have the rough list set up so that each item on the list is numbered in order, no matter if there are spaces between items. The macro I am trying to make will take that rough list and copy it to the form without any spaces. Bear with me, I have been trying to teach myself Visual Basic, so the code I have may be... messy. Currently, the problem that I am having is that I get an overflow on i = i + 1.
Sub Print_Sheet_Populate()
'
' Print_Sheet_Populate Macro
' Takes Items from Raw Data sheet and puts them in Print Sheet sheet.
'
'
Dim wsS1 As Worksheet
Dim wsS2 As Worksheet
Dim ending As Long
Dim copy() As Long
Dim i As Long
Set wsS1 = Sheets("Raw Data")
Set wsS2 = Sheets("Print Sheet")
With wsS1.Range("A:A") 'To copy the item numbers in the rough data to an array
i = 1
Set c = .Find(i, LookIn:=xlValues)
If Not c Is Nothing Then
ReDim copy(i)
copy(i - 1) = c.Value
Do
i = i + 1
ending = i
Loop While Not c Is Nothing
End If
End With
With wsS2.Range("A24:A324") 'To paste the data from the array to the form
i = 1
If Not i = ending Then
Do
Worksheets("wsS2").Range("A" & i).Value = copy(i - 1)
i = i + 1
Loop While Not c Is Nothing
End If
End With
End Sub
Taken from Range.Find Method (Excel):
When the search reaches the end of the specified search range, it
wraps around to the beginning of the range. To stop a search when this
wraparound occurs, save the address of the first found cell, and then
test each successive found-cell address against this saved address.
This is my first post, so please provide any feedback about my approach to presenting the problem.
I'm building a sub that (ultimately) is supposed to copy a range from one sheet ("Sandbox") to another ("Master"). The steps are:
Identify the selected rows
Loop through the Sandbox rows, determining whether to find a matching Master row or add as a new end-row in Master
Copy the values only from each selected Sandbox row to the appropriate Master row
The error pops with the setting the range for the PasteSpecial function. That line consistently gives a "1004 (Method 'Range' of object '_Global' failed" message.
Here's the code :
Sub UpdateMaster()
Dim currentSelection As Range
Set currentSelection = Selection
Dim sheetSB As Worksheet
Set sheetSB = ThisWorkbook.Sheets("Sandbox")
Dim sheetMaster As Worksheet
Set sheetMaster = ThisWorkbook.Sheets("Master")
Dim lastTargetRow As Integer
lastTargetRow = sheetMaster.Range("IDRange").End(xlDown).Row + 1
Dim startingTargetColumn As Integer
startingTargetColumn = sheetMaster.Range("IDRange").Column
Dim thisID As String
Dim thisStatus As String
For Each thisrow In currentSelection.Rows
' Capture the current ID value
thisID = Cells(thisrow.Row, Range("IDRange").Column).Value
' Capture the current Status value
thisStatus = Cells(thisrow.Row, Range("NewRange").Column).Value
' If the row has no ID...
If thisID = "" Then
' ...do nothing
' If the row is flagged as new...
ElseIf thisStatus = "New" Then
'...identify the first blank row, and set all data columns to be copied
Range(Cells(thisrow.Row, Range("IDRange").Column), Cells(thisrow.Row, Range("LastSandboxColumn")).Column).Copy _
Destination:=sheetMaster.Range(lastTargetRow, startingTargetColumn)
' Increment the next available last row by 1
lastTargetRow = lastTargetRow + 1
Else
' Otherwise, find the corresponding row and set the non-ID columns to be copied
Dim sourceColumn1 As Integer, sourceColumn2 As Integer
Dim targetRow As Integer, targetColumn As Integer
Dim matchRow As Integer
sourceColumn1 = Range("IDRange").Column + 1
sourceColumn2 = Range("LastSandboxColumn").Column
targetRow = Application.WorksheetFunction.Match(thisID, sheetMaster.Range("IDRange"), 0)
targetColumn = startingTargetColumn + 1
Range(Cells(thisrow.Row, sourceColumn1), Cells(thisrow.Row, sourceColumn2)).Copy
Range(sheetMaster.Cells(targetRow, targetColumn)).PasteSpecial xlPasteValues
End If
Next
End Sub
The error is happening on the last line:
Range(sheetMaster.Cells(targetRow, targetColumn)).PasteSpecial xlPasteValues
Inexplicably, the following seems to work:
Range(Cells(thisrow.Row, sourceColumn1), Cells(thisrow.Row, sourceColumn2)).Copy _
Destination:=Range(sheetMaster.Cells(targetRow, targetColumn))
Unfortunately, I want only the values; bringing over formulas and formatting will screw up other behavior in the sheet.
I have tried many variations, but essentially it will not allow me to create a range, referencing Master, if I use Cells().
Any help much appreciated.
Just do:
sheetMaster.Cells(targetRow, targetColumn).PasteSpecial xlPasteValues
An error could occur with this, if the sheetMaster isn't the ActiveSheet at runtime:
Range(sheetMaster.Cells(targetRow, targetColumn).PasteSpecial) xlPasteValues
Also note, for this problem:
Unfortunately, I want only the values; bringing over formulas and formatting will screw up other behavior in the sheet.
You can get the range's .Value as an array, and write it directly to the other sheet without invoking either Copy or Paste/PasteSpecial methods. The answer below shows several methods of copying/pasting from one workbook to another, but could easily be modified for sheet-to-sheet transfer, instead.
Copy from one workbook and paste into another