VBA script causes Excel to not respond after 15 loops - vba

I am running a script to find and delete rows that contain data from after 2018. I am searching through around 650000 rows. Every time I run the script after 5 seconds my cursor becomes the spinning circle and the excel program becomes unresponsive. Here is the code I am using.
Option Explicit
Option Base 1 'row and column index will match array index
Sub removeWrongYear()
Dim i As Long, yearA As Long, rowsCnt As Long
Dim rowsToDelete As Range
Dim vData As Variant
With ActiveSheet
'1st to 635475 row, 20th column
vData = Range(.Cells(1, 20), .Cells(635475, 20))
For i = UBound(vData) To 2 Step -1
If Val(Right(vData(i,1),2)) > 17 Then
Debug.Print Val(Right(vData(i,1),2))
rowsCnt = rowsCnt + 1
If rowsCnt > 1 Then
Set rowsToDelete = Union(rowsToDelete, .Rows(i))
ElseIf rowsCnt = 1 Then
Set rowsToDelete = .Rows(i)
End If
End If
Next i
End With
If rowsCnt > 0 Then
Application.ScreenUpdating = False
rowsToDelete.EntireRow.Delete
Application.ScreenUpdating = True
End If
End Sub

Every time I run the script after 5 seconds my cursor becomes the spinning circle and the excel program becomes unresponsive.
That's normal. VBA is running on the single available UI thread, the same one Excel runs on. While it's busy running your loop, it's not able to respond to other stimuli, and tells you that by putting "(not responding)" in the title bar, until it completes the work and is able to resume doing everything else it needs to do (i.e. listen for mouse & keyboard messages, etc.).
You could add a little DoEvents in the body of that loop to allow Excel to breathe and process pending messages between iterations, but then there's a catch: first, your code will take even longer to complete, and second, if the user is able to select/activate another sheet in the middle of that loop, then this unqualified Range call:
vData = Range(.Cells(1, 20), .Cells(635475, 20))
...will be the source of a run-time error 1004, since you can't do Sheet1.Range(Sheet2.Cells(1,20), Sheet2.Cells(635475,20)) and expect Excel to know what to do with that (assuming Sheet2 was active when the loop started, and the user activated Sheet1 in the middle of it).
This answer provides what appears to be the most efficient approach to conditionally deleting lines when a lot of rows are involved. If you can, add a helper column to calculate your criteria (e.g. make it return TRUE for rows to keep and FALSE for rows to delete), then use Worksheet.Replace and Worksheet.SpecialCells to perform the filtering and deletion:
.Columns("Z:Z").Replace What:=False, _
Replacement:="", _
LookAt:=xlPart, _
SearchOrder:=xlByRows, _
MatchCase:=False, _
SearchFormat:=False, _
ReplaceFormat:=False
.Columns("Z:Z").SpecialCells(xlCellTypeBlanks).EntireRow.Delete
Then you don't need a loop, and it might actually complete before you get to count to 5 seconds.
Other than that, long-running operations are just that: long-running operations. Own it:
Application.StatusBar = "Please wait..."
Application.Cursor = xlWait
Application.ScreenUpdating = False
Application.Calculation = xlCalculationManual
Application.EnableEvents = False
'..code..
Application.Cursor = xlDefault
Application.ScreenUpdating = True
Application.Calculation = xlCalculationAutomatic
Application.EnableEvents = True
Application.StatusBar = False

This seems pretty quick. It puts results in U1 and down so you'd probably want to amend that. This extracts the values you want into a second array.
Sub removeWrongYear()
Dim i As Long, vData As Variant, v2(), j As Long
vData = Range(Cells(1, 20), Cells(635475, 20))
ReDim v2(1 To UBound(vData, 1), 1 To 1)
For i = UBound(vData) To 2 Step -1
If Val(Right(vData(i, 1), 2)) <= 17 Then
j = j + 1
v2(j, 1) = vData(i, 1)
End If
Next i
Range("U1").Resize(j, 1) = v2
End Sub

This uses an AutoFilter - the more rows to delete, the faster it gets
Rows: 1,048,575 (Deleted: 524,286), Cols: 21 (70 Mb xlsb file)
Time: 6.90 sec, 7.49 sec, 7.21 sec (3 tests)
Test data shown in images bellow
How it works
It generates a temporary helper column with formula "=RIGHT(T1, 2)" (first empty column)
Applies a filter for the years to keep ("<18") in the temp column
Copies all visible rows to a new sheet (not including the temp column)
Removes the initial sheet
Renames the new sheet to the initial sheet name
Option Explicit
Public Sub RemoveYearsAfter18()
Dim ws As Worksheet, wsName As String, lr As Long, lc As Long
Dim ur As Range, filterCol As Range, newWs As Worksheet
Set ws = Sheet1 'Or: Set ws = ThisWorkbook.Worksheets("Sheet1")
wsName = ws.Name
lr = ws.Cells(ws.Rows.Count, "T").End(xlUp).Row 'Last Row in col T (or 635475)
lc = ws.Cells(1, ws.Columns.Count).End(xlToLeft).Column 'Last Col in row 1
Set ur = ws.Range(ws.Cells(1, 1), ws.Cells(lr, lc))
Set filterCol = ws.Range(ws.Cells(1, lc + 1), ws.Cells(lr, lc + 1)) 'Exclude Headers
OptimizeApp True
Set newWs = ThisWorkbook.Worksheets.Add(After:=ws) 'Add new sheet
With filterCol
.Formula = "=RIGHT(T1, 2)"
.Cells(1) = "FilterCol" 'Column header
.Value2 = .Value2 'Convert formulas to values for filter
End With
filterCol.AutoFilter Field:=1, Criteria1:="<18" 'Reverse filter
ur.Copy 'Copy visible data
With newWs.Cells
.PasteSpecial xlPasteColumnWidths
.PasteSpecial xlPasteAll 'Paste data on new sheet
.Cells(1).Select
End With
ws.Delete 'Delete old sheet
newWs.Name = wsName
OptimizeApp False
End Sub
Private Sub OptimizeApp(ByVal speedUp As Boolean)
Application.Calculation = IIf(speedUp, xlCalculationManual, xlCalculationAutomatic)
Application.ScreenUpdating = Not speedUp
Application.DisplayAlerts = Not speedUp
Application.EnableEvents = Not speedUp
End Sub
Before
After

This code process 635475 Rows x 20 Columns in 12.48 seconds on my fast computer and 33.32 seconds on my old computer (0.84 and 2.06 seconds for 38k x 20).
Option Explicit
Sub removeWrongYear2()
Const DATE_COLUMN = 20
Dim StartTime As Double: StartTime = Timer
Dim data() As Variant, results() As Variant
Dim c As Long, r As Long, r2 As Long
With ActiveSheet
data = .UsedRange.Value
ReDim results(1 To UBound(data), 1 To UBound(data, 2))
For r = 2 To UBound(data)
If Val(Right(data(r, DATE_COLUMN), 2)) <= 17 Then
r2 = r2 + 1
For c = 1 To UBound(data, 2)
results(r2, c) = data(r, c)
Next
End If
Next
If r2 > 0 Then
Application.ScreenUpdating = False
Application.Calculation = xlCalculationManual
.UsedRange.Offset(1).Value = results
Application.Calculation = xlCalculationAutomatic
Application.ScreenUpdating = True
End If
End With
Debug.Print Round(Timer - StartTime, 2)
End Sub
Sub Setup()
Dim data, r, c As Long
Const LASTROW = 635475
Cells.Clear
data = Range(Cells(1, 1), Cells(LASTROW, 20)).Value
For r = 1 To UBound(data)
For c = 1 To 19
data(r, c) = Int((LASTROW * Rnd) + 100)
Next
data(r, 20) = Int((10 * Rnd) + 10)
Next
Application.ScreenUpdating = False
Range(Cells(1, 1), Cells(LASTROW, 20)).Value = data
Application.ScreenUpdating = True
End Sub

Sort() & AutoFilter() are always a good pair:
Sub nn()
Dim sortRng As Range
With ActiveSheet.UsedRange ' reference all data in active sheet
With .Offset(, .Columns.Count).Resize(, 1) ' get a helper column right outside data
.Formula = "=ROW()" ' fill it with sequential numbers from top to down
.Value = .Value ' get rid of formulas
Set sortRng = .Cells ' store the helper range
End With
With .Resize(, .Columns.Count + 1) ' consider data and the helper range
.Sort key1:=.Cells(1, 20), order1:=xlAscending, Header:=xlNo ' sort it by data in column 20
.AutoFilter Field:=20, Criteria1:=">=01/01/2018" ' filter it for data greater than 2017
.Resize(.Rows.Count - 1).Offset(1).SpecialCells(xlCellTypeVisible).EntireRow.Delete ' delete filtered data
.Parent.AutoFilterMode = False ' remove filter
.Sort key1:=sortRng(1, 1), order1:=xlAscending, Header:=xlNo ' sort things back by means of helper column
.Columns(.Columns.Count).ClearContents ' clear helper column
End With
End With
End Sub
in my test a 768k row by 21 columns data took 11 seconds

Related

Populating another sheet using keywords [duplicate]

I have this issue that I'm trying to solve. each day I get an report containing data that I need to send forward. So in order to make it a bit easier I have tried to find a macro that creates a new sheet with the name of the agent and moves the data for each agent in the created sheet...
I have found one that suppose to do pretty much that. But since this isn't really my area of expertise I'm not able to modify it to handle my request, and even make it work probably. Anyone have any idea ?
Const cl& = 2
Const datz& = 1
Dim a As Variant, x As Worksheet, sh As Worksheet
Dim rws&, cls&, p&, i&, ri&, j&
Dim u(), b As Boolean, y
Application.ScreenUpdating = False
Sheets("Sheet1").Activate
rws = Cells.Find("*", , , , xlByRows, xlPrevious).Row
cls = Cells.Find("*", , , , xlByColumns, xlPrevious).Column
Set x = Sheets.Add(After:=Sheets("Sheet1"))
Sheets("Sheet1").Cells(1).Resize(rws, cls).Copy x.Cells(1)
Set a = x.Cells(1).Resize(rws, cls)
a.Sort a(1, cl), 2, Header:=xlYes
a = a.Resize(rws + 1)
p = 2
For i = p To rws + 1
If a(i, cl) <> a(p, cl) Then
b = False
For Each sh In Worksheets
If sh.Name = a(p, cl) Then b = True: Exit For
Next
If Not b Then
Sheets.Add.Name = a(p, cl)
With Sheets(a(p, cl))
x.Cells(1).Resize(, cls).Copy .Cells(1)
ri = i - p
x.Cells(p, 1).Resize(ri, cls).Cut .Cells(2, 1)
.Cells(2, 1).Resize(ri, cls).Sort .Cells(2, datz), Header:=xlNo
y = .Cells(datz).Resize(ri + 1)
ReDim u(1 To 2 * ri, 1 To 1)
For j = 2 To ri
u(j, 1) = j
If y(j, 1) <> y(j + 1, 1) Then u(j + ri, 1) = j
Next j
.Cells(cls + 1).Resize(2 * ri) = u
.Cells(1).Resize(2 * ri, cls + 1).Sort .Cells(cls + 1), Header:=xlYes
.Cells(cls + 1).Resize(2 * ri).ClearContents
End With
End If
p = i
End If
Next i
Application.DisplayAlerts = False
x.Delete
Application.DisplayAlerts = True
Application.ScreenUpdating = True
This is an example of my report I receive
example
I keep getting error on row: a.Sort a(1, cl), 2, Header:=xlYes
That in self i don't really know what it does. Can anyone explain?
Here is a generic model (heavily commented) that should produce your individual agent worksheets. This copies the original 'master' worksheet and removes information that does not pertain to each individual agent.
Module1 code
Option Explicit
Sub agentWorksheets()
Dim d As Long, agnt As Variant, vAGNTs As Variant, dAGNTs As Object
Dim wsn As String, wb As Workbook
'set special application environment
'appTGGL bTGGL:=False 'uncomment this after debuging is complete
Set wb = ThisWorkbook '<~~ set to any open workbook or open a closed one
wsn = "Agents" '<~~ rename to the right master workbook
'create the dictionary and
Set dAGNTs = CreateObject("Scripting.Dictionary")
dAGNTs.CompareMode = vbTextCompare
'first the correct workbook
With wb
'work with the master worksheet
With .Worksheets(wsn)
'get all of the text values from column B
vAGNTs = .Range(.Cells(6, "B"), .Cells(Rows.Count, "B").End(xlUp)).Value2
'construct a dictionary of the agents usin unique keys
For d = LBound(vAGNTs) To UBound(vAGNTs)
'overwrite method - no check to see if it exists (just want unique list)
dAGNTs.Item(vAGNTs(d, 1)) = vbNullString
Next d
End With
'loop through the agents' individual worksheets
'if one does not exist, create it from the master workbook
For Each agnt In dAGNTs
'set error control to catch non-existant agent worksheets
On Error GoTo bm_Need_Agent_WS
With Worksheets(agnt)
On Error GoTo bm_Safe_Exit
'if an agent worksheet did not exist then
'one has been created with non-associated data removed
'perform any additional operations here
'example: today's date in A1
.Cells(1, "A") = Date
End With
Next agnt
End With
'slip past agent worksheet creation
GoTo bm_Safe_Exit
bm_Need_Agent_WS:
'basic error control for bad worksheet names, etc.
On Error GoTo 0
'copy the master worksheet
wb.Worksheets(wsn).Copy after:=Sheets(Sheets.Count)
With wb.Worksheets(Sheets.Count)
'rename the copy to the agent name
.Name = StrConv(agnt, vbProperCase)
'turn off any existing AutoFilter
If .AutoFilterMode Then .AutoFilterMode = False
'filter on column for everything that isn't the agent
With .Range(.Cells(5, "B"), .Cells(Rows.Count, "B").End(xlUp))
.AutoFilter field:=1, Criteria1:="<>" & agnt
'step off the header row
With .Resize(.Rows.Count - 1, 1).Offset(1, 0)
'check if there is anything to remove
If CBool(Application.Subtotal(103, .Cells)) Then
'delete all non-associated information
.EntireRow.Delete
End If
End With
End With
'turn off the AutoFilter we just created
.AutoFilterMode = False
End With
'go back to the thrown error
Resume
bm_Safe_Exit:
'reset application environment
appTGGL
End Sub
'helper sub to set/restore all of the environment settings
Public Sub appTGGL(Optional bTGGL As Boolean = True)
With Application
.ScreenUpdating = bTGGL
.EnableEvents = bTGGL
.DisplayAlerts = bTGGL
.Calculation = IIf(bTGGL, xlCalculationAutomatic, xlCalculationManual)
.CutCopyMode = False
.StatusBar = vbNullString
End With
Debug.Print Timer
End Sub
Sometimes it is just easier to remove what you do not want than recreate many parts of what you started with.
With #Jeeped great answer, I will also add second answer. :-)
To separate each agent data to separate sheets you can do the following...
see comment on the code
Option Explicit
Sub Move_Each_Agent_to_Sheet()
' // Declare your Variables
Dim Sht As Worksheet
Dim Rng As Range
Dim List As Collection
Dim varValue As Variant
Dim i As Long
' // Set your Sheet name
Set Sht = ActiveWorkbook.Sheets("Sheet1")
' // set your auto-filter, A6
With Sht.Range("A6")
.AutoFilter
End With
' // Set your agent Column range # (2) that you want to filter it
Set Rng = Range(Sht.AutoFilter.Range.Columns(2).Address)
' // Create a new Collection Object
Set List = New Collection
' // Fill Collection with Unique Values
On Error Resume Next
For i = 2 To Rng.Rows.Count
List.Add Rng.Cells(i, 1), CStr(Rng.Cells(i, 1))
Next i
' // Start looping in through the collection Values
For Each varValue In List
' // Filter the Autofilter to macth the current Value
Rng.AutoFilter Field:=2, Criteria1:=varValue
' // Copy the AutoFiltered Range to new Workbook
Sht.AutoFilter.Range.Copy
Worksheets.Add.Paste
ActiveSheet.Name = Left(varValue, 30)
Cells.EntireColumn.AutoFit
' // Loop back to get the next collection Value
Next varValue
' // Go back to main Sheet and removed filters
Sht.AutoFilter.ShowAllData
Sht.Activate
End Sub

How to create a multiple criteria advance filter in VBA?

I'm trying to create an advanced filter for the below table but the code below is just hiding the cells. It's working but my problem with it is if i filter something and then I drag to fill status or any other cells it will override the cells in between for example in filter mode I have 2 rows one is 1st row and the other one is at row 20 if I drag to fill status it will replace the status of all cells in between 1 and 20 and don't know how to work it out, i know this happens because I'm hiding the cells and not actually filtering them.
Any help will be much appreciated.
[Data Table][1]
Private Sub Worksheet_Change(ByVal Target As Range)
Application.ScreenUpdating = False
Application.EnableEvents = False
r1 = Target.Row
c1 = Target.Column
If r1 <> 3 Then GoTo ending:
If ActiveSheet.Cells(1, c1) = "" Then GoTo ending:
Dim LC As Long
With ActiveSheet
LC = .Cells.Find(What:="*", After:=[A1], SearchOrder:=xlByColumns, SearchDirection:=xlPrevious).Column
End With
ActiveSheet.Range("4:10000").Select
Selection.EntireRow.Hidden = False
LR = Cells.Find(What:="*", After:=[A1], SearchOrder:=xlByRows, SearchDirection:=xlPrevious).Row
For r = 5 To LR
For c = 1 To LC
If ActiveSheet.Cells(2, c) = "" Or ActiveSheet.Cells(3, c) = "" Then GoTo nextc:
If ActiveSheet.Cells(2, c) = "exact" And UCase(ActiveSheet.Cells(r, c)) <> UCase(ActiveSheet.Cells(3, c)) Then ActiveSheet.Rows(r).EntireRow.Hidden = True: GoTo nextr:
If Cells(2, c) = "exact" Then GoTo nextc:
j = InStr(1, UCase(ActiveSheet.Cells(r, c)), UCase(ActiveSheet.Cells(3, c)))
If ActiveSheet.Cells(2, c) = "partial" And j = 0 Then ActiveSheet.Rows(r).EntireRow.Hidden = True: GoTo nextr:
nextc:
Next c
nextr:
Next r
ending:
Application.EnableEvents = True
Application.ScreenUpdating = True
End Sub
The below code will be the answer to the question on how to create an advanced search based on multiple criteria on what the user selects in the table.
I will need a little bit of help with how to check if the user selected by mistake an empty cell I will need to make excel ignore filtering the blank cell. Also, I will need to make excel first to check if the yellow cells A3 to T3 has data in and if it has and i press the filter button will filter by the range A3:T3 and ignore the current user selection if there is no data in range A3:T3 will filter by the user selection and in the range A3:T3, if it has data will only filter by data cell that has data in them and ignore empty ones.
Sub advancedMultipleCriteriaFilter()
Dim cellRng As Range, tableObject As Range, subSelection As Range
Dim filterCriteria() As String, filterFields() As Integer
Dim i As Integer
If Selection.Rows.Count > 1 Then
MsgBox "Cannot apply filters to multiple rows within the same column. Please make another selection and try again.", vbInformation, "Selection Error!"
Exit Sub
End If
Application.ScreenUpdating = False
i = 1
ReDim filterCriteria(1 To Selection.Cells.Count) As String
ReDim filterFields(1 To Selection.Cells.Count) As Integer
Set tableObject = Selection.CurrentRegion
For Each subSelection In Selection.Areas
For Each cellRng In subSelection
filterCriteria(i) = cellRng.Text
filterFields(i) = cellRng.Column - tableObject.Cells(1, 1).Column + 1
i = i + 1
Next cellRng
Next subSelection
With tableObject
For i = 1 To UBound(filterCriteria)
.AutoFilter field:=filterFields(i), Criteria1:=filterCriteria(i)
Next i
End With
Set tableObject = Nothing
Application.ScreenUpdating = True
End Sub
Sub resetFilters()
Dim sht As Worksheet
Dim LastRow As Range
Application.ScreenUpdating = False
On Error Resume Next
If ActiveSheet.FilterMode Then
ActiveSheet.ShowAllData
End If
Range("A3:T3").ClearContents
Application.ScreenUpdating = True
Call GetLastRow
End Sub
Private Sub GetLastRow()
'Step 1: Declare Your Variables.
Dim LastRow As Long
'Step 2: Capture the last used row number.
LastRow = Cells(Rows.Count, 8).End(xlUp).Row
'Step 3: Select the next row down
Cells(LastRow, 8).Offset(1, 0).Select
End Sub

Improving For Loop Efficiency VBA

Currently I have 10Kx15 Rows worth of raw data imported in an excel spreadsheet.
I have a number of fields that are cleansed but the one of interest is a field called "Hazard". For every instance of Hazard encountered, we need to strip this out.
This is the code I use to cleanse (partially) my data set:
Sub dataCleanse()
Dim Last
Application.ScreenUpdating = False
Application.Calculation = xlCalculationManual
Last = Cells(Rows.Count, "F").End(xlUp).Row
For i = Last To 1 Step -1
If (Cells(i, "F").Value) = "Hazard" Then
Cells(i, "A").EntireRow.Delete
End If
Next i
Application.Calculation = xlCalculationAutomatic
Application.ScreenUpdating = True
End Sub
To process 10,000 records or so it takes 10-15 seconds. I have experimented with using auto-filter, but when I use .EntireRow.Delete it strips out the rows underneath the filtered criteria.
i.e. If we have rows 1 and 3 with 'Hazard' and use auto-filter, it will also delete row2 which does not have 'Hazard'.
I have also set the calculation to Manual first and then Automatic so it doesn't refresh each time.
Are there any suggestions that could be offered to increase the speed of my macro?
Thank you!
you could go with the following Autofilter approach
Option Explicit
Sub dataCleanse()
Application.ScreenUpdating = False
Application.Calculation = xlCalculationManual
Application.DisplayAlerts = False
With ActiveSheet
' insert "dummy" header cell for Autofilter to work
.Range("F1").Insert
.Range("F1").value = "header"
With .Range("F1", .Cells(.Rows.Count, "F").End(xlUp))
.AutoFilter Field:=1, Criteria1:="Hazard"
With .Offset(1).Resize(.Rows.Count - 1)
If Application.WorksheetFunction.Subtotal(103, .Columns(1)) > 1 Then .SpecialCells(xlCellTypeVisible).EntireRow.Delete
End With
.AutoFilter
End With
.Range("F1").Delete 'remove "dummy" header cell
End With
Application.DisplayAlerts = True
Application.Calculation = xlCalculationAutomatic
Application.ScreenUpdating = True
End Sub
processing 10,000 records of 250 columns each in much less then a second
I am not sure if this will be faster, but my suggestion is to select column F, find an instance of "Hazard", delete that row, and repeat the process until "Hazard" is not found in column F.
Dim iRow As Integer
Application.ScreenUpdating = False
Columns("F:F").Select
Set RangeObj = Selection.Find(What:="Hazard", LookIn:=xlValues, MatchCase:=True)
Do Until (RangeObj Is Nothing)
iRow = RangeObj.Row
Rows(iRow & ":" & iRow).Delete
Columns("F:F").Select
Set RangeObj = Selection.Find(What:="Hazard", LookIn:=xlValues, MatchCase:=True)
Loop
Application.ScreenUpdating = True
Please give it a try.
This solution is not faster for small datasets, but it will be for very large datasets. The code looks longer, but handling the arrays is much faster than manipulating the workbook. (I am sure there are more efficient ways to shorten the array). BTW - your code worked for me on the example dataset I put together. If this doesn't work on your data, please post a small example of your input and what the result should look like.
Example input:
Output from macro:
Macro code using arrays:
Option Explicit
Sub dataCleanse2()
Dim nRows As Long, nCols As Long
Dim i As Long, j As Long, k As Long
Dim myRng As Range
Dim myArr() As Variant, myTmpArr() As Variant
Application.ScreenUpdating = False
Application.Calculation = xlCalculationManual
Set myRng = Sheets("Sheet1").UsedRange
myArr = myRng.Value2
nRows = UBound(myArr, 1)
nCols = UBound(myArr, 2)
For i = nRows To 1 Step -1
If CStr(myArr(i, 6)) = "Hazard" Then
ReDim Preserve myTmpArr(1 To nRows - 1, 1 To nCols)
For j = 1 To i - 1
For k = 1 To nCols
myTmpArr(j, k) = myArr(j, k)
Next k
Next j
For j = i To nRows - 1
For k = 1 To nCols
myTmpArr(j, k) = myArr(j + 1, k)
Next k
Next j
nRows = UBound(myTmpArr, 1)
Erase myArr
myArr = myTmpArr
Erase myTmpArr
End If
Next i
myRng.Clear
Set myRng = Sheets("Sheet1").Range(Cells(1, 1), Cells(nRows, nCols))
myRng.Value2 = myArr
Set myRng = Nothing
Erase myArr
Application.Calculation = xlCalculationAutomatic
Application.ScreenUpdating = True
End Sub
Thanks for the help everyone! I went for an alternative approach (apart from the one user has posted), that made use of an array. I'm still familiarizing myself with arrays (newish to VBA / programming in general), but found that when I loaded the values in an array there was an improvement by around 50% in speed! I don't know the exact reason why loading into an array is that much faster, but I'm assuming it is to do with the fact that it processes the array as an aggregate rather than individual cell values.
Sub CleanseAction()
Dim Last
Dim prevCalcMode As Variant
Application.ScreenUpdating = False
prevCalcMode = Application.Calculation
Application.Calculation = xlCalculationManual
Last = Cells(Rows.Count, "H").End(xlUp).Row
For i = Last To 1 Step -1
If (Cells(i, "H").Value) = "Hazard" Then
Cells(i, "A").EntireRow.Delete
End If
Next i
Application.Calculation = prevCalcMode
Application.ScreenUpdating = True

Create a new sheet for each unique agent and move all data to each sheet

I have this issue that I'm trying to solve. each day I get an report containing data that I need to send forward. So in order to make it a bit easier I have tried to find a macro that creates a new sheet with the name of the agent and moves the data for each agent in the created sheet...
I have found one that suppose to do pretty much that. But since this isn't really my area of expertise I'm not able to modify it to handle my request, and even make it work probably. Anyone have any idea ?
Const cl& = 2
Const datz& = 1
Dim a As Variant, x As Worksheet, sh As Worksheet
Dim rws&, cls&, p&, i&, ri&, j&
Dim u(), b As Boolean, y
Application.ScreenUpdating = False
Sheets("Sheet1").Activate
rws = Cells.Find("*", , , , xlByRows, xlPrevious).Row
cls = Cells.Find("*", , , , xlByColumns, xlPrevious).Column
Set x = Sheets.Add(After:=Sheets("Sheet1"))
Sheets("Sheet1").Cells(1).Resize(rws, cls).Copy x.Cells(1)
Set a = x.Cells(1).Resize(rws, cls)
a.Sort a(1, cl), 2, Header:=xlYes
a = a.Resize(rws + 1)
p = 2
For i = p To rws + 1
If a(i, cl) <> a(p, cl) Then
b = False
For Each sh In Worksheets
If sh.Name = a(p, cl) Then b = True: Exit For
Next
If Not b Then
Sheets.Add.Name = a(p, cl)
With Sheets(a(p, cl))
x.Cells(1).Resize(, cls).Copy .Cells(1)
ri = i - p
x.Cells(p, 1).Resize(ri, cls).Cut .Cells(2, 1)
.Cells(2, 1).Resize(ri, cls).Sort .Cells(2, datz), Header:=xlNo
y = .Cells(datz).Resize(ri + 1)
ReDim u(1 To 2 * ri, 1 To 1)
For j = 2 To ri
u(j, 1) = j
If y(j, 1) <> y(j + 1, 1) Then u(j + ri, 1) = j
Next j
.Cells(cls + 1).Resize(2 * ri) = u
.Cells(1).Resize(2 * ri, cls + 1).Sort .Cells(cls + 1), Header:=xlYes
.Cells(cls + 1).Resize(2 * ri).ClearContents
End With
End If
p = i
End If
Next i
Application.DisplayAlerts = False
x.Delete
Application.DisplayAlerts = True
Application.ScreenUpdating = True
This is an example of my report I receive
example
I keep getting error on row: a.Sort a(1, cl), 2, Header:=xlYes
That in self i don't really know what it does. Can anyone explain?
Here is a generic model (heavily commented) that should produce your individual agent worksheets. This copies the original 'master' worksheet and removes information that does not pertain to each individual agent.
Module1 code
Option Explicit
Sub agentWorksheets()
Dim d As Long, agnt As Variant, vAGNTs As Variant, dAGNTs As Object
Dim wsn As String, wb As Workbook
'set special application environment
'appTGGL bTGGL:=False 'uncomment this after debuging is complete
Set wb = ThisWorkbook '<~~ set to any open workbook or open a closed one
wsn = "Agents" '<~~ rename to the right master workbook
'create the dictionary and
Set dAGNTs = CreateObject("Scripting.Dictionary")
dAGNTs.CompareMode = vbTextCompare
'first the correct workbook
With wb
'work with the master worksheet
With .Worksheets(wsn)
'get all of the text values from column B
vAGNTs = .Range(.Cells(6, "B"), .Cells(Rows.Count, "B").End(xlUp)).Value2
'construct a dictionary of the agents usin unique keys
For d = LBound(vAGNTs) To UBound(vAGNTs)
'overwrite method - no check to see if it exists (just want unique list)
dAGNTs.Item(vAGNTs(d, 1)) = vbNullString
Next d
End With
'loop through the agents' individual worksheets
'if one does not exist, create it from the master workbook
For Each agnt In dAGNTs
'set error control to catch non-existant agent worksheets
On Error GoTo bm_Need_Agent_WS
With Worksheets(agnt)
On Error GoTo bm_Safe_Exit
'if an agent worksheet did not exist then
'one has been created with non-associated data removed
'perform any additional operations here
'example: today's date in A1
.Cells(1, "A") = Date
End With
Next agnt
End With
'slip past agent worksheet creation
GoTo bm_Safe_Exit
bm_Need_Agent_WS:
'basic error control for bad worksheet names, etc.
On Error GoTo 0
'copy the master worksheet
wb.Worksheets(wsn).Copy after:=Sheets(Sheets.Count)
With wb.Worksheets(Sheets.Count)
'rename the copy to the agent name
.Name = StrConv(agnt, vbProperCase)
'turn off any existing AutoFilter
If .AutoFilterMode Then .AutoFilterMode = False
'filter on column for everything that isn't the agent
With .Range(.Cells(5, "B"), .Cells(Rows.Count, "B").End(xlUp))
.AutoFilter field:=1, Criteria1:="<>" & agnt
'step off the header row
With .Resize(.Rows.Count - 1, 1).Offset(1, 0)
'check if there is anything to remove
If CBool(Application.Subtotal(103, .Cells)) Then
'delete all non-associated information
.EntireRow.Delete
End If
End With
End With
'turn off the AutoFilter we just created
.AutoFilterMode = False
End With
'go back to the thrown error
Resume
bm_Safe_Exit:
'reset application environment
appTGGL
End Sub
'helper sub to set/restore all of the environment settings
Public Sub appTGGL(Optional bTGGL As Boolean = True)
With Application
.ScreenUpdating = bTGGL
.EnableEvents = bTGGL
.DisplayAlerts = bTGGL
.Calculation = IIf(bTGGL, xlCalculationAutomatic, xlCalculationManual)
.CutCopyMode = False
.StatusBar = vbNullString
End With
Debug.Print Timer
End Sub
Sometimes it is just easier to remove what you do not want than recreate many parts of what you started with.
With #Jeeped great answer, I will also add second answer. :-)
To separate each agent data to separate sheets you can do the following...
see comment on the code
Option Explicit
Sub Move_Each_Agent_to_Sheet()
' // Declare your Variables
Dim Sht As Worksheet
Dim Rng As Range
Dim List As Collection
Dim varValue As Variant
Dim i As Long
' // Set your Sheet name
Set Sht = ActiveWorkbook.Sheets("Sheet1")
' // set your auto-filter, A6
With Sht.Range("A6")
.AutoFilter
End With
' // Set your agent Column range # (2) that you want to filter it
Set Rng = Range(Sht.AutoFilter.Range.Columns(2).Address)
' // Create a new Collection Object
Set List = New Collection
' // Fill Collection with Unique Values
On Error Resume Next
For i = 2 To Rng.Rows.Count
List.Add Rng.Cells(i, 1), CStr(Rng.Cells(i, 1))
Next i
' // Start looping in through the collection Values
For Each varValue In List
' // Filter the Autofilter to macth the current Value
Rng.AutoFilter Field:=2, Criteria1:=varValue
' // Copy the AutoFiltered Range to new Workbook
Sht.AutoFilter.Range.Copy
Worksheets.Add.Paste
ActiveSheet.Name = Left(varValue, 30)
Cells.EntireColumn.AutoFit
' // Loop back to get the next collection Value
Next varValue
' // Go back to main Sheet and removed filters
Sht.AutoFilter.ShowAllData
Sht.Activate
End Sub

Delete duplicate row in excel by comparing a column

Given below is an example of data i'm working with
As you can see it has duplicate entries.(actual database is 30000 entries)
I would like to find a method on how I can remove duplicate rows base on the corresponding column where percentage is listed.
The method should compare duplicate row percentages and choose the highest one and discard the other
This the question(initial):
I hope its clear with this output.
This is the result, I want
Any help would be appreciated!
Try this. It will (should) sort the data by the email and percentage column, then remove duplicates leaving the highest percentage intact.
With ActiveSheet
.Range("A1:B" & Cells(Rows.Count, "A").End(xlUp).Row).Sort _
Key1:=Range("A1"), Order1:=xlDescending, Header:=xlYes, KEY2:=Range("B1"), Order2:=xlDescending, Header:=xlYes
.Range("A1:B" & Cells(Rows.Count, "A").End(xlUp).Row).RemoveDuplicates Columns:=Array(1), Header:=xlYes
End With
If you only need this method once you can do it manually.
Step 1: Sort by percentage descending.
Step 2: Use the 'Remove Duplicates' function on the data ribbon. Use it on the 'Email' column only.
1- clic DATA tab
2-select your data and clic Remove Duplicates
3-Select corresponding columns and clic OK.
Here is an altered version of the following post:
Delete all rows if duplicate in excel - VBA
Sub remDup2()
Dim rng As Range, dupRng As Range, lastrow As Long, ws As Worksheet
Dim col As Long, col2 As Long, offset As Long, deletecurrent As Boolean
'Disable all the stuff that is slowing down
Application.ScreenUpdating = False
Application.EnableEvents = False
Application.Calculation = xlCalculationManual
'Define your worksheet here
Set ws = Worksheets(1)
'Define your column and row offset here
col = 1 'Column with E-Mail
col2 = 2 'Column with percentage
offset = 1 'Startrow with entries
'Find first empty row
Set rng = ws.Cells(offset + 1, col)
lastrow = rng.EntireColumn.Find( _
What:="", After:=ws.Cells(offset + 1, col)).Row - 1
'Loop through list
While (rng.Row < lastrow)
Do
Set dupRng = ws.Range(ws.Cells(rng.Row + 1, col), ws.Cells(lastrow, col)).Find( _
What:=rng, LookAt:=xlWhole)
If (Not (dupRng Is Nothing)) Then
If (ws.Cells(rng.Row, col2) > ws.Cells(dupRng.Row, col2)) Then
dupRng.EntireRow.Delete
lastrow = lastrow - 1
Else
deletecurrent = True
Exit Do
End If
If (lastrow = rng.Row) Then Exit Do
Else
Exit Do
End If
Loop
Set rng = rng.offset(1, 0)
'Delete current row
If (deletecurrent) Then
rng.offset(-1, 0).EntireRow.Delete
lastrow = lastrow - 1
End If
deletecurrent = False
Wend
'Enable stuff again
Application.ScreenUpdating = True
Application.EnableEvents = True
Application.Calculation = xlCalculationAutomatic
End Sub