Using Autofilter with a dynamic range - excel

Still learning the ropes so bear with! I have a monthly data dump that will be copied into the workbook, it is always in the same format. I'm trying to write a macro that filters the data in a preset column using a list of names from another sheet within the workbook. Ideally I want to be able to add or remove names from the list. Once it has filtered I'd like it to copy all those visible cells and paste them into a new sheet.
I've started with using the autofilter and then a counting array, but I am getting an error AND it's not filtering. In that the filter is applied to the sheet, but it doesn't seem to be able to look for the actual names, and just returns blanks.
It does seem to count the right number of names in my dynamic list... so I'll take that.
So example data:
Worksheet: Names
Worksheet: Books
Code ideally takes the list of names from the Person column in "Names", looks through the Name column "Books", finds each match and then copies and dumps the entire row to a new sheet.
Here is my best attempt at writing something.
Sub FilterName()
Dim i As Long
Dim lastrow As Long
Dim arrSummary() As Variant
With ThisWorkbook.Sheets("Names")
lastrow = .Cells(Rows.Count, "B").End(xlUp).Row
ReDim arrSummary(1 To lastrow)
For i = 1 To lastrow
arrSummary(i) = .Cells(i, 1)
Next
End With
For i = LBound(arrSummary) To UBound(arrSummary)
With ThisWorkbook.Sheets("Books")
.Range("F:F").AutoFilter Field:=1, Criteria1:=arrSummary(i), Operator:=xlFilterValues
.ThisWorkbook.Sheets("Books").Range("A1:AA100000").SpecialCells(xlCellTypeVisible).Copy
'Getting error 438 here
.ThisWorkbook.Sheets("Loans").Paste
End With
Next i
End Sub
I did contemplate advanced filter but couldn't make that work even outside of VBA, and then didn't want to do the find route as felt it was clunky...Willing to explore these options though.
Cheers :)

Filter Names
It will write the values from column B (cCol) of the criteria worksheet (cws) to a 2D one-based one-column array (cData). Then it will loop through the values in the array and filter the 6th column (scCol) of the source worksheet (sws) by each of the array's values and copy the source range's (A:AA) rows that contain the matching cells to the first available row of the destination worksheet (dws) starting in column A (dfCol).
Option Explicit
Sub FilterNames()
' Criteria
Const cName As String = "Names"
Const cCol As String = "B"
Const cfRow As Long = 2
' Source
Const sName As String = "Books"
Const sCols As String = "A:AA"
Const scCol As Long = 6 ' also used for AutoFilter's Field parameter
Const sfRow As Long = 1
' Destination
Const dName As String = "Loans"
Const dfCol As String = "A"
Const dfRow As Long = 2
' Workbook
Dim wb As Workbook: Set wb = ThisWorkbook
' Criteria
Dim cws As Worksheet: Set cws = wb.Worksheets(cName)
Dim clRow As Long: clRow = cws.Cells(cws.Rows.Count, cCol).End(xlUp).Row
If clRow < cfRow Then Exit Sub
Dim crCount As Long: crCount = clRow - cfRow + 1
Dim crg As Range: Set crg = cws.Cells(cfRow, cCol).Resize(crCount)
Dim cData As Variant
If crCount = 1 Then
ReDim cData(1 To 1, 1 To 1): cData(1, 1) = crg.Value
Else
cData = crg.Value
End If
' Source
Dim sws As Worksheet: Set sws = wb.Worksheets(sName)
Dim srg As Range: Set srg = sws.UsedRange.Columns(sCols)
Dim sdrg As Range: Set sdrg = srg.Resize(srg.Rows.Count - 1).Offset(1)
Dim sdcrg As Range: Set sdcrg = sdrg.Columns(scCol)
' Destination
Dim dws As Worksheet: Set dws = wb.Worksheets(dName)
Dim dlRow As Long: dlRow = dws.Cells(dws.Rows.Count, dfCol).End(xlUp).Row
Dim dCell As Range
If dlRow < dfRow Then
Set dCell = dws.Cells(dfRow, dfCol)
Else
Set dCell = dws.Cells(dlRow, dfCol).Offset(1)
End If
Application.ScreenUpdating = False
Dim drCount As Long
Dim r As Long
For r = 1 To UBound(cData, 1)
sws.AutoFilterMode = False
srg.AutoFilter scCol, CStr(cData(r, 1)), xlFilterValues
drCount = Application.Subtotal(103, sdcrg)
Debug.Print drCount, cData(r, 1)
If drCount > 0 Then
sdrg.SpecialCells(xlCellTypeVisible).Copy
dCell.PasteSpecial xlPasteValues
Set dCell = dCell.Offset(drCount)
End If
Next r
Application.CutCopyMode = False
sws.AutoFilterMode = False
If dws Is ActiveSheet Then
dws.Range("A1").Activate
Else
Dim ash As Worksheet: Set ash = ActiveSheet
dws.Activate
dws.Range("A1").Activate
ash.Activate
End If
'wb.Save
Application.ScreenUpdating = True
MsgBox "Data transferred.", vbInformation, "Filter Names"
End Sub

You can achieve your goal without VBA but with the new FILTER-function if you have Excel 365.
In my example I created two tables (Insert > Table) named them tblPeople and tblBooks.
That way the formula is very easy to read:
Regarding your code: When you have a lot of data this process will be very slow.
In general you achieve a better performance when reading the data into an array (like you already did with the peoples sheet), do the filtering in the array and then write the array back to the sheet (you will find a lot of examples here on SO.
By the way: you can read a range to an array like this:
arrSummary = rg.value where rg is the range you want to read.

Related

Extract values from each cell to a separate sheet

I have a file with a few sheets, I need to extract values from each not empty cell into a column on another sheet.
Would be awesome if while doing that duplicates can be removed as well.
The following code infinitely loops. I don't see how to break the loop since all the events are being used in the body of the code.
Range where the cells are being looked for on both sheets are different, that is why I used .End(xlUp) to define the last row with values in cells.
I cannot use empty cells as a trigger for stopping the loop because there are empty cells between cells with values.
Sub updt()
Dim ws As Worksheet, currWs As Worksheet, Lng As Integer, rng As Range
Set ws = wb.Worksheets("Sheet1") 'the source sheet
Set currWs = Sheets("Sheet2") 'the destination sheet
Lng = ws.Cells(Rows.Count, 1).End(xlUp).Row
Set rng = ws.Range("A2:A" & Lng)
For Each c Lng rng
If WorksheetFunction.CountIf(currWs.Range("A:A"), c.Value) = 0 Then
currWs.Range("A" & currWs.Cells(Rows.Count, 1).End(xlUp).Row)(2) = c.Value
End If
Next
End Sub
Update Column With Unique Non-Existing Values From a Column of Another Worksheet Using a Dictionary
To avoid further complications, no arrays are used.
Option Explicit
Sub UpdateWorksheet()
' Reference the workbook.
Dim wb As Workbook: Set wb = ThisWorkbook ' workbook containing this code
' Reference the source worksheet, calculate the last row
' and reference the source column range.
Dim sws As Worksheet: Set sws = wb.Worksheets("Sheet1")
Dim slRow As Long: slRow = sws.Cells(sws.Rows.Count, "A").End(xlUp).Row
Dim srg As Range: Set srg = sws.Range("A2:A" & slRow)
' Reference the destination worksheet and calculate the last row.
Dim dws As Worksheet: Set dws = wb.Worksheets("Sheet2")
Dim dlRow As Long: dlRow = dws.Cells(dws.Rows.Count, "A").End(xlUp).Row
' Define a dictionary (object).
Dim dict As Object: Set dict = CreateObject("Scripting.Dictionary")
dict.CompareMode = vbTextCompare ' case-insensitive
' Declare variables.
Dim cCell As Range
Dim cKey As Variant
' Write the unique values from the destination column range
' to the dictionary.
If dlRow > 1 Then ' 1 means 'first row - 1' i.e. '2 - 1'
Dim drg As Range: Set drg = dws.Range("A2:A" & dlRow)
For Each cCell In drg.Cells
cKey = cCell.Value
If Not IsError(cKey) Then ' exclude error values
If Len(cKey) > 0 Then ' exclude blanks
dict(cKey) = Empty
End If
End If
Next cCell
End If
' Add the unique values from the source column range
' to the dictionary.
For Each cCell In srg.Cells
cKey = cCell.Value
If Not IsError(cKey) Then ' exclude error values
If Len(cKey) > 0 Then ' exclude blanks
dict(cKey) = Empty
End If
End If
Next cCell
' Check if the dictionary is empty.
If dict.Count = 0 Then
MsgBox "No valid values found.", vbCritical
Exit Sub
End If
' Clear the previous values from the destination first cell to the bottom
' of the worksheet.
Dim dCell As Range: Set dCell = dws.Range("A2")
With dCell
.Resize(dws.Rows.Count - .Row + 1).ClearContents
End With
' Write the unique values from the dictionary to the destination worksheet.
For Each cKey In dict.Keys
dCell.Value = cKey ' write
Set dCell = dCell.Offset(1) ' reference the cell below
Next cKey
' Inform.
MsgBox "Worksheet updated.", vbInformation
End Sub
You might want to use AdvancedFilter:
Option Explicit
Sub Copy_Advanced()
Dim ws As Worksheet, currWs As Worksheet, Lng As Integer, rng As Range
Set ws = Worksheets("Sheet1") 'the source sheet
Set currWs = Sheets("Sheet2") 'the destination sheet
Lng = ws.Cells(Rows.Count, 1).End(xlUp).Row
Set rng = ws.Range("A1:A" & Lng)
ws.Range("D1").Value = ws.Range("A1").Value
ws.Range("D2") = ">0"
ws.Range(rng.Address).AdvancedFilter Action:=xlFilterCopy, _
CriteriaRange:=ws.Range("D1:D2"), _
CopyToRange:=currWs.Range("A1"), _
Unique:=True
End Sub

Use Column Name Rather than Column Number (even after using .Offset) VBA

Here is a walkthrough of the situation:
My excel has 2 tabs, one called AssetName Sheet and the other called AMP Sheet
After clicking a vba button, the user is able to generate names in column B of the AssetName Sheet
I have another button that generates an AMP ID for the user. The easiest way for me to explain how this works is through an Index - Match function
=IFERROR(INDEX('AMP Sheet'!$L:$L,MATCH("*"&B2&"*", 'AMP Sheet'!$B:$B,0)), "Not Found")
Column B in the AssetName Sheet is named GeneratedAssetName and column L in the AMP Sheet is named ID.
So, in this example, My_Sandwich_6S_HeroRegular_Mobile exists in the AMP Sheet. Since this is a match, it will grab the associated ID from the AMP Sheet and copy it over to column E of the AssetName Sheet:
My logic (which does the exact same thing as the function I listed in step 3) is housed within a VBA macro button. The code is shown below:
Sub AMPTabid()
Dim wsAN As Worksheet
Set wsAN = Sheets("AssetName Sheet")
Dim wsAMP As Worksheet
Set wsAMP = Sheets("AMP Sheet")
Dim LastRow As Long
LastRow = wsAN.Cells(wsAN.Rows.Count, 2).End(xlUp).Row
Dim i As Long
For i = 2 To LastRow
Dim rFind As range
Set rFind = wsAMP.Columns(2).Find(what:=wsAN.Cells(i, 2), LookIn:=xlValues, LookAt:=xlPart, MatchCase:=True)
If Not rFind Is Nothing Then
wsAN.Cells(i, 5).Value = rFind.Offset(0, 10).Value
End If
Next i
End Sub
Basically, I would like to use my existing code shown in step 4, but with column Names (rather than column numbers). The names I would like to use are Name and ID from the AMP Sheet.
The .Offset logic in my code is the tricky part, since it's going from column B to Column L in the AMP Sheet, so it count column L as 10, rather than 12.
Thanks!
Find Headers and Lookup Values Using Application.Match
Option Explicit
Sub AMPTabid()
' Source
Const sName As String = "AMP Sheet"
Const slTitle As String = "Name"
Const svTitle As String = "ID"
' Destination
Const dName As String = "AssetName Sheet"
Const dlTitle As String = "Generated Asset Name"
Const dvTitle As String = "AMP ID"
' Workbook
Dim wb As Workbook: Set wb = ThisWorkbook ' workbook containing this code
' Source
Dim sws As Worksheet: Set sws = wb.Worksheets(sName)
Dim srg As Range: Set srg = sws.Range("A1").CurrentRegion ' Table Range
Dim shrg As Range: Set shrg = srg.Rows(1) ' Header Range
' Get the column numbers.
Dim slCol As Variant: slCol = Application.Match(slTitle, shrg, 0) ' Lookup
Dim svCol As Variant: svCol = Application.Match(svTitle, shrg, 0) ' Value
' Reference the data ranges.
Dim srCount As Long: srCount = srg.Rows.Count - 1
Dim sdrg As Range: Set sdrg = srg.Resize(srCount).Offset(1) ' Data Range
' Lookup Column Range ('Application.Match' works faster with ranges)
Dim slrg As Range: Set slrg = sdrg.Columns(slCol) ' lookup stays in range
Dim svrg As Range: Set svrg = sdrg.Columns(svCol) ' Value Range
Dim svData As Variant: svData = svrg.Value ' value data to array
' Destination
Dim dws As Worksheet: Set dws = wb.Worksheets(dName)
Dim drg As Range: Set drg = dws.Range("A1").CurrentRegion ' Table Range
Dim dhrg As Range: Set dhrg = drg.Rows(1) ' Header Range
' Get the column numbers.
Dim dlCol As Variant: dlCol = Application.Match(dlTitle, dhrg, 0) ' Lookup
Dim dvCol As Variant: dvCol = Application.Match(dvTitle, dhrg, 0) ' Value
' Reference the data ranges.
Dim drCount As Long: drCount = drg.Rows.Count - 1
Dim ddrg As Range: Set ddrg = drg.Resize(drCount).Offset(1) ' Data Range
Dim dlrg As Range: Set dlrg = ddrg.Columns(dlCol) ' Lookup Range
' The same array will be used for lookup and values (results).
Dim dData As Variant: dData = dlrg.Value ' lookup data to array
Dim dvrg As Range: Set dvrg = ddrg.Columns(dvCol) ' to be written to
Dim sIndex As Variant ' Source Lookup (and Value) Index
Dim dlValue As Variant ' Destination Lookup Value
Dim dr As Long
For dr = 1 To drCount
dlValue = dData(dr, 1)
If Not IsError(dlValue) Then ' exclude error values
If Len(dlValue) > 0 Then ' exclude blanks
sIndex = Application.Match(dlValue, slrg, 0)
If IsNumeric(sIndex) Then ' match found
dData(dr, 1) = svData(sIndex, 1)
Else ' no match found
dData(dr, 1) = Empty
End If
End If
End If
Next dr
dvrg.Value = dData
MsgBox "Ids updated.", vbInformation
End Sub

Multiple cells AutoFilter

I have a code that selects non empty cells in column C. Now If I want to select these cells in my autofilter it only pics the first found value of OutRng. How do i fix this?
Sub SelectNonBlankCells()
Sheets("Rekenblad").Select
Dim Rng As Range
Dim OutRng As Range
Dim xTitle As String
SearchCol = "10"
On Error Resume Next
xTitle = Range("C:C")
Set InputRng = Range("C:C")
For Each Rng In InputRng
If Not Rng.Value = "" Then
If OutRng Is Nothing Then
Set OutRng = Rng
Else
Set OutRng = Application.Union(OutRng, Rng)
End If
End If
Next
If Not (OutRng Is Nothing) Then
OutRng.Copy
Sheets("Plakken").Select
ActiveSheet.Range("$A$1:$K$13").AutoFilter Field:=10, Criteria1:=Array(OutRng) _
, Operator:=xlFilterValues
End If
End Sub
AutoFilter on Multiple (an Array of) Values
Range("C:C") is quite a huge range and it could take ages to get processed.
OutRng.Copy makes no sense unless you plan to copy it somewhere.
Since OutRng is declared as a range, Array(OutRng) is an array containing one element which is the actual range (object, not values).
If a range contains more than one cell and is contiguous (a single range, one area), you can use OutRng.Value but this is a 2D one-based array which in this case (it's one-column array) could be converted to a 1D one-based array using Application.Transpose(OutRng.Value) with its limitations. But since you have combined various cells into a range, it is expected that the range is non-contiguous (has several areas, is a multi-range), you're again at a dead end.
No matter what, it was an interesting try (IMHO).
Option Explicit
Sub FilterRange()
' Source
Const sName As String = "Rekenblad"
Const sCol As String = "C"
Const sfRow As Long = 2
' Destination
Const dName As String = "Plakken"
Const dField As Long = 10
' Workbook
Dim wb As Workbook: Set wb = ThisWorkbook ' workbook containing this code
' Create a reference to the Source Range ('srg').
Dim sws As Worksheet: Set sws = wb.Worksheets(sName)
'If sws.AutoFilterMode Then sws.AutoFilterMode = False
Dim slRow As Long: slRow = sws.Cells(sws.Rows.Count, sCol).End(xlUp).Row
Dim srCount As Long: srCount = slRow - sfRow + 1
If srCount < 1 Then Exit Sub ' no data
Dim srg As Range: Set srg = sws.Cells(sfRow, sCol).Resize(srCount)
' Write the values from the Source Range to the Source Array ('sData').
Dim sData As Variant
If srCount = 1 Then ' one cell
ReDim sData(1 To 1, 1 To 1): sData(1, 1) = srg.Value
Else ' multiple cells (in column)
sData = srg.Value
End If
' Write the unique values from the Source Array to the keys
' of a dictionary ('dict').
Dim dict As Object: Set dict = CreateObject("Scripting.Dictionary")
dict.CompareMode = vbTextCompare ' A = a
Dim Key As Variant
Dim r As Long
For r = 1 To srCount
Key = sData(r, 1)
If Not IsError(Key) Then ' not error value
If Len(Key) > 0 Then ' not blank
dict(CStr(Key)) = Empty
'Else ' blank
End If
' Else ' error value
End If
Next r
If dict.Count = 0 Then Exit Sub ' only blanks and error values
' Filter the Destination Range ('drg') by the values in the dictionary.
Dim dws As Worksheet: Set dws = wb.Worksheets(dName)
If dws.AutoFilterMode Then dws.AutoFilterMode = False ' remove previous
Dim drg As Range: Set drg = dws.Range("A1").CurrentRegion
' If the previous line doesn't work, use another way,
' or revert to the static:
'Set drg = dws.Range("A1:K13")
drg.AutoFilter dField, dict.Keys, xlFilterValues
'dws.activate
End Sub

How do you paste filtered values only in the cells that were affected by such filter using vba?

I'm currently working on a Macro that its currently filtering a table based on a value and then it copies the data under a column after the filters have been applied (got that to work). However, I can't figure out how to paste those values in the same table overwriting the data under the visible cells within a different column. Values highlighted in red (picture) are being copied, now I need to paste them over only in the cells highlighted yellow. Thank you!
Public Sub DxcDateUpdate()
Application.ScreenUpdating = False
Dim Mwb As Workbook
Dim ws As Worksheet
Set Mwb = ThisWorkbook
Set ws = Mwb.Worksheets("Commission")
Set ws2 = Mwb.Worksheets("test")
lr = ws.Cells(ws.Rows.Count, 1).End(xlUp).Row
ws.Range("A1").AutoFilter Field:=31, Criteria1:="DXC/TPV.com Enrollment"
ws.Range("AG2:AG" & lr).SpecialCells(xlCellTypeVisible).Copy Destination:=ws.Range '''here is where idk what to do?'''
Application.ScreenUpdating = True
End Sub
You cannot paste a discontinuous range as discontinuous. You should iterate between each range cell and copy it using offset, or building the range to Paste using c.row. Please, try the next adapted code:
Sub DxcDateUpdate()
Dim Mwb As Workbook, ws As Worksheet, rngVis As Range, c As Range, LR As Long
Set Mwb = ThisWorkbook
Set ws = Mwb.Worksheets("Commission")
Set ws2 = Mwb.Worksheets("test")
LR = ws.cells(ws.rows.Count, 1).End(xlUp).row
ws.Range("A1").AutoFilter field:=31, Criteria1:="DXC/TPV.com Enrollment"
Set rngVis = ws.Range("AG2:AG" & LR).SpecialCells(xlCellTypeVisible)
For Each c In rngVis.cells
c.Offset(0, -28).value = c.value
Next
End Sub
In order to make the code faster, of course, you should use some optimization lines (ScreenUpdating = False, EnableEvents = False, Calculation = xlCalculationManual, followed after by True, True, xlCalculationAutomatic).
Copy 'Filtered' Values Using Arrays
The following will loop through the criteria column to find the criteria (string). When found, in the same row, the value from the source column will be copied to the destination column.
The columns' values are written to arrays to speed up the process (the loop).
Option Explicit
Sub DxcDateUpdate()
Const wsName As String = "Commission"
Const fRow As Long = 2
Const cCol As String = "AE" ' Criteria
Const sCol As String = "AG" ' Source
Const dCol As String = "E" ' Destination
Const Criteria As String = "DXC/TPV.com Enrollment"
Dim wb As Workbook: Set wb = ThisWorkbook
Dim ws As Worksheet: Set ws = wb.Worksheets(wsName)
Dim rg As Range: Set rg = ws.Range("A1").CurrentRegion
Dim lRow As Long: lRow = rg.Rows.Count
Dim cData As Variant: cData = rg.Columns(cCol).Value
Dim sData As Variant: sData = rg.Columns(sCol).Value
With rg.Columns(dCol)
Dim dData As Variant: dData = .Value
Dim r As Long
For r = fRow To lRow
If cData(r, 1) = Criteria Then
dData(r, 1) = sData(r, 1)
End If
Next r
.Value = dData
End With
End Sub

How to copy one row of data at a time from one sheet and paste into another sheet

I want to copy one row of data at a time from one sheet and pasting into another sheet. I need to repeat this 100 times. I also need to modify a couple of column values after pasting them.
My data is not pasting into new sheet correctly.
'Get column numbers which need to be modified
PolicyReference = DataWS.Range("A1", DataWS.Range("IV1").End(xlToLeft)).Find("PolicyReference").Column
InsuredCode = DataWS.Range("A1", DataWS.Range("IV1").End(xlToLeft)).Find("InsuredCode").Column
InsuredDescription = DataWS.Range("A1", DataWS.Range("IV1").End(xlToLeft)).Find("InsuredDescription").Column
For j = 1 To 100
'Worksheets(DataWS).Range("A1:A100").Copy Worksheets(DestinationWS).Range("A1")
'1. Find last used row in the copy range based on data in column A
CopyLastRow = DataWS.Cells(DataWS.Rows.count, "A").End(xlUp).Row
'2. Find first blank row in the destination range based on data in column A
DestLastRow = DestinationWS.Cells(DestinationWS.Rows.count, "A").End(xlUp).Offset(1).Row
'3. Copy & Paste Data
DataWS.Rows(j).EntireRow.Copy DestinationWS.Range("A" & DestLastRow)
DataWS.Range("A1:A100").Copy
DestinationWS.Range("A" & Rows.count).End(xlUp).Offset(2).PasteSpecial Paste:=xlPasteValues
Next j
This code will copy all but the first row from DataWs to DestinationWs. If you want to be more selective in what you copy modifications must be made to the code in the loop, at the bottom.
Private Sub Study()
' 244
Dim DataWs As Worksheet
Dim DestinationWs As Worksheet
Dim PolicyReference As Long
Dim InsuredCode As Long
Dim InsuredDescription As Long
Dim Fnd As Range
Dim CopyLastRow As Long
Dim DestLastRow As Long
Dim R As Long ' loop counter: rows
Set DataWs = Worksheets("Sheet1")
Set DestinationWs = Worksheets("Sheet2")
With DestinationWs
DestLastRow = .Cells(.Rows.Count, "A").End(xlUp).Row
End With
'Get column numbers which need to be modified
With DataWs
Set Fnd = .Rows(1).Find("PolicyReference") ' spaces between words are permissible
' make sure the column is found before using it in your further code
If Fnd Is Nothing Then Exit Sub
PolicyReference = Fnd.Column
Set Fnd = .Rows(1).Find("InsuredCode")
If Fnd Is Nothing Then Exit Sub ' perhaps give a message before exiting
InsuredCode = Fnd.Column
Set Fnd = .Rows(1).Find("InsuredDescription")
If Fnd Is Nothing Then Exit Sub ' perhaps give a message before exiting
InsuredDescription = Fnd.Column
CopyLastRow = .Cells(.Rows.Count, "A").End(xlUp).Row
Application.ScreenUpdating = False ' speeds up execution
For R = 2 To CopyLastRow ' start in row 2
DestLastRow = DestLastRow + 1
.Rows(R).Copy DestinationWs.Cells(DestLastRow, "A")
Next R
Application.ScreenUpdating = True
End With
End Sub
Columns and Ranges
I am considering these as two problems. Revealing the connection between them might lead to a more suitable solution.
The first part (including the function) illustrates how you can write the column numbers to an array which can later be used to process the data in those columns.
The second part illustrates how to copy values most efficiently. The loop is ignored.
Option Explicit
Sub ColumnsAndRanges()
Const sName As String = "Sheet1"
Const shRow As Long = 1
Const sHeadersList As String _
= "PolicyReference,InsuredCode,InsuredDescription"
Const sFirst As String = "A1"
Const dName As String = "Sheet2"
Dim wb As Workbook: Set wb = ThisWorkbook ' workbook containing this code
Dim sws As Worksheet: Set sws = wb.Worksheets(sName)
'Part 1: Column Numbers
Dim shrg As Range: Set shrg = sws.Rows(shRow)
' Use the function 'getColumnNumbers'.
Dim sColNums As Variant: sColNums = getColumnNumbers(shrg, sHeadersList)
If IsEmpty(sColNums) Then
MsgBox "Could not find all the headers."
Exit Sub
End If
' Column Numbers Example:
Dim n As Long
For n = 1 To UBound(sColNums)
Debug.Print n, sColNums(n)
Next n
'Part 2: Copy Range Values
' Create a reference to the Source Range.
Dim slCell As Range ' Source Last Cell
Set slCell = sws.Cells(sws.Rows.Count, "A").End(xlUp)
Dim srg As Range
' Note how a cell address (sFirst) or a cell range (slCell) can be used.
Set srg = sws.Range(sFirst, slCell).EntireRow
' Create a reference to the Destination Range.
Dim dws As Worksheet: Set dws = wb.Worksheets(dName)
Dim dfCell As Range ' Destination First Cell
' When 'EntireRow' is used, only "A" or 1 can be used.
Set dfCell = dws.Cells(dws.Rows.Count, "A").End(xlUp).Offset(1)
Dim drg As Range: Set drg = dfCell.Resize(srg.Rows.Count, srg.Columns.Count)
' Copy by assignment (most efficient when only values are to be copied).
drg.Value = srg.Value
End Sub
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
' Purpose: Returns the column numbers in a one-based array.
' Remarks: The column numbers refer to the columns of the given range,
' not necessarily to the columns of the worksheet.
' If any of the headers cannot be found, 'Empty' is returned.
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
Function getColumnNumbers( _
ByVal RowRange As Range, _
ByVal HeadersList As String, _
Optional ByVal Delimiter As String = ",") _
As Variant
If RowRange Is Nothing Then Exit Function
If Len(HeadersList) = 0 Then Exit Function
Dim Headers() As String: Headers = Split(HeadersList, Delimiter)
Dim ColNums As Variant
ColNums = Application.Match(Headers, RowRange.Rows(1), 0)
If Application.Count(ColNums) = UBound(Headers) + 1 Then
getColumnNumbers = ColNums
End If
End Function
The following one line of code using AdvancedFilter will paste data to the destination sheet.
Sub CopyDataToAnotherSheet()
DataWS.Range("A1").CurrentRegion.AdvancedFilter _
Action:=xlFilterCopy, _
CriteriaRange:=DataWS.Range("A1", _
DataWS.Cells(1, DataWS.Columns.Count).End(xlToLeft)), _
CopyToRange:=DestinationWS.Range("A1")
End Sub

Resources