Last Column command overwriting existing cell intermittently vba - vba

Screenshot of what is happening here, for simplicity sake, I have changed the values to represent the columns that they rightfully belong in
I am working on a program where I need to copy and reorganize data from multiple worksheets into one master. One row per sheet. From columns G to R I will need to set up an if statement, so that if a value on the sheet is greater than 0 it will be copy/pasted to the next available column in it's row. For testing I have eliminated the if statement, so that I always get a result. The problem I am having is that on the first row of data the "B" column is being overwritten, subsequent rows work as expected. Any ideas as to why this could be happening?
Sub CopyRangeFromMultiWorksheets()
Dim sh As Worksheet
Dim DestSh As Worksheet
Dim LastR As Long
Dim LastC As Long
With Application
.ScreenUpdating = False
.EnableEvents = False
End With
'Delete the sheet "Master" if it exist
Application.DisplayAlerts = False
On Error Resume Next
ActiveWorkbook.Worksheets("Master").Delete
On Error GoTo 0
Application.DisplayAlerts = True
'Add a worksheet with the name "Master"
Set DestSh = ActiveWorkbook.Worksheets.Add
DestSh.Name = "Master"
'loop through all worksheets and copy the data to the DestSh
For Each sh In ActiveWorkbook.Worksheets
If sh.Name <> DestSh.Name Then
'Find the last row with data on the DestSh
With ActiveSheet
LastR = .Cells(.Rows.Count, "a").End(xlUp).Row
End With
With ActiveSheet
LastC = .Cells(LastR, .Columns.Count).End(xlToLeft).Column
End With
sh.Range("B2").Copy
DestSh.Cells(LastR + 1, "A").PasteSpecial xlPasteValues 'customer'
DestSh.Cells(LastR + 1, "B").Value = ("Glass") 'Product"
DestSh.Cells(LastR + 1, "C").Value = sh.Name 'Color Name
sh.Range("H32").Copy
DestSh.Cells(LastR + 1, "D").PasteSpecial xlPasteValues 'based on QTY'
DestSh.Cells(LastR + 1, "E").Value = ("Liters") 'based on Units'
DestSh.Cells(LastR + 1, "F").Value = ("Clear") 'Base'
sh.Range("F13").Copy
DestSh.Cells(LastR + 1, LastC + 1).PasteSpecial xlPasteValues 'THIS IS THE LINE GIVING ME TROUBLE'
End If
Next
ExitTheSub:
Application.Goto DestSh.Cells(1)
'AutoFit the column width in the DestSh sheet
DestSh.Columns.AutoFit
With Application
.ScreenUpdating = True
.EnableEvents = True
End With
End Sub

Try to replace ActiveSheet with DestSh, probably this is the reason for the problem:
'Find the last row with data on the DestSh
With DestSh
LastR = .Cells(.Rows.Count, "a").End(xlUp).Row
End With
With DestSh
LastC = .Cells(LastR, .Columns.Count).End(xlToLeft).Column
End With
In your case, LastC = .Cells(LastR, .Columns.Count).End(xlToLeft).Column does not return the last column in the parent worksheet, but the last column in row LastR. Try this for the real last column:
LastC = LastRow(DestSh)
Function LastRow(sh As Worksheet)
On Error Resume Next
LastRow = sh.Cells.Find(What:="*", _
After:=sh.Range("A1"), _
Lookat:=xlPart, _
LookIn:=xlFormulas, _
SearchOrder:=xlByRows, _
SearchDirection:=xlPrevious, _
MatchCase:=False).Row
On Error GoTo 0
End Function
And this is worth reading - https://www.rondebruin.nl/win/s9/win005.htm

Related

Error running the Macro - Next without For

I'm running the below code and getting Next without For , I'm I missing something. I want to retain the four excel sheets which comes first namely
1. Sheet1
2. InvoicesConsolidated
3. Merge_Excel
4. Consolidated
Rest of the sheets were imported into excel and need to be consolidated into sheet name "Consolidated" and get deleted after consolidation. I try to include the sheets names which I don't want to delete and added end if results in error while executing.
Code1: (this code checks invoices in A range of sheet1 with invoices range in "invoicesconsolidated" by filtering the K column and copying the filtered items into new sheet with sheet named with invoice number
Sub filter()
Application.ScreenUpdating = False
Dim x As Range
Dim rng As Range
Dim Last As Long
Dim sht As String
Dim shtb As String
sht = "InvoicesConsolidated"
shtb = "Sheet1"
'change filter column in the following code
Last = Sheets(sht).Cells(Rows.Count, "K").End(xlUp).Row
Set rng = Sheets(sht).Range("A1:K" & Last)
'Sheets(shtb).Range("A1:A" & last).AdvancedFilter Action:=xlFilterCopy, CopyToRange:=Range("AA1"), Unique:=True
For Each x In shtb.Range([A2], Cells(Rows.Count, "A").End(xlUp))
With rng
.AutoFilter
.AutoFilter Field:=11, Criteria1:=x.Value
.SpecialCells(xlCellTypeVisible).Copy
Sheets.Add(After:=Sheets(Sheets.Count)).Name = x.Value
ActiveSheet.Paste
End With
Next x
'Turn off filter
Sheets(sht).AutoFilterMode = False
With Application
.CutCopyMode = False
.ScreenUpdating = True
End With
Sheets("InvoicesConsolidated").Select
End Sub
Code 2: (this code actually, consolidates the sheets which created after matching invoices into one single sheet and deleting the rest of the sheets.
Private Sub CommandButton2_Click()
Dim sh As Worksheet
Dim DestSh As Worksheet
Dim Last As Long
Dim shLast As Long
Dim CopyRng As Range
Dim StartRow As Long
With Application
.ScreenUpdating = False
.EnableEvents = False
End With
' Delete the summary sheet if it exists.
Application.DisplayAlerts = False
On Error Resume Next
ActiveWorkbook.Worksheets("Consolidated").Delete
On Error GoTo 0
Application.DisplayAlerts = False
' Add a new summary worksheet.
Set DestSh = ActiveWorkbook.Worksheets.Add
DestSh.Name = "Consolidated"
' Fill in the start row.
StartRow = 1
' Loop through all worksheets and copy the data to the
' summary worksheet.
For Each sh In ActiveWorkbook.Worksheets
If sh.Name <> DestSh.Name Then
If sh.Name <> "Merge_Excel" Then
If sh.Name <> "Sheet1" Then
If sh.Name <> "InvoicesConsolidated" Then
' Find the last row with data on the summary
' and source worksheets.
Last = LastRow(DestSh)
shLast = LastRow(sh)
' If source worksheet is not empty and if the last
' row >= StartRow, copy the range.
If shLast > 0 And shLast >= StartRow Then
'Set the range that you want to copy
Set CopyRng = sh.Range(sh.Rows(StartRow), sh.Rows(shLast))
' Test to see whether there are enough rows in the summary
' worksheet to copy all the data.
If Last + CopyRng.Rows.Count > DestSh.Rows.Count Then
MsgBox "There are not enough rows in the " & _
"summary worksheet to place the data."
GoTo ExitTheSub
End If
End If
End If
StartRow = 1
' This statement copies values and formats.
CopyRng.Copy
With DestSh.Cells(Last + 1, "A")
.PasteSpecial xlPasteValues
.PasteSpecial xlPasteFormats
Application.CutCopyMode = False
End With
End If
End If
End If
Application.DisplayAlerts = False
If sh.Name <> "Merge_Excel" Then
If sh.Name <> "Sheet1" Then
If sh.Name <> "InvoicesConsolidated" Then
On Error Resume Next
If sh.Name <> "Consolidated" Then ActiveWorkbook.Worksheets(sh.Name).Delete
On Error GoTo 0
Application.DisplayAlerts = True
End If
Next
ExitTheSub:
' AutoFit the column width in the summary sheet.
DestSh.Columns.AutoFit
'ThisWorkbook.Sheets("Consolidated").Range("A1:K50000").Sort Key1:=ThisWorkbook.Sheets("Consolidated").Range("A2"), Order1:=xlDescending, Header:=xlYes
ReadOutlineCells
With Application
.ScreenUpdating = True
.EnableEvents = True
End With
MsgBox ("Consolidated")
End Sub
Function LastRow(sh As Worksheet)
On Error Resume Next
LastRow = sh.Cells.Find(What:="*", _
After:=sh.Range("A1"), _
Lookat:=xlPart, _
LookIn:=xlFormulas, _
SearchOrder:=xlByRows, _
SearchDirection:=xlPrevious, _
MatchCase:=False).Row
On Error GoTo 0
End Function
Function LastCol(sh As Worksheet)
On Error Resume Next
LastCol = sh.Cells.Find(What:="*", _
After:=sh.Range("A1"), _
Lookat:=xlPart, _
LookIn:=xlFormulas, _
SearchOrder:=xlByColumns, _
SearchDirection:=xlPrevious, _
MatchCase:=False).Column
On Error GoTo 0
End Function
Function ReadOutlineCells()
Dim rng As Range
Set rng = ActiveWorkbook.Worksheets("Consolidated").Range("A1:K10000")
With rng.Borders
.LineStyle = xlContinuous
.Color = vbBlack
End With
End Function'
Your multiple criteria of If <> ... in the section below:
If sh.Name <> DestSh.Name Then
If sh.Name <> "Merge_Excel" Then
If sh.Name <> "Sheet1" Then
If sh.Name <> "InvoicesConsolidated" Then
Could be easily replaced with a Select Case like in the code below:
Select Case sh.Name
Case DestSh.Name, "Merge_Excel", "Sheet1", "InvoicesConsolidated"
' do nothing
Case Else
' this is the scenario you are describing in your code
' rest of your code goes here
End Select
If you correct your indentations, you'll find the problem much quicker.
I have made an attempt at it below and that led to some added rows and some deleted ones. I'm not sure if that is the logic you sought, but the message here is to keep your indentation in good shape at all times - especially while writing the code.
Private Sub CommandButton2_Click()
Dim sh As Worksheet
Dim DestSh As Worksheet
Dim Last As Long
Dim shLast As Long
Dim CopyRng As Range
Dim StartRow As Long
With Application
.ScreenUpdating = False
.EnableEvents = False
End With
' Delete the summary sheet if it exists.
Application.DisplayAlerts = False
On Error Resume Next
ActiveWorkbook.Worksheets("Consolidated").Delete
On Error GoTo 0
Application.DisplayAlerts = False
' Add a new summary worksheet.
Set DestSh = ActiveWorkbook.Worksheets.Add
DestSh.Name = "Consolidated"
' Fill in the start row.
StartRow = 1
' Loop through all worksheets and copy the data to the
' summary worksheet.
For Each sh In ActiveWorkbook.Worksheets
If sh.Name <> DestSh.Name Then
If sh.Name <> "Merge_Excel" Then
If sh.Name <> "Sheet1" Then
If sh.Name <> "InvoicesConsolidated" Then
' Find the last row with data on the summary
' and source worksheets.
Last = LastRow(DestSh)
shLast = LastRow(sh)
' If source worksheet is not empty and if the last
' row >= StartRow, copy the range.
If shLast > 0 And shLast >= StartRow Then
'Set the range that you want to copy
Set CopyRng = sh.Range(sh.Rows(StartRow), sh.Rows(shLast))
' Test to see whether there are enough rows in the summary
' worksheet to copy all the data.
If Last + CopyRng.Rows.Count > DestSh.Rows.Count Then
MsgBox "There are not enough rows in the " & _
"summary worksheet to place the data."
GoTo ExitTheSub
End If
End If
End If
End If
StartRow = 1
' This statement copies values and formats.
CopyRng.Copy
With DestSh.Cells(Last + 1, "A")
.PasteSpecial xlPasteValues
.PasteSpecial xlPasteFormats
Application.CutCopyMode = False
End With
End If
End If
Application.DisplayAlerts = False
If sh.Name <> "Merge_Excel" Then
If sh.Name <> "Sheet1" Then
If sh.Name <> "InvoicesConsolidated" Then
On Error Resume Next
If sh.Name <> "Consolidated" Then ActiveWorkbook.Worksheets(sh.Name).Delete
On Error GoTo 0
Application.DisplayAlerts = True
End If
End If
End If
Next
ExitTheSub:
' AutoFit the column width in the summary sheet.
DestSh.Columns.AutoFit
'ThisWorkbook.Sheets("Consolidated").Range("A1:K50000").Sort Key1:=ThisWorkbook.Sheets("Consolidated").Range("A2"), Order1:=xlDescending, Header:=xlYes
ReadOutlineCells
With Application
.ScreenUpdating = True
.EnableEvents = True
End With
MsgBox ("Consolidated")
End Sub

VBA - Copy Data to Master Sheet and Insert Sheet Name Next to Each Row

I have multiple sheets, each with data only in the first two columns:
Column A - ID
Column B - Name
I am trying to consolidate all these sheets into a master sheet. The format of the master sheet should be:
Column A - Sheet Name (From where the data was copied)
Column B - ID
Column C - Name
I have found a site that has code that does more or less this, however, after messing around with it for what feels like an eternity I just cannot get it to work.
The code works, in the sense that it copies the correct range and inputs the sheet name into column A, however, it doesn't stop by the "last row" of the range in the master sheet, it continues to populate the ENTIRE column A and the IF Statement that counts the rows is triggered and I get the msgbox pop up (see below in code). At this point, the code just ends and it does not get a chance to execute for the remaining sheets.
Link to site: https://www.rondebruin.nl/win/s3/win002.htm
Below is the code from the original site, with some minor adjustments for the range I will be using:
Sub CopySheetNameToColumn()
Dim sh As Worksheet
Dim DestSh As Worksheet
Dim Last As Long
Dim CopyRng As Range
With Application
.ScreenUpdating = False
.EnableEvents = False
End With
'Delete the sheet "RDBMergeSheet" if it exist
Application.DisplayAlerts = False
On Error Resume Next
ActiveWorkbook.Worksheets("RDBMergeSheet").Delete
On Error GoTo 0
Application.DisplayAlerts = True
'Add a worksheet with the name "RDBMergeSheet"
Set DestSh = ActiveWorkbook.Worksheets.Add
DestSh.Name = "RDBMergeSheet"
'loop through all worksheets and copy the data to the DestSh
For Each sh In ActiveWorkbook.Worksheets
If sh.Name <> DestSh.Name Then
'Find the last row with data on the DestSh
Last = LastRow(DestSh)
'Fill in the range that you want to copy
Set CopyRng = sh.Range("A:B")
'Test if there enough rows in the DestSh to copy all the data
If Last + CopyRng.Rows.count > DestSh.Rows.count Then
MsgBox "There are not enough rows in the Destsh"
GoTo ExitTheSub
End If
'This example copies values/formats, if you only want to copy the
'values or want to copy everything look at the example below this macro
CopyRng.Copy
With DestSh.Cells(Last + 1, "B")
.PasteSpecial xlPasteValues
.PasteSpecial xlPasteFormats
Application.CutCopyMode = False
End With
'Optional: This will copy the sheet name in the H column
DestSh.Cells(Last + 1, "A").Resize(CopyRng.Rows.count).Value = sh.Name
End If
Next
ExitTheSub:
Application.Goto DestSh.Cells(1)
'AutoFit the column width in the DestSh sheet
DestSh.Columns.AutoFit
With Application
.ScreenUpdating = True
.EnableEvents = True
End With
End Sub
Functions:
Function LastRow(sh As Worksheet)
On Error Resume Next
LastRow = sh.Cells.Find(What:="*", _
After:=sh.Range("A1"), _
Lookat:=xlPart, _
LookIn:=xlFormulas, _
SearchOrder:=xlByRows, _
SearchDirection:=xlPrevious, _
MatchCase:=False).Row
On Error GoTo 0
End Function
Function LastCol(sh As Worksheet)
On Error Resume Next
LastCol = sh.Cells.Find(What:="*", _
After:=sh.Range("A1"), _
Lookat:=xlPart, _
LookIn:=xlFormulas, _
SearchOrder:=xlByColumns, _
SearchDirection:=xlPrevious, _
MatchCase:=False).Column
On Error GoTo 0
End Function
Instead of
Set CopyRng = sh.Range("A:B")
try
Set CopyRng = sh.Range("A1", sh.Range("B" & Rows.Count).End(xlUp))
as the former covers every row of the worksheet, hence the message box and the name running down the whole sheet.
Something like:
Option Explicit
Sub CopySheetNameToColumn()
Dim sh As Worksheet
Dim DestSh As Worksheet
Dim Last As Long
Dim CopyRng As Range
With Application
.ScreenUpdating = False
.EnableEvents = False
End With
Application.DisplayAlerts = False
On Error Resume Next
ActiveWorkbook.Worksheets("RDBMergeSheet").Delete
On Error GoTo 0
Application.DisplayAlerts = True
Set DestSh = ActiveWorkbook.Worksheets.Add
DestSh.Name = "RDBMergeSheet"
For Each sh In ActiveWorkbook.Worksheets
If sh.Name <> DestSh.Name Then
Last = GetLastRow(DestSh, 1)
With sh
Set CopyRng = sh.Range("A1:B" & GetLastRow(sh, 1))
End With
If Last + CopyRng.Rows.Count > DestSh.Rows.Count Then
MsgBox "There are not enough rows in the Destsh"
GoTo ExitTheSub
Else
CopyRng.Copy IIf(Last = 1, DestSh.Cells(1, "B"), DestSh.Cells(Last + 1, "B"))
End If
If Last = 1 Then
DestSh.Cells(Last, "A").Resize(CopyRng.Rows.Count).Value = sh.Name
Else
DestSh.Cells(Last + 1, "A").Resize(CopyRng.Rows.Count).Value = sh.Name
End If
End If
Next
ExitTheSub:
Application.Goto DestSh.Cells(1)
DestSh.Columns.AutoFit
With Application
.ScreenUpdating = True
.EnableEvents = True
End With
End Sub
Public Function GetLastRow(ByVal ws As Worksheet, Optional ByVal columnNumber As Long = 1) As Long
With ws
GetLastRow = .Cells(.Rows.Count, columnNumber).End(xlUp).Row
End With
End Function
You can shorten this significantly... there are lots of posts about getting items on a master sheet, 4 from yesterday alone.
Take a look at this:
Dim lrSrc As Long, lrDst As Long, i As Long
For i = 1 To Sheets.Count
If Not Sheets(i).Name = "Destination" Then
lrSrc = Sheets(i).Cells(Sheets(i).Rows.Count, "A").End(xlUp).Row
lrDst = Sheets("Destination").Cells(Sheets("Destination").Rows.Count, "A").End(xlUp).Row
With Sheets(i)
.Range(.Cells(2, "A"), .Cells(lrSrc, "B")).Copy Sheets("Destination").Range(Sheets("Destination").Cells(lrDst + 1, "B"), Sheets("Destination").Cells(lrDst + 1 + lrSrc, "C")) 'Assumes headers in first row aren't being copied
Sheets("Destination").Range(Sheets("Destination").Cells(lrDst + 1, "A"), Sheets("Destination").Cells(lrDst + 1 + lrSrc, "A")).Value = Sheets(i).Name
End With
End If
Next i
Code now tested

Copy highlighted cell and sheet name to destination worksheet

I am trying to copy cell values that meet two conditions: (1) highlighted row, and (2) have a particular region code, e.g. "WA". Need to copy cell values from column B to the destination worksheet below the header in column A. In addition, copy the sheet name that corresponds to those values that meet those conditions to column C to the destination worksheet.
Problems I have encountered:
As soon as I add this code it runs but doesn't past any values to the destination sheet. LCase(Cells(Cell.Row, "A").Value) = "wa"
If I remove the line of code above, and change the target area to look in column 2 Set Target = .Range(.Cells(1, 2), .Cells(LastRow, 2)), it will list the values that are highlighted in column B and paste them down starting on A1, instead of starting below the header.
Partial Target Area (full target area has different region codes and values going down these columns):
Sub Criteria()
Application.ScreenUpdating = False
Application.DisplayAlerts = False
Dim wkb As Workbook
Dim Sh As Worksheet
Dim DestSh As Worksheet
Dim LastRow As Long, LastCol As Long, Last As Long
Dim Target As Range, Cell As Range, Dest As Range
Dim DestCounter As Long
' Delete the data off of AdvFilter sheet.
ActiveWorkbook.Worksheets("AdvFilter").Range("A5:F5" & Last + 1).Cells.Clear
On Error GoTo 0
'initialize destination counter
DestCounter = 1
Set DestSh = ThisWorkbook.Worksheets("AdvFilter")
For Each Sh In ThisWorkbook.Worksheets
If ActiveSheet.Visible = True Then
Last = fLastRow(DestSh)
With Sh
LastRow = .Cells.Find("*", SearchOrder:=xlByRows, SearchDirection:=xlPrevious).Row
Set Target = .Range(.Cells(1, 1), .Cells(LastRow, 2))
End With
For Each Cell In Target '<~ loop through each cell in the target space
If AmIYellow(Cell) And Cell.Value <> "" And _
LCase(Cells(Cell.Row, "A").Value) = "wa" Then
Set Dest = DestSh.Cells(Last + DestCounter, 1)
Cell.Copy Dest
DestCounter = DestCounter + 1
End If
Next Cell
End If
Next Sh
Application.DisplayAlerts = True
Application.ScreenUpdating = True
End Sub
Function fLastRow(Sh As Worksheet)
On Error Resume Next
LastRow = Sh.Cells.Find(What:="*", _
After:=Sh.Range("A1"), _
Lookat:=xlPart, _
LookIn:=xlFormulas, _
SearchOrder:=xlByRows, _
SearchDirection:=xlPrevious, _
MatchCase:=False).Row
On Error GoTo 0
End Function
If you only need the data and not the formatting; you'll be better off using an array to collect the data and write all the data in one operation to the target range.
Sub Criteria()
Dim ws As Worksheet
Dim r As Range
Dim x As Long
Dim Data
ReDim Data(1 To 2, 1 To 1)
With ActiveWorkbook.Worksheets("AdvFilter")
.Range(.Range("A" & .Rows.Count).End(xlUp), "F5").Cells.Clear
End With
For Each ws In ThisWorkbook.Worksheets
If ws.Visible = xlSheetVisible Then
With ws
For Each r In .Range("A2", .Range("A" & .Rows.Count).End(xlUp))
If LCase(r.Value) = "wa" And r.Interior.ColorIndex = 6 Then
x = x + 1
ReDim Preserve Data(1 To 2, 1 To x)
Data(1, x) = r.Offset(0, 1)
Data(2, x) = ws.Name
End If
Next
End With
End If
Next
With ActiveWorkbook.Worksheets("AdvFilter")
With .Range("A" & .Rows.Count).End(xlUp).Offset(1)
If x > 0 Then .Resize(x, 2).Value = Application.Transpose(Data)
End With
End With
End Sub

VBA: Trying to consolidate all worksheets into one new worksheet in single workbook

I am trying to copy all worksheets, one at a time, and pasting into a new worksheet. These files come from multiple third parties so the worksheets can vary. I'm running into a problem below when trying to determine last row Lrow and last column Lcol because an error appears saying Object doesn't support this property or method. I do plan on submitting this to my work so any help with error proofing or general macro tips are appreciated.
Sub ws_copy()
Dim Lrow As Long
Dim Lcol As Long
Dim Pasterow As Long
Dim WSCount As Integer
Dim i As Integer
'On Error Resume Next
'Application.DisplayAlerts = False
i = Application.InputBox(prompt:="Enter the place order of first tab to be copied.", Title:="Worksheet Consolidation", Type:=1)
If IsEmpty(i) = True Then
Exit Sub
Else
If IsNumeric(i) = False Then
MsgBox "Enter a numeric value."
Else
If IsNumeric(i) = True Then
Worksheets.Add(before:=Sheets(1)).Name = "Upload"
WSCount = Worksheets.Count
For i = i + 1 To WSCount
Lrow = Worksheets(i).Find("*", After:=Cells(1, 1), _
LookIn:=xlFormulas, _
Lookat:=xlPart, _
SearchOrder:=xlByRows, _
SearchDirection:=xlPrevious, _
MatchCase:=False).Row
Lcol = Worksheets(i).Find("*", After:=Cells(1, 1), _
LookIn:=xlFormulas, _
Lookat:=xlPart, _
SearchOrder:=xlByColumns, _
SearchDirection:=xlPrevious, _
MatchCase:=False).Row
Pasterow = Lrow + 1
Workbook.Worksheets(i).Range(Cells(1, 1), Cells(Lrow, Lcol)).Copy
Workbook.Worksheets("Upload").Cells(Pasterow, 1).Paste
Next i
Else
Exit Sub
End If
End If
End If
'On Error GoTo 0
'Application.DisplayAlerts = False
End Sub
A common way to find the last row/column is:
With Worksheets(i)
Lrow = .Cells(.Rows.Count, 1).End(xlUp).Row
Lcol = .Cells(1, .Columns.Count).End(xlToLeft).Column
End With
hth
Based on the comment that:
I can't assume any one column or row has the last piece of data because of the variety of the files received.
You should look at using the UsedRange property of the Worksheet (MSDN). UsedRange expands as more data is entered onto the worksheet.
Some people will avoid using UsedRange because if some data has been entered, and then deleted then UsedRange will include these 'empty' cells. The UsedRange will update itself when the workbook is saved. However, in your case, it doesn't sound like this is a relevant issue.
An example would be:
Sub Test()
Dim wsSource As Worksheet
Dim wsTarget As Worksheet
Dim rngSource As Range
Dim rngTarget As Range
Set wsSource = ThisWorkbook.Worksheets("Sheet1")
Set wsTarget = ThisWorkbook.Worksheets("Sheet2")
Set rngSource = wsSource.UsedRange
rngSource.Copy Destination:=wsTarget.Cells
End Sub
Here is a method of finding the last used row and last used column in a worksheet. It avoids the issues with UsedRange and also your issues of not knowing which row might have the last column (and which column might have the last row). Adapt to your purposes:
Option Explicit
Sub LastRowCol()
Dim LastRow As Long, LastCol As Long
With Worksheets("sheet1") 'or any sheet
If Application.WorksheetFunction.CountA(.Cells) > 0 Then
LastRow = .Cells.Find(what:="*", after:=[A1], _
LookIn:=xlFormulas, _
searchorder:=xlByRows, _
searchdirection:=xlPrevious).Row
LastCol = .Cells.Find(what:="*", after:=[A1], _
LookIn:=xlFormulas, _
searchorder:=xlByColumns, _
searchdirection:=xlPrevious).Column
Else
LastRow = 1
LastCol = 1
End If
End With
Debug.Print LastRow, LastCol
End Sub
Although the basic technique has been long used, Siddarth Rout, some time ago, posted a version adding COUNTA to account for the case where the worksheet might be empty -- a useful addition.
If you want to merge data on each sheet into one MasterSheet, run the script below.
Sub CopyRangeFromMultiWorksheets()
Dim sh As Worksheet
Dim DestSh As Worksheet
Dim Last As Long
Dim CopyRng As Range
With Application
.ScreenUpdating = False
.EnableEvents = False
End With
'Delete the sheet "RDBMergeSheet" if it exist
Application.DisplayAlerts = False
On Error Resume Next
ActiveWorkbook.Worksheets("RDBMergeSheet").Delete
On Error GoTo 0
Application.DisplayAlerts = True
'Add a worksheet with the name "RDBMergeSheet"
Set DestSh = ActiveWorkbook.Worksheets.Add
DestSh.Name = "RDBMergeSheet"
'loop through all worksheets and copy the data to the DestSh
For Each sh In ActiveWorkbook.Worksheets
If sh.Name <> DestSh.Name Then
'Find the last row with data on the DestSh
Last = LastRow(DestSh)
'Fill in the range that you want to copy
Set CopyRng = sh.Range("A1:G1")
'Test if there enough rows in the DestSh to copy all the data
If Last + CopyRng.Rows.Count > DestSh.Rows.Count Then
MsgBox "There are not enough rows in the Destsh"
GoTo ExitTheSub
End If
'This example copies values/formats, if you only want to copy the
'values or want to copy everything look at the example below this macro
CopyRng.Copy
With DestSh.Cells(Last + 1, "A")
.PasteSpecial xlPasteValues
.PasteSpecial xlPasteFormats
Application.CutCopyMode = False
End With
'Optional: This will copy the sheet name in the H column
DestSh.Cells(Last + 1, "H").Resize(CopyRng.Rows.Count).Value = sh.Name
End If
Next
ExitTheSub:
Application.Goto DestSh.Cells(1)
'AutoFit the column width in the DestSh sheet
DestSh.Columns.AutoFit
With Application
.ScreenUpdating = True
.EnableEvents = True
End With
End Sub
Function LastRow(sh As Worksheet)
On Error Resume Next
LastRow = sh.Cells.Find(What:="*", _
After:=sh.Range("A1"), _
Lookat:=xlPart, _
LookIn:=xlFormulas, _
SearchOrder:=xlByRows, _
SearchDirection:=xlPrevious, _
MatchCase:=False).Row
On Error GoTo 0
End Function
Function LastCol(sh As Worksheet)
On Error Resume Next
LastCol = sh.Cells.Find(What:="*", _
After:=sh.Range("A1"), _
Lookat:=xlPart, _
LookIn:=xlFormulas, _
SearchOrder:=xlByColumns, _
SearchDirection:=xlPrevious, _
MatchCase:=False).Column
On Error GoTo 0
End Function
Also, see the link below for some other options to do this slightly differently.
http://www.rondebruin.nl/win/s3/win002.htm

Data overlaps when merging multiple sheets

I have an Excel workbook which contains n sheets. I want to merge the data from each sheet to one single sheet. The header and data from the first sheet should be on top, the data from second sheet should be below it and so on. All the sheets have the same columns and headers structure. So, the header should appear only once i.e take header and data from first sheet and only data from remaining sheets. I have the following code:
Sub Combine()
'This macro will copy all rows from the first sheet
'(including headers)
'and on the next sheets will copy only the data
'(starting on row 2)
Dim i As Integer
Dim j As Long
Dim SheetCnt As Integer
Dim lstRow1 As Long
Dim lstRow2 As Long
Dim lstCol As Integer
Dim ws1 As Worksheet
With Application
.DisplayAlerts = False
.EnableEvents = False
.ScreenUpdating = False
End With
On Error Resume Next
'Delete the Target Sheet on the document (in case it exists)
Sheets("Target").Delete
'Count the number of sheets on the Workbook
SheetCnt = Worksheets.Count
'Add the Target Sheet
Sheets.Add after:=Worksheets(SheetCnt)
ActiveSheet.Name = "Target"
Set ws1 = Sheets("Target")
lstRow2 = 1
'Define the row where to start copying
'(first sheet will be row 1 to include headers)
j = 1
'Combine the sheets
For i = 1 To SheetCnt
Worksheets(i).Select
'check what is the last column with data
lstCol = ActiveSheet.Cells(1, ActiveSheet.Columns.Count).End(xlToLeft).Column
'check what is the last row with data
lstRow1 = ActiveSheet.Cells(ActiveSheet.Rows.Count, "A").End(xlUp).Row
'Define the range to copy
Range("A2:G2" & j, Cells(lstRow1, lstCol)).Select
'Copy the data
Selection.Copy
ws1.Range("A2:G2" & lstRow2).PasteSpecial
Application.CutCopyMode = False
Selection.Offset(1, 0).Resize(Selection.Rows.Count - 1).Select
'Define the new last row on the Target sheet
lstRow2 = ws1.Cells(65535, "A").End(xlUp).Row + 1
'Define the row where to start copying
'(2nd sheet onwards will be row 2 to only get data)
j = 3
Next
With Application
.DisplayAlerts = True
.EnableEvents = True
.ScreenUpdating = True
End With
Sheets("Target").Select
Cells.EntireColumn.AutoFit
Range("A1").Select
End Sub
With this code, my data from all sheets is getting overlapped. I want the data to be one below the other.
It's overlapping because you don't increment the paste area on the Target sheet
To fix the problem offset the paste area correspondingly:
Sheet 1: copy 10 rows-paste -> increment paste start & end area by 10
Sheet 2: copy 15 rows-paste -> increment paste start & end area by 25: 10 + 15 and so on...
You can also replace this:
Sheets.Add after:=Worksheets(SheetCnt) 'Add the Target Sheet
ActiveSheet.Name = "Target"
Set ws1 = Sheets("Target")
with this:
Set ws1 = Sheets.Add(after:=Worksheets(SheetCnt)) 'Add the Target Sheet
ws1.Name = "Target"
If you eliminate all "Select" statements and refer to each object explicitly it will allow you to reduce code, and un-needed complexity
Here is my version:
Option Explicit
Public Sub Combine()
Const HEADR As Byte = 1
Dim i As Long, rngCurrent As Range
Dim ws As Worksheet, wsTarget As Worksheet
Dim lCol As Long, lCel As Range
Dim lRow As Long, toLRow As Long
With Application
.DisplayAlerts = False
.EnableEvents = False
.ScreenUpdating = False
End With
For Each ws In Worksheets 'Delete Target Sheet if it exists
With ws
If .Name = "Target" Then
.Delete
Exit For
End If
End With
Next
Set wsTarget = Worksheets.Add(After:=Worksheets(Worksheets.Count))
wsTarget.Name = "Target"
Set lCel = GetMaxCell(Worksheets(1).UsedRange)
If lCel.Row > 1 Then
With Worksheets(1)
'Expected: all sheets will have the same number of columns
lCol = lCel.Column
lRow = HEADR
toLRow = HEADR
.Range(.Cells(HEADR, 1), .Cells(HEADR, lCol)).Copy
With wsTarget
.Range(.Cells(HEADR, 1), .Cells(HEADR, lCol)).PasteSpecial xlPasteAll
End With
End With
For i = 1 To Worksheets.Count 'concatenate data ---------------------------
Set lCel = GetMaxCell(Worksheets(i).UsedRange)
If lCel.Row > 1 Then
With Worksheets(i)
If .Name <> "Target" Then 'exclude the Target
toLRow = toLRow + lRow 'last row on Target
lRow = lCel.Row 'last row on current
Set rngCurrent = .Range(.Cells(HEADR + 1, 1), _
.Cells(lRow, lCol))
lRow = lRow - HEADR
With wsTarget
.Range(.Cells(toLRow, 1), _
.Cells(toLRow + (lRow - HEADR), lCol)) = _
rngCurrent.Value
End With
End If
End With
End If
Next '--------------------------------------------------------------------
With wsTarget
.Columns.AutoFit
.Range("A1").Select
End With
With Application
.CutCopyMode = False
.DisplayAlerts = True
.EnableEvents = True
.ScreenUpdating = True
End With
End If
End Sub
Public Function GetMaxCell(Optional ByRef rng As Range = Nothing) As Range
'Returns the last cell containing a value, or A1 if Worksheet is empty
Const NONEMPTY As String = "*"
Dim lRow As Range, lCol As Range
If rng Is Nothing Then Set rng = Application.ActiveWorkbook.ActiveSheet.UsedRange
If WorksheetFunction.CountA(rng) = 0 Then
Set GetMaxCell = rng.Parent.Cells(1, 1)
Else
With rng
Set lRow = .Cells.Find(What:=NONEMPTY, LookIn:=xlFormulas, _
After:=.Cells(1, 1), _
SearchDirection:=xlPrevious, _
SearchOrder:=xlByRows)
If Not lRow Is Nothing Then
Set lCol = .Cells.Find(What:=NONEMPTY, LookIn:=xlFormulas, _
After:=.Cells(1, 1), _
SearchDirection:=xlPrevious, _
SearchOrder:=xlByColumns)
Set GetMaxCell = .Parent.Cells(lRow.Row, lCol.Column)
End If
End With
End If
End Function
'--------------------------------------------------------------------------------------
Offsetting the paste area is done by incrementing lRow and toLRow
Edit:
If you use this code and you want to transfer cell formatting for all data cells replace this section:
'copy data to Target sheet
With wsTarget
.Range(.Cells(toLRow, 1), .Cells(toLRow + (lRow - HEADR), lCol)) = _
rngCurrent.Value
End With
with this:
'copy data to Target sheet
rngCurrent.Copy
With wsTarget
With .Range(.Cells(toLRow, 1), .Cells(toLRow + (lRow - HEADR), lCol))
.PasteSpecial xlPasteAll
End With
End With
but it will become slower if you're processing a lot of sheets
EDIT: to show how to handle special cases
The above solution is more generic and dynamically detects the last column and row containing data
The number of columns (and rows) to be processed can be manually updated. For example, if your sheets contain 43 columns with data, and you want to exclude the last 2 columns, make the following change to the script:
Line
Set lCel = GetMaxCell(Worksheets(1).UsedRange)
changes to
Set lCel = Worksheets(1).UsedRange("D41")