I have data that doesnt seem to be merging all the rows! I need it to merge even with empty columns.
For example:
On Sheet CPW, Column W is blank. So when merged all the entries for CPW should show blank in Column W and the information from Sheet CCI would only show.
That's just one example. There are many more on these two sheets.
This is my code for the merge. How can it be edited to do what I require?
Sub Combine()
Dim J As Integer
Dim wrk As Workbook 'Workbook object - Always good to work with object variables
Dim r1, r2, r3, r4, ra, rb, rc, rd, re, rf, rg As Range
Sheets("Sheet2").Select
Set r1 = Range("A:C")
Set r2 = Range("E:X")
Set r3 = Range("Y:AW")
Set r4 = Range("AX:BK")
Sheets("Sheet3").Select
Set ra = Range("A:A")
Set rb = Range("C:C")
Set rc = Range("B:B")
Set rd = Range("D:G")
Set re = Range("I:AL")
Set rf = Range("AM:AP")
Set rg = Range("AQ:BK")
Set wrk = Workbooks.Add
ActiveWorkbook.Sheets(2).Activate
Sheets(2).Name = "CPW"
r1.Copy Range("A1")
r2.Copy Range("D1")
r3.Copy Range("Y1")
r4.Copy Range("AY1")
Range("A1:BK100").Font.ColorIndex = 3
ActiveWorkbook.Sheets(3).Activate
Sheets(3).Name = "CCI"
ra.Copy Range("A1")
rb.Copy Range("B1")
rc.Copy Range("C1")
rd.Copy Range("D1")
re.Copy Range("H1")
rf.Copy Range("AM1")
rg.Copy Range("AQ1")
On Error Resume Next
Sheets(1).Select
Sheets(1).Name = "Combined"
Sheets(2).Activate
Range("A2").EntireRow.Select
Selection.Copy Destination:=Sheets(1).Range("A1")
For J = 2 To Sheets.Count
Sheets(J).Activate
Range("A2").Select
Selection.CurrentRegion.Select
Selection.Offset(1, 0).Resize(Selection.Rows.Count - 1).Select
Selection.Copy Destination:=Sheets(1).Range("A65536").End(xlUp)(2)
Sheets(1).Select
Range("A1:BK1000").Sort _
Key1:=Range("E1"), Key2:=Range("J1"), Header:=xlYes
Next
End Sub
Use of Select and Activate is not recommended (unless essential to the code’s objectives) because they are slow commands and are confusing.
You have blank columns because your copies do not line up. With the creation of the source ranges so far from their use, this is not obvious.
My macro achieves the same result as yours. I have bought all the code for copying together so it is much more obvious where you have left gaps. I have included questions where I suspect your code does not do what you want. I have included comments explaining aspects of your code I do not like.
Work through my code and study how I have achieved the same effects as yours. Come back with questions as necessary but the more you can understand on your own, the faster you will develop your skills.
Option Explicit
Sub Combine()
' Here it does not really matter since J is only used in a small block of
' code but avoid names like J. When you return to update this macro in
' 12 months will you remember what J is? I have a system of names that I
' have used for years. I can look at a macro I wrote 5 years ago and
' immediately know what all the variables. This speeds the work of
' remembering what the macro did. If you do not like my naming system,
' design your own but have a system.
' "Integer" defines a 16-bit integer which requires special processing on
' a post-16-bit computer. Use Long which defines a 32-bit integer
'Dim J As Integer
Dim InxWsht As Long
Dim WbkThis As Workbook
Dim Rng As Range
Dim Row1Next As Long
Dim WbkNew As Workbook
Dim WshtNew2 As Worksheet
Dim WshtNew3 As Worksheet
Dim WshtThis2 As Worksheet
Dim WshtThis3 As Worksheet
' ThisWorkbook is the workbook containing the macro. It is not
' necessarily the active workbook
Set WbkThis = ThisWorkbook
Set WshtThis2 = WbkThis.Worksheets("Sheet2")
Set WshtThis3 = WbkThis.Worksheets("Sheet3")
Set WbkNew = Workbooks.Add
Set WshtNew2 = WbkNew.Worksheets(2)
Set WshtNew3 = WbkNew.Worksheets(3)
WshtNew2.Name = "CPW"
WshtThis2.Range("A:C").Copy Destination:=WshtNew2.Range("A1")
' Note columns E:X are written to columns D:W. X is left blank
WshtThis2.Range("E:X").Copy Destination:=WshtNew2.Range("D1")
WshtThis2.Range("Y:AW").Copy Destination:=WshtNew2.Range("Y1")
' Note the previous destination end in column AW while the next
' starts with AY. Column AX is left blank.
WshtThis2.Range("AX:BK").Copy Destination:=WshtNew2.Range("AY1")
' Why are only the first hundred rows coloured red?
' Why don't you colour column BL?
WshtNew2.Range("A1:BK100").Font.ColorIndex = 3
WshtNew3.Name = "CCI"
WshtThis3.Range("A:A").Copy Destination:=WshtNew3.Range("A1")
' Did you mean to reverse columns B and C?
WshtThis3.Range("B:B").Copy Destination:=WshtNew3.Range("C1")
WshtThis3.Range("C:C").Copy Destination:=WshtNew3.Range("B1")
WshtThis3.Range("D:G").Copy Destination:=WshtNew3.Range("D1")
WshtThis3.Range("I:AL").Copy Destination:=WshtNew3.Range("H1")
WshtThis3.Range("AM:AP").Copy Destination:=WshtNew3.Range("AM1")
WshtThis3.Range("AQ:BK").Copy Destination:=WshtNew3.Range("AQ1")
'On Error Resume Next
' This statement means ignore all errors which you should never do.
' Use this statement so:
'On Error Resume Next
'Statement that may fail for reasons you cannot control or stop
'On Error GoTo 0
'If Err.Number = 0 Then
' No error
'Else
' Display Err.Description or take corrective action according
' to value of Err.Number
'End If
'Selection.CurrentRegion.Select
' Since you have just created the worksheet it is probably safe to
' use "CurrentRegion". However, Excel's definition of CurrentRegion
' is not always what you might expect.
With WbkNew
With .Worksheets(1)
.Name = "Combined"
' Did you mean to copy row 2?
WshtNew2.Rows(2).Copy Destination:=.Rows(1)
End With
Row1Next = 3 ' Next free row in worksheets(1)
For InxWsht = 2 To Worksheets.Count
' This Find searches backwards from A1 by row for the first cell
' containing a value. This will give you what you expect more
' often that CurrentRegion
With Worksheets(InxWsht)
Set Rng = .Cells.Find(What:="*", After:=.Range("A1"), _
SearchOrder:=xlByRows, _
SearchDirection:=xlPrevious)
If Rng Is Nothing Then
' Probably not necessary here but best to be safe. If a worksheet
' is empty Find will return Nothing
Else
.Rows("2:" & Rng.Row).Copy Destination:=WbkNew.Worksheets(1).Cells(Row1Next, 1)
' Unless I absolutely know that column A will be the last column with
' a value, I prefer to caluclate the next free row.
Row1Next = Row1Next + Rng.Row - 1
End If
End With
Next
' I do not see the point of having the Sort within the or Loop
With .Worksheets(1)
.Cells.Sort Key1:=.Range("E1"), Key2:=Range("J1"), Header:=xlYes
End With
End With
End Sub
Related
I pretty much have an already working macro for me but for the future it may cause problems because the macro i have finds the column i gave it and then starts to input the formula there. Now my data may change in the future and in that column i might have something new so the macro would obviously run the formulas to the wrong column. Changing it manually is possible but hectic and a lot of work. Is there any possible way i can select a cell with a specific text in it instead of the column? since the text will never change this will me much easier for me to work with. Because doing this the formulas will always be posted in the correct column.
EDIT! I added the whole code to the post so you can see it more clearly and understand what i mean more clearly.
Sub HW_Copy_RawData_Formulas()
Dim intChoice As Integer
Dim strPath As String
Dim I As Integer
Dim filePath As String
Dim SourceWb As Workbook
Dim TargetWb As Workbook
Dim Lastrow As Long
Dim Nrow As Long
Set TargetWb = ActiveWorkbook
' Delete Rows
On Error Resume Next
TargetWb.Worksheets("Raw Data").Activate
Range("A2:AL2").Select
Range(Selection, Selection.End(xlDown)).Select
Selection.Delete Shift:=xlUp
Range("A2:AL2").Select
Range(Selection, Selection.End(xlDown)).Select
Selection.Delete Shift:=xlUp
'Copy Formulas
Range("AF2").Formula = "=IF([#ServDt]<DATE(2013,1,1), DATE(YEAR([#ServDt]),12,31),EOMONTH([#ServDt],0))"
Range("AG2").Formula = "=IF([#Amount]>1,[#Quantity],0)"
Range("AH2").Formula = "=IF([#Amount]<>0,[#Amount]-[#Adj]-[#[Adjustment ]],0)"
Range("AI2").Formula = "=IF(AND([#Department]=""HH"",[#Pay]=0),[#Amount]/2,0)"
Range("AJ2").Formula = "=IF([#Amount]<>0,[#Bal]-[#[Adjustment ]],[#Bal]+[#Adj])"
Range("AK2").Formula = "=VLOOKUP([Department],Service[#All],2,FALSE)"
Range("AL2").Formula = "=VLOOKUP([#Entity],Site,3,FALSE)"
MSG1 = MsgBox("Add Raw Data", vbYesNo)
If MSG1 = vbYes Then
'only allow the user to select one file
Application.FileDialog(msoFileDialogOpen).AllowMultiSelect = False
'make the file dialog visible to the user
intChoice = Application.FileDialog(msoFileDialogOpen).Show
'determine what choice the user made
If intChoice <> 0 Then
'get the file path selected by the user
strPath = Application.FileDialog( _
msoFileDialogOpen).SelectedItems(1)
Else: GoTo endmsg
End If
'Setting source of data
Set SourceWb = Workbooks.Open(strPath)
Lastrow = SourceWb.Worksheets(1).Cells(Rows.Count, 1).End(xlUp).Row
SourceWb.Worksheets(1).Range("A2:BJ" & Lastrow).SpecialCells(xlCellTypeVisible).Select
Selection.Copy Destination:=TargetWb.Sheets("Raw Data").Range("A2")
' Close the source workbook without saving changes.
SourceWb.Close savechanges:=False
Else
endmsg:
MsgBox "Complete"
End If
Range("AF2:AL2").Select
Range(Selection, Selection.End(xlDown)).Select
Selection.Copy
Range("AF2").PasteSpecial xlPasteValues
End Sub
The following code snippet might be of use to you. It acquires the range of the cell given a specific value. It can also be used to search a specific row with .Rows() instead.
Dim *YOURCELL* As Range
Set *YOURCELL*= .Columns(1).Find(What:= *WHATYOUWANTTOFIND*, LookAt:=xlWhole, MatchCase:=False, searchformat:=False)
If, however, you do not know where the last used cell is located, then consider reading this other post.
EDIT:
The while loop runs as long as the currently selected cell is not empty. In this loop, it selects the next cell to the right and increments a count. After the loop has finished, the currently selected cell is the first empty cell in the second row. Count has found the column number of it by incrementing alongside the loop, so it can then be used as needed. I used cells instead of range afterwards because it can use the column number.
Range("A2").Select
Dim count As Integer
count = 1
'skip all used cells in the row
Do While Not (ActiveCell.value = None)
ActiveCell.Offset(0, 1).Range("A1").Select
count = count + 1
Loop
Cells(count, 2).Formula = your_formula
Cells(count + 1, 2).Formula = your_formula ' next cell to the right
Cells(count + 2, 2).Formula = your_formula ' next cell to the right
Chopped down from uber-detail history mode per suggestion.
My level of expertise: Hacked some fairly complex dialog-boxing multi-workbook macro systems ten years ago, experienced but not formally trained and rusty.
The complicated stuff in this macro works; its central bug is that it won't change that CurrentClientAnchor Range variable, the most basic operation in Excel VBA, no matter what I do. It loops as many times as you like anchored on cell A2, correctly finding the cell that should next become CurrentClientAnchor (on the real data, A4, two cells down), and creating the invoice sheet perfectly from the selected data as long as you give it permission to overwrite the copy it just created a second ago. I won't be surprised if my special last record routine breaks something, but manually stepping through, none of that If clause ever runs. The program correctly steps over it. WhatsMyAnchor should be 4 just before the last Loop command, but never changes from 2.
The only method I know for accomplishing what I want that doesn't have a commented fossil left in the code is the first one I wrote, assigning a ClientsRange as Range over Range("A2", Cells(LastRow,1)) and then putting everything in a For...Next loop. That version also just ran over and over on the first record.
In what way am I being incredibly stupid, please?
Option Explicit
Sub FillOutInvoices()
Dim BilledDate As String
Dim ServiceYear As String
Dim ServiceMonth As String
Dim CompBasePath As String
Dim InvoiceTemplatePath As String
InvoiceTemplatePath = "H:\Comp\Comp Invoice BLANK PRINT COPY.xls"
'The info to change for each invoicing
'========================
'========================
CompBasePath = "H:\Comp\2014 Invoices\"
ServiceYear = "2014"
ServiceMonth = "September"
BilledDate = "02/01/2015"
'========================
'========================
Dim InvoiceFolder As String
InvoiceFolder = CompBasePath & ServiceYear & " " & ServiceMonth & " generated invoices" & "\"
If Dir(InvoiceFolder, vbDirectory) = vbNullString Then
MkDir InvoiceFolder
End If
'Find the last used row on the sheet with a web recipe to speed things up
'and avoid arbitrary search windows.
Dim LastRow As Long
LastRow = ActiveSheet.UsedRange.Rows.Count
'We assume our first client is in A2
Dim CurrentClientAnchor As Range
Set CurrentClientAnchor = Range("A2")
Dim DataHeight As Single
Dim NoMoreRecords As Boolean
NoMoreRecords = False
'Debugging variable so I don't have to paw through
'a zillion properties of CCA in the Watch pane all the time
Dim WhatsMyAnchor As Single
WhatsMyAnchor = CurrentClientAnchor.Row
Do Until NoMoreRecords = True 'Loop captures falling through the last record, internal exit catches
'the next result each time
'Surprisingly the main loop. For each client, find the next one or end of job,
'use that as an upper and lower bound to create and write the invoice
'Transplanted inline from what should be a sub, because I need it to Just Work Now.
'As a sub, causes Object Required error on passing the range which is a range into the range slot that's designated as a range.
'This should become some clever run-once array of nonempty ranges someday
'Find next nonempty A. If none before lastrow, last record; find last nonempty F, set rows, copy data, terminate macro.
'If found, set rows and copy data
DataHeight = 1
Do Until CurrentClientAnchor.Offset(DataHeight, 0).Value <> ""
'Find the next nonempty cell below CurrentClientAnchor and record the offset
'We're falling off the bottom of the last one, have to do our special last search up front here.
If CurrentClientAnchor.Offset(DataHeight, 0).Row = LastRow Then 'special finder for last record down F to first empty cell
NoMoreRecords = True
DataHeight = 1
Do Until CurrentClientAnchor.Offset(DataHeight, 5).Value = ""
DataHeight = DataHeight + 1
Loop
Exit Do
End If
DataHeight = DataHeight + 1
Loop
'We now have our DataHeight value for the grunt work.
'Subtract one from it, to convert to the cell offsets we'll use
DataHeight = DataHeight - 1
'Inlined from sub again because I apparently don't know how to pass a variable.
'MakeInvoiceFile
Dim SourceBook As Workbook
Set SourceBook = ThisWorkbook
Dim InvoiceFileName As String
InvoiceFileName = InvoiceFolder & _
CurrentClientAnchor.Value & " " & ServiceYear & " " & ServiceMonth & " Invoice" & ".xls"
Dim DestBook As Workbook
Dim Template As Workbook
Application.Workbooks.Open InvoiceTemplatePath
Set Template = ActiveWorkbook
Set DestBook = ActiveWorkbook
DestBook.SaveAs (InvoiceFileName)
SourceBook.Activate
'Close for debugging cleanliness, more elegant keep open behavior later
'Doesn't work. Maybe not even ugly, anyway cut for dev time.
'Template.Close
'More debugging watchable variables
Dim WhereCopyingRow As Single
Dim WhereCopyingColumn As Single
Dim CopyRange As Range
'Client name into job name
Set CopyRange = CurrentClientAnchor
WhereCopyingRow = CopyRange.Row
WhereCopyingColumn = CopyRange.Column
CopyRange.Copy
DestBook.Sheets(1).Cells(3, 4).PasteSpecial xlPasteValues, xlPasteSpecialOperationNone
'Service address into job location
Set CopyRange = CurrentClientAnchor.Offset(0, 3)
WhereCopyingRow = CopyRange.Row
WhereCopyingColumn = CopyRange.Column
CopyRange.Copy
DestBook.Sheets(1).Cells(4, 4).PasteSpecial xlPasteValues, xlPasteSpecialOperationNone
'Billing address into billing address
Set CopyRange = CurrentClientAnchor.Offset(0, 4)
WhereCopyingRow = CopyRange.Row
WhereCopyingColumn = CopyRange.Column
CopyRange.Copy
DestBook.Sheets(1).Cells(9, 2).PasteSpecial xlPasteValues, xlPasteSpecialOperationNone
'Billing Date into Date Billed
'Currently discarded for progress
'DestBook.Sheets(1).Cells(24, 3).PasteSpecial xlPasteValues, xlPasteSpecialOperationNone
'Descriptions
Set CopyRange = Range(CurrentClientAnchor.Offset(0, 5), CurrentClientAnchor.Offset(DataHeight, 5))
WhereCopyingRow = CopyRange.Row
WhereCopyingColumn = CopyRange.Column
CopyRange.Copy
DestBook.Sheets(1).Cells(13, 2).PasteSpecial xlPasteValues, xlPasteSpecialOperationNone
'Totals
Set CopyRange = Range(CurrentClientAnchor.Offset(0, 14), CurrentClientAnchor.Offset(DataHeight, 15))
WhereCopyingRow = CopyRange.Row
WhereCopyingColumn = CopyRange.Column
CopyRange.Copy
DestBook.Sheets(1).Cells(13, 6).PasteSpecial xlPasteValues, xlPasteSpecialOperationNone
'Overall total
Set CopyRange = CurrentClientAnchor.Offset(DataHeight, 16)
WhereCopyingRow = CopyRange.Row
WhereCopyingColumn = CopyRange.Column
CopyRange.Copy
DestBook.Sheets(1).Cells(24, 6).PasteSpecial xlPasteValues, xlPasteSpecialOperationNone
DestBook.Save
DestBook.Close
'SourceBook appears to be activated when we close DestBook, but it's failing to iterate so let's make sure.
SourceBook.Activate
'CurrentClientAnchor = CurrentClientAnchor.Offset(DataHeight + 1, 0)
'WhatsMyAnchor = CurrentClientAnchor.Row
'Apparently we can't assign a range to its offset, fails to iterate, so
'we pop out to selection and back to the variable.
'CurrentClientAnchor.Offset(DataHeight + 1, 0).Select
'CurrentClientAnchor = Selection
'WhatsMyAnchor = CurrentClientAnchor.Row
'Nope. Escalate to activating and assigning.
'CurrentClientAnchor.Offset(DataHeight + 1, 0).Activate
'CurrentClientAnchor = ActiveCell
'WhatsMyAnchor = CurrentClientAnchor.Row
'That doesn't iterate either, it's really hard for a programming language in
'Excel to iterate on the most common object in Excel,
'so let's turn the blasted stupid debugging variable into an absolute cell selector
Set CurrentClientAnchor = ActiveSheet.Cells(WhatsMyAnchor + DataHeight + 1, 0)
WhatsMyAnchor = CurrentClientAnchor.Row
'That throws a 1004 error with or without the Set, "application or object-defined error", thanks.
'It's just impossible to move a Range down a few cells. Excel VBA can't do that. You can't vary a Range variable.
Loop
MsgBox "All successfully written"
End Sub
That is a lot of writing for a relatively small question, I would recommend cutting out any non-essential text in future questions; a lot of people will just see the sheer volume of text and move on.
With respect to your issue I think a minor change would do the job:
The examples you have commented out should work if you just add Set in front of them:
Set CurrentClientAnchor = CurrentClientAnchor.Offset(DataHeight + 1, 0)
As you have it with the line
Set CurrentClientAnchor = ActiveSheet.Cells(WhatsMyAnchor + DataHeight + 1, 0)
Changed to
Set CurrentClientAnchor = ActiveSheet.Range("A" & WhatsMyAnchor + DataHeight + 1)
Should also work.
I want to copy some columns with headers from a worksheet to another one. I've created an array that looks for the different headers needed so I can copy and paste the entire column into the new tab. I know I have an error somewhere because I'm getting a type mismatch error and possibly other types as well. Can someone take a look and see what I'm missing/have wrong?
Dim rngCell As Range
Dim strHeader() As String
Dim intColumnsMax As Integer
Sheets.Add.Name = "Material Master"
Sheets.Add.Name = "BOM"
intColumnsMax = Sheets("HW Zpure Template").UsedRange.Columns.Count
ReDim strHeader(1 To intColumnsMax)
strHeader(1) = "MATERIAL"
strHeader(2) = "MATERIAL TYPE"
strHeader(3) = "MATERIAL DESCRIPTION"
For Each rngCell In Rows(4)
For i = 1 To intColumnsMax
If strHeader(i) = rngCell.Value Then
rngCell.EntireColumn.Copy
Sheets("Material Master").Select
ActiveSheet.Paste Destination:=Worksheets("Material Master").Cells(1, i)
Sheets("HW Zpure Template").Select
End If
Next i
Next
I prefer to use Application.Match to locate a specific column header label rather than cycling through them trying to find a match. To that end, I've heavily modified your code.
Dim c As Long, v As Long, vHDRs As Variant
Dim s As Long, vNWSs As Variant, wsMM As Worksheet
vHDRs = Array("MATERIAL", "MATERIAL TYPE", "MATERIAL DESCRIPTION")
vNWSs = Array("Material Master", "BOM")
For v = LBound(vNWSs) To UBound(vNWSs)
For s = 1 To Sheets.Count
If Sheets(s).Name = vNWSs(v) Then
Application.DisplayAlerts = False
Sheets(s).Delete
Application.DisplayAlerts = True
Exit For
End If
Next s
Sheets.Add after:=Sheets(Sheets.Count)
Sheets(Sheets.Count).Name = vNWSs(v)
Next v
Set wsMM = Sheets("Material Master")
With Sheets("HW Zpure Template")
For v = LBound(vHDRs) To UBound(vHDRs)
If CBool(Application.CountIf(.Rows(4), vHDRs(v))) Then
c = Application.Match(vHDRs(v), .Rows(4), 0)
Intersect(.UsedRange, .Columns(c)).Copy _
Destination:=wsMM.Cells(1, Application.CountA(wsMM.Rows(1)) + 1)
End If
Next v
End With
Set wsMM = Nothing
Correct me if I'm wrong, but your code seemed to be looking for the column labels in row 4. That is what I'm using above but if that assumption is incorrect then the fix should be fairly self-evident. I've also stacked the copied columns into the first available column to the right. Your code may have been putting them in the original position.
When you run the above, please note that it will remove worksheets named Material Master or BOM without asking in favor of inserting its own worksheets of those names. Given that, it's probably best to run on a copy of your original.
Using the Find() method is a very efficient way of finding the data you want. Below are a few suggestions to optimize your existing code.
Dim rngCell As Range
Dim strHeader() As String
Dim intColumnsMax As Integer
Dim i As Integer
Sheets.Add.Name = "Material Master"
Sheets.Add.Name = "BOM"
'Quick way to load a string array
'This example splits a comma delimited string.
'If your headers contain commas, replace the commas in the next line of code
'with a character that does not exist in the headers.
strHeader = Split("MATERIAL,MATERIAL TYPE,MATERIAL DESCRIPTION", ",")
'Only loop through the headers needed
For i = LBound(strHeader) To UBound(strHeader)
Set rngCell = Sheets("HW Zpure Template").UsedRange.Find(What:=strheader(i), LookAt:=xlWhole)
If Not rngCell Is Nothing Then
'Taking the intersection of the used range and the entire desired column avoids
'copying a lot of unnecessary cells.
Set rngCell = Intersect(Sheets("HW Zpure Template").UsedRange, rngCell.EntireColumn)
'This method is more memory consuming, but necessary if you need to copy all formatting
rngCell.Copy Destination:=Worksheets("Material Master").Range(rngCell.Address)
'This method is the most efficient if you only need to copy the values
Worksheets("Material Master").Range(rngCell.Address).Value = rngCell.Value
End If
Next i
What I am trying to do is copy variable data ranges, but identical headers, from all sheets and paste into the Master sheet one after the other. The original code (CODE 1 below) renewed the data in the master whenever I clicked on another sheet and back onto the master. The problem now is that there are other sheets in the Workbook that I do not want included in the copy process.
I have edited the code I received below (CODE 2 below) to try and define start and end sheets for running a "loopindex" and also removing the "copy headers" line of code as the headers for each worksheet are appearing throughout the mastersheet. Obviously it does not work and I was wondering if someone could help.
Could you please help me correct the combined code or provide a more elegant solution? Thanks.
Original question here - Excel Forum post
Secondary code from here - Stack post LoopIndex
Original CODE 1
Private Sub Worksheet_Activate()
Dim ws As Worksheet
Application.ScreenUpdating = False
Me.UsedRange.Clear
For Each ws In ThisWorkbook.Worksheets
If ws.Name <> Me.Name Then
If Range("A1") = "" Then ws.Range("A1").EntireRow.Copy Me.Range("A1")'copy in the headers
ws.UsedRange.Offset(1).Copy Me.Range("A" & Rows.Count).End(xlUp).Offset(1)'copy data
End If
Next ws
Application.ScreenUpdating = True
End Sub
Edited CODE 2
Private Sub Worksheet_Activate()
Dim ws As Worksheet
Application.ScreenUpdating = False
Me.UsedRange.Clear
Dim StartIndex, EndIndex, LoopIndex As Integer
StartIndex = Sheets("Master sheet").Index + 1
EndIndex = Sheets("End").Index - 1
For LoopIndex = StartIndex To EndIndex
If Range("A1") = "" Then ws.Range("A1").Offset(1).Copy Me.Range("A" &Rows.Count).End(xlUp).Offset(1) 'copy data
Next LoopIndex
Application.ScreenUpdating = True
End Sub
I can just about understand why you had this as a Worksheet Activate event routine against worksheet "Master list" when there was only one source worksheet. I am having more difficulty in seeing this as convenient when you have multiple source worksheets. I am not asking you to justify your decision since I do not have a full understanding of workbook but you might like to reconsider your approach. I have coded the routine below as an normal macro but you can change this easily if you wish.
I do not like the approach of assuming the worksheets to be loaded are from Sheets("Master sheet").Index + 1 to Sheets("End").Index - 1. I would have thought that was unstable although I have never tried this approach.
I have created a hidden worksheet "Load List":
This lists the worksheets to be loaded in the sequence to be loaded.
I have filled worksheet "Sheet1" with data:
Not very imaginative data but it makes it easy to check that "Master list" is loaded with the correct data. Worksheets "Sheet2" to "Sheet5" have similar data except that the number of data rows vary and "S1" is replaced by "S2", "S3", "S4" and "S5".
After the macro has run, the top of "Master list" contains:
You can see I have loaded all rows from the first worksheet then data rows only from subsequent worksheets.
I do not say a great deal about the VBA I have used. Once you know a statement exists it is normally easy to look it up. Ask if necessary. I hope I have provided an adequate explanation of what the code does. Again ask if necessary.
Option Explicit
Sub CombinedSelected()
Dim ColSrcMax As Long
Dim LoadList As Variant
Dim RowListCrnt As Long
Dim RowListMax As Long
Dim RowMasterNext As Long
Dim RowSrcMax As Long
With Worksheets("Load List")
RowListMax = .Cells(Rows.Count, "A").End(xlUp).Row
' Load the values from column A of worksheet "Load List" to LoadList.
' The statement converts LoadList to a 2 dimensional array. It is the
' equivalent of Redim LoadList(1 To RowListMax, 1 to 1)
LoadList = .Range(.Cells(1, "A"), .Cells(RowListMax, "A")).Value
End With
RowMasterNext = 1
With Worksheets("Master sheet")
.Cells.EntireRow.Delete ' Delete existing contents
End With
For RowListCrnt = 2 To RowListMax
With Worksheets(LoadList(RowListCrnt, 1))
' Find last used row and column containing a value.
' Warning. These statements do not allow for any of the source worksheets being empty
RowSrcMax = .Cells.Find("*", .Range("A1"), xlFormulas, , xlByRows, xlPrevious).Row
ColSrcMax = .Cells.Find("*", .Range("A1"), xlFormulas, , xlByColumns, xlPrevious).Column
If RowListCrnt = 2 Then
' For first source worksheet only include header row
.Range(.Cells(1, 1), .Cells(RowSrcMax, ColSrcMax)).Copy _
Destination:=Worksheets("Master sheet").Cells(RowMasterNext, 1)
RowMasterNext = RowMasterNext + RowSrcMax
Else
' Data rows only to be copied
.Range(.Cells(2, 1), .Cells(RowSrcMax, ColSrcMax)).Copy _
Destination:=Worksheets("Master sheet").Cells(RowMasterNext, 1)
RowMasterNext = RowMasterNext + RowSrcMax - 1
End If
End With
Next
End Sub
i want a macro to consolidate the data form multiple sheets to one sheet.. here i given the example ..
Sheet 1
a1:Name b1:Age
a2:sathish b2:22
a3:sarathi b3:24
.
sheet 2
a1:Age b1:Name c1:Dept
a2:60 b2:saran c2:Comp sce
a3:31 b3:rajan c3:B.com
the result should be like this
consolidate sheet
a1:Name b1:Age c1:Dept
a2:sathish b2:22
a3:sarathi b3:24
a4:saran b4:60 c4:Comp sce
a5:rajan b5:31 c5:B.com
Here is the code which i used for consolidate data-
Sub consolidate()
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
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"
StartRow = 1
For Each sh In ActiveWorkbook.Worksheets
If sh.Name <> DestSh.Name Then
Last = LastRow(DestSh)
shLast = LastRow(sh)
If shLast > 0 And shLast >= StartRow Then
Set CopyRng = sh.Range(sh.Rows(StartRow), sh.Rows(shLast))
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
CopyRng.Copy
With DestSh.Cells(Last + 1, "A")
.PasteSpecial xlPasteValues
.PasteSpecial xlPasteFormats
Application.CutCopyMode = False
End With
End If
End If
Next
ExitTheSub:
Application.Goto DestSh.Cells(1)
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
I can able consolidate the data but can't re-arrange as per the column title..
Please help me in this ..THanks in advance
First I identify some mistakes and bad practices in your code then I consider how to redesign your macro to achieve your objectives.
Issue 1
The primary purpose of On Error is to allow you to terminate tidily if an unexpected error occurs. You should not use it to avoid errors you expect and you should not ignore errors.
Consider the functions LastRow and LastCol. In both cases, if the Find fails, you ignore the error and carry on. But that means these functions return an incorrect value, so you get another error in the calling routine. If the Find fails you should investigate not ignore. This is true of any other error.
Issue 2
Find returns Nothing if the sheet is empty. You call functions LastRow and LastCol for worksheet "RDBMergeSheet" when it is empty. The code should be:
Set Rng = sh.Cells.Find( ...)
If Rng Is Nothing Then
' Sheet sh is empty
LastRow = 0
Else
LastRow = Rng.Row
End If
Here I have set LastRow to 0 if the worksheet is empty. This ceases to be a side effect of an error but a documented feature of the function: "Return value = 0 means the worksheet is empty." The calling routine must check for this value and skip any empty worksheets. There are other approaches but the key point is: provide code to handle expected or possible errors in a tidy manner. For function LastCol you need LastCol = Rng.Column.
Issue 3
The minimum syntax for a function statement is:
Function Name( ... parameters ...) As ReturnType
The two function statements should end: As Long.
Issue 4
Consider: "ActiveWorkbook.Worksheets("RDBMergeSheet")"
If you are working on multiple workbooks, ActiveWorkbook is not enough. If you are only working on one workbook, ActiveWorkbook is unnecessary. Please do not work with multiple workbooks until your understanding of Excel VBA is better.
Issue 5
You delete worksheet "RDBMergeSheet" and then recreate it which hurts my soul. More importantly, you have lost the column headings. I will discuss this matter further under Redesign.
Replace:
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"
with:
Set DestSh = Worksheets("RDBMergeSheet")
With DestSh
.Range(.Cells(2, 1), .Cells(Rows.Count, Columns.Count)).EntireRow.Delete
End With
You use Rows.Count, With and Cells in your code so I will not explain them.
.Range(.Cells(RowTop, ColLeft), .Cells(RowBottom, ColRight)) is an easy method of specifying a range with the top left and bottom right cells.
I have used .EntireRow so I do not need the column numbers. The following gives the same effect:
.Rows("2:" & Rows.Count).EntireRow.Delete
As far as I know ClearContents (which some people favour) has the same effect as Delete. It certainly takes the same number of micro-seconds. For the usages above, both remove any values or formatting from the second row to the last row of the worksheet.
The above change means that row 1 is unchanged and the column widths are not lost. I do not need AutoFit which you have used.
Issue 6
Please be systematic in the naming of your variables. You use StartRow as the first row and shLast as the last row of the source worksheet and Last as the last row of the destination worksheet. Will a colleague who takes over maintenance of your macro find this easy to understand? Will you remember it in six months when this macro needs some maintenance?
Develop a naming system that works for you. Better still, get together with colleagues and agree a single system so all your employer's macros look the same. Document this system for the benefit of future staff. I would name these variables: RowNumDestLast, RowNumSrcStart and RowNumSrcLast. That is: <purpose of variable> <worksheet> <purpose within worksheet>. This system works for me but your system could be completely different. The key feature of a good system is that you can look at your code in a year and immediately know what each statement is doing.
Issue 7
If shLast > 0 And shLast >= StartRow Then
You set StartRow to 1 and never change it so if shLast >= StartRow then shLast > 0. The following is enough:
If shLast >= StartRow Then
Issue 8
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
It is good that you are checking for conditions that will result in fatal errors but is this the most likely error? Even if you are using Excel 2003, you have room for 65,535 people and a heading line. You will break the size limit on a workbook before you exceed the maximum number of rows.
Issue 9
Set CopyRng = sh.Range(sh.Rows(StartRow), sh.Rows(shLast))
This includes the heading row in the range to be copied. Since I will suggest a totally different method later, I will not suggest a correction.
Issue 10
With DestSh.Cells(Last + 1, "A")
.PasteSpecial xlPasteValues
.PasteSpecial xlPasteFormats
Why are you pasting the values and formats separately?
Redesign
With the corrections above, the code sort of works. With your source data, it sets the destination sheet to:
Age Name Dept
Name Age
Sathish 22
Sarathi 24
Age Name Dept
60 Saran Comp sce
31 Rajan B.com
This is not what you seek. So the rest of this answer is about design: how do you achieve the appearance you seek? There are many approaches but I offer one and explain why I have picked it without discussing alternatives.
Key issues:
How do you determine which columns to consolidate and in which sequence?
If there is a column in a source worksheet that you are not expecting, what do you do? Is someone collecting information for which there is no central interest or is the column name misspelt?
I have decided to use the existing column names within worksheet "RDBMergeSheet" to determine the sequence. To prepare the macro for a new column name, just add that name to "RDBMergeSheet". If I discover a column name in a source sheet that is not in "RDBMergeSheet", I add it on the right. This second decision will highlight the error if a column name is misspelt but will not be a benefit if someone is collecting extra information in a source worksheet.
I do not copy formats to worksheet "RDBMergeSheet" since, if the source worksheets are formatted differently, each part of worksheet "RDBMergeSheet" would be different.
New statements and explanations
Const RowFirstData As Long = 2
Const WShtDestName As String = "RDBMergeSheet"
A constant means I use the name in the code and can change the value by changing the Const statement.
I assume the first row of every worksheet contains column names and the first data row is 2. I use a constant to make this assumption clear. It would be possible to use this to write code that would handle a different number of heading rows but I have not done so because it would complicate the code for little advantage.
ColNumDestLast = .Cells(1, Columns.Count).End(xlToLeft).Column
.Cells(1, Columns.Count) identifies the last column of row 1 which I assume is blank. .End(xlToLeft) is the VBA equivalent of the keyboard Ctrl+Left. If .Cells(1, Columns.Count) is blank, .Cells(1, Columns.Count).End(xlToLeft) returns the first cell to the left which is not blank. .Column gives the column number of that cell. That is, this statement sets ColNumDestStart to the column number of the last cell in row 1 with a value.
ColHeadDest = .Range(.Cells(1, 1), .Cells(1, ColNumDestLast)).Value
This copies the values from row 1 to the variant array ColHeadDest. ColHeadDest will be redimensioned by this statement to (1 to 1, 1 to ColNumDestLast). The first dimension is for the rows, of which there is only one, and the second dimension is for the columns.
Replacement consolidate
I hope I have added enought comments for the code to make sense. You still need the corrected LastRow and LastCol. I could have replaced LastRow and LastCol but I think I have provided enough new code to be getting on with.
Option Explicit
Sub consolidate()
Dim ColHeadCrnt As String
Dim ColHeadDest() As Variant
Dim ColNumDestCrnt As Long
Dim ColNumDestLast As Long
Dim ColNumSrcCrnt As Long
Dim ColNumSrcLast As Long
Dim Found As Boolean
Dim RowNumDestCrnt As Long
Dim RowNumDestStart As Long
Dim RowNumSrcCrnt As Long
Dim RowNumSrcLast As Long
Dim WShtDest As Worksheet
Dim WShtSrc As Worksheet
Dim WShtSrcData() As Variant
Const RowNumFirstData As Long = 2
Const WShtDestName As String = "RDBMergeSheet"
'With Application
' .ScreenUpdating = False ' Don't use these
' .EnableEvents = False ' during development
'End With
Set WShtDest = Worksheets(WShtDestName)
With WShtDest
' Clear existing data and load column headings to ColHeadDest
.Rows("2:" & Rows.Count).EntireRow.Delete
ColNumDestLast = .Cells(1, Columns.Count).End(xlToLeft).Column
ColHeadDest = .Range(.Cells(1, 1), _
.Cells(1, ColNumDestLast)).Value
End With
' Used during development to check array loaded correctly
'For ColNumDestCrnt = 1 To ColNumDestLast
' Debug.Print ColHeadDest(1, ColNumDestCrnt)
'Next
RowNumDestStart = RowNumFirstData ' Start for first source worksheet
For Each WShtSrc In Worksheets
ColNumSrcLast = LastCol(WShtSrc)
RowNumSrcLast = LastRow(WShtSrc)
If WShtSrc.Name <> WShtDestName And _
RowNumSrcLast <> 0 Then
' Source sheet is not destination sheet and it is not empty.
With WShtSrc
' Load entire worksheet to array
WShtSrcData = .Range(.Cells(1, 1), _
.Cells(RowNumSrcLast, ColNumSrcLast)).Value
End With
With WShtDest
For ColNumSrcCrnt = 1 To ColNumSrcLast
' For each column in source worksheet
Found = False
ColHeadCrnt = WShtSrcData(1, ColNumSrcCrnt)
' Find matching column in destination worksheet
For ColNumDestCrnt = 1 To ColNumDestLast
If ColHeadCrnt = ColHeadDest(1, ColNumDestCrnt) Then
Found = True
Exit For
End If
Next ColNumDestCrnt
If Not Found Then
' Current source column's name is not present in the
' destination sheet Add new column name to array and
' destination worksheet
ColNumDestLast = ColNumDestLast + 1
ReDim Preserve ColHeadDest(1 To 1, 1 To ColNumDestLast)
ColNumDestCrnt = ColNumDestLast
With .Cells(1, ColNumDestCrnt)
.Value = ColHeadCrnt
.Font.Color = RGB(255, 0, 0)
End With
ColHeadDest(1, ColNumDestCrnt) = ColHeadCrnt
End If
' I could extract data from WShtSrcData to another array
' suitable for downloading to a column of a worksheet but
' it is easier to move the data directly to the worksheet.
' Also, athought downloading via an array is marginally
' faster than direct access, loading the array will reduce,
' and perhaps eliminate, the time benefit of using an array.
RowNumDestCrnt = RowNumDestStart
For RowNumSrcCrnt = RowNumFirstData To RowNumSrcLast
' Copy value from array of source data to destination sheet
.Cells(RowNumDestCrnt, ColNumDestCrnt) = _
WShtSrcData(RowNumSrcCrnt, ColNumSrcCrnt)
RowNumDestCrnt = RowNumDestCrnt + 1
Next
Next ColNumSrcCrnt
End With ' WShtDest
' Adjust RowNumDestStart ready for next source worksheet
RowNumDestStart = RowNumDestStart + RowNumSrcLast - RowNumFirstData + 1
End If ' Not destination sheet and not empty source sheet
Next WShtSrc
With WShtDest
' Leave workbook with destination worksheet visible
.Activate
End With
'With Application
' .ScreenUpdating = True
' .EnableEvents = True
'End With
End Sub