I have written a macro, which should read the value in every sheet (Row and Column) based on the value given it should Lock the cell or leave it unlocked. The way the code is written right now it takes forever to compute. I was suggested it be done using arrays. However the array are also not working
My excel file has got 15 sheets.
My Code is below.
Private Sub Workbook_Open()
Dim sh As Object
Dim sheetnames As String
Dim i As Integer
Dim col As Range
Dim rng As Variant: Set rng = Application.Range("I16:BI300") 'Value Lock & Unlock is in Column Range I16 to BI16 and Row Range B16 to B300
Dim rngCell As Variant
Application.ScreenUpdating = False
For Each sh In Sheets 'First Each
If sh.Name <> "Configuration" Then 'Configuration If
sheetnames = sh.Name
Worksheets(sheetnames).Activate
ActiveSheet.Unprotect Password:="sos"
For Each rngCell In Range("I22:BI300")
If (Cells(1, rngCell.Column) = "Lock" And Cells(rngCell.Row, 1) = "Lock") Or (Cells(1, rngCell.Column) = "UnLock" And Cells(rngCell.Row, 1) = "Lock") Or (Cells(1, rngCell.Column) = "Lock" And Cells(rngCell.Row, 1) = "Unlock") Then
rngCell.Locked = True
rngCell.Font.Color = -16776961
Else
rngCell.Locked = False
rngCell.Font.ColorIndex = xlAutomatic
End If
Next rngCell
ActiveSheet.Protect Password:="sos"
End If 'End of Configuration If
Next sh 'End of First Each
Sheets(1).Select
End Sub
Based on a combination of values in Column and Rows the result should produce values.
Column Row Value
Lock Lock Lock
Unlock Lock Lock
Lock Unlock Lock
Unlock Unlock Unlock
I'm not sure how arrays would speed this up as really it is the locking/unlocking of cells which is causing the main speed issue (Although arrays could improve the read time). Anyway, I'd suggest setting the values you wish to lock/unlock to a range and then doing them all in one go instead of individually as that will be where your main performance impact is.
Private Sub Workbook_Open()
Dim sh As Object
Dim sheetnames As String
Dim i As Integer
Dim col As Range, LockRng As Range, UnLockRng As Range
Dim rng As Variant: Set rng = Application.Range("I16:BI300") 'Value Lock & Unlock is in Column Range I16 to BI16 and Row Range B16 to B300
Dim rngCell As Variant
Application.ScreenUpdating = False
For Each sh In Sheets 'First Each
' Reset Ranges for each sheet
Set LockRng = Nothing
Set UnLockRng = Nothing
If sh.Name <> "Configuration" Then 'Configuration If
sheetnames = sh.Name
Worksheets(sheetnames).Activate
ActiveSheet.Unprotect Password:="sos"
For Each rngCell In Range("I22:BI300")
If (Cells(1, rngCell.Column) = "Lock" And Cells(rngCell.Row, 1) = "Lock") _
Or (Cells(1, rngCell.Column) = "UnLock" And Cells(rngCell.Row, 1) = "Lock") _
Or (Cells(1, rngCell.Column) = "Lock" And Cells(rngCell.Row, 1) = "Unlock") Then
' Create LockRng
If LockRng Is Nothing Then
Set LockRng = rngCell
Else
Set LockRng = Union(LockRng, rngCell)
End If
Else
' Create UnLockRng
If UnLockRng Is Nothing Then
Set UnLockRng = rngCell
Else
Set UnLockRng = Union(UnLockRng, rngCell)
End If
End If
Next rngCell
ActiveSheet.Protect Password:="sos"
End If 'End of Configuration If
' Lock all cells in LockRng
If Not LockRng Is Nothing Then
LockRng.Locked = True
LockRng.Font.Color = -16776961
End If
' Unlock all cells in UnLockRng
If Not UnLockRng Is Nothing Then
UnLockRng.Locked = False
UnLockRng.Font.ColorIndex = xlAutomatic
End If
Next sh 'End of First Each
Sheets(1).Select
End Sub
Related
I am aiming to lock entire rows where the word "Done" appears in a specific column. My code below achieves what I seek but it takes 18 seconds to compute (too long). Is there a faster/more efficient coding alternative?
There is an existing question on StackOverflow similar to this (found here) but my data does not exist in defined tables (this won't change), so I don't know how to adapt the suggestion there.
Private Sub Lock_Rows(ByVal Target As Range)
Dim DestSh As Worksheet
Dim lastrow As Long
Dim i As Long ' Integer
Set DestSh = Worksheets(8) 'Data tab for Item 1
With DestSh
'finds the last row with data on B column, B column has dates
lastrow = .Range("B" & .Rows.Count).End(xlUp).Row
'parse all rows
For i = 26 To lastrow 'rows of data begin at row 26
'if your conditions are met
If .Cells(i, "Z").Value = "Done" Then
.Cells(i, "Z").EntireRow.Cells.Locked = True 'lock the row
Else
.Cells(i, "Z").EntireRow.Cells.Locked = False 'leave rows unlocked
End If
Next i
.Protect UserInterfaceOnly:=True
End With
Set DestSh = Nothing
End Sub
The Lock / Unlock operations om individual rows are quite slow. Better to build a range reference to Lock / Unlock and do that operation in on go at the end.
Something like
Private Sub Lock_Rows(ByVal Target As Range)
Dim DestSh As Worksheet
Dim lastrow As Long
Dim i As Long ' Integer
Dim rLock As Range, rUnlock As Range
Set DestSh = Worksheets(8) 'Data tab for Item 1
With DestSh
'finds the last row with data on B column, B column has dates
lastrow = .Range("B" & .Rows.Count).End(xlUp).Row
'parse all rows
For i = 26 To lastrow 'rows of data begin at row 26
'if your conditions are met
If .Cells(i, "Z").Value = "Done" Then
If rLock Is Nothing Then
Set rLock = .Cells(i, "Z").EntireRow
Else
Set rLock = Application.Union(rLock, .Cells(i, "Z").EntireRow)
End If
Else
If rUnlock Is Nothing Then
Set rUnlock = .Cells(i, "Z").EntireRow
Else
Set rUnlock = Application.Union(rUnlock, .Cells(i, "Z").EntireRow)
End If
End If
Next i
If Not rLock Is Nothing Then rLock.Locked = True
If Not rUnlock Is Nothing Then rUnlock.Locked = False
.Protect UserInterfaceOnly:=True
End With
Set DestSh = Nothing
End Sub
It will be faster still you could incorporate use of Variant Arrays on the loop
On my hardware it takes about 6 s to process 500,000 rows
Try with this solution which seems to be much faster than original one:
Private Sub Lock_Rows_new(ByVal Target As Range)
Debug.Print "s:" & Timer
Dim DestSh As Worksheet
Dim lastrow As Long
Dim i As Long ' Integer
Set DestSh = Worksheets(8) 'Data tab for Item 1
With DestSh
'range to search
Dim firstRNGRow As Variant '!! important
firstRNGRow = 26
Dim firstRNG As Range
Set firstRNG = .Cells(firstRNGRow, "Z")
Dim lastRNG As Range
Set lastRNG = .Cells(.Range("B" & .Rows.Count).End(xlUp).Row, "Z")
'unlock all
Range(firstRNG, lastRNG).EntireRow.Cells.Locked = False
'search for first done
firstRNGRow = Application.Match("Done", Range(firstRNG, lastRNG), 0)
Do While (Not IsError(firstRNGRow))
Set firstRNG = .Cells(firstRNG.Row + firstRNGRow, "Z")
firstRNG.Offset(-1, 0).EntireRow.Cells.Locked = True 'lock the row
If firstRNG.Row > lastRNG.Row Then Exit Do
firstRNGRow = Application.Match("Done", Range(firstRNG, lastRNG), 0)
Loop
.Protect UserInterfaceOnly:=True
End With
Set DestSh = Nothing
Debug.Print "e:" & Timer
End Sub
Edited to add a faster solution combining Sort() and AutoFilter()
AutoFilter() can make things fast:
Private Sub Lock_Rows(ByVal Target As Range)
With Worksheets(8)
If IsEmpty(.Range("Z25")) Then .Range("Z25").Value = "xxx" ' be sure you have a column "header" for data in column Z from row 26 downwards
With .Range("Z25:Z" & .Cells(.Rows.Count, "B").End(xlUp).Row)
.EntireRow.Locked = False ' unlock all cells
.AutoFilter field:=1, Criteria1:="Done"
With Intersect(ActiveSheet.UsedRange, .EntireColumn).Resize(.Rows.Count - 1, .Columns.Count).Offset(1, 0)
If CBool(Application.Subtotal(103, .Cells)) Then .SpecialCells(xlCellTypeVisible).EntireRow.Locked = True ' lock only filtered range rows
End With
End With
If .Range("Z25").Value = "xxx" Then .Range("Z25").ClearContents ' remove any "not original" column header
End With
End Sub
if you know that cell Z25 content is always not empty then you can omit the lines:
If IsEmpty(.Range("Z25")) Then .Range("Z25").Value = "xxx"
If .Range("Z25").Value = "xxx" Then .Range("Z25").ClearContents
and if you Sort things, it's even faster:
Option Explicit
Private Sub Lock_Rows(ByVal Target As Range)
Dim dataRange As Range, sortRange As Range, lockRange As Range
With Worksheets("8") ' reference wanted sheet
Set dataRange = .Range("Z25", .Cells(.Rows.Count, "B").End(xlUp))
Set lockRange = Intersect(.Columns("Z"), dataRange)
Set sortRange = Intersect(dataRange.EntireRow, .UsedRange.Columns(.UsedRange.Columns.Count + 1)) ' reference the range in same rows as referenced one but in first "not used" column
Set dataRange = .Range(dataRange, sortRange)
End With
With sortRange
.Formula = "=ROW()" ' write rows indexes in referenced range. this will be used to sort things back
.Value = .Value ' get rid of formulas
End With
dataRange.Sort key1:=lockRange.Resize(1), order1:=xlAscending, Header:=xlYes ' sort data on columns with possible "Done" values
If IsEmpty(lockRange(1, 1)) Then lockRange(1, 1).Value = "xxx" ' remove any "not original" column header
With dataRange ' reference referenced sheet column B range in
.AutoFilter field:=lockRange.Column - Columns(1).Column, Criteria1:="Done"
With .Resize(.Rows.Count - 1, .Columns.Count).Offset(1, 0)
If CBool(Application.Subtotal(103, .Cells)) Then .SpecialCells(xlCellTypeVisible).EntireRow.Locked = True ' lock only filtered range rows
End With
.Parent.AutoFilterMode = False
.Sort key1:=sortRange.Resize(1), order1:=xlAscending, Header:=xlYes ' sort things back
sortRange.ClearContents ' delete rows index, not needed anymore
End With
If lockRange(1, 1).Value = "xxx" Then lockRange(1, 1).ClearContents ' remove any "not original" column header
End Sub
again, if you know that cell Z25 content is always not empty then you can omit the lines:
If IsEmpty(lockRange(1, 1)) Then lockRange(1, 1).Value = "xxx" ' remove any "not original" column header
If lockRange(1, 1).Value = "xxx" Then lockRange(1, 1).ClearContents ' remove any "not original" column header
I have a workbook with 10+ sheets, with hundreds of thousands of values in each (125k sheet1, 240k sheet 2, 400k sheet 3, etc.) I am trimming down the sheets by keeping every thousandth or so point in each sheet.
I have been unable to get the code to finish trimming data on the first sheet. The code has run for well over an hour without finishing the first sheet. I've tried with smaller data sets as well (~1000 points in 5 sheets), but the macro only successfully trims points on the first sheet. The other sheets are not modified
Below is the code I'm using to delete an interval of rows; it is the most customizable way to delete rows I could find (which is exactly what i'm looking for: customization/simplicity
lastRow = Application.ActiveSheet.UsedRange.Rows.Count
For i = 2 To lastRow Step 1 'Interval of rows to delete
Range(Rows(i), Rows(i + 997)).Delete Shift:=xlUp
Next i
The code for this specific task is inserted into a modified version of a codes found in this question *credit to those who originally wrote them
Question: Excel VBA Performance - 1 million rows - Delete rows containing a value, in less than 1 min
Here's Helper Functions paul bica used in his code
Public Sub FastWB(Optional ByVal opt As Boolean = True)
With Application
.Calculation = IIf(opt, xlCalculationManual, xlCalculationAutomatic)
.DisplayAlerts = Not opt
.DisplayStatusBar = Not opt
.EnableAnimations = Not opt
.EnableEvents = Not opt
.ScreenUpdating = Not opt
End With
FastWS , opt
End Sub
Public Sub FastWS(Optional ByVal ws As Worksheet = Nothing, _
Optional ByVal opt As Boolean = True)
If ws Is Nothing Then
For Each ws In Application.ActiveWorkbook.Sheets
EnableWS ws, opt
Next
Else
EnableWS ws, opt
End If
End Sub
Private Sub EnableWS(ByVal ws As Worksheet, ByVal opt As Boolean)
With ws
.DisplayPageBreaks = False
.EnableCalculation = Not opt
.EnableFormatConditionsCalculation = Not opt
.EnablePivotTable = Not opt
End With
End Sub
A nifty little code for generating a test set by marko2049:
Sub DevelopTest()
Dim index As Long
FastWB True
ActiveSheet.UsedRange.Clear
For index = 1 To 1000000 '1 million test
ActiveSheet.Cells(index, 1).Value = index
If (index Mod 10) = 0 Then
ActiveSheet.Cells(index, 2).Value = "Test String"
Else
ActiveSheet.Cells(index, 2).Value = "Blah Blah Blah"
End If
Next index
Application.StatusBar = ""
FastWB False
End Sub
After generating a test set and copying it to several sheets, I ran a modified version of the code below
The main body of the code was made by user marko5049
Sub DeleteRowFast()
Dim curWorksheet As Worksheet 'Current worksheet vairable
Dim rangeSelection As Range 'Selected range
Dim startBadVals As Long 'Start of the unwanted values
Dim endBadVals As Long 'End of the unwanted values
Dim strtTime As Double 'Timer variable
Dim lastRow As Long 'Last Row variable
Dim lastColumn As Long 'Last column variable
Dim indexCell As Range 'Index range start
Dim sortRange As Range 'The range which the sort is applied to
Dim currRow As Range 'Current Row index for the for loop
Dim cell As Range 'Current cell for use in the for loop
On Error GoTo Err
Set rangeSelection = Application.InputBox("Select the (N=) range to be checked", "Get Range", Type:=8) 'Get the desired range from the user
Err.Clear
M1 = MsgBox("This is recommended for large files (50,000 or more entries)", vbYesNo, "Enable Fast Workbook?") 'Prompt the user with an option to enable Fast Workbook, roughly 150% performace gains... Recommended for incredibly large files
Select Case M1
Case vbYes
FastWB True 'Enable fast workbook
Case vbNo
FastWB False 'Disable fast workbook
End Select
strtTime = Timer 'Begin the timer
Set curWorksheet = ActiveSheet
lastRow = CLng(rangeSelection.SpecialCells(xlCellTypeLastCell).Row)
lastColumn = curWorksheet.Cells(1, 16384).End(xlToLeft).Column
Set indexCell = curWorksheet.Cells(1, 1)
On Error Resume Next
If rangeSelection.Rows.Count > 1 Then 'Check if there is anything to do
lastVisRow = rangeSelection.Rows.Count
Set sortRange = curWorksheet.Range(indexCell, curWorksheet.Cells(curWorksheet.Rows(lastRow).Row, 16384).End(xlToLeft)) 'Set the sort range
sortRange.Sort Key1:=rangeSelection.Cells(1, 1), Order1:=xlAscending, Header:=xlNo 'Sort by values, lowest to highest
startBadVals = rangeSelection.Find(What:="Test String", LookAt:=xlWhole, MatchCase:=False).Row
endBadVals = rangeSelection.Find(What:="Test String", LookAt:=xlWhole, SearchDirection:=xlPrevious, MatchCase:=False).Row
curWorksheet.Range(curWorksheet.Rows(startBadVals), curWorksheet.Rows(endBadVals)).EntireRow.Delete 'Delete uneeded rows, deleteing in continuous range blocks is quick than seperated or individual deletions.
sortRange.Sort Key1:=indexCell, Order1:=xlAscending, Header:=xlNo 'Sort by index instead of values, lowest to highest
End If
Application.StatusBar = "" 'Reset the status bar
FastWB False 'Disable fast workbook
MsgBox CStr(Round(Timer - strtTime, 2)) & "s" 'Display duration of task
Err:
Exit Sub
End Sub
I've modified the above code as follows
Sub DeleteRowFastMod()
Dim lastRow As Long
Dim i As Long
Dim ws As Worksheet
Dim wb As Workbook
Set wb = Application.ActiveWorkbook
On Error GoTo Err
'Get the desired range from the user
Err.Clear
FastWB True 'Enable fast workbook
strtTime = Timer 'Begin the timer
On Error Resume Next
For Each ws In wb.Worksheets(1) 'Loop through sheets in workbook
ws.Activate
lastRow = Application.ActiveSheet.UsedRange.Rows.Count
If lastRow > 1 Then 'Check if there is anything to do
For i = 2 To lastRow Step 1 'Interval of rows to delete
Range(Rows(i), Rows(i + 997)).Delete Shift:=xlUp
Next i
End If
Next
Application.StatusBar = "" 'Reset the status bar
FastWB False 'Disable fast workbook
MsgBox CStr(Round(Timer - strtTime, 2)) & "s" 'Display duration of task
Err:
Exit Sub
End Sub
I am not sure how to further modify this code to run on each sheet in the workbook in a timely manner.
Thanks in advance for any guidance
You could use the same method as in the link
Excel VBA Performance - 1 million rows - Delete rows containing a value, in less than 1 min
The code bellow (Module 2) sets up test data - 30 million formulas (3 full columns) in 10 Worksheets
The sub in Module 1 loops through all sheets and
hides 1K rows sets
copies visible rows to a new sheet
deletes the initial sheet
Module 1 - Main Sub
Option Explicit
Public Sub TrimLargeData() 'Time: 12.531 sec
Const TRIM_SZ = 1000
Dim t As Double, wb As Workbook, ws As Worksheet
Dim lr As Long, r As Long, newWs As Worksheet, done As Collection
t = Timer: Set wb = ThisWorkbook
FastWB True
Set done = New Collection
For Each ws In wb.Worksheets
done.Add ws
Next
For Each ws In done
lr = ws.UsedRange.Rows.Count
For r = 1 To lr Step TRIM_SZ
If r >= lr - (TRIM_SZ + 1) Then
ws.Range(ws.Cells(r + 1, 1), ws.Cells(lr - 1, 1)).EntireRow.Hidden = True
Exit For
End If
ws.Range(ws.Cells(r + 1, 1), ws.Cells(r + TRIM_SZ - 1, 1)).EntireRow.Hidden = True
Next
Set newWs = Worksheets.Add(After:=Worksheets(Worksheets.Count))
newWs.Name = Left("Trimmed " & ws.Name, 30)
ws.UsedRange.SpecialCells(xlCellTypeVisible).Copy newWs.Cells(1)
ws.Delete
Next
FastWB False: Debug.Print "Time: " & Format(Timer - t, "0.000") & " sec"
End Sub
Module 2 - setup test data subs, and helper procedures
Option Explicit
'generates 30 million formulas (3 full columns) on 10 Worksheets, in about 1 min
Public Sub MakeTestData()
Dim t As Double, ur As Range, ws As Worksheet
t = Timer
FastWB True
FormatCells
MakeWorksheets
With ThisWorkbook
Set ws = .Worksheets(1)
Set ur = ws.Range("A1:C" & ws.Rows.Count)
ur.Formula = "=Address(Row(), Column(), 4)"
.Worksheets.FillAcrossSheets ur
End With
FastWB False
Debug.Print "Time: " & Format(Timer - t, "0.000") & " sec"
End Sub
Private Sub FormatCells()
With ThisWorkbook.Worksheets(1).Cells
.HorizontalAlignment = xlCenter
.VerticalAlignment = xlCenter
.WrapText = False
.IndentLevel = 0
.MergeCells = False
End With
End Sub
Private Sub MakeWorksheets()
Dim ws As Worksheet, i As Long, wsName As Long
With ThisWorkbook
If .Worksheets.Count > 1 Then
For Each ws In .Worksheets
If ws.Index <> 1 Then ws.Delete
Next
End If
For i = 1 To 10
wsName = .Worksheets.Count
.Worksheets.Add(After:=.Worksheets(wsName)).Name = wsName
Next
End With
End Sub
Public Sub FastWB(Optional ByVal opt As Boolean = True)
With Application
.Calculation = IIf(opt, xlCalculationManual, xlCalculationAutomatic)
.DisplayAlerts = Not opt
.DisplayStatusBar = Not opt
.EnableAnimations = Not opt
.EnableEvents = Not opt
.ScreenUpdating = Not opt
End With
FastWS , opt
End Sub
Public Sub FastWS(Optional ByVal ws As Worksheet = Nothing, _
Optional ByVal opt As Boolean = True)
If ws Is Nothing Then
For Each ws In Application.ActiveWorkbook.Sheets
EnableWS ws, opt
Next
Else
EnableWS ws, opt
End If
End Sub
Private Sub EnableWS(ByVal ws As Worksheet, ByVal opt As Boolean)
With ws
.DisplayPageBreaks = False
.EnableCalculation = Not opt
.EnableFormatConditionsCalculation = Not opt
.EnablePivotTable = Not opt
End With
End Sub
I think your biggest performance anchor is that you're deleting so frequently and Excel is having to move around so much data. You might consider clearing contents first and/or using a UNION function to do the delete all in one effort. So here's an example of how both approaches would be written:
Sub UnionExample()
Dim deleteRNG As Range
'You need one start statement that is not a union.
Set deleteRNG = Rows(2)
'Now you can start a loop or use some method to include members in your delete range
Set deleteRNG = Union(deleteRNG, Rows(4))
'when finished creating the delete range, clear contents (it's helped my performance)
deleteRNG.ClearContents
'then do your full delete
deleteRNG.Delete shift:=xlUp
End Sub
Using The SpreadSheetGuru's Timer I removed a total of 1,599,992 from 4 Worksheets in 13.53 seconds.
Sub ProcessWorksheets()
Dim ws As Worksheet
With Application
.ScreenUpdating = False
.Calculation = xlCalculationManual
End With
For Each ws In ThisWorkbook.Worksheets
KeepNthRows ws.UsedRange, 2, 1000
Next
With Application
.ScreenUpdating = True
.Calculation = xlCalculationAutomatic
End With
End Sub
Sub KeepNthRows(Target As Range, FirstRow As Long, NthStep As Long)
Dim data As Variant, results As Variant
Dim x1 As Long, x2 As Long, y As Long
If Target.Rows.Count < 2 Then Exit Sub
FirstRow = FirstRow - 1 'Adjustment needed for using Range.Offset
data = Target.Offset(FirstRow).Value
ReDim results(1 To UBound(data, 1), 1 To UBound(data, 2))
For x1 = FirstRow To UBound(data, 1) Step NthStep
x2 = x2 + 1
For y = 1 To UBound(data, 2)
results(x2, y) = data(x1, y)
Next
Next
Target.Offset(FirstRow).Value = results
End Sub
Long-time user of this forum, first request for VBA help. Still consider myself a very beginner in VBA.
I need to make a daily batch file more meaningful by breaking up the rows in a single worksheet- "Main" (between 13,000 - 1,000,000 rows) into new worksheets. As this file gets processed daily, my requirement is that we can move rows based on the "Record Type" cell in column A.
The "Record Type" e.g. "25" or "41" or "ZA" could each have 3 populated columns, whilst Record Type "26" could have 30 populated... hence important to have entire row moved.
I am limited in my abilities and knowledge here, and have researched many examples on how to move rows (or a range of cells within a row) but these are limited to static options such as YES/NO, PAID/NOT PAID.
So in summary I need to:
1. Create a new worksheet for each distinct record in column A ("Record Type" in "Main")
2. Move entire row from "Main" to subsequently created worksheet in row 2.
Here is my attempt that somewhat creates the new worksheets (though I have to disable the error-handling and can't run as a script- have to step-through)
Sub breakout1()
Workbooks(1).Activate
Dim lastCol As Integer
Dim LastRow As Long
Dim x As Long
Dim rng As Range
Dim Rng1 As Range
Dim Rng2 As Range
Dim Rng3 As Range
Dim SheetNameArray
Dim fn As WorksheetFunction
Dim CalcSetting As Integer
Dim newsht As Worksheet
Set fn = Application.WorksheetFunction
With Application
CalcSetting = .Calculation
.Calculation = xlCalculationManual
.ScreenUpdating = False
End With
With ActiveSheet
Set rng = .UsedRange
Set Rng1 = Intersect(rng, .Range("A:A"))
lastCol = rng.Column + rng.Columns.Count - 1
.Range("A:A").AdvancedFilter Action:=xlFilterCopy, _
CopyToRange:=.Cells(1, lastCol + 2), Unique:=True
Set Rng2 = Intersect(.Columns(lastCol + 2).CurrentRegion, _
.Rows("2:" & Rows.Count))
ReDim SheetNameArray(1 To Rng2.Cells.Count)
SheetNameArray = fn.Transpose(Rng2)
.Columns(lastCol + 2).Clear
For x = LBound(SheetNameArray) To UBound(SheetNameArray)
On Error Resume Next
Set newsht = ThisWorkbook.Sheets(CStr(SheetNameArray(x)))
If Err <> 0 Then
Worksheets.Add
ActiveSheet.Name = CStr(SheetNameArray(x))
Err.Clear
End If
'On Error GoTo 0
'rng.AutoFilter Field:=1, Criteria1:=SheetNameArray(x)
'Set Rng3 = Intersect(rng, .Cells.SpecialCells(xlCellTypeVisible))
'Rng3.Copy Workbooks(1).Sheets(CStr(SheetNameArray(x))).Range("A1")
'rng.AutoFilter
Next x
End With
Range("A1").Select
Application.Calculation = CalcSetting
End Sub
I didn't focus on your true goal which I couldn't grasp out of your description
but here's a refactoring of your code that works for creating and/or populating sheets named after what found in unique values in "base" sheet (se code to set it properly) column "A
Option Explicit
Sub breakout2()
Dim x As Long
Dim rng As Range
Dim SheetNameArray As Variant
Dim CalcSetting As Integer
Dim newsht As Worksheet, BaseSht As Worksheet
With Application
CalcSetting = .Calculation
.Calculation = xlCalculationManual
.ScreenUpdating = False
End With
Set BaseSht = ThisWorkbook.Sheets("breakout") '<== choose "base" sheet
'Set BaseSht= Workbooks(1).ActiveSheet '<== this would activate the first workbook opend in current excel session. is it the one you actually want?
With BaseSht
Set rng = .UsedRange
SheetNameArray = GetSheetNames(rng, 1, 2)
For x = LBound(SheetNameArray) To UBound(SheetNameArray)
Set newsht = SetSheet(CStr(SheetNameArray(x)))
rng.AutoFilter Field:=1, Criteria1:=SheetNameArray(x)
Intersect(rng, .Cells.SpecialCells(xlCellTypeVisible)).Copy Parent.Sheets(CStr(SheetNameArray(x))).Range("A1")
rng.AutoFilter
Next x
End With
Range("A1").Select '<=== what for? Selection is rarely a good programming habit. set and use 'range' type variables instead
With Application
.Calculation = CalcSetting
.ScreenUpdating = True
End With
End Sub
Function SetSheet(shtName As String) As Worksheet
On Error Resume Next
ThisWorkbook.Sheets(shtName).Activate
If Err <> 0 Then
On Error GoTo 0
ThisWorkbook.Worksheets.Add
ActiveSheet.Name = shtName
End If
Set SetSheet = ActiveSheet
End Function
Function GetSheetNames(usedRng As Range, colWithSheetNames As Long, colShift As Long) As Variant
Dim sht As Worksheet
Dim rangeToScan As Range, rangeWithNames As Range, rngToCopyTo As Range
With usedRng
Set sht = .Parent
Set rngToCopyTo = sht.Columns(.Columns(.Columns.Count).column + 2)
End With
With sht
Set rangeToScan = Intersect(usedRng, .Columns(colWithSheetNames))
rangeToScan.AdvancedFilter Action:=xlFilterCopy, CopyToRange:=rngToCopyTo, Unique:=True
Set rangeWithNames = .Range(rngToCopyTo.Cells(1, 1).Offset(1), .Cells(.Rows.Count, rngToCopyTo.column).End(xlUp))
End With
GetSheetNames = Application.WorksheetFunction.Transpose(rangeWithNames)
rngToCopyTo.Clear
End Function
I am currently trying to hide certain columns if Row(8:8) meets certain criteria. My For Each loop is currently not working. Could someone point me in the right direction?
Sub hide()
' hide Macro
'
Dim rng As Range
For Each rng In Range("F:BJ").Columns
If Rows.Range("8") = "Test" Or Rows.Range("8") = "Test1" Then
Column.rng.EntireColumn.Hidden = True
End If
Next rng
End Sub
You can do it this way:
Dim rng As Range
For Each rng In Range("F8:BJ8")
If rng.Value = "Test" Or rng.Value = "Test1" Then
rng.EntireColumn.Hidden = True
End If
Next rng
Presumably, you would want to unhide the columns if the value in row 8 was changed programmatically or otherwise.
Dim rng As Range
With Worksheets("Sheet1")
For Each rng In .Range("F8:BJ8")
rng.EntireColumn.Hidden = _
CBool(LCase(rng.Value) = "test" Or LCase(rng.Value) = "test1")
Next rng
End With
The Rows and Columns ranges refer to the whole spreadsheet if you don't specify a range.
Sub hideColumn()
Dim rng As Range
For Each rng In Range("F:BJ").Columns
If rng.Rows(8) = "Test" Or rng.Rows(8) = "Test1" Then
rng.EntireColumn.Hidden = True
End If
Next rng
End Sub
I am working with some very large datasets (various sheets with 65K+ rows and many columns each). I am trying to write some code to copy filtered data from one sheet to a new empty sheet as fast as possible, but have not had much success so far.
I can include the rest of the code by request, but all it does is calculates the source and destination ranges (srcRange and destRange). The time taken to calculate these is negligible. The vast majority of the time is being spent on this line (4 minutes 50 seconds to be precise):
srcRange.Rows.SpecialCells(xlCellTypeVisible).Copy Destination:=destRange
Additionally I've tried this:
destRange.Value = srcRange.Rows.SpecialCells(xlCellTypeVisible).Value
But it doesn't work properly when there's a filter.
Function FastCopy(srcSheet As String, srcCol As String, destSheet As String, destCol As String)
Application.ScreenUpdating = False
Application.Calculation = xlCalculationManual
Dim srcRange As Range
Dim destRange As Range
Set srcRange = GetColumnRangeByHeaderName(srcSheet, srcCol, -1)
Set destRange = GetColumnRangeByHeaderName(destSheet, destCol, srcRange.Rows.Count)
'destRange.Value = srcRange.Rows.SpecialCells(xlCellTypeVisible).Value
srcRange.Rows.SpecialCells(xlCellTypeVisible).Copy Destination:=destRange
Application.ScreenUpdating = True
Application.Calculation = xlCalculationManual
End Function
This is a slow, dual core machine with 2GB of RAM running excel 2010. Results will obviously vary on a faster machine.
Try something like this to work with filtered ranges. You're on the right track, the .Copy method is expensive and simply writing values from range to range should be much faster, however as you observe, this doesn't work when a range is filtered. When the range is filtered, you need to iterate the .Areas in the range's .SpecialCells:
Sub Test()
Dim rng As Range
Dim subRng As Range
Dim destRng As Range
Set destRng = Range("A10")
Set rng = Range("A1:B8").SpecialCells(xlCellTypeVisible)
For Each subRng In rng.Areas
Set destRng = destRng.Resize(subRng.Rows.Count, subRng.Columns.Count)
destRng.Value = subRng.Value
Set destRng = destRng.Cells(destRng.Rows.Count, 1).Resize(1, 1).Offset(1, 0)
Next
End Sub
Modified for your purposes, but untested:
Function FastCopy(srcSheet As String, srcCol As String, destSheet As String, destCol As String)
Application.ScreenUpdating = False
Application.Calculation = xlCalculationManual
Dim srcRange As Range
Dim destRange As Range
Dim subRng As Range
Set srcRange = GetColumnRangeByHeaderName(srcSheet, srcCol, -1)
Set destRange = GetColumnRangeByHeaderName(destSheet, destCol, srcRange.Rows.Count)
For Each subRng In srcRange.Areas
Set destRng = destRng.Resize(subRng.Rows.Count, subRng.Columns.Count)
destRng.Value = subRng.Value
Set destRng = destRng.Cells(destRng.Rows.Count, 1).Resize(1, 1).Offset(1, 0)
Next
Application.ScreenUpdating = True
Application.Calculation = xlCalculationManual
End Function
Simplest copying (no filter)
Range("F1:F53639").Value = Range("A1:A53639").Value
To expand on my comment
Sub Main()
Application.ScreenUpdating = False
' paste the Range into an array
Dim arr
arr = Range("$A$1:$A$53639").Value
' fill the range based on the array
Range("$F$1").Resize(UBound(arr, 1), UBound(arr, 2)) = arr
' apply the same filter to your copied range as the original range
'+ i don't know how you have applied your filter but just re-apply it to column F
' and delete the invisible cells
' unfortunately there is no xlCellTypeHidden or xlCelltypeInvisible hehe so you have to iterate
Dim i As Long
For i = Range("F" & Rows.Count).End(xlUp).Row To 1 Step -1
If (Range("F" & i).EntireRow.Hidden) Then Range("F" & i).Delete
' or Range("F" & i).EntireRow.Delete
Next i
Application.ScreenUpdating = True
End Sub
If you could provide the time it took you to run it that would be great I am very curious
I just ran this code on 53639 rows and it took less than 1 second
Sub Main()
Application.ScreenUpdating = False
Dim tNow As Date
tNow = Now
' paste the Range into an array
Dim arr
arr = Range("$A$1:$A$53639").Value
' fill the range based on the array
Range("$F$1").Resize(UBound(arr, 1), UBound(arr, 2)) = arr
' apply the same filter to your copied range as the original range
ActiveSheet.Range("$F$1:$F$53640").AutoFilter Field:=1, Criteria1:="a"
' and delete the invisible cells
' unfortunately there is no xlCellTypeHidden or xlCelltypeInvisible hehe so you have to iterate
Dim i As Long
For i = Range("F" & Rows.Count).End(xlUp).Row To 1 Step -1
If (Range("F" & i).EntireRow.Hidden = True) Then
Range("F" & i).Delete
End If
Next i
Debug.Print DateDiff("s", tNow, Now)
Application.ScreenUpdating = True
End Sub