VBA code for comparing first column of multiple spreadsheet with mastersheet - vba

I have VBA code for comparing first column and highlight the cell in first column of sheet2 which is not there in sheet1.
I am new to VBA and not good in coding part and need this to work in a excel sheet which has multiple spreadsheet(This may vary from 5 to 10).
sheet1 is the master sheet and other sheet should be compared with the master sheet and highlight the cell which is not present in master sheet.
Public Sub CompareSheets()
Dim ws1 As Worksheet, ws2 As Worksheet
Dim cell As Range, rng As Range
Set ws1 = Worksheets("Sheet1")
Set ws2 = Worksheets("Sheet2")
Set rng = ws1.Range("A1:A20")
For Each cell In rng
Celladdress = cell.Address
If cell <> ws2.Range(Celladdress) Then
cell.Interior.Color = vbYellow
ws2.Range(Celladdress).Interior.Color = vbYellow
End If
Next cell
End Sub

This code avoids a row-by-row comparison (per your comments) and looks for each value in column A of Sheet2, Sheet3, Sheet4, etc to be found somewhere in column A of Sheet1. It also locates rows that are greater than the total number of rows with values in Sheet1.
This does not force the cells to vbYellow. Instead, it uses conditional formatting to show vbYellow on non-matching cells. These can be filtered for just as forced vbYellow cells can. The benefit here is that once a value has been corrected (a match to Sheet1!A:A is made), the highlight will be auto-magically removed.
Option Explicit
Sub CompareSheets()
Dim lrw1 As Long, lrwn As Long, w As Long
'get the last row of values in master sheet
With Worksheets(1)
lrw1 = .Cells(.Rows.Count, "A").End(xlUp).Row
End With
'the first worksheet (e.g. worksheets(1) ) is the 'master sheet' so we start at 2
For w = 2 To Worksheets.Count
With Worksheets(w)
lrwn = .Cells(.Rows.Count, "A").End(xlUp).Row
With .Range(.Cells(1, "A"), .Cells(Application.Max(lrwn, lrwn), "A"))
.FormatConditions.Delete
With .FormatConditions.Add(Type:=xlExpression, _
Formula1:="=OR(ROW()>" & lrw1 & ", ISNA(MATCH($A1, " & Worksheets(1).Columns("A").Address(external:=True) & ", 0)))")
With .Interior
.PatternColorIndex = xlAutomatic
.Color = vbYellow
End With
.StopIfTrue = True
End With
End With
End With
Next w
End Sub
FWIW, I found your narrative a bit confusing. It either contradicted the supplied code or contradicted itself. The logic behind this proposed solution comes largely from your comments.

Related

Excel VBA: If statement to copy/paste into a new worksheet then delete rows of what was copied

Just started learning VBA today to try to make life a bit easier at my new job. I'm essentially trying to look for every instance where column E has the letter "a" copy and paste it into a newly created worksheet called "Aton" then delete the original rows with the "a"s.
I tried to modify the solution found here: VBA: Copy and paste entire row based on if then statement / loop and push to 3 new sheets
When I changed the above solution to make this line "If wsSrc.Cells(i, "E").Value = "a" Then" that's when I run into problems.
Sub Macro3()
'Need "Dim"
'Recommend "Long" rather than "Integer" for referring to rows and columns
'i As Integer
Dim i As Long
'Declare "Number"
Dim Number As Long
'Declare a variable to refer to the sheet you are going to copy from
Dim wsSrc As Worksheet
Set wsSrc = ActiveSheet
'Declare a variable to refer to the sheet you are going to copy to
Dim wsDest As Worksheet
'Declare three other worksheet variables for the three potential destinations
Dim wsEqualA As Worksheet
'Create the three sheets - do this once rather than in the loop
Set wsEqualA = Worksheets.Add(After:=Worksheets(Worksheets.Count))
'Assign the worksheet names
wsEqualA.Name = "Aton"
'Determine last row in source sheet
Number = wsSrc.Cells(wsSrc.Rows.Count, "C").End(xlUp).Row
For i = 1 To Number
'Determine which destination sheet to use
If wsSrc.Cells(i, "E").Value = "a" Then
Set wsDest = wsEqualA
Else
End If
'Copy the current row from the source sheet to the next available row on the
'destination sheet
With wsDest
wsSrc.Rows(i).Copy .Cells(.Rows.Count, "A").End(xlUp).Offset(1, 0)
End With
'Delete row if column E has an a
If wsSrc.Cells(i, "E").Value = "a" Then
Selection.EntireRow.Delete
Else
End If
Next i
End Sub
Sticking to your code, you have three issues
when deleting rows you have to loop backwards and avoid skipping rows
you’re copying and (trying to) deleting rows outside the ‘If wsSrc.Cells(i, "E").Value = "a"‘ block, hence regardless of current row “i” column E value
you don’t want to delete currently selected range rows, but currently loop “i” row
Putting it all together here’s the correct relevant snippet;
Set wsDest = wsEqualA 'set target sheet once and for all outside the loop
For i = Number To 1 Step -1 'Loop backwards
If wsSrc.Cells(i, "E").Value = "a" Then
'Copy the current row from the source sheet to the next available row on the destination sheet
With wsDest
wsSrc.Rows(i).Copy .Cells(.Rows.Count, "A").End(xlUp).Offset(1, 0) 'Copy wsSrc current “i” row and paste it to wsDest
wsSrc.Rows(i).Delete 'Delete wsSrc current “i” row
End With
End If
Next
As a possible enhancement, you could swap the sheets references in the “With...End With” block, since it’s more effective to reference the mostly “used” one:
With wsSrc
.Rows(i).Copy wsDest.Cells(.Rows.Count, "A").End(xlUp).Offset(1, 0) 'Copy wsSrc current “i” row and paste it to wsDest
.Rows(i).Delete 'Delete wsSrc current “i” row
End With
You need to qualify which sheet the original values are on. Change Sheet on the line Set ws = ThisWorkbook.Sheets("Sheet1") to your sheet name.
Create new sheet and set objects
Create range to loop through, LoopRange (E2 down to last row in column)
Loop through LoopRange. If criteria is met, add the cell, MyCell, to a collection of cells (TargetRange)
If the TargetRange is not empty (meaning your criteria was met at least once) then copy header from ws to ns
Copy TargetRange from ws to ns
Delete TargetRange from ws
The benifit if using Union to collect cells is that you avoid many iterations of copy/paste/delete. If you have 50 cells in your range that meet your criteria, you will have 50 instance each for copy/paste/delete for a grand total of 150 actions.
Using the Union method, you will just have 1 instance for each action for a grand total of 3 actions which will boost run time.
Option Explicit
Sub Learning()
Dim ws As Worksheet: Set ws = ThisWorkbook.Sheets("Sheet1")
Dim ns As Worksheet: Set ns = Worksheets.Add(After:=(ThisWorkbook.Sheets.Count)) 'ns = new sheet
ns.Name = "Aton"
Dim LoopRange As Range, MyCell As Range, TargetRange As Range
Set LoopRange = ws.Range("E2:E" & ws.Range("E" & ws.Rows.Count).End(xlUp).Row)
For Each MyCell In LoopRange 'Loop through column E
If MyCell = "a" Then
If TargetRange Is Nothing Then 'If no range has been set yet
Set TargetRange = MyCell
Else 'If a range has already been set
Set TargetRange = Union(TargetRange, MyCell)
End If
End If
Next MyCell
Application.ScreenUpdating = False
If Not TargetRange Is Nothing Then 'Make sure you don't try to copy a empty range
ws.Range("A1").EntireRow.Copy ns.Range("A1") 'copy header from original sheet
TargetRange.EntireRow.Copy ns.Range("A2")
TargetRange.EntireRow.Delete
Else
MsgBox "No cells were found in Column E with value of 'a'"
End If
Application.ScreenUpdating = True
End Sub
First, don't use ActiveSheet, it can cause multiple problems. If sheet1 is not your source worksheet then change it to meet your needs. I prefer using a filter, as urdearboy suggested, which dosn't require a loop and is faster. I always try to keep the code simple, so try this...
Sheets.Add(After:=Sheets(Sheets.Count)).Name = "Aton"
With Sheet1.UsedRange
.AutoFilter Field:=5, Criteria1:="a", Operator:=xlFilterValues
.Offset(1).SpecialCells(xlCellTypeVisible).Copy Sheets("Aton").Range("A1")
.Offset(1).Resize(.Rows.Count - 1).SpecialCells(xlCellTypeVisible).EntireRow.Delete
.AutoFilter
End With

copy rows from 1 source worksheet to worksheets that match the worksheet name

I have a master worksheet that contains data with many columns.
Next I have also created multiple worksheets from a list.
Now, I would like to copy the rows from the master worksheet to the respective worksheets if the value in the column matches against all the worksheet name, else copy to an 'NA' sheet.
Previously I could only think of hardcoding, but it is not feasible because the number of worksheets may increase to 50+, so I need some help on how I can achieve this..
'find rows of master sheet
With sh
LstR = .Cells(.Rows.Count, "C").End(xlUp).Row 'find last row of column C
Set rng = .Range("C3:C" & LstR) 'set range to loop
End With
'start the loop
'loop through, then loop through each C cell in template. if cell.value == worksheet name, copy to respective worksheet... elseif... else copy to NA
For Each c In rng.Cells
If c = "WEST" Then
c.EntireRow.Copy wsl1.Cells(wsl1.Rows.Count, "A").End(xlUp).Offset(1) 'copy row to first empty row in WEST
ElseIf c = "PKM" Then
c.EntireRow.Copy wsl2.Cells(wsl2.Rows.Count, "A").End(xlUp).Offset(1)
Else
c.EntireRow.Copy wsl7.Cells(wsl7.Rows.Count, "A").End(xlUp).Offset(1)
End If
Next c
Thanks to #user9770531, I was able to do what I want for the macro.
However, now I would like to make the macro more flexible.
For example, I have this additional table in another worksheet with
ColA_id and ColB_group
Instead of just matching checking worksheet name against the values in column C, I would like to do this:
if the master file column C matches "ColA_id", copy the data to respective "ColB_group" worksheets. Assuming ColB_group have been used to create the worksheet names.
Use code bellow - all subs in the same (standard) module
It searches Master.ColumnC for each sheet name (except Master and NA)
Uses AutoFilter for each sheet name, and copies all rows at once
All rows not assigned to a specific sheet will be copied to NA
It assumes sheet NA is already created, with Headers
Option Explicit
Const NA_WS As String = "NA" 'Create sheet "NA" if it doesn't exist
Public Sub DistributeData()
Const MASTER_WS As String = "Master"
Const MASTER_COL As String = "C" 'AutoFilter column in Master sheet
Dim wb As Workbook
Set wb = Application.ThisWorkbook
Dim ws As Worksheet, lr As Long, lc As Long, ur As Range, fCol As Range, done As Range
With wb.Worksheets(MASTER_WS)
lr = .Cells(.Rows.Count, MASTER_COL).End(xlUp).Row
lc = .Cells(1, .Columns.Count).End(xlToLeft).Column
Set ur = .Range(.Cells(3, 1), .Cells(lr, lc))
Set fCol = .Range(.Cells(2, MASTER_COL), .Cells(lr, MASTER_COL))
Set done = .Range(.Cells(1, MASTER_COL), .Cells(2, MASTER_COL))
End With
Application.ScreenUpdating = False
For Each ws In wb.Worksheets
If ws.Name <> MASTER_WS And ws.Name <> NA_WS Then
fCol.AutoFilter Field:=1, Criteria1:=ws.Name
If fCol.SpecialCells(xlCellTypeVisible).Cells.Count > 1 Then
UpdateWs ws, ur
Set done = Union(done, fCol.SpecialCells(xlCellTypeVisible))
End If
End If
Next
If wb.Worksheets(MASTER_WS).AutoFilterMode Then
fCol.AutoFilter
UpdateNA done, ur
End If
Application.ScreenUpdating = True
End Sub
Private Sub UpdateWs(ByRef ws As Worksheet, ByRef fromRng As Range)
fromRng.Copy
With ws.Cells(ws.Rows.Count, "A").End(xlUp).Offset(1, 0)
.PasteSpecial xlPasteAll
End With
ws.Activate
ws.Cells(1).Select
End Sub
Private Sub UpdateNA(ByRef done As Range, ByRef ur As Range)
done.EntireRow.Hidden = True
If ur.SpecialCells(xlCellTypeVisible).Cells.Count > 1 Then
UpdateWs ThisWorkbook.Worksheets(NA_WS), ur.SpecialCells(xlCellTypeVisible)
End If
done.EntireRow.Hidden = False
Application.CutCopyMode = False
ur.Parent.Activate
End Sub

Formula not filling down

I am trying to fill down two before (A, and B) to the last row in column c.
however, My code only insert the formula and doesn't fill down. if I continue to execute the code it will fill one line. then if I click execute again it will fill another line.
Sub row()
Cells(Rows.Count, 1).End(xlUp).Offset(1, 1).Select
ActiveCell.Formula = "=year(today())" 'this will be inserted into the first empty cell in column B
ActiveCell.Offset(0, -1).Value = "Actual" ''this will be inserted into the first empty cell in column A
ActiveCell.FillDown
end sub
Perhaps you mean this? You need to read up on Filldown as you are not specifying a destination range.
Sub row()
With Range(Cells(1, 3), Cells(Rows.Count, 3).End(xlUp))
.Offset(, -1).Formula = "=year(today())"
.Offset(, -2).Value = "Actual"
End With
End Sub
First, take Mat's Mug's suggestion from the comments and make sure you qualify which sheet/workbook you are calling the Cells method on. Then, try the code below. I believe FillDown will only work if there is something in the cells below to replace. Otherwise the function wouldn't know where to stop if it is filling empty cells. Instead, find the last used cell in column C and then blast the value/functions you want in all of the cells in rows A and B at once.
Sub row()
Dim wb As Workbook
Dim ws as Worksheet
Dim rngA As Range
Dim rngB As Range
Dim rngC As Range
Set wb = ThisWorkbook
Set ws = wb.Worksheets("SheetNameHere") ' change this to an actual sheet name
Set rngA = ws.Cells(Rows.Count, 1).End(xlUp).Offset(1, 0)
Set rngB = rngA.Offset(0,1)
Set rngC = ws.Cells(Rows.Count, 3).End(xlUp)
ws.Range(rngA,rngC.Offset(-2,0)).Value = "Actual" ''this will be inserted into ever empty cell in column A up to the last cell in Column C
ws.Range(rngB,rngC.Offset(-1,0)).Formula = "=year(today())" 'this will be inserted into every empty cell in column B up to the last cell in Column C
End Sub

How to do a partial look up in excel and get the data in next column till four rows in VBA

I have sheet 1 with Column name: Main task
MainTask
And, I have Sheet 2 where the Sub-tasks are given based on the characters between 1st and 2nd hyphen(-) of the Data in Main Task for eg: Under the main task column there is "Pyramid - IoT Forecast - Latin America - Argentina - 2017". So, based on the string " IoT Forecast" the sub tasks are given as in the below image.
Out Put:
Now In sheet 3 I need every title from the main task should be copied and pasted from the Sheet 1 and look for relevant sub tasks and pasted in the next column like the below image.
I have used, Wild cards, partial V-look up with Mid Function but only single sub task is populating. Please help me provide code in VBA.
Your Sheet 3 is identical to Sheet 2 but with the full main task in it instead of just the extract. I suggest the following method.
Create a column in Sheet 1 in which you write only the extract. This column would be identical in contents to column A of sheet 2. Use this formula to populate that column (where A2 contains the full main task).
=TRIM(LEFT(MID($A2,FIND("-",$A2)+1,100),FIND("-",MID($A2,FIND("-",$A2)+1,100))-1))
Make a copy of Sheet 2 as Sheet 3 and add a blank column B in it. Populate this column with this formula (where A:A is the column containing the full task, and C:C the column you added in step 1.
=INDEX('Sheet 1'!A:A,MATCH(A2,'Sheet 1'!C:C,0))
Replace the formulas in Sheet 3 with values (Copy / Paste values) and Remove column A from that sheet. Sort this sheet on what is now column A.
Remove the column you added in Sheet 1 to restore Sheet 1 to its original state.
You will need to do an array formula, something similar to the below where the sub category is in C1
=INDEX($A$2:$A$6,SMALL(IF(NOT(ISERROR(SEARCH("-" & $C$1 & "-",$A$2:$A$6))),ROW($A$2:$A$6)-1),ROWS($D$1:D1)))
If you are open to a VBA solution, you may try something like this.
The following code assumes that there are three sheets in the workbook named as "Sheet1", "Sheet2" and "Sheet3".
If the sheet names are different in your original workbook, please change them in the code in following lines before testing the code.
Set ws1 = Sheets("Sheet1")
Set ws2 = Sheets("Sheet2")
Set ws3 = Sheets("Sheet3")
Place the following code on a Standard Module and run the code to get the desired output on Sheet3.
Sub LookupData()
Dim ws1 As Worksheet, ws2 As Worksheet, ws3 As Worksheet
Dim rng As Range, cell As Range, MainTask As Range
Dim lr2 As Long, lr3 As Long
Dim MainTaskStr As String, wht As String
Application.ScreenUpdating = False
Set ws1 = Sheets("Sheet1")
Set ws2 = Sheets("Sheet2")
Set ws3 = Sheets("Sheet3")
lr2 = ws2.Cells(Rows.Count, 1).End(xlUp).Row
Set rng = ws2.Range("A2:A" & lr2)
ws3.Cells.Clear
ws3.Range("A1:B1").Value = Array("Main Task", "Sub-Task")
If ws2.FilterMode Then ws2.ShowAllData
For Each cell In rng
If cell.Value <> MainTaskStr Then
MainTaskStr = cell.Value
lr3 = ws3.Cells.Find("*", SearchOrder:=xlByRows, SearchDirection:=xlPrevious).Row + 1
wht = "- " & cell.Value & " -"
Set MainTask = ws1.Range("A:A").Find(what:=wht, LookIn:=xlValues, lookat:=xlPart, MatchCase:=False)
If Not MainTask Is Nothing Then
With ws2.Rows(1)
.AutoFilter field:=1, Criteria1:=MainTaskStr
ws3.Range("A" & lr3) = MainTask.Value
ws2.Range("B2:B" & lr2).SpecialCells(xlCellTypeVisible).Copy ws3.Range("B" & lr3)
End With
End If
End If
Next cell
If ws2.FilterMode Then ws2.AutoFilterMode = False
ws3.UsedRange.Columns.AutoFit
Application.ScreenUpdating = True
End Sub

Excel VBA copying range within filtered data and appending to end of table on another worksheet

I have a problem, but my VBA is novice and can't figure out what's going wrong with my code.
What I'm trying to achieve is:
Step 1. In Sheet 1 I have lots of data beneath the headings in cells B8:BR8
Step 2. I filter on cell BE8 for non-blanks
Step 3. I copy the filtered data beneath BE8:BN8 (excluding the headings and I don't need all of the data hence I'm just copying a subset of the full data)
Step 4. I go to Sheet 2 where I have a populated table with headings in C8:L8 that correspond exactly to the headings BE8:BN8 from Sheet 1
Step 5. I want to append this new copied set of data to the end of this table in Sheet 2
Step 6. I want to go back to Sheet 1 and delete some of the filtered data, specifically those under headings BE8,BK8:BN8
Here's my attempt which I've tried to adapt from another code:
Sub TransferData()
Dim WS1 As Worksheet, WS2 As Worksheet
Dim RngBeforeFilter As Range, RngAfterFilter As Range
Dim LCol As Long, LRow As Long
With ThisWorkbook
Set WS1 = .Sheets("Sheet1")
Set WS2 = .Sheets("Sheet2")
End With
With WS1
'Make sure no other filters are active.
.AutoFilterMode = False
'Get the correct boundaries.
LRow = .Range("BE" & .Rows.Count).End(xlUp).Row
LCol = .Range("BE8:BN8").Column
'Set the range to filter.
Set RngBeforeFilter = .Range(.Cells(1, 2), .Cells(LRow, LCol)).Offset(1)
RngBeforeFilter.Rows(8).AutoFilter Field:=56, Criteria1:="<>"
'Set the new range, but use visible cells only.
Set RngAfterFilter = .Range(.Cells(1, 7), .Cells(LRow, LCol)).SpecialCells(xlCellTypeVisible)
'Copy the visible cells from the new range.
RngAfterFilter.Copy WS2.Range("C65536").End(xlUp)
'Clear filtered data (not working)
Sheets("Sheet1").Range("B8", Range("B8").End(xlDown)).SpecialCells(xlCellTypeVisible).ClearContents
.ShowAllData
End With
End Sub
I would appreciate any help that you could provide.
Thanks
Jacque
A few problems here:
.Range("BE8:BN8").Column
probably isn't doing what you expect - it will just return the column number of BE (ie 57).
RngBeforeFilter is doing nothing - you can just use
.Rows(8).AutoFilter Field:=56, Criteria1:="<>"
You say you want to copy data in BE:BN, but you start RngAfterFilter from column A (ie .Cells(1, 7)).
WS2.Range("C65536").End(xlUp)
gives the last row used, whereas you'll want to paste into the next row down.
You're clearing column B, rather than columns BE, BK and BN.
As such, try this instead:
Sub TransferData()
Dim WS1 As Worksheet, WS2 As Worksheet
Dim RngBeforeFilter As Range, RngAfterFilter As Range
Dim BECol As Long, BNCol As Long, LRow As Long
With ThisWorkbook
Set WS1 = .Sheets("Sheet1")
Set WS2 = .Sheets("Sheet2")
End With
With WS1
'Make sure no other filters are active.
.AutoFilterMode = False
'Get the correct boundaries.
LRow = .Range("BE" & .Rows.Count).End(xlUp).Row
BECol = .Range("BE8").Column
BNCol = .Range("BN8").Column
'Set the range to filter.
.Rows(8).AutoFilter Field:=BECol - 1, Criteria1:="<>"
'Set the new range, but use visible cells only.
Set RngAfterFilter = .Range(.Cells(9, BECol), .Cells(LRow, BNCol)).SpecialCells(xlCellTypeVisible)
'Copy the visible cells from the new range.
RngAfterFilter.Copy WS2.Range("C65536").End(xlUp).Offset(1)
'Clear filtered data
.Range("BE9", Range("BE8").End(xlDown)).SpecialCells(xlCellTypeVisible).ClearContents
.Range("BK9", Range("BK8").End(xlDown)).SpecialCells(xlCellTypeVisible).ClearContents
.Range("BN9", Range("BN8").End(xlDown)).SpecialCells(xlCellTypeVisible).ClearContents
.ShowAllData
End With
End Sub