clear cells while preserving Autofilter settings? - vba

I'm writing a script for a worksheet whose cells are populated based on an Access database. I'm trying to clear the contents of the worksheet without removing any Autofilters that the user has set, and then reload the data based on on the database. Right now I'm using:
Sub populateSheet()
Dim sht As Worksheet
Dim db As Database
Dim rs As Recordset
Set db = OpenDatabase("c:\myDB.mdb")
Set rs = db.OpenRecordset("myData")
Set sht = ThisWorkbook.Sheets("my output")
With sht
.Cells.value=empty
For c = 0 To rs.Fields.Count - 1
.Cells(1, c + 1) = rs.Fields(c).name
Next
.Range("a2").CopyFromRecordset rs
End With
End Sub
sub buildTable()
dim ws as workspace
dim db as database
dim dbPath as string
set ws=dbengine.workspaces(0)
set db=ws.createdatabase("c:\myDB.mdb")
db.execute "create table myData (field1 text,field2 text)"
db.execute "insert into myData (field1,field2) values (""1"",""a"")"
db.execute "insert into myData (field1,field2) values (""2"",""b"")"
db.execute "insert into myData (field1,field2) values (""3"",""a"")"
db.close
end sub
sub test()
buildTable
populateSheet
end sub
When I run .cells.clear, it wipes out the Autofilter. Is there a way I can keep the Autofilter settings so the new data will be filtered the same way? Or maybe record them and re-apply the same settings? I tried working with this solution, but I had trouble getting it to detect which columns were filtered.
EDIT:
I applied Jean-FrançoisCorbett's approach in the above code, but it has a problem. Try this with a test table:
1 a
2 b
3 a
After you run populateSheet, then autofilter the 2nd column to only include "a", the worksheet shows:
1 a
3 a
Then run populateSheet again, the sheet now shows:
1 a
1 a
If you remove the autofilter, rerun populateSheet, and re-apply the autofilter, you get the correct data, but that's a very cumbersome extra step to get correct output.
EDIT:
I added code to create a database and make a table that you can use to test populateSheet, and changed some of the arguments in populateSheet to reflect this test database.

If you just want to clear the values in the cells and nothing else, then you can do this:
sht.Cells.Value = Empty
EDIT Responding to the OP's edit:
Oddly enough, the statement above does not empty the cells in any rows that were hidden by the Autofilter! I think that the autofilter-hidden rows also cause unexpected behaviour when importing data with CopyFromRecordset.
The solution to that problem is of course to, prior to emptying all cells and importing data, unhide all rows by setting the autofilters to (All), which in VBA is done like this:
With Range("C1:D1") ' or wherever the filters are
.AutoFilter Field:=1
.AutoFilter Field:=2
' ... continue to set all fields to (All).
End With

Ok, I have a solution for this. Basically, I record the autofilter criteria in an array of variants, then remove the Autofilter settings, populate the sheet (using #Jean-FrançoisCorbett's suggestion of sht.Cells.Value = Empty) then reapply the settings from the array. Code as follows:
Sub reapplyAutofilter()
Dim fltr As Filter
Dim columnCriteria1() As Variant
Dim columnCriteria2() As Variant
Dim columnOperators() As Variant
Dim filterCriteria() As Variant
Dim sht As Worksheet
Dim rangeAddr As String
Dim fieldCount As Long
Dim ctr As Long
Set sht = ThisWorkbook.Sheets("mySheet")
'don't analyze autofilter if it's not on
If Not sht.AutoFilterMode Then
populateSheet
Exit Sub
End If
'put autofilter settings in arrays for criteria1, criteria2, and operator
With sht.AutoFilter
rangeAddr = .Range.Address
fieldCount = .Filters.Count
ReDim columnCriteria1(1 To fieldCount)
ReDim columnCriteria2(1 To fieldCount)
ReDim columnOperators(1 To fieldCount)
For ctr = 1 To fieldCount
With .Filters(ctr)
If .On Then
columnCriteria1(ctr) = .Criteria1
columnOperators(ctr) = .Operator
If (.Operator = xlOr) or (.Operator=xlAnd) Then
columnCriteria2(ctr) = .Criteria2
Else
columnCriteria2(ctr) = Null
End If
Else
columnCriteria1(ctr) = Null
End If
End With
Next ctr
End With
'clear autofilter
sht.AutoFilterMode = False
populateSheet
're-apply autofilter settings
With sht.Range(rangeAddr)
For ctr = 1 To fieldCount
If Not IsNull(columnCriteria1(ctr)) Then
If IsNull(columnCriteria2(ctr)) Then
.AutoFilter Field:=ctr, Criteria1:=columnCriteria1(ctr), Operator:=xlFilterValues
Else
.AutoFilter Field:=ctr, Criteria1:=columnCriteria1(ctr), Criteria2:=columnCriteria2(ctr), Operator:=columnOperators(ctr)
End If
End If
Next
End With
End Sub
I'm not sure if this counts as me solving the problem, because I was inspired by Jean-FrançoisCorbett's code. I did have to figure out on my own how to capture and reapply the Autofilter settings. Feel free to chime in about who should get credit on this one; otherwise, I'll accept my own answer.

Related

Pasting into last column of table

I've been creating a VBA code to help me with a worksheet I use but I'm stuck at a certain point.
The code looks at the table on the current worksheet, adds a new column to the end of the table and then I get it to copy the first column in the worksheet (as this has the formats and some calculated cells). This is where my coding finishes. Ideally I would then like it to take the copied cells and paste them into the new end column of the table.
This is what I have so far:
Sub AddNewColumn()
Application.ScreenUpdating = False
Dim oSh As Worksheet
Set oSh = ActiveSheet
With oSh.ListObjects("Labour")
.ListColumns.Add
Range("Labour[[#All],[Column16]]").Select
Selection.Copy
End With
Application.ScreenUpdating = True
End Sub
(Labour being the name of the current table).
If I can get this to work fantastic but then I think I will encounter another issue. The table is on a template worksheet and contained on this I have a command button to create a copy of the template (for different tasks). This would then change the name of the table (Labour1 then Labour2 etc as new worksheets are created). How would I get the code to work on new worksheets as the code I have at the minute would simply want to link back to the original table (Labour).
You don't need actually copy values from the first column to the newly created, just use formula. I have modified your code:
Sub AddNewColumn()
Application.ScreenUpdating = False
Dim oSh As Worksheet
Dim oList As ListObject
Dim str As String
Set oSh = ActiveSheet
Set oList = oSh.ListObjects("Labour")
With oList
.ListColumns.Add
str = .ListColumns(1).Name
.ListColumns(.ListColumns.Count).DataBodyRange.FormulaR1C1 = "=[#[" & str & "]]"
End With
End Sub
If you need actual values, not formulas, you may copy and paste special the last column. Before end with add:
With .ListColumns(.ListColumns.Count).DataBodyRange
.Copy
.PasteSpecial xlPasteValues
End With
This is answer to your first question. Unfortunately, I am not able to understand the second. Besides, I think you should ask it separately.
OK I have tweaked your code #MarcinSzaleniec and it appears to be working.
Sub AddNewColumn()
Application.ScreenUpdating = False
Dim oSh As Worksheet
Dim oList As ListObject
Dim str As String
Set oSh = ActiveSheet
Set oList = oSh.ListObjects("Labour")
With oList
.ListColumns.Add
str = .ListColumns(1).Name
Range("Labour[[#All],[Column16]]").Select
Selection.Copy
.ListColumns(.ListColumns.Count).DataBodyRange.PasteSpecial xlPasteAll
Application.ScreenUpdating = True
End With
End Sub
The reason I need:
Range("Labour[[#All],[Column16]]").Select
Selection.Copy
Is due to it being a column hidden out the way and has the blank bits blank and the formula bits as formulas.
Many thanks for everybody's help. Now to ask the second part of my question on here.

vba select slicer item excel

I've recently discovered VBA code to filter slicers based off of variable names. It is a great tool for filtering what you want to see
The next step in my code is to potentially REMOVE visible data from my pivot table/chart (automatically).
Lets say I already have a variable "Remove_ITEM" that needs to be removed from the data shown. Remove_item is inside slicer ("slicer_Order").
The data is also inside a data model.
The code below is to ONLY show REmove_Item:
ActiveWorkbook.SlicerCaches("Slicer_Order").'VisibleSlicerItemsList = ("[Actuals_Table].[Order].&["& Remove_item &"]")
Now i want to do the opposite
I hope I understood what you are trying to achieve in your post.
Try the code below and let me know if it works as you intended:
Option Explicit
Sub SlicersTst()
Dim WB As Workbook
Dim OrderSlcrCache As SlicerCache
Dim OrderSlcItem As SlicerItem
Dim RemoveItem As Variant
Set WB = ThisWorkbook
Set OrderSlcrCache = WB.SlicerCaches("Slicer_Order") '<-- set Slicer Cache to "Order" slicer
OrderSlcrCache.ClearManualFilter '<-- clear manual filters
RemoveItem = "c" '<-- set value for test
' loop through all slicer items in slicer "Order"
For Each OrderSlcItem In OrderSlcrCache.SlicerItems
If OrderSlcItem.Name = RemoveItem Then OrderSlcItem.Selected = False
Next OrderSlcItem
End Sub
Please keep in mind this is specifically for data model usage with a connection.
Sub Variables()
Part_Number = Worksheets("Solumina_Data_Page").Cells(Row, "B").Value
End Sub
Sub Sort_Part_Number()
Application.ScreenUpdating = False
Page1 = ActiveSheet.Name
Call Variables
Sheets("Dashboard").Activate
ActiveWorkbook.SlicerCaches("Slicer_Material").VisibleSlicerItemsList = "[Part List].[Material].&[" & Part_Number & "]"
' "[Part List].[Material].&[77C726210G1]" <<What we want to see
Sheets(Page1).Activate
Application.ScreenUpdating = True
End Sub
Heres a similar example using the array
Sub Use_ARRAY()
Dim ARR() As String
ReDim ARR(1 To 2)
Call Array_Actuals_Data 'get data
Call Array_Outliers_removed 'sort data
ARR(1) = "[Actuals_Table].[Order].&[000010840921]"
ARR(2) = "[Actuals_Table].[Order].&[000010949159]"
ActiveWorkbook.SlicerCaches("Slicer_order").VisibleSlicerItemsList = ARR()
End Sub

What is the best way to automate copy and paste specific ranges in excel?

I am very new to VBA and there is a task I would like to automate and don't know where to start. I have a data set that looks like below.
Sample Data
What I'm trying to do is loop through column A and if it has something in it (will always be an email) select all rows until there is something in column A again. Copy and paste into new tab. So row 2-5 would copy and paste into a new tab. Then row 6-9 into a different new tab. Also row 1 would copy to each tab as well. I haven't been able to find code to help with this specific need and any help would be greatly appreciated.
I found this code and started modifying it but, it's nowhere close to what I need or working for that matter.
Sub split()
Dim rng As Range
Dim row As Range
Set rng = Range("A:A")
For Each row In rng
'test if cell is empty
If row.Value <> "" Then
'write to adjacent cell
row.Select
row.Copy
Worksheets("Sheet2").Activate
Range("A2").Select
row.PasteSpecial
Worksheets("Sheet1").Activate
End If
Next
End Sub
This code should provide what you need:
Sub Split()
Dim wb As Workbook
Set wb = ThisWorkbook
Dim ws As Worksheet
Set ws = wb.Worksheets(1) 'change sheet index or use Worksheets("Sheet1") method to use exact name
Dim rngBegin As Range
Dim rngEnd As Range
With ws
Dim rngHeader As Range
Set rngHeader = .Range("A1:H1") 'to copy headers over each time
Dim lRowFinal As Long
lRowFinal = .Range("C" & .Rows.Count).End(xlUp).Row 'assumes eventually last row of needed data will have an address1
Set rngEnd = .Range("A1") ' to begin loop
Set rngBegin = rngEnd.End(xlDown) 'to begin loop
Do
Set rngEnd = rngBegin.End(xlDown).Offset(-1)
Dim wsNew As Worksheet
Set wsNew = Worksheets.Add(After:=wb.Sheets(.Index))'always after current sheet, change as needed
.Range(.Cells(rngBegin.Row, 1), .Cells(rngEnd.Row, 8)).Copy wsNew.Range("A2")
wsNew.Range("A1:H1").Value = rngHeader.Value
Set rngBegin = rngEnd.End(xlDown)
Loop Until rngBegin.Row >= lRowFinal
End With
End Sub
Try to break your process into steps and determine rules on how to proceed. Then write out some pseudo-code (code like logic) to make sure it all makes sense.
You need some sort of loop, since you are going to treat each
group of rows in the same way.
You need some code that determines what cells are contained in each block
Code to take a block (given by step 2) and paste it into a new tab.
Your Pseudo Code might look like this:
' This is the main function that runs the whole routine
Sub Main()
Set headerRg = GetHeaderRg()
Do Until IsAtTheEnd(startRow) = True
Set oneBlock = GetNextBlock(startRow)
Call ProcessBlock(oneBlock)
startRow = startRow + oneBlock.Rows.Count
Loop
End Sub
' This function returns the header range to insert into the top
Function GetHeaderRg() As Range
' Write some code here that returns the header range
End Function
' This function determines whether we are at the end of our data
Function IsAtTheEnd(current_row as Long) as Boolean
' Write some code here that determines whether we have hit the end of our data
'(probably checks the first column to see if there is data)
End Function
' This function takes the startRow of a block and returns the whole block of Rows
Function GetNextBlock(startRow) As Range
' Write some code that returns the whole range you want to copy
End Function
' This sub takes a range to be processed and a header to print and prints
' it into a new tab
Sub ProcessBlock(BlockRg As Range, headerRg as Range)
Set targetSheet = thisWorkbook.Sheets.Add()
' Write some code that pastes the headerRg and BlockRg where you want it
End Sub
If you start to have more specific questions about syntax etc, we will be happy to help here!

Overwrite double click action in a PivotTable to go to filtered source data

I'm trying to create a PivotTable in which a double click on a value leads the user to the filtered source sheet with the rows that this value represents, rather than a new sheet with the underlying data.
This is how far I've gotten, but I'm having issues extracting the relevant row and column names / values, as well as the filters currently active in the pivottable.
Private Sub Worksheet_BeforeDoubleClick(ByVal Target As Range, Cancel As Boolean)
Dim rng As Range
Dim wks As Worksheet
Dim pt As PivotTable
' Based on http://stackoverflow.com/questions/12526638/how-can-you-control-what-happens-when-you-double-click-a-pivot-table-entry-in-ex
Set wks = Target.Worksheet
For Each pt In wks.PivotTables()
Set rng = Range(pt.TableRange1.Address)
If Not Intersect(Target, rng) Is Nothing Then
Cancel = True
End If
Next
' Source: http://www.mrexcel.com/forum/excel-questions/778468-modify-pivottable-double-click-behavior.html
On Error GoTo ExitNow
With Target.PivotCell
If .PivotCellType = xlPivotCellValue And _
.PivotTable.PivotCache.SourceType = xlDatabase Then
SourceTable = .PivotTable.SourceData
MsgBox SourceTable
' I found the sourcetable, how would I collect the row/column
' names and values in order to filter this table?
End If
End With
ExitNow: Exit Sub
End Sub
In order to filter the source sheet, I need to extract the following characteristics upon a double click:
The filters active in the current PivotTable (the original** 'Fieldname' and the relevant filters)
The original** headers and row names and values relevant to the aggregate being selected (e.g. FieldX = 2013, FieldY="X"), that will enable me to filter the source sheet and present the underlying rows.
** Note that I'm not sure if this is relevant, but I extensively stumble upon PivotTables in which the row names shown are not the same as those in the source datasheet (by manually renaming them in the PivotTable). Also, is it possible to extract the 'groupings' created in the PivotTables?
Using these characteristics, the VBA for locating the source data and applying the relevant filters should be relatively straightforward. In most cases, the source table is an 'Excel Table', if this is relevant.
Any help is greatly appreciated.
The solution to this depends greatly on the filters you have in place. The way that PivotFilters are defined is different from the way that AutoFilters are defined. This means that you will need to do a translation for each type of filter that is in place.
AutoFilters do all of their magic in the Criteria1 whereas the PivotFilters have a FilterType and Value1 to make it work. This is the translation step.
For simple equality, it is fairly easy and that is the code included below. It address the issue of how to find the column header and set the filter.
Private Sub Worksheet_BeforeDoubleClick(ByVal Target As Range, Cancel As Boolean)
Dim pt As PivotTable
Dim wks As Worksheet
Set wks = Target.Worksheet
For Each pt In wks.PivotTables()
If Not Intersect(Target, pt.TableRange1) Is Nothing Then
Cancel = True
End If
Next
If Cancel <> True Then
Exit Sub
End If
Set pt = Target.PivotCell.PivotTable
Dim rng As Range
Set rng = Application.Range(Application.ConvertFormula(pt.SourceData, xlR1C1, xlA1))
Dim sht_rng As Worksheet
Set sht_rng = rng.Parent
sht_rng.AutoFilterMode = False
Dim pf As PivotField
For Each pf In pt.PivotFields
Dim pfil As PivotFilter
For Each pfil In pf.PivotFilters
If pfil.FilterType = xlCaptionEquals Then
rng.AutoFilter Field:=Application.Match(pf.SourceName, rng.Rows(1), 0), Criteria1:=pfil.Value1
End If
Next pfil
Next pf
sht_rng.Activate
rng.Cells(1, 1).Select
End Sub
Couple of notes:
I am using PivotTable.SourceData to get the range of cells that are involved. This returns a value in R1C1 notation, so I convert it to A1 notation using Application.ConvertFormula. I then need to use Application.Range to look up this string. (Since this code is executing within the scope of a specific Worksheet you need to add Application here so it expands the scope of the search)
After that it is a simple matter of iterating through all the PivotFields and their PivotFilters.
Inside that loop, then you need to find the column header (using Application.Match in the header row: .Rows(1)) and add the filter. This is where the conversion steps are required. You could do a Select... Case for each supported type of filter.
You might also want to check out CurrentPage if any of the fields is a filter instead of a row/column.
Finally, it is possible for there to be manual filters instead of the label filters which I am iterating through. You can loop through PivotItems and check for Visible if you want those.
Hopefully this code gets you started but also hints at the complexity of the task involved. You will likely want to limit yourself to supporting specific types of filters.
Pictures of Pivot and data
pivot table with filters
all data
filtered data
Using the snippets brought forward by Byron's answer, I came up with the following. It doesn't work with grouped columns, nor does it work with tables. For now, at least I can work with regular ranges and PivotTables with tidy source data.
I use the following code to call a second procedure, note that (by far) I'm not an expert in VBA; I just wanted this functionality in a spreadsheet I'm working on:
Private Sub Worksheet_BeforeDoubleClick(ByVal target As Range, Cancel As Boolean)
Application.ScreenUpdating = False
Dim pt As PivotTable
Dim wks As Worksheet
Set wks = target.Worksheet
' Find out if we selected a pivottable, cancel default behaviour
For Each pt In wks.PivotTables()
If Not Intersect(target, pt.TableRange1) Is Nothing Then
Cancel = True
End If
Next
If Cancel <> True Then
Exit Sub
End If
Call pivot_filter_source(target)
Application.ScreenUpdating = True
End Sub
And the procedures that are called:
Public Sub pivot_filter_source(target As Range)
'Dim target As Range
'Set target = Sheets("Pivot").Range("E11")
Dim pt As PivotTable
Dim wks As Worksheet
Set wks = target.Worksheet
' Derive PivotTable
Set pt = target.PivotCell.PivotTable
Dim rng As Range
' Define the source data
Set rng = Application.Range(Application.ConvertFormula(pt.SourceData, xlR1C1, xlA1))
' TODO: Make it work with Excel Tables
Dim pf As PivotField
Dim pi As PivotItem
Dim page_filters As Collection
Set page_filters = New Collection
Dim list As Variant
' Loop over page filters and add their values to array
For Each pf In pt.PageFields
Debug.Print "Filter field: " & pf.SourceName
For Each pi In pf.PivotItems
' Find out if filter contains filtered items
If pi.Visible Then
Debug.Print "... filter: " & pi.Value
If Contains(page_filters, pf.SourceName) Then
list = page_filters.Item(pf.Name)
ReDim Preserve list(UBound(list) + 1)
filter_value = pi.Value
If pi.Value = "(blank)" Then
filter_value = "="
End If
list(UBound(list)) = filter_value
page_filters.Remove (pf.SourceName)
page_filters.Add list, pf.SourceName
Else
list = Array(pf.SourceName, pi.Value)
page_filters.Add list, pf.SourceName
Set list = Nothing
End If
End If
Next pi
Next pf
Set rng = Application.Range(Application.ConvertFormula(pt.SourceData, xlR1C1, xlA1))
rng.Parent.Activate
On Error Resume Next
ActiveSheet.ShowAllData
' Loop over the extracted filters, apply
Dim source_column_name As String
Dim fieldname As String
Dim filter_values As Variant
For Each source_column In page_filters
' Handle '(blank)' values
For i = 0 To UBound(source_column) - 1
If source_column(i) = "(blank)" Then source_column(i) = "="
Next
fieldname = source_column(0)
filter_values = source_column
Call filter_range(rng, fieldname, filter_values)
Next
' Loop over columns of interest
For Each pi In target.PivotCell.ColumnItems
Debug.Print pi.Parent.SourceName & " ==> " & pi.SourceName
filter_values = Array(pi.SourceName)
If pi.SourceName = "(blank)" Then filter_values = Array("=")
Call filter_range(rng, pi.Parent.SourceName, filter_values)
Next
' Loop over rows of interest
For Each pi In target.PivotCell.RowItems
Debug.Print pi.Parent.SourceName & " ==> " & pi.SourceName
filter_values = Array(pi.SourceName)
If pi.SourceName = "(blank)" Then
filter_values = Array("=")
End If
Call filter_range(rng, pi.Parent.SourceName, filter_values)
Next
rng.Parent.Activate
End Sub
Public Sub filter_range(rng As Range, field_name As String, filter_values As Variant)
rng.AutoFilter _
Field:=Application.Match(field_name, rng.Rows(1), 0), _
Criteria1:=filter_values, _
Operator:=xlFilterValues
End Sub
Public Function Contains(col As Collection, key As Variant) As Boolean
Dim obj As Variant
On Error GoTo err
Contains = True
obj = col(key)
Exit Function
err:
Contains = False
End Function
Thanks a lot for this thread.
I don't know why Microsoft has never provided this feature natively.
When you check your data in Pivots you often want to clean the source data directly.
Your idea is to apply the pivot structure and filters individually on the source data, however you can also apply the following simple trick:
To filter the source list I have been using the advanced filter.
After a double click in a pivot a new sheet gets created.
This contains a result list that becomes my filter criteria for the advanced filter on the source data.
Since the advanced filter allows multiple ANDs (by column) and ORs (by row) conditions your source list should always be filtered properly.
I usually Limit the filter range to the first column where my unique record ID from the source table is displayed, this way my filtered list is always a hundred procent correct.
One disadvantage of advanced filters is that it removes the autofilter in the Header of your source list.
That is why your above solution is smarter ;-)

Insert Rows VBA

Right now I have a master excel workbook that employees use for data entry. Each of them downloads a copy to their desktops and then marks their progress on various entries by entering an "x" in a comlun next to the data they've finished. Each product has its own row with its respective data listed across that row. The master workbook is filled out throughout the quarter with new data for the products as it becomes available, which is currently updated on each individuals workbook by use of a macro that simply copies the range where the data is (see code below).
Sub GetDataFromClosedWorkbook()
'Created by XXXX 5/2/2014
Application.ScreenUpdating = False ' turn off the screen updating
Dim wb As Workbook
Set wb = Workbooks.Open("LOCATION OF FILE", True, True)
' open the source workbook, read only
With ThisWorkbook.Worksheets("1")
' read data from the source workbook: (Left of (=) is paste # destination, right of it is copy)
.Range("F8:K25").Value = wb.Worksheets("1").Range("F8:K25").Value
End With
With ThisWorkbook.Worksheets("2")
' read data from the source workbook: (Left of (=) is paste # destination, right of it is copy)
.Range("V5:Z359").Value = wb.Worksheets("2").Range("V5:Z359").Value
End With
wb.Close False ' close the source workbook without saving any changes
Set wb = Nothing ' free memory
Application.ScreenUpdating = True ' turn on the screen updating
End Sub
The problem I'm having is this: every once and a while, I'll need to add a new product, which adds a row on the master (this is opposed to adding data, which is just added across the row). Sometimes this row is at the end, sometimes it's in the middle. As you can see from the code below, my VBA currently can't handle this row change as it is just copy/pasting from a predefined range. Each users's workbook does not pick up on this change in row # and thus the data in the colums becomes associated with the wrong rows. Normally, you could just copy the entire sheet and problem solved. The issue I have is that each user needs to be able to record their own process in their own workbook next to their data. Is there a way to code this so that a new row on the master sheet will be accounted for and added to all the others without erasing/moving the marks made by each user? I've been trying to find a way to make it "insert" rows if they're new in the master, as this would preserve the data, but can't figure it out. Also, due to security on the server at work- linking workbooks, etc is not an option. Does anyone have any thoughts on this?
One way to approach this problem would be using the Scripting.Dictionary Object. You could create a dictionary for both the target and source identifiers and compare those. I suppose you don't really need the Key-Value pair to achieve this, but hopefully this gets you on the right track!
Sub Main()
Dim source As Worksheet
Dim target As Worksheet
Dim dictSource As Object
Dim dictTarget As Object
Dim rng As Range
Dim i As Integer
Dim j As Integer
Dim idSource As String
Dim idTarget As String
Dim offset As Integer
Set source = ThisWorkbook.Sheets(2)
Set target = ThisWorkbook.Sheets(1)
offset = 9 'My data starts at row 10, so the offset will be 9
Set rng = source.Range("A10:A" & source.Cells(source.Rows.Count, "A").End(xlUp).Row)
Set dictSource = CreateObject("Scripting.Dictionary")
For Each cell In rng
dictSource.Add Key:=cell.Value, Item:=cell.Row
Next
Set rng = target.Range("A10:A" & target.Cells(target.Rows.Count, "A").End(xlUp).Row)
Set dictTarget = CreateObject("Scripting.Dictionary")
For Each cell In rng
dictTarget.Add Key:=cell.Value, Item:=cell.Row
Next
i = 1
j = source.Range("A10:A" & source.Cells(source.Rows.Count, "A").End(xlUp).Row).Rows.Count
Do While i <= j
Retry:
idSource = source.Cells(i + offset, 1).Value
idTarget = target.Cells(i + offset, 1).Value
If Not (dictSource.Exists(idTarget)) And idTarget <> "" Then
'Delete unwanted rows
target.Cells(i + offset, 1).EntireRow.Delete
GoTo Retry
End If
If dictTarget.Exists(idSource) Then
'The identifier was found so we can update the values here...
dictTarget.Remove (idSource)
ElseIf idSource <> "" Then
'The identifier wasn't found so we can insert a row
target.Cells(i + offset, 1).EntireRow.Insert
'And you're ready to copy the values over
target.Cells(i + offset, 1).Value = idSource
End If
i = i + 1
Loop
Set dictSource = Nothing
Set dictTarget = Nothing
End Sub