I have a rather large sheet (approx 60K rows by 50 cols). I'm trying to copy several (2 to 8) rows into clipboard and then insert copied cells. This operation takes more than a minute to complete!
I've tried disabling automatic calculations, initiating this operation from VBA, like this:
Range("A1").Insert xlShiftDown
to no available. If I paste (Ctrl-V) rather than insert it works like a snap.
Any ideas how to work around this issue?
Since you can paste the data quickly enough use that instead of inserting, then sort the rows:
In an empty column on the first row of data type the number of rows you want to insert plus 1 (e.g. to insert 3 rows type 4)
Add the next number in the next row, then select both cells and autocomplete the column so that each row has an increasing number
Paste the new data at the end of the old data, immediately after the last row
Number the first row pasted as 1, the 2nd as 2 etc
Sort the sheet ascending on the number column then delete the column
I implemented Absinthe's algorithm, here's the code:
Sub InsertRows()
Dim SourceRange, FillRange As Range
Dim LastCol, LastRow, ActRow, CpdRows As Long
Dim i As Integer
If Application.CutCopyMode <> xlCopy Then Exit Sub
Application.ScreenUpdating = False
Application.EnableEvents = False
With ActiveSheet
LastCol = .UsedRange.Columns.Count
LastRow = .UsedRange.Rows.Count
ActRow = ActiveCell.Row
.Paste Destination:=.Cells(LastRow + 1, 1)
CpdRows = .UsedRange.Rows.Count - LastRow
Application.Calculation = xlCalculationManual
Set SourceRange = .Range(.Cells(ActRow, LastCol + 1), .Cells(ActRow + 1, LastCol + 1))
SourceRange.Cells(1).Value = CpdRows + 1
SourceRange.Cells(2).Value = CpdRows + 2
Set FillRange = .Range(.Cells(ActRow, LastCol + 1), .Cells(LastRow, LastCol + 1))
SourceRange.AutoFill Destination:=FillRange
For i = 1 To CpdRows
.Cells(LastRow + i, LastCol + 1).Value = i
Next i
.Range(.Cells(ActRow, 1), .Cells(LastRow + CpdRows, LastCol + 1)).Sort Key1:=.Cells(ActRow, LastCol + 1), _
Order1:=xlAscending, Header:=xlNo, MatchCase:=False, Orientation:=xlTopToBottom
.Columns(LastCol + 1).Delete
End With
Application.Calculation = xlCalculationAutomatic
Application.EnableEvents = True
Application.ScreenUpdating = True
End Sub
It's works definitely faster than "insert copied cells" and it seems it's accelerating after the 1st use (I mean, when I run the macro for the 2nd, 3rd etc time it works even faster than on the 1st run). There are the cons, too. For example, named ranges do not automatically expand when you insert the lines in this manner.
And the most significant problem of this method: Excel does not move the borders with the cells when sorting. Therefore, the border structure will be ruined. The only workaround I know of is to use conditional formatting for the borders.
This all being said, it's a good workaround
Related
The last two days I've been trying to get the resize vba to work.
I need 3 columns (Q,R,S) to be copied and pasted after column 19. This has to happen until the number of 3 column sets (i, copies of Q:S) is equal to the value in cell ("C18"), likewise, if the number of repeats of QRS is greater than the value in C18 the unnecessary copies should be deleted.
The resize worked fine when it was just one column but now that I try to get a set of 3 added or deleted it goes wrong..the number of copies is not equal to the value in ("C18") and the number of copies made or deleted is not constant when I rerun the sub.
Does anyone have a solution?
Sub resize()
Dim SLastCol As Long
Dim i As Long
i = Range("C18").Value * 3
SLastCol = Cells(1, Columns.Count).End(xlToLeft).Column - 19
If SLastCol < i Then
Columns("Q:S").EntireColumn.copy
Columns("T").EntireColumn.Resize(, Abs(SLastCol - i)).Insert shift:=xlToRight
ElseIf SLastCol > i Then
Columns("T:W").EntireColumn.Resize(, Abs(SLastCol - i)).Delete shift:=xlToLeft
End If
Application.CutCopyMode = False
End Sub
Please, test the next code. It will copy all columns in the range colsRng, as many times as is written in "C8":
Sub resizeColumnsCopy()
Dim i As Long, colsRng As Range, lastCol As Long, rngDel As Range, arrCols, arrPrevCols
'identify the previous processed columns and delete them, if any
lastCol = cells(1, Columns.count).End(xlToLeft).Column
arrPrevCols = Range(cells(1, 20), cells(1, lastCol)).Value 'place the headers after column 20 in an array
arrCols = Range("Q1:S1").Value 'do the same with the copied columns headers
For i = 1 To UBound(arrPrevCols, 2) Step 3 'iterate in the larger array, from three to three columns
If arrPrevCols(1, i) = arrCols(1, 1) Then 'finding the first column header
If rngDel Is Nothing Then
Set rngDel = Range(cells(1, 19 + i), cells(1, 19 + i + 2)) 'create a range of the three involved columns
Else
Set rngDel = Union(rngDel, Range(cells(1, 19 + i), cells(1, 19 + i + 2))) 'careate a Union between the previous range and the next three
End If
End If
Next i
If Not rngDel Is Nothing Then rngDel.EntireColumn.Delete 'if cases of processed columns found, then delete the columns
i = Range("C18").Value
Set colsRng = Columns("Q:S")
colsRng.Copy
cells(1, colsRng.Column + colsRng.Columns.count).EntireColumn.resize(, i * colsRng.Columns.count).Insert Shift:=xlToRight
Application.CutCopyMode = False
End Sub
But, please edit your question and explain about the necessity of previous processed columns deletion. Otherwise, somebody else looking to my code will think that I recently hit my head...
Its been a while since I have done any sort of coding, so I am very rusty.
I am trying to write some VBA code, so that when a button is clicked in the excel sheet it will check another sheet in the same workbook and copy of specific cell values. This is based on a criteria in one of the columns (column 15, I counted); I should probably add that the data is in a table. if that row meets the specified criteria from column 15, then specific columns are copied over to the worksheet with the button.
I do have some code, but I know there is a lot missing from it.
Would appreciate some input, am I on the right track? can anyone help on there I can get more info and tips on the coding I need to use. Not sure if there is an easy way to do this using tables?
Private Sub CommandButton1_Click()
''This will count how many rows are populated in the table''
a = Worksheets("Billable").Cells(Rows.Count, 1).End(xlUp).Row
''Loop will run from row 6 to the last row (Row 6 is the first row in table)''
For i = 6 To a
''If statement which will check the status column for Detailed Estimate Submitted > column 15''
If Worksheets("Billable").Cells(i, 15).Value = "Detailed Estimate Submitted" Then
Worksheets("Billable").Rows(i).Copy
Worksheets("PM_Forecast").Activate
b = Worksheets("PM_Forecast").Cells(Rows.Count, 1).End(xlUp).Row
Worksheets("PM_Forecast").Cells(a + 1, 1).Select
ActiveSheet.Paste
End If
Next
Application.CutCopyMode = False
End Sub
An example of the table: -
I only need to copy over 3 of the columns if they meet a specific criteria in the status column
You did not answer my question regarding the format pasting...
So, please, test the next code which pastes on a classical way. But without selecting and declaring all used variables:
Private Sub CommandButton1_Click()
Dim shB As Worksheet, shPM As Worksheet, lastRowB As Long, lastRowPM As Long
Dim i As Long, lastCol As Long
Set shB = Worksheets("Billable")
Set shPM = Worksheets("PM_Forecast")
lastRowB = Worksheets("Billable").Cells(Rows.Count, 1).End(xlUp).row
'Loop will run from row 6 to the last row (Row 6 is the first row in table)''
For i = 6 To lastRowB
If shB.Cells(i, 15).Value = "Detailed Estimate Submitted" Then
lastCol = shB.Cells(i, Columns.Count).End(xlToLeft).Column
lastRowPM = shPM.Cells(Rows.Count, 1).End(xlUp).row
shB.Range(shB.Range("A" & i), shB.Cells(i, lastCol)).Copy _
shPM.Cells(lastRowPM + 1, 1)
End If
Next
Application.CutCopyMode = False
End Sub
For array using variant you must only declare a new variable Dim arr As Variant and replace this part:
shB.Range(shB.Range("A" & i), shB.Cells(i, lastCol)).Copy _
shPM.Cells(lastRowPM + 1, 1)
with this one:
arr = shB.Range(shB.Range("A" & i), shB.Cells(i, lastCol)).Value
shPM.Cells(lastRowPM + 1, 1).Resize(, UBound(arr, 2)).Value = arr
Then, delete the code line Application.CutCopyMode = False. It is not necessary, anymore, since the Clipboard memory is not used...
And use Option Explicit on top of your module. It will save you many times, when the code will become complex.
I cant comment so I will ask the question through this answer:
Your variable b is currently not being used, I think it was meant to be in this line: Worksheets("PM_Forecast").Cells(b + 1, 1).Select but you have written a instead of b.
Does this solve your current issue?
Sample WorkbookI have repeating macros that freeze with an error after running 500 to 600 times through. The number of times I need it to run will change every time but mostly be around 2000 times.Error Notice
Line of code it stops onMaE.png
The entire code is below, multiple macros running after each other and calling others until report completes. It runs fine if it runs less than 500 times.
Sub Start_New_Report()
'
' Start_New_Report Macro
' Clear Old data and prepare for new lines.
'
Application.ScreenUpdating = False
Sheets("Filtered Report").Select
Range("A2:I1048576").Select
Selection.ClearContents
Range("A2").Select
ActiveCell.FormulaR1C1 = "1"
Call Filter_Data
End Sub
Sub Filter_Data()
' Filter raw Syteline data to usable lines
Worksheets("Filtered Report").Range("B2").Value = _
Worksheets("PurchaseOrderStatus").Range("A5:E5").Value
Worksheets("Filtered Report").Range("C2").Value = _
Worksheets("PurchaseOrderStatus").Range("A6:C6").Value
Worksheets("Filtered Report").Range("D2").Value = _
Worksheets("PurchaseOrderStatus").Range("A7:F7").Value
Worksheets("Filtered Report").Range("E2").Value = _
Worksheets("PurchaseOrderStatus").Range("J5").Value
Worksheets("Filtered Report").Range("F2").Value = _
Worksheets("PurchaseOrderStatus").Range("O7").Value
Worksheets("Filtered Report").Range("G2").Value = _
Worksheets("PurchaseOrderStatus").Range("P6:R6").Value
Worksheets("Filtered Report").Range("H2").Value = _
Worksheets("PurchaseOrderStatus").Range("P7:T7").Value
Worksheets("Filtered Report").Range("I2").Value = _
Worksheets("PurchaseOrderStatus").Range("V7").Value
Call Clear_Raw_Data
End Sub
Sub Clear_Raw_Data()
' Clear Raw Data Lines
Sheets("PurchaseOrderStatus").Select
Rows("5:7").Delete
Call Blank_Cells
End Sub
Sub Blank_Cells()
' Check if blank cells exist in current line
Sheets("Filtered Report").Select
Range("B2").Select
If IsEmpty(Range("B2").Value) Then
Call Copy_Up
Else
Call Blank_Cells_Raw_Data
End If
End Sub
Sub Copy_Up()
'
' Copy Data Up from line below if cells are empty.
'
Range("B3:D3").Copy Range("B2:D2")
Call Blank_Cells_Raw_Data
End Sub
Sub Blank_Cells_Raw_Data()
Sheets("PurchaseOrderStatus").Select
Range("V5").Select
If IsEmpty(ActiveCell.Value) Then
Call Finalize_Report
Else
Call Clear_for_Next_Line
End If
End Sub
Sub Clear_for_Next_Line()
'
' Clear_for_Next_Line Macro
'
' Insert_line Macro
Sheets("Filtered Report").Select
Range("2:2").Insert CopyOrigin:=xlFormatFromRightOrBelow
' Create next index number
Worksheets("Filtered Report").Range("A2").Value = _
Worksheets("Filtered Report").Range("A3").Value + 1
Call Filter_Data
End Sub
Sub Finalize_Report()
'
' Finalize_Report Macro
' Finish report and sort the order.
'
Sheets("Filtered Report").Select
Range("A1") = "Index"
Columns("A:I").Sort key1:=Range("A2"), _
order1:=xlAscending, Header:=xlYes
End Sub
In essence, I discarded the entire model where separate subroutines were calling each other in sequence and replaced it with a single subroutine that performs all of the functions.
I opted to rewrite the sample code by removing the use of .Select (see link) and defining worksheet variables whenever possible.
One other thing I noticed was in Blank_Cells and Blank_Cells_Raw_Data, I don't think you meant to use IsEmpty there (which checks to see if a variable is initialized; see link), but rather determine if the cell itself is empty. I changed this to If Application.WorksheetFunction.CountA(Range) = 0 in both instances.
In Filter_Data, I noticed you're setting the value of one cell (e.g. B2) to the value of multiple cells (e.g. A5:E5). In testing this just set the first cell to the first value in the range defined (i.e. cell A5). Assuming you didn't mean to do something like Application.WorksheetFunction.Sum(ws2.Range("A5:E5")) (to sum the values in those cells) I just changed these to get the first cell.
I changed Filter_Data and a few other spots to use cell/column references instead of ranges when possible.
In Copy_Up I replaced the .Copy function with actually setting the cells to the values (Copy can get weird sometimes so I avoid using it whenever possible).
Additionally, since .Delete and .Insert both slow down the macro considerably, I used a method that avoids doing either by just checking one group of three rows on 'PurchaseOrderStatus' at a time then moving to the next one, and by writing to the first free row on 'Filtered Report' instead of inserting new rows at the top. This sped the macro up considerably (~35 seconds to less than a second).
Option Explicit
Sub Start_New_Report()
Dim ws1 As Worksheet, ws2 As Worksheet
Dim rng As Range
Dim newRow As Long, lastRow As Long, x As Long
Set ws1 = ThisWorkbook.Sheets("Filtered Report")
Set ws2 = ThisWorkbook.Sheets("PurchaseOrderStatus")
' Turn screen updating / calculation off for speed
Application.ScreenUpdating = False
Application.Calculation = xlCalculationManual
' Clear Old data and prepare for new lines.
ws1.Range(ws1.Cells(2, 1), ws1.Cells(10000, 9)).ClearContents
ws1.Cells(2, 1) = 1
' Define last row
lastRow = ws2.Cells(ws2.Rows.Count, 1).End(xlUp).Row - 2
' Iterate through all groups of 3 rows on PurchaseOrderStatus sheet
For x = 5 To lastRow Step 3
' Determine new row to write to
newRow = ws1.Cells(ws1.Rows.Count, 1).End(xlUp).Row
' Filter raw Syteline data to usable lines
ws1.Cells(newRow, 2) = ws2.Cells(x, 1)
ws1.Cells(newRow, 3) = ws2.Cells(x + 1, 1)
ws1.Cells(newRow, 4) = ws2.Cells(x + 2, 1)
ws1.Cells(newRow, 5) = ws2.Cells(x, 10)
ws1.Cells(newRow, 6) = ws2.Cells(x + 2, 15)
ws1.Cells(newRow, 7) = ws2.Cells(x + 1, 16)
ws1.Cells(newRow, 8) = ws2.Cells(x + 2, 16)
ws1.Cells(newRow, 9) = ws2.Cells(x + 2, 22)
' Copy Data Up from line below if cells are empty.
If Application.WorksheetFunction.CountA(ws1.Cells(newRow, 2)) = 0 Then
ws1.Cells(newRow, 2) = ws1.Cells(newRow - 1, 2)
ws1.Cells(newRow, 3) = ws1.Cells(newRow - 1, 3)
ws1.Cells(newRow, 4) = ws1.Cells(newRow - 1, 4)
End If
' Create next index number if not the last row
If x <> lastRow Then
ws1.Cells(newRow + 1, 1) = ws1.Cells(newRow, 1).Value + 1
End If
Next x
' Finish report and sort the order.
ws1.Range(ws1.Columns(1), ws1.Columns(9)).Sort _
Key1:=ws1.Cells(2, 1), _
Order1:=xlAscending, _
Header:=xlYes
' Turn screen updating / calculation back on
Application.ScreenUpdating = True
Application.Calculation = xlCalculationAutomatic
End Sub
Can anyone help me adjust this code to fix my solution?
I have a button that adds x amount of new rows from A5 downwards. Columns A - Z.
I would like the new rows to be blank but still contain dropdowns and formula. New to VBA and struggling with this one.
I think I need to change the range and add xlPasteFormulas but unsure where and how for both. Any help hugely appreciated.
Option Explicit
Sub AddRows()
Dim x As Integer
x = InputBox("How many rows would you like to add?", "Insert Rows")
'Selecting range to insert new cells
Range(Cells(5, 1), Cells(x + 4, 1)).EntireRow.Insert
'Copys current cell A6 and past in the new cells
Cells(x + 5, 1).Copy Range(Cells(5, 1), Cells(x + 4, 1))
'if you want the cells to be blank but still have the drop down options
Range(Cells(5, 1), Cells(x + 4, 1)).ClearContents
End Sub
Please try the code below. It will copy everything from the BaseRow and then delete constant values in that range, leaving formats, including data validations, and formulas.
Sub AddRows()
Const BaseRow As Long = 11 ' modify to suit
Dim x As String ' InputBox returns text if 'Type' isn't specified
Dim Rng As Range
Dim R As Long
x = InputBox("How many rows would you like to add?", "Insert Rows")
If x = "" Then Exit Sub
R = BaseRow + CInt(x) - 1
Rows(BaseRow).Copy 'Copy BaseRow
'specify range to insert new cells
Set Rng = Range(Cells(BaseRow, 1), Cells(R, 1))
Rng.Insert Shift:=xlDown
' insert the new rows BEFORE BaseRow
' to insert below BaseRow use Rng.Offset(BaseRow - R)
Set Rng = Rng.Offset(BaseRow - R - 1).Resize(Rng.Rows.Count, ActiveSheet.UsedRange.Columns.Count)
Rng.Select
On Error Resume Next
Rng.SpecialCells(xlCellTypeConstants).ClearContents
Application.CutCopyMode = False '
End Sub
The code now has an emergency exit: If you don't enter anything in the InputBox the procedure terminates. Note that new rows are inserted above the BaseRow. After the insertion all new rows and the old row are identical. You can then choose to retain the constants in either the first or the last of these rows, effectively meaning, insert new, blank rows either above or below the BaseRow.
My 2010 macro updates on opening a sheet. Does 2016 work the same when when they have the target sheet opened in a new 'instance'? It has to be idiot proof (I don't know why they asked me to do it :P). So the macro has to run once when opening the sheet; if the sheet is opened on the second monitor run every time a value is inserted over 119 in the source sheet; Don't run unnecessary because of the potentially very large sheets and meh laptops.
I've made this macro so the sheets my colleges are using don't need 'complex' formulas or macros to clear blank rows before it's exported to Word. I've made it in 2010, but I can't test it on 2016 til next week.
The macro that on the target sheet (J03);
Private Sub worksheet_activate()
And on the source sheet (WTB);
Private Sub Run_When_Value_Greather_Than_119_Is_Entered_In_Column_G()
Google is clogged with answers and results about blank rows, copying, blank rows, running on other activation ways and non blank rows. I probably don't know what to look for either.
The full code;
Private Sub worksheet_activate()
Dim Max As Long, MaxD As Long 'Determine the amount of filled rows
Dim wsWtB As Worksheet, wsJ03 As Worksheet
Dim wb As Workbook
Dim i As Integer, j As Integer 'i and j for the row numbers
Application.ScreenUpdating = False 'screenupdating of for max speeds
Set wb = ThisWorkbook
Set wsJ03 = Sheets("J_03")
Set wsWtB = Sheets("WTB")
Max = WorksheetFunction.Max(wsWtB.Range("A3:A1600")) 'Amount of rows with data
Max = Max + 3 'Ignore the headers
MaxD = WorksheetFunction.Max(wsJ03.Range("A3:A1600"))
MaxD = MaxD + 2
j = 9 'The rownumber where the copying needs to start
wsJ03.Range("B9", Cells(MaxD, 5)).ClearContents 'Clear the old values
For i = 3 To Max 'The copying loop has to start after the headers at row 3
If wsWtB.Cells(i, 7).Value > 119 Then 'Do stuff if...
wsJ03.Cells(j, "B").Value = Chr(39) & wsWtB.Cells(i, "B").Value 'At a '
wsJ03.Cells(j, "C").Value = Chr(39) & wsWtB.Cells(i, "C").Value 'at the start
wsJ03.Cells(j, "D").Value = Chr(39) & wsWtB.Cells(i, "D").Value 'so a zero is
wsJ03.Cells(j, "E").Value = Chr(39) & wsWtB.Cells(i, "E").Value 'displayed
j = j + 1 'Set the next row for the target sheet
Else
End If
Next i
Application.ScreenUpdating = True
End Sub
It's the first piece of code that I got working without hiccups :-) Feel free to comment and ad the propper tags.
Koen.
Edit; (Alternative ways to look for the last cell)
?thisworkbook.sheets("WTB").cells(rows.Count,"A").end(xlup).row
1047 '<- Rownumber of the last cell with a Formula to create/force
successive
numbers
?thisworkbook.sheets("WTB").columns("A").Find(What:="*", LookIn:=xlValues,
SearchDirection:=xlPrevious, SearchOrder:=xlByRows).Row
5 '<- Rownumber of the last cell with a value. Includes the header
rows
?WorksheetFunction.Max(thisworkbook.sheets("WTB").Range("A3:A1600"))
3 '<- Highest number in A3:A1600 and also the amount units/rows that
need to be copied to "J_03"
I needed a function that gave me the amount of 'things' on the sheet. In this case it's 3, but it could go up to 1600.
Edit 2; (google sheet so you can see what i'm working on)
https://docs.google.com/spreadsheets/d/1I5qLeOS0DWcwncs_ium_J0Vp6b4nzTsiE0ndbKtpsC0/edit?usp=sharing
Edit 3; there was an error in the clear range part.
wsJ03.Range("B9", Cells(MaxD, 5)).ClearContents 'Clear the old values
You could use something like the following, but make sure you place the code in the Sheet where the values might be changing (Sheets("WTB")):
Private Sub Worksheet_Change(ByVal Target As Range)
If Target.Column = 7 Then 'If something changed in column G
If Target.Value > 119 Then 'and if the value is higher than 119
NextFreeRow = Sheets("J_03").Cells(.Rows.Count, "B").End(xlUp).Row + 1
'Or Do your copying stuff, you can use Target.column or Target.row to find the address of the cell that got a value higher than 119
Sheets("J_03").Cells(NextFreeRow, "B").Value = Chr(39) & Sheets("WTB").Cells(Target.Row, "B").Value 'At a '
Sheets("J_03").Cells(NextFreeRow, "C").Value = Chr(39) & Sheets("WTB").Cells(Target.Row, "C").Value 'at the start
Sheets("J_03").Cells(NextFreeRow, "D").Value = Chr(39) & Sheets("WTB").Cells(Target.Row, "D").Value 'so a zero is
Sheets("J_03").Cells(NextFreeRow, "E").Value = Chr(39) & Sheets("WTB").Cells(Target.Row, "E").Value 'displayed
End If
End If
End Sub
2 months later, thought i'd show my final work;
The Union() functions lets you (or me in this case), improve the speed of the sheets:
For i = 1 to LastRow
If Ws1.Cells(i, 1).Value > 119 Then
Union(Ws2.Cells(i, 4), Ws2.Cells(i, 5), Ws2.Cells(i, 6)).Value =
Union(Ws1.Cells(y, 1), Ws1.Cells(y, 2), Ws1.Cells(y, 3)).Value: y = y + 1
end if
Next
It's about 30% faster then using cel1, 2, 3.value = cel5, 6, 7.value when it's simply copying all rows without the If.
When my workbook needs to fill 50 sheets like this and has 25 rows of data it took 4,5 seconds on average, with the Union() it's 1,6. When there are 1000 rows its goes from ~23 to 9 seconds but the variations is very high. Depending on the If's;
For some sheets it's not "If > 119 then";
If cellAL.Value = "x" Then 'if the cell exactly "x" Then do stuf
If Not cellAL.Value <> vbNullString Then 'if the cell = NotEmpty
vbNullString
is faster then "" because it's actually less ones and zeros
If InStr(cellAll, "x") Then 'looks for all x's in the cell.
To find the last row without beein affected by format, formulas and other stuff;
myLastRow = .Columns("A").Find(What:="*", LookIn:=xlValues, _
SearchDirection:=xlPrevious, SearchOrder:=xlByRows).Row
"*" 'is something like "any/all characters". A Space or Alt + Enter can make
a big mess
try the direct window to see what it does:
?activesheet.Columns("A").Find(What:="*", LookIn:=xlValues, _
SearchDirection:=xlPrevious).Row
Ps for my CanaDerp buddy; Hope you can get it working with this!