Nested If statement to cut & paste rows to different worksheet - vba

Could someone help with this code?
I'm comparing two workbooks. I've built a For loop to check to see if the unique ids in workbook1 match the ids in workbook2.
If they match I'm assigning the returned row # to variable lrow. I then need to check the value in column C for the returned row.
Depending on the value in lrow, column C I need to cut the row in workbook1, sheet1 and paste to different sheets in workbook1. I also
need to delete the row that was cut so I dont have blank rows when done.
I'm getting a syntax error on the nested Else If statements. They are all highlighted in red. I'm also getting a Compile error on
these lines that says "Must be first statement on the line".
Could you let me know what I'm missing with the nested if and also verify if my cut and paste operation is valid.
Thanks for your assistance.
Option Explicit
Sub Complete()
Dim Lastrow, Newrow As Long
Dim i, lrow As Long
Dim wb1, wb2 As Workbook
Dim ws1, ws2 As Worksheet
' Turn off notifications
Application.ScreenUpdating = False
Workbooks.OpenText Filename:="C:\workbook2.xlsx"
Set wb1 = ThisWorkbook
Set wb2 = Workbooks("workbook2.xlsx")
Set ws1 = wb1.Worksheets("Sheet1")
Set ws2 = wb2.Worksheets("Sheet1")
With wb1.Worksheets(ws1)
Lastrow = .Cells(.Rows.Count, "A").End(xlUp).Row
For i = 2 To Lastrow
If Not IsError(Application.Match(.Cells(i, "G").Value, ws2.Columns("A"), 0)) Then
lrow = Application.Match(.Cells(i, "G").Value, ws2.Columns("A"), 0)
If ws2.Cells(lrow,"C") = 18 Then
Newrow = wb1.Worksheets("Sheet3").Range("A1").End(xlDown).Row + 1
ws1.Cells(i,"G").EntireRow.Cut wb1.Worksheets("Sheet3").Cells(newrow,"A")
ws1.Cells(i,"G").EntireRow.Delete
ElseIf ws2.Cells(lrow,"C") = 23 Then
Newrow = wb1.Worksheets("Sheet4").Range("A1").End(xlDown).Row + 1
ws1.Cells(i,"G").EntireRow.Cut wb1.Worksheets("Sheet4").Cells(newrow,"A")
ws1.Cells(i,"G").EntireRow.Delete
ElseIf ws2.Cells(lrow,"C") = 24 Then
Newrow = wb1.Worksheets("Sheet4").Range("A1").End(xlDown).Row + 1
ws1.Cells(i,"G").EntireRow.Cut wb1.Worksheets("Sheet4").Cells(newrow,"A")
ws1.Cells(i,"G").EntireRow.Delete
ElseIf ws2.Cells(lrow,"C") = 36 Then
Newrow = wb1.Worksheets("Sheet5").Range("A1").End(xlDown).Row + 1
ws1.Cells(i,"G").EntireRow.Cut wb1.Worksheets("Sheet5").Cells(newrow,"A")
ws1.Cells(i,"G").EntireRow.Delete
End If
End If
Next i
End With
Workbooks("workbook2.xlsx").Close savechanges:=False
' Turn on notifications
Application.ScreenUpdating = True
' Message Box showing that process is complete.
MsgBox "Done!"
End Sub

From the last comment I made to #paulbica I corrected the line to read:
If Not IsError(Application.Match(.Cells(i, "G").Value, ws2.Columns("A"), 0)) Then
The code now runs correctly. I've update the post to reflect the changes made.
Thanks.

It's good that you solved the Type mismatch error, but there are a couple of issues left
The line With wb1.Worksheets(ws1) will throw another Type mismatch error because the Worksheets function takes the sheet name or index as an argument and ws1 is a Worksheet object, so it should be changed to With wb1.Worksheets(ws1.Name) or simply With ws1
The loop implemented like that will skip rows if they are contiguous. For example, if you start with a total of 5 rows, all of which need to be moved, in the first iteration i is 2 and row 2 will be deleted. Next iteration row 3 had become row 2 after deletion, but i is now 3, so the initial row 3 is skipped and processing moves to current row 3 which previously was 4
Depending on how much data you have your code is quite slow because it interacts with the ranges very often. For example it's extracting the value of ws2.Cells(lrow,"C") for every If branch, extracting the last row in sheets 3, 4, and 5 for every cut operation, and deleting rows one at the time
This is how I'd write the code:
Option Explicit
Public Sub Complete()
Dim i As Long, toDel As Range, copyCell As Range
Dim ws11 As Worksheet, ws13 As Worksheet, ws14 As Worksheet, ws15 As Worksheet
Dim ws13LR As Long, ws14LR As Long, ws15LR As Long
Dim wb2 As Workbook, ws21 As Worksheet, wb2row As Variant, wb2colA As Variant
Application.ScreenUpdating = False
Workbooks.OpenText Filename:="C:\workbook2.xlsx"
Set wb2 = Workbooks("workbook2.xlsx")
Set ws11 = Sheet1
Set ws13 = Sheet3: ws13LR = ws13.Cells(ws13.Rows.Count, 1).End(xlUp).Row + 1
Set ws14 = Sheet4: ws14LR = ws14.Cells(ws14.Rows.Count, 1).End(xlUp).Row + 1
Set ws15 = Sheet5: ws15LR = ws15.Cells(ws15.Rows.Count, 1).End(xlUp).Row + 1
Set ws21 = wb2.Sheets(1): wb2colA = ws21.UsedRange.Columns("A").Value2
For i = 2 To ws11.Cells(ws11.Rows.Count, 1).End(xlUp).Row + 1
wb2row = Application.Match(ws11.UsedRange.Cells(i, "G").Value, wb2colA, 0)
If Not IsError(wb2row) Then
Set copyCell = Nothing
Select Case ws21.Cells(wb2row, "C").Value2
Case 18: Set copyCell = ws13.Cells(ws13LR, "A"): ws13LR = ws13LR + 1
Case 23, 24: Set copyCell = ws14.Cells(ws14LR, "A"): ws14LR = ws14LR + 1
Case 36: Set copyCell = ws15.Cells(ws15LR, "A"): ws15LR = ws15LR + 1
End Select
If Not copyCell Is Nothing Then
With ws11.UsedRange
.Rows(i).EntireRow.Copy copyCell
If toDel Is Nothing Then
Set toDel = .Rows(i)
Else
Set toDel = Union(toDel, .Rows(i))
End If
End With
End If
End If
Next i
wb2.Close False
toDel.EntireRow.Delete
Application.ScreenUpdating = True
MsgBox "Done!"
End Sub
I moved all unnecessary operations out of the For loop, and created a new range of rows to be deleted at the end, in one operation

Related

Comparing two sheets and deleting the entire row

I have two sheets , Sheet1 and sheet2 .
Sheet 1 is my Source sheet and I am mentioning the item number in column A.
Sheet 2 is my destination sheet the contains the list of item number from the data base.
I am comparing the column A of source sheet with column E of my destination sheet, if they both have same item number then I am deleting the entire row.
I am using the below code for this. on 6 item number 4 are getting deleted and 2 are not getting deleted.
But, when I copy the same item number from the destination sheet to source sheet ,then it is getting deleted. I am not sure why this is happening. Could any one guide how I could figure this out.
below is the code
Sub spldel()
Dim srcLastRow As Long, destLastRow As Long
Dim srcWS As Worksheet, destWS As Worksheet
Dim i As Long, j As Long
Application.ScreenUpdating = False
Set srcWS = ThisWorkbook.Sheets("sheet1")
Set destWS = ThisWorkbook.Sheets("sheet2")
srcLastRow = srcWS.Cells(srcWS.Rows.count, "A").End(xlUp).Row
destLastRow = destWS.Cells(destWS.Rows.count, "E").End(xlUp).Row
For i = 5 To destLastRow - 1
For j = 1 To srcLastRow
' compare column E of both the sheets
If destWS.Cells(i, "E").Value = srcWS.Cells(j, "A").Value Then
destWS.Cells(i, "E").EntireRow.delete
End If
Next j
Next i
End Sub
Remember to loop in reverse order when you are trying to delete the rows otherwise rows may skipped from deletion even when they qualify the deletion criteria.
So the two For loops should be like this....
For i = destLastRow - 1 To 5 Step -1
For j = srcLastRow To 1 Step -1
Here is another approach:
Rather than looping through each item everytime in your source and destination sheets, just use MATCH function:
Function testThis()
Dim destWS As Worksheet: Set destWS = ThisWorkbook.Worksheets("Sheet8") ' Change to your source sheet
Dim srcWS As Worksheet: Set srcWS = ThisWorkbook.Worksheets("Sheet12") ' Change to your destination sheet
Dim iLR As Long: iLR = srcWS.Range("L" & srcWS.Rows.count).End(xlUp).Row ' Make sure you change the column to get the last row from
Dim iC As Long
Dim lRetVal As Long
On Error Resume Next
For iC = 1 To iLR
lRetVal = Application.WorksheetFunction.Match(srcWS.Range("L" & iC), destWS.Range("A:A"), 0)
If Err.Number = 0 Then
destWS.Range("A" & lRetVal).EntireRow.Delete
End If
Err.Clear
Next
On Error GoTo 0
End Function

Issue in copying rows based on certain conditions in Vba

Set ws4 = Workbooks("A.xlsx").Worksheets(1)
Lastrowto = ws4.Cells(Rows.Count, "B").End(xlUp).Row
For y = Lastrowto To 1 Step -1
If ws4.Cells(y, "B").Value = "Not found" Then
ws4.Rows(y).EntireRow.Copy
End If
Next y
The above piece of vba code copies only 1 (the first one) row but I want to copy all those rows for which the given condition is met, kindly suggest me the correct version of code.
Instead of using Copy>>Paste one row at a time, which will take a long time to process, you can use a Range object named CopyRng.
Every time the criteria is met (If .Range("B" & y).Value = "Not found"), it will add the current row to CopyRng.
After finishing looping through all your rows, you can just copy the entire rows at once using CopyRng.Copy.
Code
Option Explicit
Sub CopyMultipleRows()
Dim ws4 As Worksheet
Dim Lastrowto As Long, y As Long
Dim CopyRng As Range
Set ws4 = Workbooks("A.xlsx").Worksheets(1)
With ws4
Lastrowto = .Cells(.Rows.Count, "B").End(xlUp).Row
For y = Lastrowto To 1 Step -1
If .Range("B" & y).Value = "Not found" Then
If Not CopyRng Is Nothing Then
Set CopyRng = Application.Union(CopyRng, .Rows(y))
Else
Set CopyRng = .Rows(y)
End If
End If
Next y
End With
' copy the entire rows of the Merged Range at once
If Not CopyRng is Nothing Then CopyRng.Copy
End Sub
Where do you want to copy it to? if you specify a destination to your copy, then your code could work.
e.g. if you have a destination sheet defined as wsDest, you can replace
ws4.Rows(y).EntireRow.Copy
by
ws4.Rows(y).EntireRow.Copy wsDest.cells(rows.count,1).end(xlup).offset(1)
assuming you always have a value in column 1.
Another option is to do an autofilter on column B, with the value not found, and use the specialcells property to copy to another spot. Recording a macro will help you quite a bit, but code will be something like:
with ws4.cells(1,1).currentregion
.autofilter field:=2,criteria1:="Not found"
.specialcells(xlcelltypevisible).copy
end with
You are copying but there is no paste line.
An example, with a paste line destination of ws1.Cells(counter,"B"), assuming another worksheet variable ws1 might be:
ws4.Rows(y).EntireRow.Copy ws1.Cells(counter,"B")
See the following where a msgbox shows you each time you are entering loop and have met the condition so are copying:
Public Sub test1()
Dim ws4 As Worksheet
Dim lastrowto As Long
Dim y As Long
Dim counter As Long
Set ws4 = ThisWorkbook.Worksheets("Ben")
lastrowto = ws4.Cells(ws4.Rows.Count, "B").End(xlUp).Row 'fully qualify
counter = 0
For y = lastrowto To 1 Step -1
If ws4.Cells(y, "B").Value = "Not found" Then
ws4.Rows(y).EntireRow.Copy 'put paste destination code here e.g. ws1.Cells(counter,"B") where ws1 would be another sheet variable
counter = counter + 1
Msgbox counter 'if has entered loop print current count
End If
Next y
End Sub

Select cells that fit in the range of the counter and concatenate what is selected from the range

I've been working on a Macro that i need to copy, concatenate what has been selected through the counter. e.g. is below
excel snapshot example
so what i want to do is set a count in column c from 1 to "infinite" because each worksheet varies to go up to 10 or hundreds and when the counter hits a value of 1 again to stop concatenate column D what is in the range from 1 to "the last digit it stopped before hitting 1 again" and paste it on a different sheet. I know little to nothing on VBA but I understand the copy and paste to different sheet part. I'm just stuck on the counter and the concatenate part. Here is the code i have so far(i edited it to resemble the example for better reference)
'select counter/concatenate
Sheets(1).Select
Columns("C").EntireColumn
Do
Columns("C").Count
For i = 1 To 9999
Loop While (i <= 1)
If i = 1 Then
select.columns("D")
after the count is where i am stuck. this count is what I've come up with looking at different variations of counters.
I suggest you Forget about column and use just one cell for easier understanding. A cell is a reference that allows you to refer to any other cells on the sheet by using Offsets. You may use two Loops, the outer one crawling the columns, the inner one working downward until it finds 1
Dim i As Long ' note that in VBA integer Overflows at 65535 rows
Dim s As String
Set aCell = Worksheet("Sheet1").Range("D1")
While aCell.Column < 255
i = 0
s = ""
While Not aCell.Offset(i, 0).Value = 1
s = s & aCell.Offset(1, 0).Value
Wend
' paste s somewhere by using range.value = s
Set aCell = aCell.Offset(0, 1)
Wend
By specifying the workbook and worksheet before the range, you may refer to the proper cell without being dependent on the active worksheet or range.
Hope this works for you.
You can try this (not tested):
Dim s As String, firstAddr as String
Dim f as range, iniCell As Range
With Worksheet("MySheet") '<--| change "MySheet" to your actual sheet name
With .Range("C1", .Cells(.Rows.Count, 3).End(xlUp))
Set f = .Find(What:=1, LookAt:=xlWhole, LookIn:=xlValues, After:=.Cells(.Rows.Count, 1))
If Not f Is Nothing Then
firstAddr = f.Address
Set iniCell = f
Set f = FindNext(f)
Do While f.Address <> firstAddr
s = s & Join(Range(iniCell, f.Offset(-1)).Offset(, 1), "")
' here code to paste s somewhere
Set iniCell = f
Set f = FindNext(f)
Loop
End If
End With
End With
Here's one I actually tested, using some random data in columns C and D.
You'll have to modify a little to get exactly where you want the data to go, but should get you in the right direction.
Private Sub Worksheet_SelectionChange(ByVal Target As Range)
Dim i As Long
Dim s As String
Dim lastRow As Long
Dim c As Integer
Dim ws1 As Worksheet
Dim ws2 As Worksheet
Set ws1 = ThisWorkbook.Sheets("Sheet1")
Set ws2 = ThisWorkbook.Sheets("Sheet2")
lastRow = ws1.Cells.Find("*", searchorder:=xlByRows, searchdirection:=xlPrevious).Row
'This will get an accurate last row
c = 1
For i = 1 To lastRow
s = s & ws1.Cells(i, 4).Value 'Build the string
If ws1.Cells(i + 1, 3).Value = 1 Or ws1.Cells(i + 1, 3).Value = "" Then
ws2.Cells(c, 1).Value = s
s = ""
c = c + 1
'If the next cell to check is 1 or blank, then copy the values to the next cell in order on sheet2
End If
Next
End Sub
Walking through it, lastRow is set using the last row in the sheet with a value in it. Then, c is set to one, although you could set this to the last available row in ws2 using the same process. After that it just steps through the rows from 1 To LastRow building strings and transferring the value to ws2 when it's about to hit a 1.

Compare two workbook and copy last column if the rest of data matches

Here is a bit of background on what I'm trying to achieve.
I have 2 excel files (Old and New), which contains around 10-15 sheets and each of the sheets contain many rows of data and total number of columns in each sheet is different.
I have reviewed Old file and placed my comments for all the rows in the last column of data in each sheet.
Now whenever I receive a New file, I need to first compare the Sheet name, if matches compare the Row of that sheet to old one if found copy the comment from last column of Old sheet to new one.
In short it's kind of reconciliation sheet.I have tried the following code but not getting how to loop for comparison of Workbook and then rows.
Sub recon()
Dim wb As Workbook
Dim sht As Worksheet
Dim sht2 As Worksheet
Dim rnge As Range
Set wb = Workbooks("OldWB")
For Each sht In wb.Sheets
On Error Resume Next
Set sht2 = ActiveWorkbook.Sheets(sht.Name)
On Error GoTo 0
If Not sht2 Is Nothing Then
For Each rnge In sht.UsedRange
If sht2.Range(rnge.Address).Value = "" And rnge.Value <> "" Then
Copy sht2.Range(rnge.Address).Offset(0,1).Value = rnge.Value
End If
Next rnge
Set sht2 = Nothing
End If
Next sht
Set wb = Nothing
End Sub
Consider following points:
1. This code will give you desired result if sheets in both the workbook are in same order.
2. I am counting number of columns for each row assuming that the number of columns may vary row to row in each sheet. If this is not the case you can assign values to lastColumnCurr and lastColumnOld at the beginning of For Each loop.
Sub recon()
Dim wbOld As Workbook
Dim wsOld As Worksheet, wsCurr As Worksheet
Dim lastRowCurr As Long, lastRowOld As Long, lastColumnCurr As Long, lastColumnOld As Long
Dim i As Long, j As Long
Dim flag As Boolean
Set wbOld = Workbooks("old_test")
For Each wsOld In wbOld.Sheets
lastRowOld = wsOld.Cells(Rows.Count, "A").End(xlUp).Row
On Error Resume Next
Set wsCurr = ActiveWorkbook.Sheets(wsOld.Name)
On Error GoTo 0
If Not wsCurr Is Nothing Then
'get number of rows in current sheet
lastRowCurr = wsCurr.Cells(Rows.Count, "A").End(xlUp).Row
'loop through all the rows
For i = 1 To lastRowCurr
'get number of columns in old and current sheets
lastColumnOld = wsOld.Cells(i, Columns.Count).End(xlToLeft).Column
lastColumnCurr = wsCurr.Cells(i, Columns.Count).End(xlToLeft).Column
'maintain a boolean to check whether all the values in a row are same or not
flag = True
'now loop through all the columns in a row
'here if the row in current sheet is same as the old sheet then there will be one column...
'...less in current sheetin compared to old sheet because of your comment column at the end...
'...hence lastColumnOld - 1
If lastColumnCurr = lastColumnOld - 1 Then
For j = 1 To lastColumnCurr
'now all the cells in a row in both sheets
If wsOld.Cells(i, j).Value <> wsCurr.Cells(i, j).Value Then
'if cell is not same, change boolean to false
flag = False
Exit For
End If
Next j
'if boolean is false then there is difference in rows so do not add comment at the end
If flag = True Then
wsCurr.Cells(i, j).Value = wsOld.Cells(i, j).Value
End If
End If
Next i
Set wsCurr = Nothing
End If
Next wsOld
Set wb = Nothing
End Sub
EDIT# 1
_________________________________________________________________________________
Following code will match each row of active sheet to all rows of the sheet in old workbook.
Sub CompareRows_Mrig()
Dim wbOld As Workbook
Dim wsOld As Worksheet, wsCurr As Worksheet
Dim lastRowCurr As Long, lastRowOld As Long, lastColumnCurr As Long, lastColumnOld As Long
Dim flag As Boolean
Set wbOld = Workbooks("old_test")
For Each wsOld In wbOld.Sheets
lastRowOld = wsOld.Cells(Rows.Count, "A").End(xlUp).Row
On Error Resume Next
Set wsCurr = ActiveWorkbook.Sheets(wsOld.Name)
On Error GoTo 0
If Not wsCurr Is Nothing Then
lastRowCurr = wsCurr.Cells(Rows.Count, "A").End(xlUp).Row
Dim c As Long
Dim rIdx As Long, cIdx As Long
For rIdx = 1 To lastRowCurr
lastColumnCurr = wsCurr.Cells(rIdx, Columns.Count).End(xlToLeft).Column
c = 0
For rIdx2 = 1 To lastRowOld
lastColumnOld = wsOld.Cells(rIdx2, Columns.Count).End(xlToLeft).Column - 1
If lastColumnCurr = lastColumnOld Then
flag = True
For cIdx = 1 To lastColumnCurr
If wsCurr.Cells(rIdx, cIdx).Value <> wsOld.Cells(rIdx2, cIdx).Value Then
flag = False
Exit For
End If
Next
c = c + 1
Debug.Print c
If flag = True Then
wsCurr.Cells(rIdx, cIdx).Value = wsOld.Cells(rIdx2, cIdx).Value
End If
End If
Next
Next
End If
Next wsOld
Set wb = Nothing
End Sub
EDIT# 2
_________________________________________________________________________________
To Speed up the code add following lines in sub:
Sub CompareRows_Mrig()
Application.Calculation = xlCalculationManual
Application.ScreenUpdating = False
'*****************************
'Put Code Here
'*****************************
Application.ScreenUpdating = True
Application.Calculation = xlCalculationAutomatic
End Sub

Going through workbook and making a column in the first sheet out of the content of a particular cell across all sheets

This gives me an error:
(Constant expression required)
Code:
Dim Sheet3 As Worksheet
Dim t As Long
Dim Max As Long
Max = ActiveWorkbook.Worksheets.Count
Dim Dates(2 To Max) As String
For t = 2 To ActiveWorkbook.Worksheets.Count
Set Sheet3 = ActiveWorkbook.Worksheets(t)
If Sheet3.Name = "USA" & t Then
Dates(t) = Sheet3.Range("E4").Value
End If
Next t
Dim SummarySheet as Sheet
For r = 2 To ActiveWorkbook.Worksheets.Count
Set Sheet = ActiveWorkbook.Worksheets(r)
If Sheet.Name = "Page"& r Then
SummarySheet.Cells(Row you want to paste,Column you want to paste) = Sheet.Range("Cell you want to copy").Value
End if
Next r
I was in doubt if your t and r were different, but if so then you should make a For loop for both, or make r a calculated value from t
Edit: If you want to paste in H9 and go from there, you column will be 8 and your row 9. If you then put 9+r it will always take the next row below. Assuming r starts at 1 and has all number in a row. You can play a bit with these values until you get it right.
SomeVariable = Sheet.Range("B22").Value
If you need a way to iterate the sheets you can do this.
iFoundWorksheet = 0
For iIndex = 1 To ea.ActiveWorkbook.Worksheets.Count
Set ws = ea.Worksheets(iIndex)
If UCase(ws.Name) = "RESULTS" Then
iFoundWorksheet = iIndex
Exit For
End If
Next iIndex
If iFoundWorksheet = 0 Then
MsgBox "No worksheet was found with the name RESULTS (this is not case sensetive). Aborting."
GoTo Abort
End If
Set ws = ea.Worksheets(iFoundWorksheet)
ws.Activate
For you new issue Constant expression required
Your array definition has to be defined with constants.
Dim Max As Long
Const Max1 = 5
Dim Dates(2 To Max1) As String
This would work. But you are not going to be able to load
ActiveWorkbook.Worksheets.Count
Into a const
You are probably going to need to ReDim your Dates Array.