Merge Multiple Tabs in Excel Using Fixed Ranges - vba

Apologies in advance as I'm sure this is a simple question, and there's lots of similar answers out there, but I haven't been able to leverage them into a working solution.
My situation is I have an Excel file with 28 tabs. Each Sheet has data in the exact same format in range A1:N10000. Note though that some of the cells in each tab are blank. This holds true across every tab. I would like to have all 28 tabs merged into one new Sheet call Combined.
I have been trying to leverage this Macro:
Sub Combine()
Dim J As Integer
On Error Resume Next
Sheets(1).Select
Worksheets.Add
Sheets(1).Name = "Combined"
Sheets(2).Activate
Range("A1").EntireRow.Select
Selection.Copy Destination:=Sheets(1).Range("A1")
For J = 2 To Sheets.Count
Sheets(J).Activate
Range("A1").Select
Selection.CurrentRegion.Select
Selection.Offset(1, 0).Resize(Selection.Rows.Count - 1).Select
Selection.Copy Destination:=Sheets(1).Range("A65536").End(xlUp)(2)
Next
End Sub
Obviously I've encountered problems running this and the resulting data only has a few values pasted instead of the expected ~280,000 rows (28 tabs, 10k rows each). I suspect one of the reasons is because there are blank cells in the tabs, so this Macro isn't reading the data as I intend it to. How can I modify it just to copy A1:N10000 in each tab and paste each to the Combined tab? Also, will I hit issues with trying to populate a sheet with 280,000 rows?
Thank you!
David

CurrentRegion will not copy everything you want if there are blank cells, as you suspected. Also, it's preferable to avoid using Select - since you don't really need to select the cells before copying - and On Error Resume Next - this doesn't handle errors at all, it ignores them.
Option Explicit
Sub Combine()
Dim i As Integer
Dim combinedWs As Worksheet, ws As Worksheet
Dim copyRng As Range
Dim lastRow As Long
' Add combined worksheet and populate headers
Set combinedWs = Worksheets.Add(Before:=Sheets(1))
combinedWs.Name = "Combined"
Sheets(2).Rows(1).Copy combinedWs.Rows(1)
' Loop through rest of Sheets
For i = 2 To Sheets.Count
Set ws = Sheets(i)
With ws
lastRow = .Cells(.Rows.Count, 1).End(xlUp).Row
Set copyRng = Range(.Cells(2, 1), .Cells(lastRow, "N"))
copyRng.Copy combinedWs.Cells(.Rows.Count, 1).End(xlUp).Offset(1)
End With
Next i
End Sub
If you want to copy a specific, hard-coded range, replace the code inside With ws... End With.
Set copyRng = Range(.Cells(2, 1), .Cells(10000, 14))
copyRng.Copy combinedWs.Cells(2, 1).Offset((i-2)*copyRng.Rows.Count)
A bit of a hack on the last line: for each iteration of i you are offsetting by the number of rows in copyRng. You start in combinedWs.Cells(2, 1), or A2. On the first iteration, i - 2 = 0, so there is no offset. On subsequent iterations, you offset 9999, 19998, and so on.

You can try the below code:
Sub Combine()
Dim cmbSheet, ws As Worksheet
Dim tmpIndex As Double
Set cmbSheet = ThisWorkbook.Worksheets.Add
cmbSheet.Name = "Combined"
tmpIndex = 0
For Each ws In ThisWorkbook.Worksheets
If ws.Name <> "Combined" Then
If tmpIndex = 0 Then
cmbSheet.Cells(1, 1) = "Sheet Name"
ws.Range("A1:N1").Copy Destination:=cmbSheet.Cells(1, 2)
End If
ws.Range("A2:N10000").Copy Destination:=cmbSheet.Cells((tmpIndex * 10000) + 2 - tmpIndex, 2)
cmbSheet.Cells((tmpIndex * 10000) + 2, 1).Value = ws.Name
tmpIndex = tmpIndex + 1
End If
Next ws
End Sub

Related

Run time error '91' for copying rows to another sheet

Sub retrieve()
Dim r As Long, endrow As Long, pasterowindex As Long, Cells() As String, Columns As Range
Sheets("Raw Trade Log").Range("A4").Select
Selection.End(xlDown).Select: endrow = ActiveCell.Row
pasterowindex = 1
For r = 4 To endrow
If Cells(r, Columns(17).Value = "Y") Then
Rows(r).Select
Selection.Copy
Sheets("Completed Trade log").Select
Rows(pasterowindex).Select
ActiveSheet.Paste
pasterowindex = pasterowindex + 1
Sheets("Raw Trade Log").Select
End If
Next r
End Sub
I am trying to tell vba to automatically copy the whole row to another sheet when value in a column becomes "Y" however I keep getting
Run time error '91'
from If Cells(r, Columns(17).Value = "Y") Then and I have not idea how to fix it, can someone kindly let me know where did I made a mistake?
The error is mainly because of the Select and the Activate words. These are really not programming-friendly and one should be careful around them. Thus, the best way is to avoid them completely - How to avoid using Select in Excel VBA.
Concerning the task "How to copy rows under some condition to another worksheet" this is a small example, without the Select and Activate:
Sub TestMe()
Dim wksTarget As Worksheet: Set wksTarget = Worksheets(1)
Dim wksSource As Worksheet: Set wksSource = Worksheets(2)
Dim r As Long
For r = 4 To 50
If wksSource.Cells(r, "A") = "y" Then
wksSource.Rows(r).Copy Destination:=wksTarget.Rows(r)
End If
Next r
End Sub
the 50 is hardcoded, it can be referred as a variable as well;
the code checks for the word y in column A, but it can be changed by changing the A in If wksSource.Cells(r, "A") to something corresponding.
you could use AutoFilter():
Sub retrieve()
With Sheets("Raw Trade Log") 'reference your "source" sheet
With .Range("A3", .Cells(.Rows.Count, 1).End(xlDown)).Offset(, 16) ' reference referenced sheet column Q cells from row 3 (header) down to column A last not empty row
.AutoFilter Field:=1, Criteria1:="y" ' filtere referenced column with "y" content
If Application.Subtotal(103, .Cells) > 1 Then .Resize(.Rows.Count - 1, .Columns.Count).Offset(1).SpecialCells(xlCellTypeVisible).EntireRow.Copy Destination:=Sheets("Completed Trade log").Range("A1") ' if any filtered cell other than header, copy filtered cells entire row to "target" sheet
End With
.AutoFilterMode = False
End With
End Sub

Setting range on different sheet causing error

I am fairly new to VBA and Wondering if someone can help me out.
I have 2 different sheets in a workbook.
Sheet(Raw Data) has a range with Cost Center NameS (Cell BC3 down to empty)
I have to copy Sheet(CC Template) and name it the right 5 characters of Sheet(Raw Data).Range(BC3).Value and change Cell(2,2).value to Sheet(Raw Data).Range(BC3).Value...
Then I want it to go to the next cell in Sheet(Raw Data) ...BC4 and create the second sheet and change the name and Cell(2,2) accordingly until the list in Sheet(Raw Data) ends.
Here is my Code. It creates the first worksheet but then I get run-time Error '1004' at Sheets("Raw Data").Range("BC3").Select in the do until loop. I would like to get rid of X and CCName variable from the code also if possible.
Sub CreateCCTabsinNewPlantFile2()
Dim i As Integer
Dim x As Integer
Dim CCName As String
i = ActiveWorkbook.Worksheets.Count
x = 1
' Select cell BC3, *first line of data*.
Sheets("Raw Data").Range("BC3").Select
' Set Do loop to stop when an empty cell is reached.
Do Until IsEmpty(ActiveCell)
CCName = ActiveCell.Value
' Code to make worksheets
Worksheets("CC Template").Copy after:=Worksheets(i)
ActiveSheet.Name = Right(CCName, 5)
ActiveSheet.Cells(2, 2).Value = CCName
' Step down 1 row from present location.
Sheets("Raw Data").Range("BC3").Select
ActiveCell.Offset(x, 0).Select
x = x + 1
Loop
End Sub
Sub CreateCCTabsinNewPlantFile2()
Dim i As Integer
Dim X As Integer
X = 3 'Starting row in Sheet("Raw Data")
With ThisWorkbook.Sheets("Raw Data")
Do Until .Cells(X, 55).Value = "" 'cells(x,55)= BC3. First time x= 3 so Cells(3,55)=BC3
i = ThisWorkbook.Worksheets.Count 'we update count everytime, because we are adding new sheets
ThisWorkbook.Worksheets("CC Template").Copy after:=ThisWorkbook.Worksheets(i)
ThisWorkbook.ActiveSheet.Name = Right(.Cells(X, 55).Value, 5)
ThisWorkbook.ActiveSheet.Cells(2, 2).Value = .Cells(X, 55).Value
' We increade X. That makes check a lower rower in next loop.
X = X + 1
Loop
End With
End Sub
Hope this helps.
You get error1004 because you can use Range.Select only in Active Sheet. If you want to Select a Range in different Sheet, first you must Activate that sheet with Sheets("Whatever").Activate.
Also, I Updated your code so you can execute it from any sheet. Your code forces user to have Sheets ("Raw Data") as the ActiveSheet.
Try not use too much Select if you can avoid it. And also , try to get used to Thisworkbook instead of ActiveWorkbook. If you work always in same workbook, is not a problem, but if your macros operate several workbooks, you'll need to difference when to use each one.
Try this code
Sub Test()
Dim rng As Range
Dim cel As Range
With Sheets("Raw Data")
Set rng = .Range("BC3:BC" & .Cells(Rows.Count, "BC").End(xlUp).Row)
End With
Application.ScreenUpdating = False
For Each cel In rng
If Not SheetExists(cel.Value) Then
Sheets("CC Template").Copy After:=Sheets(Sheets.Count)
With ActiveSheet
.Name = Right(cel.Value, 5)
.Range("B2").Value = cel.Value
End With
End If
Next cel
Sheets("Raw Data").Activate
Application.ScreenUpdating = True
End Sub
Function SheetExists(sheetName As String) As Boolean
On Error Resume Next
SheetExists = (LCase(Sheets(sheetName).Name) = LCase(sheetName))
On Error GoTo 0
End Function

Macro to copy first two columns from all sheets to a master sheet is skipping sheets

I'm using this macro to copy columns A and B from all of my sheets into a new sheet named Master. What I notice is that entire sheets worth of information are missing in the master sheet and I can't figure out why. The format for my sheets is column A has a string of characters that follow this structure: M2,004,005,004,007,17,096,01:07:45,45 and column B is just a date such as 4/19/2017.
I have hundreds of these sheets in my workbook and each has 224 rows that I need to copy into a single master sheet. Could anyone help me figure out how to get this code to stop skipping sheets?
Thanks.
Sub CreateMaster()
Dim J As Integer
On Error Resume Next
Sheets(1).Select
Worksheets.Add
Sheets(1).Name = "Master"
Sheets(2).Activate
Range("A1:B1").EntireRow.Select
Selection.Copy Destination:=Sheets(1).Range("A1:B1")
For J = 2 To Sheets.Count
Sheets(J).Activate
Range("A1:B1").Select
Selection.CurrentRegion.Select
Selection.Copy Destination:=Sheets(1).Range("A65536:B65536").End(xlUp)(2)
Next
End Sub
while searching for solutions online, I came across this macro that seems to do the same thing, but also seems to skip the exact same sheets as my macro does.
Sub CopyFromWorksheets()
Dim wrk As Workbook 'Workbook object - Always good to work with object
variables
Dim sht As Worksheet 'Object for handling worksheets in loop
Dim trg As Worksheet 'Master Worksheet
Dim rng As Range 'Range object
Dim colCount As Integer 'Column count in tables in the worksheets
Set wrk = ActiveWorkbook 'Working in active workbook
For Each sht In wrk.Worksheets
If sht.Name = "Master" Then
MsgBox "There is a worksheet called as 'Master'." & vbCrLf & _
"Please remove or rename this worksheet since 'Master' would be" & _
"the name of the result worksheet of this process.", vbOKOnly + vbExclamation, "Error"
Exit Sub
End If
Next sht
'We don't want screen updating
Application.ScreenUpdating = False
'Add new worksheet as the last worksheet
Set trg = wrk.Worksheets.Add(After:=wrk.Worksheets(wrk.Worksheets.Count))
'Rename the new worksheet
trg.Name = "Master"
'Get column headers from the first worksheet
'Column count first
Set sht = wrk.Worksheets(1)
colCount = sht.Cells(1, 255).End(xlToLeft).Column
'Now retrieve headers, no copy&paste needed
With trg.Cells(1, 1).Resize(1, colCount)
.Value = sht.Cells(1, 1).Resize(1, colCount).Value
'Set font as bold
.Font.Bold = True
End With
'We can start loop
For Each sht In wrk.Worksheets
'If worksheet in loop is the last one, stop execution (it is Master worksheet)
If sht.Index = wrk.Worksheets.Count Then
Exit For
End If
'Data range in worksheet - starts from second row as first rows are the header rows in all worksheets
Set rng = sht.Range(sht.Cells(2, 1), sht.Cells(65536, 1).End(xlUp).Resize(, colCount))
'Put data into the Master worksheet
trg.Cells(65536, 1).End(xlUp).Offset(1).Resize(rng.Rows.Count, rng.Columns.Count).Value = rng.Value
Next sht
'Fit the columns in Master worksheet
trg.Columns.AutoFit
'Screen updating should be activated
Application.ScreenUpdating = True
End Sub
as a workaround, since only the most recent data is immediately pertinent, I worked around it, but deleting the first 150 sheets. that still left around 100 sheets for my macro to work on, but now the missing pieces of data seem to be there. I wonder if there's something about the quantity of sheets that is causing this to malfunction?
Comments may not get it across correctly. Restructure your loop (and add the variables mentioned).
Dim x as Long
Dim thisSht as Worksheet
For x = 1 to wrk.Worksheets.Count
set thisSht = wrk.Worksheets(x)
'Data range in worksheet - starts from second row as first rows are the header rows in all worksheets
Set rng = thisSht.Range(sht.Cells(2, 1), sht.Cells(65536, 1).End(xlUp).Resize(, colCount))
'Put data into the Master worksheet
trg.Cells(65536, 1).End(xlUp).Offset(1).Resize(rng.Rows.Count, rng.Columns.Count).Value = rng.Value
Next x

Sort, Loop, copy into new worksheet with cell value name VBA

I know this has been asked lot of times but I'm having a trouble with VBA, I am very new to VBA.
I'm working with a single workbook that has a working worksheet. basically I need to sort the Currency column, currently have 14 currencies, I need loop through it (since currency may add through time depending on the customer) then copy the row with the criteria paste it to another sheet with its cell value.
my code below.
Option Explicit
Sub SortCurrency()
Dim rng As Range
Dim xCell As Range
Dim I As Long
Dim J As Long
I = Worksheets("Sheet1").UsedRange.Rows.Count
J = Worksheets("Sheet2").UsedRange.Rows.Count
If J = 1 Then
If Application.WorksheetFunction.CountA(Worksheets("Sheet2").UsedRange) = 0 Then J = 0
End If
Set rng = Worksheets("Sheet1").Range("AB2:AB" & I)
On Error Resume Next
Application.ScreenUpdating = False
For Each xCell In rng
If CStr(xCell.Value) = "USD" Then
Sheets.Add After:=Sheets(Sheets.Count)
Sheets(Sheets.Count).Name = xCell.Value
xCell.EntireRow.Copy Destination:=Sheets(Sheets.Count).Name = xCell.Value.Range("A" & J + 1)
'Sheets.Add After:=Sheets(Sheets.Count)
'Sheets(Sheets.Count).Name = xCell.Value
Application.CutCopyMode = False
J = J + 1
End If
Next
Application.ScreenUpdating = True
End Sub
I basically got the codes from my research, add them up and not coming into the way I wanted. I wanted to keep the header and the values with criteria,
i,e currency column "AB" is USD as per example above, but the problem is it'll be a lot of coding because I have to go through all 14 currencies plus if there will be new currency that will be added,
also I know there is a way of not declaring multiple sheets and just having another new worksheet with the cell value name but I'm having a problem getting it done all at once. if there will be a simpler and powerful code. I am greatly thankful.
you may want to try this code, exploiting Autofilter() method of Range object
Option Explicit
Sub SortCurrency()
Dim currRng As Range, dataRng As Range, currCell As Range
With Worksheets("Currencies") '<--| change "Currencies" to your actual worksheet name to filter data in and paste from
Set currRng = .Range("AB1", .Cells(.Rows.Count, "AB").End(xlUp))
Set dataRng = Intersect(.UsedRange, currRng.EntireRow)
With .UsedRange
With .Resize(1, 1).Offset(, .Columns.Count)
With .Resize(currRng.Rows.Count)
.Value = currRng.Value
.RemoveDuplicates Array(1), Header:=xlYes
For Each currCell In .SpecialCells(xlCellTypeConstants)
currRng.AutoFilter field:=1, Criteria1:=currCell.Value
If Application.WorksheetFunction.Subtotal(103, currRng) - 1 > 0 Then
dataRng.SpecialCells(xlCellTypeVisible).Copy Destination:=GetOrCreateWorksheet(currCell.Value).Range("A1")
End If
Next currCell
.ClearContents
End With
End With
End With
.AutoFilterMode = False
End With
End Sub
Function GetOrCreateWorksheet(shtName As String) As Worksheet
On Error Resume Next
Set GetOrCreateWorksheet = Worksheets(shtName)
If GetOrCreateWorksheet Is Nothing Then
Set GetOrCreateWorksheet = Worksheets.Add(After:=Sheets(Sheets.Count))
GetOrCreateWorksheet.name = shtName
End If
End Function
You're pretty close with what you've got, but there's a few things to note:
On Error Resume Next is normally a bad plan as it can hide a whole lot of sins. I use it in the code below, but only because I immediately deal with any error that might have happened.
xCell.Value.Range("A" & J + 1) makes no sense. Chop out the middle of that line to leave xCell.EntireRow.Copy Destination:=Sheets(Sheets.Count).Range("A" & J + 1)
Rather than checking if the value is a specific currency, you should be taking the value, whatever currency it is, and dealing with it appropriately.
Using J as a counter works for one currency, but when dealing with multiple, it'll be easier to just check where it should go on the fly.
All told, the below code should be close to what you're looking for.
Option Explicit
Sub SortCurrency()
Dim rng As Range
Dim xCell As Range
Dim targetSheet As Worksheet
Dim I As Long
Dim J As Long
I = Worksheets("Sheet1").UsedRange.Rows.Count
J = Worksheets("Sheet2").UsedRange.Rows.Count
If J = 1 Then
If Application.WorksheetFunction.CountA(Worksheets("Sheet2").UsedRange) = 0 Then J = 0
End If
Set rng = Worksheets("Sheet1").Range("AB2:AB" & I)
Application.ScreenUpdating = False
For Each xCell In rng
Set targetSheet = Nothing
On Error Resume Next
Set targetSheet = Sheets(xCell.Value)
On Error GoTo 0
If targetSheet Is Nothing Then
Sheets.Add After:=Sheets(Sheets.Count)
Set targetSheet = Sheets(Sheets.Count)
targetSheet.Name = xCell.Value
xCell.EntireRow.Copy Destination:=targetSheet.Range("A" & J + 1)
Else
xCell.EntireRow.Copy Destination:=targetSheet.Range("A" & targetSheet.Range("A" & Rows.Count).End(xlUp).Row + 1)
End If
Application.CutCopyMode = False
Next
Application.ScreenUpdating = True
End Sub
OK, there's quite a lot going on here... I'm going to try and tackle one problem at a time.
1 - You could do with testing whether a worksheet already exists rather than creating it every time
Assuming you want to do something for each and every currency in your loop, I would suggest not using the if condition you're using at the moment, "if value = "USD"", and instead use the cell value to determine the name of the sheet, whatever the cell value is.
First of all you need a seperate function to test whether the sheet exists, like
Public Function DoesSheetExist(SheetName as String)
On Error Resume Next
Dim WkSheet as WorkSheet
'sets worksheet to be the sheet NAMED the current currency name
Set WkSheet = Sheets(SheetName)
'because of on error resume next, WkSheet will simply be "Nothing" if no such sheet exists
If WkSheet is Nothing Then
DoesSheetExist = False
Else
DoesSheetExist = True
End If
End Function
You can then call this function in your code, and only create new sheets when you need to
2 - The loop itself
So instead, I would suggest your loop probably wants to look more like this:
Dim xSheet as Worksheet 'declare this outside the loop
For Each xCell In rng
If DoesSheetExist(xCell.Value) Then
set xSheet = Sheets(xCell.Value) 'this is the code for if the sheet does exist - sets the sheet by the sheet name rather than index
Else
set xSheet = Sheets.Add After:=Sheets(Sheets.Count)
xSheet.Name = xCell.Value
End if
With this setup, for every currency your loop will either set xSheet to the currency sheet that already exists, or create that sheet. This assumes that you want to do the same thing to all currencies, if not then extra conditions will need adding in
3 - the copy/paste line itself
xCell.EntireRow.Copy Destination:=Sheets(Sheets.Count).Name = xCell.Value.Range("A" & J + 1)
I don't think this code says what you think it does - what this code actually says is "Copy the Entire Row to the last Sheet's name, and make it equal to the range within xCell's Value at A, (J)+1
I think what you actually wanted to say was this:
xCell.EntireRow.Copy Destination:=Sheets(Sheets.Count).Range("A" & J + 1)
However, if you're using the code I gave you above you can instead use this now:
xCell.EntireRow.Copy Destination:=xSheet.Range("A" & J + 1)
In fact, you'd be better off doing that, especially if there is a chance that the sheets already existed and were picked up by DoesSheetExist
Personally I would also rather transfer values over than use copy/paste any day, but that's just an efficiency thing, the above should function fine.

Clear entire row if no content/text/number found in cell

I am writing a VBA script that will delete (clear content) of the entire row if one of the cells within the rows is found to be blank or does not have any text or integer value.
I'm almost there, but I think my code is stuck in the for loop. Please let me know how I can further improve my code.
Sub Remove_Rows_BlankData()
For SheetCount = 1 To Sheets.Count 'SHEET LEVEL
Sheets(SheetCount).Visible = True
Sheets(SheetCount).Select
StartRow = 2
' EndRow = Cells(ActiveSheet.UsedRange.Rows.Count, 34)
LastRow = ActiveSheet.UsedRange.Rows.Count
LastCol = ActiveSheet.UsedRange.Columns.Count
Dim myRange As Range
Set myRange = Range(Cells(StartRow, 1), Cells(LastRow, LastCol))
'REMOVE ROWS W/ ANY BLANK CELLS
Dim DRow As Variant ' Sets DRow = Row w/ Blank
'From start row to last row
Cells.SpecialCells(xlCellTypeBlanks).EntireRow.Select
Selection.ClearContents
Next
End Sub
I've rewritten your sub to avoid having to Select a worksheet in order to use it. By referencing each worksheet in the loop using a With ... End With statement, the cells and properties of each worksheet can be dealt with without resorting to selecting¹ the worksheet just to use the inherent association of the ActiveSheet.
Sub Remove_Rows_BlankData()
Dim ws As Long, fr As Long, lr As Long, lc As Long
On Error Resume Next 'just in case there are no blank cells
For ws = 5 To Worksheets.Count 'SHEET LEVEL
With Worksheets(ws)
.Visible = True
'Sheets(SheetCount).Select 'not necessary to select in order to process
fr = 2
' EndRow = Cells(ActiveSheet.UsedRange.Rows.Count, 34)
lr = .UsedRange.Rows.Count
lc = .UsedRange.Columns.Count
With .Range(.Cells(fr, 1), .Cells(lr, lc))
.SpecialCells(xlCellTypeBlanks).EntireRow.ClearContents
End With
End With
Next ws
End Sub
Note that no variables are created (aka Dim) within the loop; only reassigned values.
¹ See How to avoid using Select in Excel VBA macros for more methods on getting away from relying on select and activate to accomplish your goals.