I want to select multiple columns by the column header in an Excel sheet, then copy these columns into a new workbook.
With the code below, Excel opens one book per column instead of pasting all of the selected columns into sheet1 of the new workbook.
I recorded a macro for this task, but the column header changes every time so I cannot depend on recorded macros.
Sub Colheadr()
Dim wsO As Worksheet
'Dim wsF As Worksheet....I comment out this line
Dim i As Integer
Application.ScreenUpdating = False
Set wsO = ActiveSheet
'Set wsF = Worksheets("Final").....I comment out this line
myColumns = Array("Facility", "Last Name", "First Name", "MRN", "adm date")
With wsO.Range("A1:W1")
For i = 0 To UBound(myColumns)
On Error Resume Next
.Find(myColumns(i)).EntireColumn.Copy
Workbook.Add
ActiveSheet.Paste
'Destination:=wsF.Cells(1, i + 1)...I comment out this line
Err.Clear
Next i
End With
Set wsO = Nothing
Set wsF = Nothing
Application.ScreenUpdating = True
End Sub
Try this out:
Public Sub CopyBetweenBooks()
Dim myCollection As Collection
Dim myIterator As Variant
Dim myRng As Range
Dim xlcell As Variant
Dim otherwb As Workbook
Dim mywb As Workbook
Dim colCounter As Integer
Set mywb = ThisWorkbook
Set myCollection = New Collection
'Create a collection of header names to search through
myCollection.Add ("Header1")
myCollection.Add ("Header2")
myCollection.Add ("Header3")
'Where to search, this is the header
Set myRng = ActiveSheet.Range("A1:W1")
Set otherwb = Workbooks.Add
colCounter = 0
For Each xlcell In myRng.Cells ' look through each cell in your header
For Each myIterator In myCollection ' look in each item in the collection
If myIterator = xlcell.Value Then ' when the header matches what you are looking for
colCounter = colCounter + 1 ' creating a column index for the new workbook
mywb.ActiveSheet.Columns(xlcell.Column).Copy
otherwb.ActiveSheet.Columns(colCounter).Select
otherwb.ActiveSheet.Paste
End If
Next
Next
End Sub
I also have the same problem 3 years later in 2019. Ryan Wildry was right. Instead of collection data type use array to maintain user defined column sequence. That's why I am referring Ryan Wildry's solution with additional lines.
Public Sub CopyBetweenBooks()
Dim myCollection(1 To 3) As String
Dim myIterator As Variant
Dim myRng As Range
Dim xlcell As Variant
Dim otherwb As Workbook
Dim mywb As Workbook
Dim colCounter As Integer
Set mywb = ThisWorkbook
'Create an array of header names to search through
myCollection(1) ="Header1"
myCollection(2) ="Header2"
myCollection(3) ="Header3"
'Where to search, this is the header
Set myRng = ActiveSheet.Range("A1:W1")
Set otherwb = Workbooks.Add
colCounter = 0
For i = LBound(myCollection) To UBound(myCollection)
For Each xlcell In myRng.Cells ' look through each cell in your header
If myCollection(i) = xlcell.Value Then ' when the header matches what you are looking for
colCounter = colCounter + 1 ' creating a column index for the new workbook
mywb.ActiveSheet.Columns(xlcell.Column).Copy
otherwb.ActiveSheet.Columns(colCounter).Select
otherwb.ActiveSheet.Paste
End If
Next
Next
Next
End Sub
Related
So I have a workbook with multiple sheets. All contain the same columns but just different categorical data. I want to grab all the data from those sheets and display/populate to a master sheet in the workbook.
I have tried different methods, but none of them are dynamic. The amount of data can be changed (+/-, either more rows or less rows) in each sheet. Each method I have found seems to be a static solution.
One example is to use the Consolidate option under the data tab, and add the respective reference/range for each sheet you would like to add (not dynamic).
Another option I found was a VBA macro, which populates the headers over and over, which I do not want to happen either, I want them all under the same header (Since the columns are already the same)
Sub Combine()
'UpdatebyExtendoffice20180205
Dim I As Long
Dim xRg As Range
Worksheets.Add Sheets(1)
ActiveSheet.Name = "Combined"
For I = 2 To Sheets.Count
Set xRg = Sheets(1).UsedRange
If I > 2 Then
Set xRg = Sheets(1).Cells(xRg.Rows.Count + 1, 1)
End If
Sheets(I).Activate
ActiveSheet.UsedRange.Copy xRg
Next
End Sub
Is this achievable?
Sheet 1
Sheet 2
Master Sheet Should Be:
But actually returns the following:
Will this constantly run each time the workbook is closed/opened/updated if it is a macro enabled workbook?
Consolidate All Worksheets
It is assumed that the Combined worksheet already exists with at least the headers which will stay intact.
To make it more efficient, only values are copied (no formats or formulas).
It will utilize the Worksheet Activate event: each time you activate (select) the combined worksheet, the data will automatically be updated.
Sheet Module of the Combined worksheet e.g. Sheet10(Combined)
Option Explicit
Private Sub Worksheet_Activate()
CombineToMaster
End Sub
Standard Module e.g. Module1
Option Explicit
Sub CombineToMaster()
Const dName As String = "Combined"
Dim wb As Workbook: Set wb = ThisWorkbook ' workbook containing this code
Dim dws As Worksheet: Set dws = wb.Worksheets(dName)
Dim drrg As Range
With dws.UsedRange
If .Rows.Count = 1 Then
Set drrg = .Offset(1)
Else
.Resize(.Rows.Count - 1).Offset(1).Clear
Set drrg = .Resize(1).Offset(1)
End If
End With
Dim sws As Worksheet
Dim srg As Range
Dim drg As Range
Dim rCount As Long
For Each sws In wb.Worksheets
If sws.Name <> dName Then
With sws.UsedRange
rCount = .Rows.Count - 1
If rCount > 0 Then
Set srg = .Resize(rCount).Offset(1)
drrg.Resize(rCount).Value = srg.Value
Set drrg = drrg.Offset(rCount)
End If
End With
End If
Next sws
End Sub
VBA Solution
Sub Combine()
Dim wsCombine As Worksheet: Set wsCombine = GetSheetCombine
Dim dataSheets As Collection: Set dataSheets = GetDataSheets
' Copy Header
dataSheets.Item(1).UsedRange.Rows(1).Copy
wsCombine.Range("A1").PasteSpecial xlPasteAll
wsCombine.Range("A1").PasteSpecial xlPasteColumnWidths
Application.CutCopyMode = False
' Copy data
Dim rngDest As Range: Set rngDest = wsCombine.Range("A2")
Dim srcRng As Range
Dim ws As Worksheet
For Each ws In dataSheets
' Drop header row
With ws.UsedRange
Set srcRng = .Offset(1, 0).Resize(.Rows.Count - 1)
End With
srcRng.Copy rngDest
Set rngDest = rngDest.Offset(srcRng.Rows.Count)
Next ws
Application.CutCopyMode = False
MsgBox "Done!", vbInformation
End Sub
Private Function GetSheetCombine() As Worksheet
Dim ws As Worksheet
With Worksheets
On Error Resume Next
Set ws = .Item("Combine")
On Error GoTo 0
If ws Is Nothing Then
Set ws = .Add(Before:=.Item(1))
ws.Name = "Combine"
Else
ws.Cells.Clear ' clear any existing data
End If
End With
Set GetSheetCombine = ws
End Function
Private Function GetDataSheets() As Collection
Dim Result As New Collection
Dim ws As Worksheet
For Each ws In Worksheets
If ws.Name <> "Combine" Then Result.Add ws
Next ws
Set GetDataSheets = Result
End Function
As to your question "Will this run every time macro enabled workbook is open?".
No. You will need to put this in a VBA module and run it every time you need, via the Macro dialog (View->Macros), or link a button to it.
Here is the sub I am using that splits loops through each tab and split them into multiple workbooks based on the user-specified column, "Manufacturer Name".
Sub SplitSheetIntoMultWkbksBasedOnCol(Col As String)
Dim objWorksheet As Excel.Worksheet
Dim nLastRow, nRow, nNextRow As Integer
Dim strColumnValue As String
Dim objDictionary As Object
Dim varColumnValues As Variant
Dim varColumnValue As Variant
Dim objExcelWorkbook As Excel.Workbook
Dim objSheet As Excel.Worksheet
Dim wsSheet As Worksheet
For Each wsSheet In Worksheets
If wsSheet.Name <> "Open" Then
wsSheet.Activate
Set objWorksheet = ActiveSheet
nLastRow = objWorksheet.Range("A" & objWorksheet.Rows.Count).End(xlUp).Row
Set objDictionary = CreateObject("Scripting.Dictionary")
For nRow = 2 To nLastRow
'Get the specific Column
strColumnValue = objWorksheet.Range(Col & nRow).Value
If objDictionary.Exists(strColumnValue) = False Then
objDictionary.Add strColumnValue, 1
End If
Next
varColumnValues = objDictionary.Keys
For i = LBound(varColumnValues) To UBound(varColumnValues)
varColumnValue = varColumnValues(i)
'Create a new Excel workbook
Set objExcelWorkbook = Excel.Application.Workbooks.Add
Set objSheet = objExcelWorkbook.Sheets(1)
objSheet.Name = objWorksheet.Name
objWorksheet.Rows(1).EntireRow.Copy
objSheet.Activate
objSheet.Range("A1").Select
objSheet.Paste
For nRow = 2 To nLastRow
If CStr(objWorksheet.Range(Col & nRow).Value) = CStr(varColumnValue) Then
objWorksheet.Rows(nRow).EntireRow.Copy
nNextRow = objSheet.Range("A" & objWorksheet.Rows.Count).End(xlUp).Row + 1
objSheet.Range("A" & nNextRow).Select
objSheet.Paste
objSheet.Columns("A:B").AutoFit
End If
Next
Next
End If
Next wsSheet
Workbooks("Open_Spreadsheet_Split.xlsm").Activate
Sheets(1).Activate
End Sub
This is ending up making way too many workbooks. So instead, for each tab, I want to copy the rows with the same Manufacturer to the same workbook.
EDIT: make sure headers from each source sheet are included on each destination sheet.
Try this out:
Sub SplitSheetIntoMultWkbksBasedOnCol(Col As String)
Dim wbSrc As Workbook, ws As Worksheet, wsTmp As Worksheet
Dim dict As Object, lastRow As Long, nRow As Long, v
Dim dictHeader As Object 'for tracking whether headers have been copied
Set dict = CreateObject("Scripting.Dictionary")
Set wbSrc = ActiveWorkbook
Application.ScreenUpdating = False
For Each ws In wbSrc.Worksheets
If ws.Name <> "Open" Then
Set dictHeader = CreateObject("Scripting.Dictionary") 'reset header-tracking dictionary
For nRow = 2 To ws.Cells(Rows.Count, "A").End(xlUp).Row
v = ws.Cells(nRow, Col).Value 'get the specific Column
'need a new workbook?
If Not dict.exists(v) Then
Set wsTmp = Application.Workbooks.Add(xlWBATWorksheet).Worksheets(1) 'add new workbook with one sheet
dict.Add v, wsTmp.Range("A1") 'add key and the first paste destination
End If
'first row from this sheet for this value of `v`?
If Not dictHeader.exists(v) Then
ws.Rows(1).Copy dict(v) 'copy headers from this sheet
Set dict(v) = dict(v).Offset(1, 0) 'set next paste position
dictHeader.Add v, True 'flag header as copied
End If
ws.Rows(nRow).Copy dict(v) 'copy the current row
Set dict(v) = dict(v).Offset(1, 0) 'set next paste position
Next nRow
End If 'not "open" sheet
Next ws
Workbooks("Open_Spreadsheet_Split.xlsm").Activate 'ThisWorkbook?
Sheets(1).Activate
End Sub
I have:
Dim xlBook as Workbook
Dim xlSheet as Worksheet
Dim xlTable as ListObject
Dim xlTable Column as ListColumn
Dim xlChartObject as ChartObject
Dim xlTableObject as ListObject
Dim ObjectArray() as String
Dim ObjectIndexArray as Integer
'set the book'
Set xlBook = ThisWorkbook
'loop through each worksheet'
For each xlSheet in XlBook.Worksheets
'if we have charts
if xlSheet.ChartObjects.Count > 0 then
'grab each name
For each xlChartObject in xlSheet.ChartObjects
'update count
ObjectArrayIndex = ObjectArrayIndex + 1
ReDim Preserve ObjectArray(ObjectArrayIndex)
'add chart object to array
ObjectArray(ObjectArrayIndex) = xlChartObject.Name & "-" & xlSheet.Name & "-" & TypeName(xlChartObject)
'grab sheet
set xlSheet = xlBook.Worksheets("Export")
'grab table from sheet
set xlTable = xlSheet.ListObjects("ExportToPowerPoint")
'grab object column from table
Set xlTableColumn = xlTable.ListColumns("Object")
'set validation dropdown
With xlTableColumn.DataBodyRange.Validation
'delete old
.delete
'add new data
.add Type:=xlValidateList, AlertStyle:=xlValidAlertStop, Operator:=xlBetween, Formula1:=Join(ObjectArray, "-")
'make sure its a dropdown
.InCellDropdown = true
end with
end sub
This code works well for grabbing charts, and similarly I utilized ListObject in order to then grab tables as well.
My Issue comes with creating another block of code to grab named ranges in excel. So not Tables or Charts
Any help would be greatly appreciated!
Activesheet.names should give array of named ranges on activeshee. Add that to for each loop and you should be done.
Names: Name, Range, Range Address
When you select a range in Excel and you use Name a range - Define name and you enter the name and press Ok, the name is 'saved' in workbook scope. So this code applies to workbook scope.
If you need to handle named ranges of worksheet scope (e.g. when you're using the same names on different worksheets), you will have to write the code somewhat differently.
The 'grab' procedures (Subs) illustrate how to return all names, range addresses and ranges (range objects) in an array and then print the contents (the Address property for ranges) to the Immediate window.
The 'get' procedure (Function) returns the array of the names and you could say it is the first 'grab' procedure written as a function.
The last procedure tests the function.
Here is another post I recently did on Names with links to some study material at the beginning.
The Code
Option Explicit
Sub grabNameNames()
Dim wb As Workbook: Set wb = ThisWorkbook
Dim nmsCount As Long: nmsCount = wb.Names.Count
Dim nms() As String: ReDim nms(1 To nmsCount)
Dim nm As Name
Dim n As Long
For Each nm In wb.Names
n = n + 1
nms(n) = nm.Name
Next nm
If Not IsEmpty(nms) Then
Debug.Print Join(nms, vbLf)
End If
End Sub
Sub grabNameAddresses()
Dim wb As Workbook: Set wb = ThisWorkbook
Dim nmsCount As Long: nmsCount = wb.Names.Count
Dim nma() As String: ReDim nma(1 To nmsCount)
Dim nm As Name
Dim n As Long
For Each nm In wb.Names
n = n + 1
nma(n) = nm.RefersToRange.Address(0, 0)
Next nm
If Not IsEmpty(nma) Then
Debug.Print Join(nma, vbLf)
End If
End Sub
Sub grabNameRanges()
Dim wb As Workbook: Set wb = ThisWorkbook
Dim nmsCount As Long: nmsCount = wb.Names.Count
Dim nmr As Variant: ReDim nmr(1 To nmsCount)
Dim nm As Name
Dim n As Long
For Each nm In wb.Names
n = n + 1
Set nmr(n) = nm.RefersToRange
Next nm
If Not IsEmpty(nmr) Then
For n = 1 To UBound(nmr)
Debug.Print nmr(n).Address
Next n
End If
End Sub
Function getNameNames(wb As Workbook)
Dim nmsCount As Long: nmsCount = wb.Names.Count
Dim nms() As String: ReDim nms(1 To nmsCount)
Dim nm As Name
Dim n As Long
For Each nm In wb.Names
n = n + 1
nms(n) = nm.Name
Next nm
getNameNames = nms
End Function
Sub TESTgetNameNames()
Dim wb As Workbook: Set wb = ThisWorkbook
Dim Data As Variant: Data = getNameNames(wb)
If Not IsEmpty(Data) Then
Debug.Print Join(Data, vbLf)
End If
End Sub
I'm currently doing VBA project which need to copy from a workbook to another, which the WBookPst is the workbook I firstly open (use) meanwhile WBookCopy is the workbook where I open based on the links where I got by listing all ".xslt" format in a File into my Sheet1 of my first workbook. Here is my code :
Sub SortFiles()
'Set up your variables and turn off screen updating.
'Dim iCounter As Integer
Application.ScreenUpdating = False
'Sort the rows based on the data in column C
Columns("A:C").Sort key1:=Range("C2"), _
order1:=xlDescending, Header:=xlYes
Application.ScreenUpdating = True
Dim WBookCopy As Workbook
Dim WBookPst As Workbook
Dim filePath As String
Dim sheetName As String
Dim sheetCopy As Worksheet
Dim sheetPate As Worksheet
Dim rngCopy As Range
Dim rngPst As Range
filePath = Range("B2").Value
Set WBookCopy = Workbooks.Open(filePath)
Columns(30).Insert
For i = 1 To Sheets.count
Cells(i, 30) = Sheets(i).Name
Next i
sheetName = Range("AD1").Value
Set sheetCopy = WBookCopy.Worksheets(sheetName)
Set rngCopy = sheetCopy.Range("A:AA").Copy
Set WBookPst = ThisWorkbook
Set sheetPaste = WBookPst.Worksheets("Sheet1").Activate
Set rngCopy = sheetPaste.Range("A:AA").Select
ActiveSheet.Paste
End Sub
At Set rngCopy = sheetCopy.Range("A:AA").Copy there's error "Objects required".
What does that mean?
By the way, is how I copy and paste the data between sheets correct?
The issue is that rngCopy is of type range and you can't set it equal to a method (copy). Remove the .Copy and you should be fine. You also don't need to set the worksheet out range to a variable. You could just do one line that says WBookCopy.SheetName.Range("A:AA").Copyand then another line to paste.
As #Wyatt mentioned - your copy\paste syntax is incorrect
Here are 2 ways to do it:
Worksheets("Sheet1").Range("A:AA").Copy
Worksheets("Sheet2").Range("A1").PasteSpecial xlPasteAll
or
Worksheets("Sheet1").Range("A:AA").Copy Destination:=Worksheets("Sheet2").Range("A1")
Hi I am using the following code to copy values from an excel worksheet into a predefined table in word. The below works fine for 1 column, how can I get it to tranfer the data for all 5 columns? Thanks
Sub ExportData()
Dim wdApp As Word.Application
Dim wdDoc As Word.Document
Dim wdCell As Word.Cell
Dim i As Long
Dim wbBook As Workbook
Dim wsSheet As Worksheet
Dim rnData As Range
Dim vaData As Variant
Set wbBook = ThisWorkbook
Set wsSheet = wbBook.Worksheets("Sheet1")
With wsSheet
Set rnData = .Range("A1:E10")
End With
'Add the values in the range to a one-dimensional variant-array.
vaData = rnData.Value
'Here we instantiate the new object.
Set wdApp = New Word.Application
'Here the target document resides in the same folder as the workbook.
Set wdDoc = wdApp.Documents.Open(ThisWorkbook.Path & "\Test.doc")
'Import data to the first table and in the first column of a table in Microsoft Word.
For Each wdCell In wdDoc.Tables(1).Columns(1).Cells
i = i + 1
wdCell.Range.Text = vaData(i, 1)
Next wdCell
'Save and close the document.
With wdDoc
.Save
.Close
End With
'Close the hidden instance of Microsoft Word.
wdApp.Quit
'Release the external variables from the memory
Set wdDoc = Nothing
Set wdApp = Nothing
MsgBox "The data has been transfered to Test.doc, vbInformation"
End Sub
So this is a bit of a late answer, but try the following:
Add to declarations
Dim j As Long
Remove from declarations
Dim rnData As Range
Change
With wsSheet
Set rnData = .Range("A1:E10")
End With
'Add the values in the range to a one-dimensional variant-array.
vaData = rnData.Value
to
ReDim vaData(1 To 10, 1 To 5)
With wsSheet
vaData = .Range("A1:E10")
End With
And change
'Import data to the first table and in the first column of a table in Microsoft Word.
For Each wdCell In wdDoc.Tables(1).Columns(1).Cells
i = i + 1
wdCell.Range.Text = vaData(i, 1)
Next wdCell
to
For j = 1 To 5
i = 0
For Each wdCell In wdDoc.Tables(1).Columns(j).Cells
i = i + 1
wdCell.Range.Text = vaData(i, j)
Next wdCell
Next j
A correction
MsgBox "The data has been transferred to Test.doc", vbInformation