Compare values in column B of sheet 1 to column B of sheet 2; append unmatched values in sheet 2 - excel

I'm trying to help a colleague with an excel report. He is not very good with computers and is making errors in copying all the relevant data from one sheet to another. He's working with a dataset that looks like this:
] [1]: https://i.stack.imgur.com/dHUpt.png (not allowed to upload images directly yet because i created a new account)
These are pending shipping values and everyday a report is generated with all the orders and the pending ones need to be copied into another sheet and then their status is updated in that excel sheet.
What I need is a solution that when I paste my report into sheet one, I can run a VBA code and compare all the values in column B of sheet one to all the values in column B of sheet two. Then, whatever is not present in column B of sheet two can be highlighted in sheet one or pasted into sheet three/ appended into sheet two. In this way, they operator does not have to do the lookup by himself.
If there is any other solution than VBA that could help, feel free to suggest. Thanks!

You can try this:
Sub CompareData()
Application.ScreenUpdating = False
On Error GoTo 0
Dim wb As Workbook
Set wb = ThisWorkbook
Dim ws1 As Worksheet, ws2 As Worksheet, ws3 As Worksheet
Set ws1 = wb.Worksheets("Sheet1") 'Change Sheet Name
Set ws2 = wb.Worksheets("Sheet2") 'Change Sheet Name
ws1.Copy after:=ws2
Set ws3 = wb.ActiveSheet
Dim LastRow1 As Long
Dim Rng As Range
With ws3
LastRow1 = .Cells(Rows.Count, 1).End(xlUp).Row
.Range("E1").Value = "Vlookup"
.Range("E2").Value = "=VLOOKUP(B2," & ws2.Name & "!B:B,1,0)"
.Range("E2").Copy .Range("E3:E" & LastRow1)
.Range("A1:E1").AutoFilter FIELD:=5, Criteria1:="#N/A"
Set Rng = .AutoFilter.Range.Offset(1, 0)
Set Rng = Rng.Resize(Rng.Rows.Count, 4)
End With
Dim LastRow2 As Long
LastRow2 = ws2.Cells(Rows.Count, 1).End(xlUp).Row
Rng.Copy ws2.Range("A" & LastRow2 + 1 & ":D" & LastRow2 + 1)
Application.DisplayAlerts = False
ws3.Delete
Application.DisplayAlerts = True
ws2.Activate
Exit Sub
0:
MsgBox "Something went wrong"
Application.ScreenUpdating = True
End Sub
Don't forget to change your sheet name.

For such tasks (comparing data in different worksheets), I usually use Excel built-in IF funcion. Example: =IF([Workbook_1]Sheet_1!B1=[Workbook_2]Sheet_2!B1,".",FALSE). Then, just fill down the formula (dragging down).
Note: . is used for easiness of distinguishing FALSE values.

Related

Optimize VBA Script to Combine and Consolidate

I am working on optimizing this script since I am working with two large (~1M rows) worksheets, each and think my code is inefficient and takes way too long to run and wondering if I can redo it to make it faster.
These are the steps:
Combine Excel Sheet 1 and Sheet 2 using Column A as common identifier
Add a column to identify if Columns E = H (True or False)
Remove all True's (this should get rid of most rows, leaving a few hundred)
Also, what does this line exactly mean? in particular the Columns (1), A, :M and G - want to confirm its picking the right matches
iRow = Application.Match(ID, ws2.UsedRange.Columns(1), 0)
If Not IsError(iRow) Then ws2.Range("A" & iRow & ":M" & iRow).Copy ws3.Range("G" & r.Row)
Sheet 1:
Sheet 2:
Final Expected Result:
Sub TestGridUpdate()
Dim ws1 As Worksheet, ws2 As Worksheet, ws3 As Worksheet
Dim TestGridFound As Boolean, r As Range
Set ws1 = ThisWorkbook.Worksheets("Sheet1")
Set ws2 = ThisWorkbook.Worksheets("Sheet2")
TestGridFound = False 'Look for TestGrid worksheet
For Each ws In Worksheets
If ws.Name = "Combined" Then TestGridFound = True
Next
If TestGridFound Then 'If Combined is found then use it else create it
Set ws3 = ThisWorkbook.Worksheets("Combined")
ws3.Cells.Clear
Else
Set ws3 = ThisWorkbook.Worksheets.Add(After:=ThisWorkbook.Worksheets(ThisWorkbook.Worksheets.Count))
ws3.Name = "Combined"
End If
ws3.Range(ws1.UsedRange.Address).Value = ws1.UsedRange.Value 'Copy ws1 to ws3 (TestGrid)
For Each r In ws3.UsedRange.Rows ' Add ws2 details to ws3 (TestGrid)
ID = r.Cells(, 1).Value
iRow = Application.Match(ID, ws2.UsedRange.Columns(1), 0)
If Not IsError(iRow) Then ws2.Range("A" & iRow & ":M" & iRow).Copy ws3.Range("G" & r.Row)
Next
End Sub
Sub FillFormula() 'Add a column to identify column matches
'Set reference to the sheet in the workbook.
Set ws = ThisWorkbook.Worksheets("Combined")
ws.Activate 'not required but allows user to view sheet if warning message appears
Range("N2").Formula = "=$E2=H2"
Range("N2", "N" & Cells(Rows.Count, 1).End(xlUp).Row).FillDown
End Sub
Sub Delete_Rows_Based_On_Value() 'Delete all matches that are true'
'Apply a filter to a Range and delete visible rows
Dim ws As Worksheet
Set ws = ThisWorkbook.Worksheets("Combined") 'Set reference to sheet in workbook.
ws.Activate 'not required but allows user to view sheet if warning message appears
On Error Resume Next 'Clear any existing filters
ws.ShowAllData
On Error GoTo 0
ws.Range("A:P").AutoFilter Field:=14, Criteria1:="TRUE" '1. Apply Filter
Application.DisplayAlerts = False '2. Delete Rows
Sheets("Combined").AutoFilter.Range.Offset(1).Delete xlShiftUp
Application.DisplayAlerts = True
On Error Resume Next '3. Clear Filter
ws.ShowAllData
On Error GoTo 0
End Sub
answer to your question,
the part:
For Each r In ws3.UsedRange.Rows ' Add ws2 details to ws3 (TestGrid)
ID = r.Cells(, 1).Value
iRow = Application.Match(ID, ws2.UsedRange.Columns(1), 0)
If Not IsError(iRow) Then ws2.Range("A" & iRow & ":M" & iRow).Copy ws3.Range("G" & r.Row)
Next
in a short way: it compares ws2 and w3 Column 1 - Column "A" values, if match is found, cell value from ws2 is copied to ws3.
Application.Match(ID, ws2.UsedRange.Columns(1), 0) will cause error if cell values do not match, accordingly, that's why the following line looks as below:
If Not IsError(iRow) Then ws2.Range("A" & iRow & ":M" & iRow).Copy ws3.Range("G" & r.Row) (perform with copying if no error).
To be honest, I suspect this code runs above a minute or even two if you deal with around 1mil rows.
Read and learn how to use arrays and how to assign ranges to arrays and how loop through them. Code will run MUCH faster as it will not be iterating through each actual cell on the excel, - all will be done in RAM memory (like in the virtual data table). No read/write (copy/paste) will be performed during the Array loop and at the end result will be written out in one step.
Quick tip, while creating large arrays, use .value2 it will also improve performance. my_Arr1 = range("my_range").Value2
Once you will understand simple arrays, get your brains to wrap around 2d arrays, as all ranges loaded to an array will end up 2d.
Examples to start from:
http://www.cpearson.com/excel/vbaarrays.htm
https://stackoverflow.com/a/23701283/8805842
How to avoid using Select in Excel VBA
https://stackoverflow.com/a/46954174/8805842
https://stackoverflow.com/a/30067221/8805842
For later read:
https://stackoverflow.com/a/51524230/8805842
https://stackoverflow.com/a/51608764/8805842

How to match columns and count the matches using vba

I am working on one scenario where I have two sheets. Sheet1 is the master sheet and sheet2 which I am creating.
Column1 of Sheet1 is Object which has duplicate objects as well. So, what I have done is I have created a macro which will produce the unique Objects and will paste it in sheet2.
Now, from Sheet2, each of the objects should be matched with Sheet1 column1 and based on the matching results, it should also count the corresponding entries from other columns in sheet1 to sheet2.
Below are the snapshots of my two sheets
Sheet1
Sheet2
here is my macro code which will first copy and paste the unique objects from sheet1 to sheet2 Column1.
Sub UniqueObj()
Dim Sh1 As Worksheet
Dim Rng As Range
Dim Sh2 As Worksheet
Set Sh1 = Worksheets("Sheet1")
Set Rng = Sh1.Range("A1:A" & Sh1.Range("A65536").End(xlUp).Row)
Set Sh2 = Worksheets("Sheet1")
Rng.Cells(1, 1).Copy Sh2.Cells(1, 1)
Rng.AdvancedFilter Action:=xlFilterCopy, CopyToRange:=Sh2.Range("A1"), Unique:=True
End Sub
But, I am unable to move forward from there. I am pretty new and any help would be very greatful.
Thanks
If I'm understanding what you want correctly, you're just counting matching columns from Sheet1 where the value in the corresponding column isn't blank? If so this should do the trick.
Option Explicit
Sub GetStuffFromSheet1()
Dim ws1 As Worksheet, ws2 As Worksheet
Dim lastRow1 As Long, lastRow2 As Long
Dim x As Long
'turn on error handling
On Error GoTo error_handler
Set ws1 = ThisWorkbook.Sheets("Sheet1")
Set ws2 = ThisWorkbook.Sheets("Sheet2")
'determine last row with data in sheet 1
lastRow1 = ws1.Cells(ws1.Rows.Count, 1).End(xlUp).Row
'determine last row with data in sheet 2
lastRow2 = ws2.Cells(ws2.Rows.Count, 1).End(xlUp).Row
'define columns in sheet 1
Const objCol1 As Long = 1
Const rProdCol1 As Long = 3
Const keysCol1 As Long = 4
Const addKeysCol1 As Long = 5
'define columns in sheet 2
Const objCol2 As Long = 1
Const rProdCol2 As Long = 2
Const keysCol2 As Long = 3
Const addKeysCol2 As Long = 4
'turn off screen updating + calculation for speed
Application.ScreenUpdating = False
Application.Calculation = xlCalculationManual
'loop through all rows of sheet 2
For x = 2 To lastRow2
'formula counts # of cells with matching obj where value isn't blank
ws2.Cells(x, rProdCol2) = WorksheetFunction.CountIfs(ws1.Columns(objCol1), ws2.Cells(x, objCol2), ws1.Columns(rProdCol1), "<>" & "")
ws2.Cells(x, keysCol2) = WorksheetFunction.CountIfs(ws1.Columns(objCol1), ws2.Cells(x, objCol2), ws1.Columns(keysCol1), "<>" & "")
ws2.Cells(x, addKeysCol2) = WorksheetFunction.CountIfs(ws1.Columns(objCol1), ws2.Cells(x, objCol2), ws1.Columns(addKeysCol1), "<>" & "")
Next x
'turn screen updating + calculation back on
Application.ScreenUpdating = True
Application.Calculation = xlCalculationAutomatic
Exit Sub
error_handler:
'display error message
MsgBox "Error # " & Err.Number & " - " & Err.Description, vbCritical, "Error"
'turn screen updating + calculation back on
Application.ScreenUpdating = True
Application.Calculation = xlCalculationAutomatic
Exit Sub
End Sub
In case a non VBA solution works for you, you can resume your data with a Pivot Table, take field Object into rows section and rest of fields into values section (choose Count)
This returns the exact output you are looking for. Easy to update and easy to create.
In case you want a VBA solution, because your design is tabular and you are counting values, you can use CONSOLIDATE:
Consolidate data in multiple worksheets
'change K1 with cell where to paste data.
Range("K1").Consolidate Range("A1").CurrentRegion.Address(True, True, xlR1C1, True), xlCount, True, True, False
'we delete column relation type and column value. This columns depends on where you paste data, in this case, K1
Range("L:L,P:P").Delete Shift:=xlToLeft
After executing code i get this:
Hope this helps

Copy new rows into excel sheet based on search on one cell value

I have one excel sheet that I manage to which I need to add rows if new rows have been added to another excel sheet that I am not managing.
To put it simply I may be sitting with an excel sheet that looks like this:
And I need to search another excel sheet (see image 2) for newly added rows based on document code column which would look like this:
And ideally I would like that my excel sheet would end up looking like this where new lines were added if they contained a document code not previously in my excel sheet:
I have seen similar questions answered before but only for more simple searches like based on fixed values in the cell e.g. like yes/no and I did not quite manage to revise those answers to my situation. .
You could try:
Sub test()
Dim wsAll As Worksheet, wsNew As Worksheet
Dim LastRowNew As Long, LastrowAll As Long, i As Long
Dim rngFound As Range
With ThisWorkbook
'The sheet with all data
Set wsAll = .Worksheets("All_Imports")
'The sheet with new data
Set wsNew = .Worksheets("New_Imports")
End With
With wsNew
LastRowNew = .Cells(.Rows.Count, "A").End(xlUp).Row
For i = 2 To LastRowNew
Set rngFound = wsAll.Columns(1).Find(.Range("A" & i).Value, LookIn:=xlValues, LookAt:=xlWhole)
If rngFound Is Nothing Then
With wsAll
LastrowAll = .Cells(.Rows.Count, "A").End(xlUp).Row + 1
.Range("A" & LastrowAll).Value = wsNew.Range("A" & i).Value
.Range("B" & LastrowAll).Value = wsNew.Range("B" & i).Value
End With
End If
Next i
End With
End Sub

VBA Copy and Paste function where two criteria are met

I am brand new to VBA coding and am confused on how I would be able to copy and paste values from one sheet to another if two criteria points are met. In the sheet below I want to copy "12, 9, and 15" and paste it into the "Expected, P10 and P90" cells on sheet2 if the names on sheet one "Orange, Green" match those on sheet 1.
I've been attempting this on my own for quite some time now with now luck.
Attached is the code I started
Sub Copy_Certain_Data()
a = Worksheets("Schedule Results").Cells(Rows.Count, 1).End(xlUp).Row
For i = 3 To a
If Worksheets("Schedule Results").Cells(i, 3).Value = "NE2P1" Then
Worksheets("schedule results").Rows(i).Copy
Worksheets("Campaign 1 Data").Activate
Range("F2").Select
ActiveSheet.Paste
Worksheets("Schedule Results").Activate
End If
Next
Application.CutCopyMode = False
End Sub
Below is a basic macro to loop through two worksheets and find the row that has matching values in columns A and B. Then writing the values from the row in sheet 1, columns C:E to the row in sheet 2, columns D:F.
Dim ws1 As Worksheet, ws2 As Worksheet
Dim xCel As Range, yCel As Range
Set ws1 = ThisWorkbook.Sheets("Sheet1") 'change sheet names as needed
Set ws2 = ThisWorkbook.Sheets("Sheet2")
For Each xCel In ws1.Range("A2", ws1.Range("A" & ws1.Rows.Count).End(xlUp)) 'loop sheet1 column A
If xCel.Value = "Orange" And xCel.Offset(, 1).Value = "Green" Then 'when both values are found in row goto sheet2 loop
For Each yCel In ws2.Range("A2", ws2.Range("A" & ws2.Rows.Count).End(xlUp)) 'Loop sheet2 Column A
If yCel.Value = "Orange" And yCel.Offset(, 1).Value = "Green" Then 'when found write values from sheet1 to sheet2
yCel.Offset(, 3).Resize(, 3).Value = xCel.Offset(, 2).Resize(, 3).Value
End If
Next yCel
End If
Next xCel
This should give you a start to get you what you are trying to accomplished based on the code you have tried. Its always best practice to set your variables and also qualify worksheets.
Using .copy and .paste can cause issues because if the cells are not the same size you will get an error stating such and that is why I always set the destination cell value = the source cell value.
Option Explict
Sub Copy_Certain_Data()
Dim wb As Workbook
Dim wsSource As Worksheet
Dim wsDest As Worksheet
Set wb = ThisWorkbook
Set wsSource = wb.Sheets("Schedule Results")
Set wsDest = wb.Sheets("Campaign 1 Data")
Dim LastRow As Long, i As Long
LastRow = wsSource.Cells(wsSource.Rows.Count, "A").End(xlUp).Row
For i = 3 To LastRow
If wsSource.Cells(i, 3).Value = "NE2P1" Then
wsDest.Cells(i, 6) = wsSource.Cells(i, 3)
End If
Next i
End Sub

Print name of the sheet along with copied cell

I have this code where it loops through all the sheets in the workbook and copies the value in F9 of each sheet and pastes it in "Summary" sheet column A. How can I also print the sheet name in column B? So the value is next to the sheet name in the "Summary" sheet.
code:
Sub loopsheet()
Dim wks As Worksheet
For Each wks In ThisWorkbook.Worksheets
If Not wks.Name = "Summary" Then
wks.Range("F9:F" & wks.Cells(Rows.Count, "F").End(xlUp).Row).Copy _
Destination:=Worksheets("Summary").Cells(Rows.Count, "A").End(xlUp).Offset(1)
End If
Next
End Sub
Thank you
Create two variables to track the last rows of your sheets as you loop. This will help with readability in your code. The combination of these two variables can also help you deduce the size of the range where you need to drop your sheet name.
I believe cLR + pLR - 11 is the size of range. The offset is due to headers, LR offset, and the fact that you are starting your copy from the 9th row. After you run this, you may need to tweak it up or down one if i'm wrong.
Option Explicit
Sub LoopSheet()
Dim ws As Worksheet
Dim Summary As Worksheet: Set Summary = ThisWorkbook.Sheets("Summary")
Dim cLR As Long, pLR As Long
For Each ws In ThisWorkbook.Worksheets
If ws.Name <> Summary.Name Then
cLR = ws.Range("F" & ws.Rows.Count).End(xlUp).Row
pLR = Summary.Range("A" & Summary.Rows.Count).End(xlUp).Offset(1).Row
ws.Range("F9:F" & cLR).Copy Summary.Range("A" & pLR)
Summary.Range(Summary.Cells(pLR, 2), Summary.Cells(cLR + pLR - 11, 2)).Value = ws.Name
End If
Next ws
End Sub

Resources