I'm trying to make something that
loops through a range (header range) of values and collects them into an array or whatever
make a dictionary of arrays with keys that are the values in the range
loop through worksheets looking for those keys
for each key it finds,
a. make an array of the values below
b. pad all the arrays so their the same length
c. concatenate it to the array stored in the dictionary with the same key
copy the concatenated values back to the cells below the header range
I did 1,2,4 and 5. I skipped 3, because that's easy and I'll do it later. But 4 is tricky because I can't get a handle on how the dictionary and arrays work. I tried to make a dictionary of arrays, but they're making copies instead of references and sometimes the copies are empty. I don't know.
In javascript, it would just be:
make a dict = {}
loop through the values and do dict[value] = []
then dict[value].concatenate(newestarray)
Then flip the dict back in to an array with a for(var k in dict){} which in google sheets you would have to transpose. Annoying, but not terrible.
Then in the end, some function to put it back into the worksheet, which in google sheets would be trivial.
Here's my code for the 4 part:
With rws
For Each Key In headerdict 'loop through the keys in the dict
Set rrng = .Cells.Find(key, , _ 'find the key in the sheet
Excel.XlFindLookIn.xlValues, Excel.XlLookAt.xlPart, _
Excel.XlSearchOrder.xlByRows, Excel.XlSearchDirection.xlNext, False)
If rrng Is Not Empty Then
'find last cell in column of data
Set rdrng = .Cells(rws.Rows.Count, rrng.Column).End(xlUp)
'get range for column of data
Set rrng = .Range(.Cells(rrng.Row + 1, rrng.Column), _
.Cells(rdrng.Row, rdrng.Column))
rArray = rrng.Value 'make an array
zMax = Max(UBound(rArray, 2), zMax) 'set max list length
fakedict(Key) = rArray 'place array in fake dict for later smoothing
End If
Next
End With
For Each Key In fakedict 'now smooth the array
If fakedict(Key) Is Not Nothing Then
nArray = fakedict(Key)
ReDim Preserve nArray(1 To zMax, 1 To 1) 'resize the array
Else
ReDim nArray(1 To zMax, 1 To 1) 'or make one from nothing
End If
fakedict(Key) = nArray 'add to fake dict
Next
Then later I can combine into the real dict. So my question is how do I resize the array? I don't think redim preserve is the best way. Others have mangled with collections, but I have too much pandas and python thinking. I'm used to deal with vectors, not munge elements. Any ideas?
I was not sure if you needed to use a dictionary of arrays to achieve this; if I was doing it I would just copy blocks of cells between sheets directly.
First bit - identify where the headers are:
Option Explicit
' Get the range that includes the headers
' Assume the headers are in sheet "DB" in row 1
Private Function GetHeaders() As Range
Dim r As Range
Set r = [DB!A1]
Set GetHeaders = Range(r, r.End(xlToRight))
End Function
Second, identify the sheets to scan (I assumed they're in the same workbook)
' Get all sheets in this workbook that aren't the target DB sheet
Private Function GetSheets() As Collection
Dim sheet As Worksheet
Dim col As New Collection
For Each sheet In ThisWorkbook.Worksheets
If sheet.Name <> "DB" Then col.Add sheet
Next sheet
Set GetSheets = col
End Function
Now, scan through and copy cells
' Main function, loop through all headers in all sheets
' and copy data
Sub CollectData()
Dim sheets As Collection, sheet As Worksheet
Dim hdrs As Range, hdr As Range
Dim found As Range
' This is the row we are writing into on DB
Dim currentrow As Integer
' This is the maximum number of entries under a header on this sheet, used for padding
Dim maxcount As Integer
Set sheets = GetSheets
Set hdrs = GetHeaders
currentrow = 1
For Each sheet In sheets
maxcount = 0
For Each hdr In hdrs.Cells
' Assume each header appears only once in each sheet
Set found = sheet.Cells.Find(hdr.Value)
If Not found Is Nothing Then
' Check if there is anything underneath
If Not IsEmpty(found.Offset(1).Value) Then
Set found = Range(found.Offset(1), found.End(xlDown))
' Note the number of items if it's more that has been found so far
If maxcount < found.Count Then maxcount = found.Count
' Copy cells
Range(hdr.Offset(currentrow), hdr.Offset(currentrow + found.Count - 1)) = found.Cells.Value
End If
End If
Next hdr
' Move down ready for the next sheet
currentrow = currentrow + maxcount
Next sheet
End Sub
I wrote this in Excel 2016 and tested that it worked based on my assumption of how your data is laid out.
Related
Need some help on sorting the values into the correct column.
I can't seem to figure out how I would return the array values to the proper column in the table.
For the output into column B "Pipe DN" it should return the first split text from the values in "Line number", and for the "Service" column F it should return the 2nd split text from "Line number".
How would I accomplish this? -
If for "Pipe DN" I use Range("B19", Range("B19").Offset(Dimension1 - 1, 1)).Value = StrArray, it will return me the correct values, but the "Service" code is not written on the correct column.
Sub SplitLinesIntoArray()
Dim LineNumber() As Variant
Dim StrArray() As Variant
Dim Dimension1 As Long, Counter As Long
LineNumber = Range("J19", Range("J19").End(xlDown))
Dimension1 = UBound(LineNumber, 1)
ReDim StrArray(1 To Dimension1, 1 To 2)
For Counter = 1 To Dimension1
'Pipe DN
StrArray(Counter, 1) = Split(LineNumber(Counter, 1), "-")(0)
Range("B19", Range("B19").Offset(Dimension1 - 1, 0)).Value = StrArray
'Service Code
StrArray(Counter, 2) = Split(LineNumber(Counter, 1), "-")(1)
Range("F19", Range("F19").Offset(Dimension1 - 1, 0)).Value = StrArray(Counter, 2)
Next Counter
'Range("B19", Range("B19").Offset(Dimension1 - 1, 1)).Value = StrArray
Erase LineNumber
Erase StrArray
End Sub
Basically you start well by analyzing a 2-dim datafield array and assigning resulting string manipulations (Split()) to it.
Results seem to (1) output correctly as for the first array "column" ("Pipe DN", starting in cell B19),
whereas (2) the second column ("Service", F19) repeats the result of the very last split action for each array "row".
This impression has to be qualified:
ad 1) You are doing unnecessary extra work by assigning the entire StrArray to the entire "Pipe DN" column,
repeating this action with each single row iteration. (Note that the StrArray gets only completely filled with the last loop).
ad 2) Basically you assign again with each iteration, but this time you get only the latest split result and fill the entire "Service" column
with the latest result assigned to StrArray(Counter,2). Eventually all items show the last split result instead of the individual LineNumber splittings.
See this abbreviated example for three example items only to understand what is happening
(this SnapShot shows the table results when code is stopped after the 2nd iteration (i.e. after Counter=2):
Immediate help
Sticking to your initial code, I'd omit
Range("B19", Range("B19").Offset(Dimension1 - 1, 0)).Value = StrArray as well as
Range("F19", Range("F19").Offset(Dimension1 - 1, 0)).Value = StrArray(Counter, 2)
within the For..Next loop, but add the following two code lines thereafter:
Range("B19", Range("B19").Offset(Dimension1 - 1, 0)).Value = Application.Index(StrArray, 0, 1)
Range("F19", Range("F19").Offset(Dimension1 - 1, 0)).Value = Application.Index(StrArray, 0, 2)
in order to slice the StrArray into columns and write each column separately to your cell target.
Further note:
Fully qualify your range references to prevent possibly unwanted results as Excel would take the currently active sheet if not referenced explicitly ... and this need not be the targeted one :-;
Using VBA, it's not necessary in standard office situations to clear (Erase) at the end of a procedure to free memory.
Possible alternative avoiding array slicing
You might profit from the the following code, which
fully qualifies your range references (note: unqualified refs invite Excel to take the currently active sheet without request),
uses a jagged array (aka as Array of Arrays) to avoid (multiple) column slicing (as needed in OP)
demonstrates the use of Private Constants on module top (used here for enumerating the sub-arrays within the jagged array
demonstrates a help procedure to provide for a correcty dimensioned jagged array:
Example code
Option Explicit ' declaration head of code module (forching variable declarations)
Private Const LineNum As Long = 0 ' enumerate sub-arrays within jagged array
Private Const Pipe As Long = 1
Private Const Service As Long = 2
Sub SplitLinesIntoJaggedArray()
'I. Set Worksheet object to memory ' fully qualify any range references!
Dim ws As Worksheet ' declare ws as of worksheet object type
Set ws = Tabelle1 ' << use the project's sheet Code(Name)
'set ws = ThisWorkbook.Worksheets("Sheet1") ' or: via a sheet's tabular name (needn't be the same)
With ws ' With .. End With structure, note the following "."-prefixes
'II.Definitions
'a) assign target start cell addresses to array tgt
Dim tgt As Variant
tgt = Split("J19,B19,F19", ",") ' split requires "Dim tgt" without brackets to avoid Error 13
'b) define source range object and set to memory
' Note: tgt(LinNum) equalling tgt(0) equalling "J19"
Dim src As Range
Set src = .Range(tgt(LineNum), .Range(tgt(0)).End(xlDown)) ' showing both enumerations only for demo:-)
Dim CountOfRows As Long: CountOfRows = src.Rows.Count ' count rows in source range
'c) provide for a correctly dimensioned jagged array to hold all 2-dim data arrays (three columns)
Dim JaggedArray() As Variant
BuildJagged JaggedArray, CountOfRows ' << call help procedure BuildJaggedArray
'III.Assign column data to JaggedArray
'a) assign LineNum column as 2-dim datafield to JaggedArray(LineNum)
JaggedArray(LineNum) = src.Value
'b) assign LineNum splits to JaggedArray(Pipe) and JaggedArray(Service)
Dim Counter As Long
For Counter = 1 To CountOfRows
'1. Pipe DN
JaggedArray(Pipe)(Counter, 1) = Split(JaggedArray(LineNum)(Counter, 1), "-")(0)
'2. Service Code
JaggedArray(Service)(Counter, 1) = Split(JaggedArray(LineNum)(Counter, 1), "-")(1)
Next Counter
'IV.Write result columns of jagged array to target addresses
' Note: tgt(Pipe)=tgt(1)="B19", tgt(Service)=tgt(2)="F19"
Dim elem As Long
For elem = Pipe To Service
.Range(tgt(elem)).Resize(CountOfRows, 1) = JaggedArray(elem)
Next
End With
End Sub
*Help procedure BuildJagged
Note that the first procedure argument passes the jagged array By Reference (=default, if not explicitly passed ByVal).
This means that any further actions within the help procedure have an immediate effect on the original array.
Sub BuildJagged(ByRef JaggedArray, ByVal CountOfRows As Long)
'Purpose: provide for correct dimensions of the jagged array passed By Reference
ReDim JaggedArray(LineNum To Service) ' include LineNum as data base (gets dimmed later)
Dim tmp() As Variant
ReDim tmp(1 To CountOfRows, 1 To 1)
Dim i As Long
For i = Pipe To Service ' suffices here to start from 1=Pipe to 2=Service
JaggedArray(i) = tmp
Next i
End Sub
Further link
Error in finding last used cell in Excel VBA
I have two columns of data in a spreadsheet.
Column A has either cells containing "X" or empty cells and Column B contains formulas.
I want to use VBA to pull Column A into an array, and paste the array into Column B, making sure the "X"s copy over but the empty array elements do not.
The method I have looks at each array element and if it is an "X" then paste that 1 element, it works but its slow for large data pools. Is there a faster method?
See code below:
Option Explicit
Sub Test()
Dim array1 As Variant, i As Integer
array1 = Sheets("Sheet1").Range("A2:A8").Value
For i = 1 To UBound(array1)
If array1(i, 1) = "X" Then
Sheets("Sheet1").Cells(i + 1, 2) = array1(i, 1)
End If
Next i
End Sub
use a second array to hold the formula in B. Then iterate both arrays and replace the second with the value where needed:
Sub Test()
With Sheets("Sheet1")
Dim aArr() As Variant
aArr = .Range("A2:A8").Value
Dim bArr() As Variant
bArr = .Range("B2:B8").Formula
Dim i As Long
For i = 1 To UBound(aArr, 1)
If aArr(i, 1) = "X" Then
bArr(i, 1) = aArr(i, 1)
End If
Next i
.Range("B2:B8").Formula = bArr
End With
End Sub
Replace Formulas with Criteria
It is assumed that
the worksheet is in ThisWorkbook, the workbook containing this code,
the Data Column is adjacent to the right of the Criteria Column, which is defined by FirstCellAddress,
the 'search' for the Criteria (X) is case-sensitive i.e. X <> x.
The Code
Option Explicit
Sub replaceFormulasWithCriteria()
Const wsName As String = "Sheet1"
Const FirstCellAddress As String = "A2"
Const Criteria As String = "X"
' Define Criteria Column Range.
Dim rng As Range
With ThisWorkbook.Worksheets(wsName).Range(FirstCellAddress)
Set rng = .Resize(.Worksheet.Cells(.Worksheet.Rows.Count, .Column) _
.End(xlUp).Row - .Row + 1)
End With
' Write values from Criteria Column Range to Criteria Array.
Dim Crit As Variant: Crit = rng.Value
' Define Data Column Range.
Set rng = rng.Offset(, 1)
' Write formulas from Data Column Range to Data Array.
Dim Data As Variant: Data = rng.Formula
Dim i As Long
' Loop through rows of Criteria/Data Column Range.
For i = 1 To UBound(Data, 1)
' Check if Criteria is found in current row in Criteria Array.
If Crit(i, 1) = Criteria Then
' Write Criteria to current row in Data Array.
Data(i, 1) = Criteria
End If
Next i
' Write modified values from Data Array to Data Column Range.
rng.Value = Data
' or:
'rng.Formula = Data
End Sub
I have a column of a list object with some non empty values at the beginning. Just assume the first 15 values are not blank.
I know it is possible to pass the values of a range to an array like this:
Dim mylistObject As ListObject
Set mylistObject = ThisWorkbook.Sheets("training").ListObjects(1)
Dim theArray() As Variant
theArray = mylistObject.listcolumn(1).DataBodyRange.value
The question is how can I pass only the non blank values.
I know how to do it with loops but the key point here is speed, if the listobject has hundreds of rows and the operation is done tens of times it takes too long.
I also know that it might be possible to calculate the number of non blank cells and redim the array accordingly and loop through values. still not elegant.
Any idea? there should be a way to tell in VBA language
mylistObject.listcolumn(1).DataBodyRange.value
' but not all the range but the non empty ones.
Thanks a lot
Using the possibilities of the Application.Index function
Demonstrate an easy approach to create and transform the listbox'es column data Array:
Get all data of first column (including blanks) as already shown in the original post (BTW the correct syntax in the array assignment is theArray = mylistObject.ListColumns(1).DataBodyRange.Value with a final "s" in .ListColumns)
Eliminate blank row numbers using the advanced features of the Application.Index function and a subordinated function call (getNonBlankRowNums())
Basic transformation syntax by one code line:
newArray = Application.Index(oldArray, Application.Transpose(RowArray), ColumnArray)
where RowArray / ColumnArray stands for an array of (remaining) row or column numbers.
Related link: Some peculiarities of the the Application.Index function
Sub NonBlanks()
' Note: encourageing to reference a sheet via CodeName instead of Thisworkbook.Worksheets("training")
' i.e. change the (Name) property in the VBE properties tool window (F4) for the referenced worksheet
' (c.f. https://stackoverflow.com/questions/58507542/set-up-variable-to-refer-to-sheet/58508735#58508735)
Dim mylistObject As ListObject
Set mylistObject = training.ListObjects(1)
' [1] Get data of first column (including blanks)
Dim theArray As Variant
theArray = mylistObject.ListColumns(1).DataBodyRange.Value ' LISTCOLUMNS with final S!!
' [2] eliminate blank row numbers
theArray = Application.Index(theArray, Application.Transpose(getNonBlankRowNums(theArray)), Array(1))
End Sub
Function getNonBlankRowNums(arr, Optional ByVal col = 1) As Variant()
' Purpose: return 1-dim array with remaining non-blank row numbers
Dim i&, ii&, tmp
ReDim tmp(1 To UBound(arr))
For i = 1 To UBound(arr)
If arr(i, col) <> vbNullString Then ' check for non-blanks
ii = ii + 1 ' increment temporary items counter
tmp(ii) = i ' enter row number
End If
Next i
ReDim Preserve tmp(1 To ii) ' redim to final size preserving existing items
' return function value (variant array)
getNonBlankRowNums = tmp
End Function
What I am trying to accomplish is this:
If any cells in columns AC-AF in my entire worksheet are blank, cut the entire row and paste to a new worksheet labeled "MissingShipping".
Code should adjust with the amount of rows, since that will never be the same.
From examples I have seen I don't understand where to insert the range of the cells I want to wade through.
I get the error
"Method 'Range' of object'_Worksheet'
on the line NewSetup.Range(Cells(Destinationrow, 1), Cells(Destinationrow, lastcolumn)).Select.
Option Explicit
Sub Shipping()
Dim MissingShipping As Worksheet
Set MissingShipping = Sheets.Add(After:=Sheets(Sheets.Count))
MissingShipping.Name = "MissingShipping"
Dim NewSetup As Worksheet
Dim lastcolumn As Integer
Dim Destinationrow As Integer
Dim lastrow As Long
Set NewSetup = Worksheets("NKItemBuildInfoResults")
Set MissingShipping = Worksheets("MissingShipping")
Destinationrow = 1
lastcolumn = NewSetup.Range("XFD1").End(xlToLeft).Column
lastrow = NewSetup.Range("A1048576").End(xlUp).Row
Dim i As Long
Dim j As Long
For i = lastrow To 1 Step -1
For j = 1 To lastcolumn
If NewSetup.Cells(i, j).Value = "" Then
NewSetup.Activate
NewSetup.Range(Cells(i, 1), Cells(i, lastcolumn)).Cut
MissingShipping.Activate
NewSetup.Range(Cells(Destinationrow, 1), Cells(Destinationrow, _
lastcolumn)).Select
ActiveSheet.Paste
NewSetup.Rows(i).Delete shift:=xlUp
Destinationrow = Destinationrow + 1
Exit For
End If
Next j
Next i
End Sub
G'day Nikki,
Welcome to the world of VBA! There are plenty of great resources on the internet to help you on your journey.
It's often easier and faster to work with a range inside your code instead of reading and writing to a sheet and selecting cells to mimic things that you would normally do if you were doing the job manually.
It's a good idea to get your head around the range object early on. It's handy for working with multiple worksheets.
The following is a good start with Ranges in Excel:
https://excelmacromastery.com/excel-vba-range-cells/
Another handy thing is a collection. If you had to store a bunch of things to work with later on, you can add them to a collection then iterate over them using a "For Each" loop. This is a good explanation of collections:
https://excelmacromastery.com/excel-vba-collections/
I had a quick look at your code and using the concept of Ranges and Collections, I have altered it to do what I think you were trying to do. I had to make a few assumptions as I haven't seen you sheet. I ran the code on a bunch of random rows on my computer to make sure it works. Consider the following:
Dim MissingShipping As Worksheet
Dim NewSetup As Worksheet
Dim rangeToCheck As Range
Dim cellsToCheck As Range
Dim targetRange As Range
Dim rw As Range 'rw is a row
Dim cl As Range 'cl is a cell
Dim rowColl As New Collection
Dim i As Long
Set NewSetup = Worksheets("NKItemBuildInfoResults")
Set MissingShipping = Worksheets("MissingShipping")
'Get the range of data to check
Set rangeToCheck = NewSetup.Range("A1").CurrentRegion
'For each row in the range
For Each rw In rangeToCheck.Rows
'For the last four cells in that row
Set cellsToCheck = rw.Cells(1, 29).Resize(1, 4)
For Each cl In cellsToCheck.Cells
'If the cell is empty
If cl.Value = "" Then
'Add the row to our collection of rows
rowColl.Add rw
'Exit the for loop because we only want to add the row once.
'There may be multiple empty cells.
Exit For
End If
'Check the next cell
Next cl
Next rw
'Now we have a collection of rows that meet the requirements that you were after
'Using the size collection of rows we made, we now know the size of the range
'we need to store the values
'We can set the size of the new range using rowColl.Count
'(that's the number of rows we have)
Set targetRange = MissingShipping.Range("A1").Resize(rowColl.Count, 32)
'Use i to step through the rows of our new range
i = 1
'For each row in our collection of rows
For Each rw In rowColl
'Use i to set the correct row in our target range an make it's value
'equal to the row we're looking at
targetRange.Rows(i) = rw.Value
'Increment i for next time
i = i + 1
Next rw
End Sub
Good luck! Hope this helps.
I have an excel Sheet1 of a thousand of rows and 20 columns from A1 to T1. Each cell in that range has some data in it, usually one or two words.
In Sheet2, A1 column I have a list of data of 1000 values.
I am working on VBA script to find words from Sheet2 list in Sheet1 and clear the values of the cells of the found ones.
I now have a VBA script that works only on A1 column of Sheet1 and it deletes the rows only. Here's the script:
Sub DeleteEmails()
Dim rList As Range
Dim rCrit As Range
With Worksheets("Sheet1")
.Range("A1").Insert shift:=xlDown: .Range("A1").Value = "Temp Header"
Set rList = .Range("A1", .Cells(Rows.Count, 1).End(xlUp))
End With
With Worksheets("Sheet2")
.Range("A1").Insert shift:=xlDown: .Range("A1").Value = "Temp Header"
Set rCrit = .Range("A1", .Cells(Rows.Count, 1).End(xlUp))
End With
rList.AdvancedFilter Action:=xlFilterInPlace, CriteriaRange:=rCrit, Unique:=False
rList.Offset(1).SpecialCells(xlCellTypeVisible).Delete shift:=xlUp
Worksheets("Sheet1").ShowAllData
rList(1).Delete shift:=xlUp: rCrit(1).Delete shift:=xlUp
Set rList = Nothing: Set rCrit = Nothing
End Sub
Could anyone help me? I need the values cleared, not rows deleted, and this should work on all columns of Sheet1, not just A1.
Here is another method using an array by minimizing the traffic between sheet (iteration via range/cells) and code. This code doesn't use any clear contents. Simply take the whole range into an array, clean it up and input what you need :) with a click of a button.
edited as per OP's request: adding comments and changing the code for his desired sheets.
Code:
Option Explicit
Sub matchAndClear()
Dim ws As Worksheet
Dim arrKeys As Variant, arrData As Variant
Dim i As Integer, j As Integer, k As Integer
'-- here we take keys column from Sheet 1 into a 1D array
arrKeys = WorksheetFunction.Transpose(Sheets(1).Range("A2:A11").Value)
'-- here we take to be cleaned-up-range from Sheet 2 into a 2D array
arrData = WorksheetFunction.Transpose(Sheets(2).Range("C2:D6").Value)
'-- here we iterate through each key in keys array searching it in
'-- to-be-cleaned-up array
For i = LBound(arrKeys) To UBound(arrKeys)
For j = LBound(arrData, 2) To UBound(arrData, 2)
'-- when there's a match we clear up that element
If UCase(Trim(arrData(1, j))) = UCase(Trim(arrKeys(i))) Then
arrData(1, j) = " "
End If
'-- when there's a match we clear up that element
If UCase(Trim(arrData(2, j))) = UCase(Trim(arrKeys(i))) Then
arrData(2, j) = " "
End If
Next j
Next i
'-- replace old data with new data in the sheet 2 :)
Sheets(2).Range("C2").Offset(0, 0).Resize(UBound(arrData, 2), _
UBound(arrData)) = Application.Transpose(arrData)
End Sub
Please not that you what you really need to set here are the ranges:
Keys range
To-Be-Cleaned up range
Output: (for displaying purpose I am using the same sheet, but you can change the sheet names as you desire.
Edit based on OP's request for running OP's file:
The reason that it didn't clean all your columns is that in the above sample is only cleaning two columns where as you have 16 columns. So you need to add another for loop to iterate through it. Not much performance down, but a little ;) Following is a screenshot after running your the sheet you sent. There is nothing to change except that.
Code:
'-- here we iterate through each key in keys array searching it in
'-- to-be-cleaned-up array
For i = LBound(arrKeys) To UBound(arrKeys)
For j = LBound(arrData, 2) To UBound(arrData, 2)
For k = LBound(arrData) To UBound(arrData)
'-- when there's a match we clear up that element
If UCase(Trim(arrData(k, j))) = UCase(Trim(arrKeys(i))) Then
arrData(k, j) = " "
End If
Next k
Next j
Next i
I don't have excel to hand right now so this may not be exactly 100% accurate on formulae name but I believe this line needs to change:
rList.Offset(1).SpecialCells(xlCellTypeVisible).Delete shift:=xlUp
to
rList.Offset(1).ClearContents
once you've set rList to your desired selection. Delete is the reason you're deleting rows and not clearing them. (1) is the reason you were doing A1 only instead of entire range.
EDIT
The final code that I tested this with was (includes going over all columns now):
Option Explicit
Sub DeleteEmails()
Dim rList As Range
Dim rCrit As Range
Dim rCells As Range
Dim i As Integer
With Worksheets("Sheet2")
.Range("A1").Insert shift:=xlDown
.Range("A1").Value = "Temp Header"
Set rCrit = .Range("A1", .Cells(Rows.Count, 1).End(xlUp))
End With
Set rCells = Sheet1.Range("$A$1:$T$1")
rCells.Insert shift:=xlDown
Set rCells = rCells.Offset(-1)
rCells.Value = "Temp Header"
For i = 1 To rCells.Count
Set rList = Sheet1.Range(rCells(1, i).address, Sheet1.Cells(Rows.Count, i).End(xlUp))
If rList.Count > 1 Then 'if a column is empty as is in my test case, continue to next column
rList.AdvancedFilter Action:=xlFilterInPlace, CriteriaRange:=rCrit, Unique:=False
rList.Offset(1).ClearContents
Worksheets("Sheet1").ShowAllData
End If
Next i
rCells.Delete shift:=xlUp
rCrit(1).Delete shift:=xlUp
Set rList = Nothing: Set rCrit = Nothing
End Sub
PS: may I request that you do not use ':' in vba. Its really hard to notice in vba's default IDE and took me a while to figure why things were happening but not making sense!