Excel macro to paste transpose based on repeated values in another column - excel

The first set of data is a snippet of may data (running into thousands of rows) in the first two columns.
The first column has repeated ticket numbers with different status. I want to have a unique row for each ticket and corresponding columns to have the various status(a transpose like). See below illustration:
Incident Number Measurement Status
INCIN0001910583 Detached
INCIN0001910583 Missed
INCIN0001908104 Detached
INCIN0001908104 Detached
INCIN0001908104 Missed
INCIN0001914487 Met
INCIN0001908444 Detached
INCIN0001908444 Detached
INCIN0001908444 Detached
INCIN0001908444 Met
INCIN0001910624 Met
INCIN0001910575 Detached
INCIN0001910575 Met
I'm looking for a macro (or formula) to achieve something like this:
INCIN0001910583 Detached Missed
INCIN0001908104 Detached Detached Missed
INCIN0001914487 Met
INCIN0001908444 Detached Detached Detached Met
INCIN0001910624 Met
INCIN0001910575 Detached Met
As Tom pointed out, below is the recorded macro I have been using to achieve this, pasting the transpose in the first occurrence of the unique Incident Number(column A) and then manually removing the blanks.(however it takes ages to complete it for thousands of rows)
Sub transpose_paste()
'
' transpose_paste Macro
'
' Keyboard Shortcut: Ctrl+t
'
Selection.Copy
Application.CutCopyMode = False
Selection.Copy
ActiveWindow.ScrollColumn = 2
ActiveWindow.ScrollColumn = 3
ActiveWindow.ScrollColumn = 4
ActiveWindow.ScrollColumn = 5
ActiveWindow.ScrollColumn = 6
ActiveWindow.ScrollColumn = 7
ActiveWindow.ScrollColumn = 8
ActiveWindow.ScrollColumn = 9
ActiveWindow.ScrollColumn = 10
ActiveWindow.ScrollColumn = 11
ActiveWindow.ScrollColumn = 12
ActiveWindow.ScrollColumn = 11
ActiveWindow.ScrollColumn = 10
ActiveWindow.ScrollColumn = 11
Cells(ActiveCell.Row, 14).Select
Selection.PasteSpecial Paste:=xlPasteAll, Operation:=xlNone, SkipBlanks:= _
False, Transpose:=True
ActiveWindow.ScrollColumn = 10
ActiveWindow.ScrollColumn = 9
ActiveWindow.ScrollColumn = 8
ActiveWindow.ScrollColumn = 7
ActiveWindow.ScrollColumn = 6
ActiveWindow.ScrollColumn = 5
ActiveWindow.ScrollColumn = 4
ActiveWindow.ScrollColumn = 3
ActiveWindow.ScrollColumn = 2
ActiveWindow.ScrollColumn = 1
End Sub

I'm not sure I understand why Tom gave you the advice he did. This wouldn't be a very good idea to get a recorded macro from because of the non-dynamic nature of recorded code as opposed to the dynamic nature of your data.
Here are two options. The first being what you asked for (run the 'PivotData_All' routine), the other being if you want to exclude non-unique items from the subsequent columns of data (run the 'PivotData_UniquesOnly' routine).
Sub PivotData_All()
With Worksheets("Sheet1")
Call PivotData(.Range("A2:B" & .Cells(.Rows.Count, 1).End(xlUp).Row), False)
End With
End Sub
Sub PivotData_UniquesOnly()
With Worksheets("Sheet1")
Call PivotData(.Range("A2:B" & .Cells(.Rows.Count, 1).End(xlUp).Row), True)
End With
End Sub
Sub PivotData( _
ByVal IncidentData As Range, _
Optional ByVal UniquesOnly As Boolean = False _
)
'
' Take data from a given range and pivot out data based on first column being incident numbers, second column being
' measurement status. Each unique incident will be given its own row and the measurment status will be pivoted out
' along columns on a new sheet.
'
' Syntax: PivotData(UniquesOnly)
'
' Parameters: IncidentData. Range. Required. A two-column set of data. Left column is incident number, right column
' is measurement status.
' UniquesOnly. Boolean. Optional. Specify whether second column of data should contain only unique values
' or not. If omitted False is passed.
'
Dim Incidents As Collection
Dim NewSheet As Worksheet
Dim Incident() As Variant
Dim IncidentItem As Variant
Dim IncidentTempValues() As Variant
Dim IncidentStep As Long
Dim IncidentMatch As Long
Dim IncidentKey As String
'// Change these as necessary
'// Get values into an array to start
IncidentTempValues = IncidentData.Value
'// Iterate through array to get unique values, append all measurements to individual array
Set Incidents = New Collection
For IncidentStep = LBound(IncidentTempValues, 1) To UBound(IncidentTempValues, 1)
IncidentKey = CStr(IncidentTempValues(IncidentStep, 1))
If InCollection(Incidents, IncidentKey) = False Then
Incident = Array(IncidentKey, IncidentTempValues(IncidentStep, 2))
Incidents.Add Incident, IncidentKey
Else
Erase Incident
Incident = Incidents.Item(IncidentKey)
IncidentMatch = 0
If UniquesOnly Then
On Error Resume Next
IncidentMatch = WorksheetFunction.Match(IncidentTempValues(IncidentStep, 2), Incident, 0)
On Error GoTo 0
End If
If IncidentMatch = 0 Then
ReDim Preserve Incident(LBound(Incident) To UBound(Incident) + 1)
Incident(UBound(Incident)) = IncidentTempValues(IncidentStep, 2)
Incidents.Remove IncidentKey
Incidents.Add Incident, IncidentKey
End If
End If
Next IncidentStep
'// Put values into new sheet
If Incidents.Count > 0 Then
Set NewSheet = Worksheets.Add
IncidentStep = 1
For Each IncidentItem In Incidents
NewSheet.Cells(IncidentStep, 1).Resize(1, UBound(IncidentItem) - LBound(IncidentItem) + 1).Value = IncidentItem
IncidentStep = IncidentStep + 1
Next IncidentItem
NewSheet.Cells.EntireColumn.AutoFit
End If
'// Message user upon completion
If Incidents.Count > 0 Then
MsgBox "New sheet created ('" & NewSheet.Name & "') with " & Incidents.Count & " record(s).", vbInformation, "Complete"
Else
MsgBox "Unable to create incident data.", vbExclamation, "Whoops!"
End If
End Sub
Public Function InCollection(CheckCollection As Collection, CheckKey As String) As Boolean
'
' Returns True if the specified key is found in the specified collection.
'
' Syntax: InCollection(CheckCollection,CheckKey)
'
' Parameters: CheckCollection. Collection. Required. The collection to search in.
' CheckKey. String. Required. The string key to search in collection for.
'
On Error Resume Next
InCollection = CBool(Not IsEmpty(CheckCollection(CheckKey)))
On Error GoTo 0
End Function
This would need to go into a standard module. Let us know if you need additional assistance with this.
Regards,
Zack Barresse

This procedure assumes the following:
Data ranges starts at A1, includes two columns and it's a continuous range of data (i.e. no blank rows in between, and column C is blank
Output data starts at D1
Sub Rng_List_Unique_Records()
Dim vSrc As Variant, sKey As String
Dim sStatus As String, aStatus As Variant
Dim lRow As Long, l As Long
With ThisWorkbook.Sheets(1)
Application.Goto .Cells(1), 1
Rem Set Array with Source Range Data
vSrc = .Cells(1).CurrentRegion.Value2
Rem Extract Unique Items
For l = 1 To UBound(vSrc)
If vSrc(l, 1) = sKey Then
Rem Same Incident - Add Measurement
sStatus = sStatus & ";" & vSrc(l, 2)
Else
If sStatus <> Empty Then
Rem Enter Measurements for Prior Incident
aStatus = Split(sStatus, ";")
.Cells(lRow, 5).Resize(, 1 + UBound(aStatus)) = aStatus
End If
Rem New Incident
lRow = 1 + lRow
sKey = vSrc(l, 1)
.Cells(lRow, 4) = sKey
sStatus = vSrc(l, 2)
End If: Next
Rem Enter Measurements for Last Incident
aStatus = Split(sStatus, ";")
.Cells(lRow, 5).Resize(, 1 + UBound(aStatus)) = aStatus
Rem Output Range Columns AutoFit
.Cells(4).CurrentRegion.EntireColumn.AutoFit
End With
End Sub
Suggest to visit the following pages to obtain a deeper understanding of the resources used:
Variables & Constants, Application Object (Excel), Excel Objects
With Statement, For Each...Next Statement, If...Then...Else Statement
Range Object (Excel), Worksheet Object (Excel)
Nevertheless let me know of any questions about the procedure

It's been a slow day so..... This will do what you want using vba. You could also achieve this as Scott has said above with formulas or even using a pivot table. However by the looks of the question you're looking for something dynamic which will expand automatically to include new incidents which the formulas won't do easily.
I've over commented it in the hopes that you will easily be able to understand for future modifications. This is probably not the only way of doing it and not necessarily the best.
Option Explicit
Sub transposeAndCombine()
' Declare all of the variable names and types we will be using
Dim inc As Object
Dim c As Integer: Dim i As Integer
Dim rng As Range
Dim f
Dim ws as worksheet
' Turns off screen updating - good practice for the majority of vba macros
Application.ScreenUpdating = False
' Declare worksheet
set ws = ThisWorkbook.Sheets("Sheet1")
' Change Sheet1 to relevant sheet
' You'll also need to change all 4's that are detailed below to your relevant destination.
' I put all the processed data into Column D in my example
' starting from row 2 to leave a row for a header
With ws
' Declare range that we are going to be considering (where the raw data is)
Set rng = Range(.Cells(2, 1), .Cells(.Cells(.Rows.Count, 1).End(xlUp).Row, 1))
' Loop through that data
For Each inc In rng
' Find if data exists in destination
Set f = .Columns(4).Find(inc.Value, LookIn:=xlValues)
' If it exists assign the row number to a variable, if not add it to the end
If Not f Is Nothing Then
i = f.Row
Else
i = .Cells(.Rows.Count, 4).End(xlUp).Row + 1
.Cells(i, 4) = inc.Value
End If
' find last column that has been used
c = .Cells(i, .Columns.Count).End(xlToLeft).Column + 1
' add the Status value to the row
.Cells(i, c) = inc.Offset(0, 1)
' Loop back for next data entry
Next inc
End With
' Turn back on screen updating for normal behaviour
Application.ScreenUpdating = True
End Sub

Related

How can I delete 123572 rows faster in VBA?

I have a file with more then 1 sheet, where in the Reports Sheet I want to filter by ASBN products and then delete them, because I already processed it in another sheet, so I need to delete the initial ones in order to paste back the processed one.
Idea is that this deleting code which is working, but is taking for at least 20 minutes, because I want to delete 123 572 rows, do you have any idea how could I make this work faster?
I also tried to clear contents first and then to delete empty rows, but it's the same.
Here you find the code:
Public Sub Remove_ABSN()
Dim area As String
Dim start As Long
area = "ABSN"
Application.ScreenUpdating = False
Application.Calculation = xlCalculationManual
Application.DisplayAlerts = False
Application.EnableEvents = False
start = Worksheets("Reports").Cells(Cells.Rows.Count, 1).End(xlUp).Row
Worksheets("Reports").Range("$A$2:$AN" & start).AutoFilter Field:=8, Criteria1:=area, Operator:=xlFilterValues
Worksheets("Reports").Range("$A$2:$AN$" & start).SpecialCells(xlCellTypeBlanks).EntireRow.Delete
Sheets("Reports").ShowAllData
Application.ScreenUpdating = True
Application.Calculation = xlCalculationAutomatic
Application.DisplayAlerts = True
Application.EnableEvents = True
End Sub
I think AutoFilter will be the fastest way to do it. Here are two sample scripts to try. You can see for yourself which one is faster.
Public Sub UnionDeleteRowsFast()
' Careful...delete runs on Sheet1
Dim sh2 As Worksheet
Set sh2 = Sheets("Sheet1")
Dim lastrow As Long
Dim Rng As Range
lastrow = Cells(Rows.Count, "B").End(xlUp).Row
For i = lastrow To 2 Step -1
If Cells(i, 2).Value = "Delete" Then
If Rng Is Nothing Then
Set Rng = Range("B" & i)
Else
Set Rng = Union(Rng, Range("B" & i))
End If
End If
Next
If Not Rng Is Nothing Then Rng.EntireRow.Delete
End Sub
Sub AutoFilterDeleteRowsFast()
' Careful...delete runs on ActiveSheet
With ActiveSheet
.AutoFilterMode = False
With Range("B4", Range("B" & Rows.Count).End(xlUp))
.AutoFilter 1, "*Delete*"
On Error Resume Next
.Offset(1).SpecialCells(12).EntireRow.Delete
End With
.AutoFilterMode = False
End With
End Sub
There is a way that is much faster.
Suppose a table of 100,000 lines (A1:B100001) with headers in line 1. Then delete condition refers to just 1 column (B).
One needs a auxiliar column (A) just to count the lines in the original order. Here I use autofill function.
So one can sort the table and after restore the original order.
Below there is a complete example, that generates randomly numbers from 1 to 10 (it's slow!), and after quickly delete all lines with values 3
Sub EraseValue()
Application.ScreenUpdating = False
Dim i As Long
Dim T1 As Single ' milisecs after booting (Start)
Dim T2 As Single ' milisecs after booting (End)
Dim LIni As Variant ' Initial line to delete
Dim LEnd As Variant ' Final line to delete
Const Fin = 100000 ' Lines in the table
Const FinStr = "100001" ' Last line (string)
Randomize (GetTickCount()) ' Seed of random generation
For i = 1 To Fin
Cells(i + 1, "B") = Int(Rnd() * 10 + 1) ' Generates from 1 to 10
If i Mod 100 = 0 Then Application.StatusBar = i
DoEvents
Next i
Application.StatusBar = False
Dim Table As Range
Dim Colu As Range
T1 = GetTickCount() ' Initial time
Cells(2, "A") = 1 ' Starting value
Cells(3, "A") = 2 ' Step
' Fill from 1 to 100,000 step 1
Range("A2:A3").AutoFill Destination:=Range("A2:A" & FinStr)
' Order by condition column
Table.Sort Key1:=Cells(1, "B"), Header:=xlYes
'One needs delete lines with column B = 3
'LIni: Search key that not exceed value 2 in the column
' (2 is immediately previous value)
'LEnd: Search key that not exceed value 3 in the column
'LIni and LFim is relative to 2 so add 1 for skip the header
'Add more 1 to Lini in order to get the first value in the column >= key
'
LIni = Application.Match(2, Colu, 1) + 2
LEnd = Application.Match(3, Colu, 1) + 1
If IsError(LIni) Or IsError(LEnd) Or LEnd < LEnd Then
MsgBox ("There is no lines to delete")
End
End If
Range(Rows(LIni), Rows(LEnd)).Delete (xlUp) ' Delete lines
Table.Sort Key1:=Cells(1, "A"), Header:=xlYes ' Restore initial order
T2 = GetTickCount() ' Get the final time
MsgBox ("Elapsed milisecs: " + Format((T2 - T1), "0"))
End Sub
In my old computer, it take a little bit more that 0.5 secs with 100,000 lines.
If one has a condition that involves 2 columns or more, one need to create an another auxiliary column with a formula that concatenate these columns related do desired condition and run the match in this column. The formula needs to usage relative references. For instance (assuming that the data of column C are string and is already filled with a header).
Cells(1,4) = "NewCol" ' New column D
Dim NewCol As Range
Set NewCol = Range("D2:D" & FinStr)
' Two previous columns concatenated. In line 2
' the formula would be "=Format(B2,"0")+C2" (B2 is a number)
NewCol.FormulaR1C1 = "=Format(RC[-2],"0") & RC[-1]"
NewCol.Copy
NewCol.PasteSpecial(XlValues) ' Convert all formulas to values
Application.CutCopyMode=false
So one usages the column D instead column B

Copy cells from one sheet and paste in another to a variable cell

I copy cells (from sheet1) if they contain a specific value (got this part figured out).
I need to paste them in a cell on sheet2 in row j.
Sheet1 has a long list of names, companies, emails, phones etc. with each person's information separated by a space. For Ex:
Column A Column B
Smith, Jill #N/A
CEO #N/A
ABC Corp 123 street ABC Corp
jill#ABC.com #N/A
#N/A
Smith, John #N/A
CTO #N/A
123 Inc ABC street 123 Inc
john#123.com #N/A
I have a variable (j) that counts each space and then if Cell b does not equal #NA, then cell a is copied and pasted into sheet2 column M and row j.
Variable j is needed because the formula in column B isn't 100% and the data is inconsistent so I need j so that the company name stays on the same line as the name. I need this because I have other code to split column A (like 4000 rows) into separate sheets by names, titles, companies, emails.
I.e. Sheet3 would have:
1. Jill Smith
2. John Smith
Sub AutoCompany()
Application.ScreenUpdating = False
Dim lr As Long, tr As Long, i As Long, j As Long, k As Long
Worksheets("Sheet1").Activate
lr = Range("A" & Rows.Count).End(xlUp).Row
tr = Worksheets("Sheet2").Range("A" & Rows.Count).End(xlUp).Row - 1
'this is my formula for column B
Range("B2:B" & lr).Formula = "=INDEX(CompaniesTbl[CompanyNamesList],IF(SUMPRODUCT(--ISNUMBER(SEARCH(CompaniesTbl[CompanyNamesList],A2)))<>0,SUMPRODUCT(--ISNUMBER(SEARCH(CompaniesTbl[CompanyNamesList],A2))*ROW($1:$" & tr & ")),NA()))"
j = 0
k = 1
For i = 2 To lr Step 1
'increase j by 1 if there is a blank space (to figure out where to paste)
If Cells(i, 1) = "" Then
j = j + 1
'extra variable just cause
k = k + 1
End If
'check for an actual value
If Application.IsNA(Cells(i, 2)) Then
Else
Worksheets("Sheet1").Cells(i, 2).Select
Selection.Copy
Worksheets("Company").Activate
Worksheets("Company").Range("M" & j).Select
Selection.PasteSpecial Paste:=xlPasteValues, Operation:=xlNone, SkipBlanks _
:=False, Transpose:=False
Worksheets("Sheet1").Activate
End If
Next
Application.ScreenUpdating = True
End Sub
This causes an error
"object defined or variable defined"
If I remove j from my paste selection, the error is gone but all my pastes are overwritten.
I can't remember what I had done before, but I basically didn't have all of the sheet activations and that caused a out of range error. Which I fix by activating a sheet, but that causes my variable to cause an error.
Edit:
Based on the comments and answer, the issue is not in how the VBA is written per se. I think it has to do with the fact that the variable j is unable to be called in the if statement. I can't figure another way to do this or how to troubleshoot that issue.
From deciphering your code I assume that you what to copy the company names from column B to Worksheets("Company") column M, starting on the first row.
Dim cel As Range, j As Long 'assign your variables
With ThisWorkbook.Sheets("Sheet1") 'use "With" so you don't have to activate your worksheets
j = 1
For Each cel In .Range("B2:B" & .Cells(.Rows.Count, "B").End(xlUp).Row) 'loop through each cell in range
If Application.IsNA(cel) Then 'test for error and skip
ElseIf cel.Value = "" Then 'test for blank cell and skip
'It is better to set a cells value equal to another cells value then using copy/paste.
Else: ThisWorkbook.Sheets("Company").Cells(j, "M").Value = cel.Value
j = j + 1 'add 1 to j to paste on the next row
End If
Next cel
End With
Check my code's comments and adjust it to fit your needs
Option Explicit ' -> Always use this at the top of your modules and classes
' Define your procedures as public or private
' Indent your code (I use RubberDuck (http://rubberduckvba.com/) which is a great piece of software!
Public Sub AutoCompany()
On Error GoTo CleanFail
Application.ScreenUpdating = False ' This should be used with an error handler see https://rubberduckvba.wordpress.com/tag/error-handling/
' Declare object variables
Dim sourceSheet As Worksheet
Dim lookupSheet As Worksheet
Dim resultsSheet As Worksheet
Dim sourceRange As Range
Dim evalCell As Range
' Declare other variables
Dim sourceSheetName As String
Dim lookupSheetName As String
Dim resultsSheetName As String
Dim sourceLastRow As Long
Dim lookupLastRow As Long
' Initialize variables
sourceSheetName = "Sheet1"
lookupSheetName = "Sheet2"
resultsSheetName = "Company"
' Initialize objects
Set sourceSheet = ThisWorkbook.Worksheets(sourceSheetName) ' This could be replaced by using the sheet's codename see https://www.spreadsheet1.com/vba-codenames.html
Set lookupSheet = ThisWorkbook.Worksheets(lookupSheetName) ' Same as previous comment
Set resultsSheet = ThisWorkbook.Worksheets(resultsSheetName) ' Same as previous comment
' Worksheets("Sheet1").Activate -> Not needed
sourceLastRow = sourceSheet.Range("A" & Rows.Count).End(xlUp).Row ' This is unreliable -> see https://stackoverflow.com/a/49971492/1521579
lookupLastRow = lookupSheet.Range("A" & Rows.Count).End(xlUp).Row - 1 ' Couldn't understand why you subtract 1
' Define the sourceRange so we can loop through the cells
Set sourceRange = sourceSheet.Range("A2:A" & sourceLastRow)
' this is my formula for column B -> Comments should tell why you do something not what you're doing
sourceSheet.Range("B2:B" & sourceLastRow).Formula = "=INDEX(CompaniesTbl[CompanyNamesList],IF(SUMPRODUCT(--ISNUMBER(SEARCH(CompaniesTbl[CompanyNamesList],A2)))<>0,SUMPRODUCT(--ISNUMBER(SEARCH(CompaniesTbl[CompanyNamesList],A2))*ROW($1:$" & lookupLastRow & ")),NA()))"
' Begin the loop to search for matching results
For Each evalCell In sourceRange
' Skip cells that are empty
If evalCell.Value <> vbNullString Then
' Check value in column B (offset = 1 refers to one column after current cell and using not before application.IsNA checks for is found)
If Not Application.WorksheetFunction.IsNA(evalCell.Offset(rowOffset:=0, ColumnOffset:=1).Value) Then
' We use current evaluated cell row in the results sheet
resultsSheet.Range("M" & evalCell.Row).Value = evalCell.Value
End If
End If
Next evalCell
CleanExit:
Application.ScreenUpdating = True
Exit Sub
CleanFail:
Debug.Print "Catched an err: " & Err.Description & " ... do something!"
Resume CleanExit
End Sub
Let me know if it works and remember to mark the answer if it does
Thanks to everyone that tried helping. I found the problem.
My J variable was set to 0 and so the first time the code ran, it tried pasting to cell 0 which is out of scope of the worksheet. The reason why I had set my variable to 0 was because I assumed the first empty row that it finds (above the dataset) would set the variable to 1 but that was not the case.
Anyways, I set J to 1 and it worked...
D'oh

VBA Excel: create graphs for unique values from flexible ranges for multiple sheets

This puzzle is getting on my nerves now, as a beginning VBA Excel user with a lot of ambition to automize things... (Maybe a bit too ambitious :) )
The things I've managed this far are: creating a new file with a worksheet for every company, containing all current available data.
A control sheet where I can select which stakeholder should receive which sheet(s), with which text and when.
This all works fine but I want to add graphs to the data to show what's going on over time.
The problem is:
- looping through a variable set of data every month there's a new column added so the range of columns should be flexible.
- the number of rows per company isn't predefined and may vary month-over-month
- the number of companies where a worksheet is created for may vary as well
My intention is to:
- create graphs for each unique value in Column D
- name the graphs (title) with the unique value in column D
- name the new created tab with the name of the Company in column A (let's say: 'Company A - graphs' as a sheet name)
- include all graphs from the current sheet in one sheet (the information on the current sheet is of ONE company)
- go to the next sheet and do the same (loop) until all sheets are done
- add another sheet with all sheet names that are currently in the file (existing + created)
- the label of the Y-values is in column G ('Name')
- the Y-values are in the columns H and further and row 2 and all the way down (flexible)
- the headers are in row 1 --> only the months (H >>) should be included on the X-axis
- So the information in the columns A:F shouldn't be used other than mentioned above
I got quite a piece of script but I'm on a dead end street. Any help would be very much appreciated!
If you have any questions please let me know.
Many many thanks in advance!
Wouter :-)
P.S.: Here's the file: http://we.tl/786d6b6cs0
Sub WJS_CreateGraphs()
Response = MsgBox("Are you sure you want to create graphs for all worksheets?", vbYesNo, "Graph Creator")
If Response = vbNo Then
Exit Sub
End If
' ------------------------------------ Now we will create pivot tables for all scenario's
Dim WS_Count As Integer
Dim C As Integer
' Set WS_Count equal to the number of worksheets in the active workbook.
WS_Count = ActiveWorkbook.Worksheets.Count
' Begin the loop.
For C = 1 To WS_Count
Dim I As Integer
Dim selecta As Range
Dim grFilter As Range, grUniques As Range
Dim grCell As Range, grCounter As Integer
Dim arow As Integer
Dim acol As Integer
Dim StartPoint As Integer
Dim EndPoint As Integer
Dim rStartPoint As Integer
Dim rEndPoint As Integer
ActiveSheet.Range("D1").Select
Set selecta = Range(Selection, ActiveCell.SpecialCells(xlLastCell))
Set grFilter = Range("D1", Range("D" & Rows.Count).End(xlUp))
With grFilter
' Filter column A to show only one of each item (uniques) in column A
.AdvancedFilter Action:=xlFilterInPlace, Unique:=True
' Set a variable to the Unique values
Set grUniques = Range("D2", Range("D" & Rows.Count).End(xlUp)).SpecialCells(xlCellTypeVisible)
' Filter, Copy, and Paste each unique to its' own sheet in the new workbook
For Each cell In grUniques
counter = counter + 1
'NOTE - this filter is on column D(field:=1), to change
'to a different column you need to change the field number relative to the Unique Value range above
grFilter.AutoFilter field:=1, Criteria1:=cell.Value
'********************************************************************************************************************************
temp_StartPoint = 2
temp_EndPoint = ActiveSheet.UsedRange.Rows.Count
For arow = temp_StartPoint To temp_EndPoint
StartPoint = 2
EndPoint = ActiveSheet.UsedRange.Rows.Count
FirstColumn = 7
LastColumn = ActiveSheet.UsedRange.Columns.Count
' remember the sheet to return to, this is the current active sheet --> after creating a graph VBA will return to this sheet
MyPrevSheet = ActiveSheet.name
Charts.Add
ActiveChart.ChartArea.Select
ActiveChart.ChartType = xlLine 'Type of graph
' Return to previous sheet
If Len(MyPrevSheet) > 0 Then
Sheets(MyPrevSheet).Activate
Else
MsgBox "You have not switched sheets yet since opening the file!"
End If
ActiveChart.SetSourceData Source:=Range(Cells(StartPoint, FirstColumn) & ":" & Cells(EndPoint, LastColumn))
', PlotBy:=xlRows 'data source
ActiveChart.SeriesCollection(1).XValues = ActiveSheets.Range(FirstColumn & "1:" & Cells(LastColumn, 1))
'naming the x-axis
ActiveChart.SeriesCollection(1).name = "Spwr" ' Name of 1st data series 1
ActiveSheet.ShowAllData
On Error Resume Next
With ActiveChart.SeriesCollection(1) 'put labels on 1st data series
.HasDataLabels = True
.DataLabels.NumberFormat = "##"
End With
With ActiveChart.Axes(xlValue)
.HasMajorGridlines = False
.HasMinorGridlines = False
End With
ActiveChart.PlotArea.Select ' Background of graph
With Selection.Border
.ColorIndex = 16
.Weight = xlThin
.LineStyle = xlContinuous
End With
Selection.Interior.ColorIndex = xlNone
ActiveChart.SeriesCollection.NewSeries
ActiveChart.SeriesCollection(2).name = "salespower"
ActiveChart.SeriesCollection(2).Values = ActiveSheets.Range("G2:m2")
With ActiveChart.SeriesCollection(2) 'put labels on 2nd line
.HasDataLabels = True
.DataLabels.NumberFormat = "##"
End With
ActiveChart.SeriesCollection.NewSeries
ActiveChart.SeriesCollection(3).name = "Tests"
ActiveChart.SeriesCollection(3).Values = ActiveSheets.Range("G2:m2")
With ActiveChart.SeriesCollection(3) 'put labels on 3rd line
.HasDataLabels = True
.DataLabels.NumberFormat = "##"
End With
ActiveChart.Legend.Position = xlLegendPositionBottom
ActiveChart.HasTitle = True
ChartTitle = "Naam van de chart"
Next arow
Next cell
rngFilter.Parent.AutoFilterMode = False
Application.ScreenUpdating = True
'***********************************************************************************************************************************************
End With
Next C
End Sub

Most efficient way to delete row with VBA

I currently have a macro that I use to delete a record if the ID doesn't exist in a list of ID's I created from an XML document. It does work like I want it to, however I have over 1000 columns in the spreadsheet (one for each day of the year until end of 2015) so it takes ages to delete the row and it can only do 1 or 2 before it says "Excel ran out of resources and had to stop". Below is the code I'm using for the macro, is there another way I can do this so that Excel doesn't run of of resources?
Sub deleteTasks()
Application.ScreenUpdating = False
Dim search As String
Dim sheet As Worksheet
Dim cell As Range, col As Range
Set sheet = Worksheets("misc")
Set col = sheet.Columns(4)
ActiveWorkbook.Sheets("Schedule").Activate
ActiveSheet.Range("A4").Select
ActiveSheet.Unprotect
ActiveSheet.Range("A:C").EntireColumn.Hidden = False
Do While ActiveCell.Value <> ""
search = ActiveCell.Value
Set cell = col.Find(What:=search, LookIn:=xlValues, _
LookAt:=xlWhole, SearchOrder:=xlByRows, SearchDirection:=xlNext, _
MatchCase:=False, SearchFormat:=False)
If cell Is Nothing Then 'If the taskID is not in the XML list
Debug.Print "Deleted Task: " & ActiveCell.Value
Selection.EntireRow.Delete
End If
ActiveCell.Offset(1, 0).Select 'Select next task ID
Loop
ActiveSheet.Range("A:B").EntireColumn.Hidden = True
ActiveSheet.Protect
End Sub
After trying lots of different options, including all the answers listed below. I have realized that whatever the method is, deleting a row with ~1100 columns is going to take a while on my average laptop (2.20 Ghz, 4GB RAM). Since the majority of the rows are empty I have found alternative method which is a lot faster. I just clear the cells which contain data (A:S) and then resize the table to remove the row where I just deleted the data from. This end result is exactly the same as entireColumn.Delete. Below is the code I'm using now
'New method - takes about 10 seconds on my laptop
Set ws = Worksheets("Schedule")
Set table = ws.ListObjects(1)
Set r = ws.Range("A280:S280")
r.Clear
table.Resize Range("A3:VZ279")
Using anything involving EntireColumn.Delete or just manually selecting the row and deleting it takes about 20-30 seconds on my laptop. Of course this method only works if your data is in a table.
The short answer:
Use something like
ActiveSheet.Range(DelStr).Delete
' where DelStr = "15:15" if you want to delete row 15
' = "15:15,20:20,32:32" if you want to delete rows 15,20 and 32
The long answer:
Important: If you have ~ 30 / 35 rows to delete, the following code works very efficiently. Beyond which it would throw up an error. For code to handle arbitrary number of rows efficiently see the very long answer below this.
If you have a function which lets you list out which rows you want to delete, try the code below. This is what I use to very efficiently delete multiple rows with minimum overhead. (the example assumes that you've obtained the rows you need to delete through some program, here I manually feed them in):
Sub DeleteRows()
Dim DelRows() As Variant
ReDim DelRows(1 To 3)
DelRows(1) = 15
DelRows(2) = 18
DelRows(3) = 21
'--- How to delete them all together?
Dim i As Long
For i = LBound(DelRows) To UBound(DelRows)
DelRows(i) = DelRows(i) & ":" & DelRows(i)
Next i
Dim DelStr As String
DelStr = Join(DelRows, ",")
' DelStr = "15:15,18:18,21:21"
'
' IMPORTANT: Range strings have a 255 character limit
' See the other code to handle very long strings
ActiveSheet.Range(DelStr).Delete
End Sub
The (very long) efficient solution for arbitrary number of rows and benchmark results:
Here are the benchmark results obtained by deleting rows (Time in seconds vs. no. of rows).
The rows are on a clean sheet and contain a volatile formula in the D column from D1:D100000
i.e. for 100,000 rows, they have a formula =SIN(RAND())
The code is long and not too pretty, but it splits the DelStr into 250 character substrings and forms a range using these. Then the new DeleteRng range is deleted in a single operation.
The time to delete may depend on the contents of the cells. The testing/benchmarking, in congruence with a bit of intuition suggests the following results.
Sparse rows/empty cells delete fastest
Cells with values take somewhat longer
Cells with formulas take even longer
Cells which feed into formulas in other cells take longest as their deletion triggers the #Ref reference error.
Code:
Sub DeleteRows()
' Usual optimization
' Events not disabled as sometimes you'll need to interrupt
' You can optionally keep them disabled
Application.Calculation = xlCalculationManual
Application.ScreenUpdating = False
' Declarations...
Dim DelRows() As Variant
Dim DelStr As String, LenStr As Long
Dim CutHere_Str As String
Dim i As Long
Dim MaxRowsTest As Long
MaxRowsTest = 1000
' Here I'm taking all even rows from 1 to MaxRowsTest
' as rows to be deleted
ReDim DelRows(1 To MaxRowsTest)
For i = 1 To MaxRowsTest
DelRows(i) = i * 2
Next i
'--- How to delete them all together?
LenStr = 0
DelStr = ""
For i = LBound(DelRows) To UBound(DelRows)
LenStr = LenStr + Len(DelRows(i)) * 2 + 2
' One for a comma, one for the colon and the rest for the row number
' The goal is to create a string like
' DelStr = "15:15,18:18,21:21"
If LenStr > 200 Then
LenStr = 0
CutHere_Str = "!" ' Demarcator for long strings
Else
CutHere_Str = ""
End If
DelRows(i) = DelRows(i) & ":" & DelRows(i) & CutHere_Str
Next i
DelStr = Join(DelRows, ",")
Dim DelStr_Cut() As String
DelStr_Cut = Split(DelStr, "!,")
' Each DelStr_Cut(#) string has a usable string
Dim DeleteRng As Range
Set DeleteRng = ActiveSheet.Range(DelStr_Cut(0))
For i = LBound(DelStr_Cut) + 1 To UBound(DelStr_Cut)
Set DeleteRng = Union(DeleteRng, ActiveSheet.Range(DelStr_Cut(i)))
Next i
DeleteRng.Delete
Application.Calculation = xlCalculationAutomatic
Application.ScreenUpdating = True
End Sub
The code to generate the formulas in a blank sheet is
Sub FillRandom()
ActiveSheet.Range("D1").FormulaR1C1 = "=SIN(RAND())"
Range("D1").AutoFill Destination:=Range("D1:D100000"), Type:=xlFillDefault
End Sub
And the code to generate the benchmark results above is
Sub TestTimeForDeletion()
Call FillRandom
Dim Time1 As Single, Time2 As Single
Time1 = Timer
Call DeleteRows
Time2 = Timer
MsgBox (Time2 - Time1)
End Sub
Note: Many thanks to brettdj for pointing out the error which gets thrown when the length of DelStr exceeding 255 characters. It seems to be a known problem and as I painfully found out, it still exists for Excel 2013.
This code uses AutoFilter and is significantly faster than looping through rows.I use it daily and it should be pretty easy to figure out.Just pass it what you're looking for and the column to search in.You could also hard-code the column if you want.
private sub PurgeRandy
Call FindDelete("F", "Randy")
end sub
Public Sub FindDelete(sCOL As String, vSearch As Variant) 'Simple find and Delete
Dim lLastRow As Integer
Dim rng As Range
Dim rngDelete As Range
Range(sCOL & 1).Select
[2:2].Insert
[2:2] = "***"
Range(sCOL & ":" & sCOL).Select
With ActiveSheet
.UsedRange
lLastRow = .Cells.SpecialCells(xlCellTypeLastCell).Row
Set rng = Range(sCOL & 2, Cells(lLastRow, sCOL))
rng.AutoFilter Field:=1, Criteria1:=vSearch
Set rngDelete = rng.SpecialCells(xlCellTypeVisible)
rng.AutoFilter
rngDelete.EntireRow.Delete
.UsedRange
End With
End Sub
In this case a simple working formula can be used to see if each of the values in your range to be tested (column A of schedule) exist in column F of misc
In B4 it would =MATCH(A4,misc!D:D,0)
This can be used manually or with code for an efficient delete as the formula by design returns an error if there is no match which we can efficiently delete with VBA with either:
AutoFilter
SpecialCells (the design piece*)
In xl2007 note that there is a limit of 8192 discrete areas that can be selected with SpecialCells
code
Sub ReCut()
Dim ws1 As Worksheet
Dim ws2 As Worksheet
Dim rng1 As Range
Set ws1 = Sheets("misc")
Set ws2 = Sheets("schedule")
With Application
.ScreenUpdating = False
.EnableEvents = False
.DisplayAlerts = False
End With
Set rng1 = ws2.Range(ws2.[a4], ws2.Cells(Rows.Count, "A").End(xlUp))
ws2.Columns(2).Insert
With rng1.Offset(0, 1)
.FormulaR1C1 = "=MATCH(RC[-1],'" & ws1.Name & "'!C[2],0)"
On Error Resume Next
.Cells.SpecialCells(xlCellTypeFormulas, xlErrors).EntireRow.Delete
On Error GoTo 0
End With
ws2.Columns(2).Delete
With Application
.ScreenUpdating = True
.EnableEvents = True
.DisplayAlerts = True
End With
End Sub
Note: I don't have enough "reputation" to add my comments thus posting as answer. Credit to hnk for wonderful answer (Long Answer). I have one edit as suggestion:
Once you split the long string and in case the last block is more than the set character then it is having "!" at the end which is throwing error for range method. Addition of IF statement and MID is ensuring that there is no such character.
To handle that, use:
For i = LBound(DelStr_Cut) + 1 To UBound(DelStr_Cut)
If Right(DelStr_Cut(i), 1) = "!" Then
DelStr_Cut(i) = Mid(DelStr_Cut(i), 1, Len(DelStr_Cut(i)) - 1)
Set DeleteRng = Union(DeleteRng, ActiveSheet.Range(DelStr_Cut(i)))
Else
Set DeleteRng = Union(DeleteRng, ActiveSheet.Range(DelStr_Cut(i)))
End If
Next i
Thanks,
Bakul

How to delete a row if it contains a string?

I have only one column of data. I need to write a macro that would go through all the values and delete all rows that contain the word "paper".
A B
1 678
2 paper
3 3
4 09
5 89
6 paper
The problem is that the number of rows is not fixed. Sheets may have different number of rows.
Here is another simple macro that will remove all rows with non-numeric values in column A (besides row 1).
Sub DeleteRowsWithStringsInColumnA()
Dim i As Long
With ActiveSheet '<~~ Or whatever sheet you may want to use the code for
For i = .Cells(.Cells(.Rows.Count, 1).End(xlUp).Row, 1).Row To 2 Step -1 '<~~ To row 2 keeps the header
If IsNumeric(.Cells(i, 1).Value) = False Then .Cells(i, 1).EntireRow.Delete
Next i
End With
End Sub
If you're confident that the rows in question would always contain "paper" specifically and never any other string, you should match based on the value paper rather than it being a string. This is because, particularly in Excel, sometimes you may have numbers stored as strings without realizing it--and you don't want to delete those rows.
Sub DeleteRowsWithPaper()
Dim a As Integer
a = 1
Do While Cells(a, 1) <> ""
If Cells(a, 1) = "paper" Then
Rows(a).Delete Shift:=xlUp
'Row counter should not be incremented if row was just deleted
Else
'Increment a for next row only if row not deleted
a = a + 1
End If
Loop
End Sub
The following is a flexible macro that allows you to input a string or number to find and delete its respective row. It is able to process 1.04 million rows of simple strings and numbers in 2.7 seconds.
Sub DeleteRows()
Dim Wsht As Worksheet
Dim LRow, Iter As Long
Dim Var As Variant
Var = InputBox("Please specify value to find and delete.")
Set Wsht = ThisWorkbook.ActiveSheet
LRow = Wsht.Cells(Rows.Count, 1).End(xlUp).Row
StartTime = Timer
Application.ScreenUpdating = False
With Wsht
For Iter = LRow To 1 Step -1
If InStr(.Cells(Iter, 1), Var) > 0 Then
.Cells(Iter, 1).EntireRow.Delete
End If
Next Iter
End With
Application.ScreenUpdating = True
Debug.Print Timer - StartTime
End Sub

Resources