I have a sheet with ~300 lines and 30 columns of numbers. I need to paint cells as a result of processing SelectionChange event. Performance is imporant as issue of usability.
First way is to take a Range object for every cell I'm going to highlight:
For x = 1 To 30: For y = 1 To lastNonemptyRow
If someClause(CInt(Cells(rowIdx, colIdx).Value)) Then
Range(Cells(rowIdx, colIdx).Value).Interior.Color = Rgb(255, 0, 0)
End If
Next y: Next x
This way is quite slow even with disabled ScreenUpdating.
Second way is to make a string with set of addresses:
addressesToHighlight = ""
For x = 1 To 30: For y = 1 To lastNonemptyRow
If someClause(CInt(Cells(rowIdx, colIdx).Value)) Then
addressesToHighlight = addressesToHighlight & Cells(rowIdx, colIdx).Address & ", "
End If
Next y: Next x
Range(addressesToHighlight).Interior.Color = Rgb(255, 0, 0)
This way gives error when there is 42 or more cells to highlight.
Third way is to create a range as union of two ranges which are previously accumulated cells and current cell:
Set resultRange = Nothing
For x = 1 To 30: For y = 1 To lastNonemptyRow
If someClause(CInt(Cells(rowIdx, colIdx).Value)) Then
If resultRange is Nothing then
Set resultRange = Range(Cells(rowIdx, colIdx))
Else
Set resultRange = Union(resultRange, Range(Cells(rowIdx, colIdx)))
End if
End If
Next y: Next x
resultRange.Interior.Color = RGB(255, 0, 0)
This way is quite fast but after 1000 cells its execution time grows exponentially: 1000 cells are highlighted in 1.5 sec, 2000 cells are highlighted in 8 sec.
What is the fastest way to specify and highlight arbitrary 1000..10000 cells?
This is sort of what you are looking to do. Without further information re what sort of clause you would use I had to come up with own puzzle I employee many (all?) of the techniques used to speed up programs. 10 executes had average runtime of .2254 seconds with 10k cells painted
Option Explicit
Private Declare Function timeGetTime Lib "winmm.dll" () As Long
Sub newnew()
Dim started As Long
Dim ws As Worksheet
Dim paintRng As String
Dim rng As Range
Dim ColumnCount As Long
Dim RowCount As Long
Dim arrRng() As Variant
Dim wsTwo As Worksheet
Dim rngTwo As Range
Dim colNum As Long
Dim rowNum As Long
Dim ended As Long
started = timeGetTime
With Application
.Calculation = xlCalculationManual
.ScreenUpdating = False
.DisplayStatusBar = False
.EnableEvents = False
End With
started = timeGetTime
Set ws = Sheets("Sheet1")
ws.DisplayPageBreaks = False
paintRng = "A1:J1000"
Set rng = ws.Range(paintRng)
ColumnCount = rng.Columns.Count
RowCount = rng.Rows.Count
ReDim arrRng(1 To RowCount, 1 To ColumnCount)
arrRng = rng
Debug.Print ColumnCount
Debug.Print RowCount
Set ws = Nothing
Set rng = Nothing
Set wsTwo = Sheets("Sheet2")
wsTwo.DisplayPageBreaks = False
Set rngTwo = wsTwo.Range(paintRng)
With rngTwo
For colNum = 1 To ColumnCount
For rowNum = 1 To RowCount
If arrRng(rowNum, colNum) = 1 Then
.Cells(rowNum, colNum).Interior.Color = RGB(255, 0, 0)
ElseIf arrRng(rowNum, colNum) = 2 Then
.Cells(rowNum, colNum).Interior.Color = RGB(125, 0, 0)
ElseIf arrRng(rowNum, colNum) = 3 Then
.Cells(rowNum, colNum).Interior.Color = RGB(0, 255, 0)
ElseIf arrRng(rowNum, colNum) = 4 Then
.Cells(rowNum, colNum).Interior.Color = RGB(0, 0, 255)
ElseIf arrRng(rowNum, colNum) = 5 Then
.Cells(rowNum, colNum).Interior.Color = RGB(125, 125, 0)
ElseIf arrRng(rowNum, colNum) = 6 Then
.Cells(rowNum, colNum).Interior.Color = RGB(125, 0, 125)
ElseIf arrRng(rowNum, colNum) = 7 Then
.Cells(rowNum, colNum).Interior.Color = RGB(75, 75, 200)
ElseIf arrRng(rowNum, colNum) = 8 Then
.Cells(rowNum, colNum).Interior.Color = RGB(50, 125, 255)
End If
Next rowNum
Next colNum
End With
Set wsTwo = Nothing
Set rngTwo = Nothing
With Application
.Calculation = xlCalculationAutomatic
.ScreenUpdating = True
.DisplayStatusBar = True
.EnableEvents = True
End With
ended = timeGetTime
Debug.Print ColumnCount * RowCount & " Cells Painted In " & (ended - started) / 1000 & " seconds"
End Sub
Related
I have been working on this code for sometime, taking what I can from other posts and learning as I go. I am new to VBA. I am trying to have a master spreadsheet update from other excel sheets. I have wrote a code to check the value of column C and if it has a value in the Master that is not in the other to highlight the row red. IF the other sheet has a value that the master does not it, inserts the entire row and highlights green. The part that I can not seem to get working is how to update the existing rows with new information when the value of column C is a match. Everytime I try, it messes everything up.
Here is my code:
Sub FindDifferences()
Application.ScreenUpdating = False
Dim cell As Range
Dim cel1 As Range
Dim cel2 As Range
Dim wkb1 As Workbook
Dim wkb2 As Workbook
Dim wks1 As Worksheet
Dim wks2 As Worksheet
Dim lRow As Long
Dim iCntr As Long
Dim r1 As Range
Dim r2 As Range
Dim i As Integer
Dim j As Integer
Dim lastRow1 As Integer
Dim lastRow2 As Integer
Dim lastRow As Long
Dim recRow As Long
Dim p As Long
Dim fCell As Range
Set wkb1 = Workbooks.Open(Filename:="C:\Users\James.R.Dickerson\...\09-24-2018-2.xlsx.xlsm")
Set wks1 = wkb1.Worksheets("Job List")
Set wkb2 = ThisWorkbook
Set wks2 = wkb2.Worksheets("Code 200 TECH ASSISTs")
lRow = 200
recRow = 1
For iCntr = lRow To 1 Step -1
If Cells(iCntr, 1).Interior.Color = RGB(156, 0, 6) Then
Rows(iCntr).Delete
End If
Next
With wks1
Set r1 = .Range("C2", .Cells(.Rows.Count, .Columns("C:C").Column).End(xlUp))
End With
With wks2
Set r2 = .Range("C2", .Cells(.Rows.Count, .Columns("C:C").Column).End(xlUp))
End With
lastRow1 = wks2.UsedRange.Rows.Count
lastRow2 = wks1.UsedRange.Rows.Count
For i = 1 To lastRow1
For j = 1 To lastRow2
If r2(i).Value <> "" Then 'This will omit blank cells at the end (in the event that the column lengths are not equal.
If r1(j).Value = r2(i).Value Then
r2(i).EntireRow.Delete
r1(j).EntireRow.Copy
r2(i).EntireRow.Insert
r2(i).EntireRow.Interior.Color = RGB(255, 255, 255) 'White background
r2(i).EntireRow.Font.Color = RGB(0, 0, 0) 'Black font color
Application.CutCopyMode = False
Exit For
Else
If InStr(1, r1(j).Value, r2(i).Value, vbTextCompare) > 0 Then
'You may notice in the above instr statement, I have used vbTextCompare instead of its numerical value, _
I find this much more reliable.
r2(i).EntireRow.Interior.Color = RGB(255, 255, 255) 'White background
r2(i).EntireRow.Font.Color = RGB(0, 0, 0) 'Black font color
Exit For
Else
r2(i).EntireRow.Interior.Color = RGB(156, 0, 6) 'Dark red background
r2(i).EntireRow.Font.Color = RGB(255, 199, 206) 'Light red font color
End If
End If
End If
Next j
Next i
With wks1
lastRow = .Cells(.Rows.Count, "C").End(xlUp).Row
For i = 2 To lastRow
'See if item is in Master sheet
Set fCell = wks2.Range("C:C").Find(what:=.Cells(i, "C").Value, lookat:=xlWhole, MatchCase:=False)
If Not fCell Is Nothing Then
'Record is already in master sheet
recRow = fCell.Row
Else
'Need to move this to master sheet after last found record
.Cells(i, "C").EntireRow.Copy
wks2.Cells(recRow + 1, "C").EntireRow.Insert
wks2.Cells(recRow + 1, "C").EntireRow.Interior.Color = RGB(0, 190, 8)
recRow = recRow + 1
End If
Next i
End With
Application.CutCopyMode = False
wkb1.Close
Application.ScreenUpdating = True
'ActiveWorkbook.Save
End Sub
Update is the code above works fine, it just skips a few rows and I can not figure out why. Any assistance is appreciated. Thank you.
This block:
.Cells(p, "C").EntireRow.Copy
wks2.Cells(p, "C").EntireRow.Delete
wks2.Cells(recRow1 + 1, "C").EntireRow.Insert
is in wrong order because .Delete empties copy buffer so you insert an empty row. Change order of commands this way:
wks2.Cells(p, "C").EntireRow.Delete
.Cells(p, "C").EntireRow.Copy
wks2.Cells(recRow1 + 1, "C").EntireRow.Insert
and it will be better :)
I have written the following code for one of my worksheets.
Sub Hide_Projects()
Application.ScreenUpdating = False
i = 6
For i = 6 To 350
Cells(9, i).Select
If Selection.Value = "Project" Then
ActiveCell.EntireColumn.Hidden = True
Else
ActiveCell.EntireColumn.Hidden = False
End If
Next i
Application.ScreenUpdating = True
End Sub
It works fine, does exactly what I need it to every time without crashing or lagging. However, when I use a similar code on a different worksheet, only this time applied to rows rather than columns, it either crashes my Excel or takes about 2 minutes to run, even though the code is identical. This is the second code:
Sub Hide_Projects_5yr()
Application.ScreenUpdating = False
i = 6
For i = 6 To 350
Cells(i, 7).Select
If Selection.Value = "Project" Then
ActiveCell.EntireRow.Hidden = True
Else
ActiveCell.EntireRow.Hidden = False
End If
Next i
Application.ScreenUpdating = True
End Sub
Does anyone have any idea why this is the case?
Thank you!
Obviously columns are times faster to hide than rows. I have tried this:
Option Explicit
Public Sub TestingSpeed()
Dim lngCount As Long
Dim dtTime As Date
Columns.Hidden = False
rows.Hidden = False
dtTime = Now
For lngCount = 1 To 300
rows(lngCount).Hidden = True
Next lngCount
Debug.Print "Rows: -> "; DateDiff("s", dtTime, Now())
dtTime = Now
For lngCount = 1 To 300
Columns(lngCount).Hidden = True
Next lngCount
Debug.Print "Cols: -> "; DateDiff("s", dtTime, Now())
End Sub
The result is the following (in seconds):
Rows: -> 9
Cols: -> 2
And the difference grows somehow exponentially.
With 1.000 samples it is like this:
Rows: -> 11
Cols: -> 1
With 10.000 like this:
Rows: -> 19
Cols: -> 10
It is very likely that your active sheet is not the one you intend to work on. It is always best to avoid Select and ActiveCell, because you are dependent on the cursor location. Not sure you need the false case, unless you use the same sheet over and over again and it may be hidden.
Sub Hide_Projects_5yr()
Application.ScreenUpdating = False
Dim ws as Worksheet
Set ws = Sheets("YourSheetName")
For i = 6 To 350
If ws.Cells(i, 7).Value = "Project" Then
ws.Cells(i, 7).EntireRow.Hidden = True
Else
ws.Cells(i, 7).EntireRow.Hidden = False
End If
Next i
Application.ScreenUpdating = True
End Sub
Could you try giving your code full addresses to your cells? Besides, it is a good idea not using the select command. Here's my modifications to your code:
Sub Hide_Projects()
Application.ScreenUpdating = False
With ThisWorkbook.Sheets("Put the name of your sheet here")
For i = 6 To 350
If .Cells(9, i).Text = "Project" Then
.Columns(i).Hidden = True
Else
.Columns(i).Hidden = False
End If
Next i
End With
Application.ScreenUpdating = True
End Sub
Your second code would look like this:
Sub Hide_Projects_5yr()
Application.ScreenUpdating = False
With ThisWorkbook.Sheets("Put the name of your second sheet here")
For i = 6 To 350
If .Cells(i, 7).Text = "Project" Then
.Rows(i).Hidden = True
Else
.Rows(i).Hidden = False
End If
Next i
End With
Application.ScreenUpdating = True
End Sub
Let me know if the error message keeps appearing.
Your main slowdown is a result of reading data from the worksheet too many times. Load the cell values into an array first, then loop through that.
You can also gain a bit of speed by unhiding the rows all at once at the outset, then hiding if the "="Project" condition is true. Again, this reduces the number of calls to the worksheet; your current version sets the ".Hidden" property of each row one-by-one.
Application.ScreenUpdating = False
Dim i As Long
Dim j As Long
Dim tempArr As Variant
tempArr = Range(Cells(6, 7), Cells(350, 7)).Value
Rows("6:350").Hidden = False
j = 1
For i = LBound(tempArr, 1) To UBound(tempArr, 1)
If tempArr(i, 1) = "Project" Then
Rows(j + 5).Hidden = True
End If
j = j + 1
Next
Application.ScreenUpdating = True
If you're really concerned about speed, you could also reduce the number of trips to the worksheet by checking for consecutive rows containing "Project". This version runs ~2x as fast as the other one (tested on a sample of 200k rows). It makes the code a lot more complex, though.
Application.ScreenUpdating = False
Dim i As Long
Dim j As Long
Dim k As Long
Dim tempArr As Variant
Dim consBool As Boolean
tempArr = Range(Cells(6, 7), Cells(350, 7)).Value
Rows("6:350").Hidden = False
j = 1
For i = LBound(tempArr, 1) To UBound(tempArr, 1)
consBool = True
If tempArr(i, 1) = "Project" Then
k = i
Do Until consBool = False
If k = UBound(tempArr, 1) Then
consBool = False
ElseIf tempArr(k + 1, 1) = "Project" Then
k = k + 1
Else
consBool = False
End If
Loop
Rows(j + 5 & ":" & k + 5).Hidden = True
j = j + 1 + (k - i)
i = k
Else
j = j + 1
End If
Next
Application.ScreenUpdating = True
Here's what it'd look like if I were going to implement this in a larger project. Among other optimizations, I've added some features (it can check for partial matches, check multiple columns for your criteria, and do an "inverted" mode that hides all rows not containing your criteria) and made sure that you're required to specify your worksheet.
Option Explicit
Sub exampleMacro()
Application.ScreenUpdating = False
Application.Calculation = xlCalculationManual
Call hideRows(ThisWorkbook.Sheets("Example WS"), 6, 350, "Project", 7, 7)
Application.Calculation = xlCalculationAutomatic
Application.ScreenUpdating = True
End Sub
Sub hideRows(ws As Worksheet, startRow As Long, endRow As Long, valCrit As String, Optional startCol As Long = 1, Optional endCol As Long = 1, Optional invert As Boolean = False, Optional checkAll As Boolean = False)
'Hides rows in a range (startRow to endRow) in a worksheet (ws)
'Hides when row contains a value (valCrit; partial strings are accepted) in a column or series of columns (startCol to endCol)
'In inverted mode (invert), hides rows that do *not* contain value
'If (checkAll) is True, all columns must contain value to be hidden/unhidden
'Usage examples:
'Call hideRows(exampleWS, 1, 1000, "Foo", 1, 10) -> hides rows that contain a cell in columns 1-10 with exact value "Foo"
'Call hideRows(exampleWS, 1, 1000, "*Foo*", 1, 10) -> hides rows that contain a cell in columns 1-10 that contains partial string "*Foo*"
'Call hideRows(exampleWS, 1, 1000, "Foo", 1, 10, True) -> hides rows that contain no cells in columns 1-10 with exact value "Foo"
'Call hideRows(exampleWS, 1, 1000, "Foo", 1, 10, False, True) -> hides rows in which all cells in columns 1-10 contain the exact value "Foo"
'Call hideRows(exampleWS, 1, 1000, "Foo", 1, 10, True, True) -> hides rows in which no cells in columns 1-10 contain the exact value "Foo"
Dim loopCounter As Long
Dim rowCounter As Long
Dim colCounter As Long
Dim endConsRow As Long
Dim tempArr As Variant
Dim toAdd As Long
Dim toHide As String
Dim consBool As Boolean
Dim tempBool As Boolean
Dim rowStr As String
Dim goAhead As Boolean
Dim i As Long
If startRow > endRow Then
toAdd = endRow - 1
Else
toAdd = startRow - 1
End If
ws.Rows(startRow & ":" & endRow).Hidden = False
tempArr = ws.Range(ws.Cells(startRow, startCol), ws.Cells(endRow, endCol)).Value
loopCounter = 1
For rowCounter = LBound(tempArr, 1) To UBound(tempArr, 1)
For colCounter = LBound(tempArr, 2) To UBound(tempArr, 2)
goAhead = False
If tempArr(rowCounter, colCounter) Like valCrit Then
If (Not checkAll) Or (colCounter = UBound(tempArr, 2)) Then
If invert Then
loopCounter = loopCounter + 1
Exit For
End If
goAhead = True
End If
ElseIf checkAll Or colCounter = UBound(tempArr, 2) Then
If Not invert Then
loopCounter = loopCounter + 1
Exit For
End If
goAhead = True
End If
If goAhead Then
endConsRow = rowCounter
consBool = True
Do Until consBool = False
tempBool = False
For i = LBound(tempArr, 2) To UBound(tempArr, 2)
If endConsRow = UBound(tempArr, 1) Then
Exit For
ElseIf tempArr(endConsRow + 1, i) Like valCrit Then
If (Not checkAll) Or (i = UBound(tempArr, 2)) Then
If Not invert Then
endConsRow = endConsRow + 1
tempBool = True
End If
Exit For
End If
ElseIf checkAll Or i = UBound(tempArr, 2) Then
If invert Then
endConsRow = endConsRow + 1
tempBool = True
End If
Exit For
End If
Next
If Not tempBool Then
consBool = False
End If
Loop
rowStr = loopCounter + toAdd & ":" & endConsRow + toAdd
If toHide = "" Then
toHide = rowStr
ElseIf Len(toHide & "," & rowStr) > 255 Then
ws.Range(toHide).EntireRow.Hidden = True
toHide = rowStr
Else
toHide = toHide & "," & rowStr
End If
loopCounter = loopCounter + 1 + (endConsRow - rowCounter)
rowCounter = endConsRow
Exit For
End If
Next
Next
If Not toHide = "" Then
ws.Range(toHide).EntireRow.Hidden = True
End If
End Sub
I can not manage to cleanse my data of the "empty" rows. There is no problem in deleting the "0" but those cells which are empty are not empty but have something like "null strings" in it.
Sub Reinigung()
Application.ScreenUpdating = False
Application.EnableEvents = False
ListeEnde3 = ThisWorkbook.Sheets("input").Cells(Rows.Count, 1).End(xlUp).Row
For Zeile1 = 2 To ListeEnde3
If ThisWorkbook.Sheets("input").Cells(Zeile1, 14) = "0" Or ThisWorkbook.Sheets("2018").Cells(Zeile1, 14) = "" Then
ThisWorkbook.Sheets("input").Rows(Zeile1).Delete
Zeile1 = Zeile1 - 1
Else
End If
Next
' ThisWorkbook.Sheets("import").Columns(14).SpecialCells(xlCellTypeBlanks).EntireRow.Delete
Application.ScreenUpdating = True
Application.EnableEvents = True
End Sub
That code just freezes my excel, if i leave out the
thisWorkbook.Sheets("2018").Cells(Zeile1, 14) = ""
part, it works and deletes all rows, where colum 14 contains a "0".
If I check the cells which appear blank with =isblank it returns "false". There is no "space" in the cell and no " ' ".
What to do?
edit
After the first tips my code looks like this now:
Sub Reinigung()
Dim ListeEnde3 As Long
Dim Zeile1 As Long
Application.ScreenUpdating = False
Application.EnableEvents = False
ListeEnde3 = ThisWorkbook.Sheets("import").Cells(Rows.Count, 1).End(xlUp).Row
For Zeile1 = ListeEnde3 To 2 Step -1
Set rngX = ThisWorkbook.Sheets("import").Cells(Zeile1, 14)
If (rngX = "0" Or rngX = "") Then 'or rngY = vbNullString
ThisWorkbook.Sheets("import").Rows(Zeile1).Delete
End If
Next Zeile1
' ThisWorkbook.Sheets("import").Columns(14).SpecialCells(xlCellTypeBlanks).EntireRow.Delete
Application.ScreenUpdating = True
Application.EnableEvents = True
End Sub
Excel still crashes / freezes (I waited for 5 minutes) but since the code runs "smoothly" with F8 I wanted to give it a shot with less data: It works!
If I am not reducing the data there are ~ 70000 rows to check. I let it run on 720 rows and it worked.
Any way to tweak the code in a way that it can handle the 70000+ rows? I didn't think that it would be too much.
Thanks!
You can use AutoFilter and delete the visible rows (not tested) :
Dim ws As Worksheet: Set ws = ThisWorkbook.Worksheets("import")
ws.UsedRange.AutoFilter 14, Array("=0", "="), xlFilterValues
ws.UsedRange.Offset(1).EntireRow.Delete
ws.AutoFilterMode = False
Another way is to simply use internal arrays and write out the new data set which has valid rows.
It is very fast.
If your dataset has formulas then you'll have to use extra code, but if it's constants only, then the below should do:
Sub Reinigung()
'Here I test with column E to Z, set Ranges appropriately
Application.ScreenUpdating = False
Application.EnableEvents = False
Dim ListeEnde3 As Long, x As Long, y As Long
'last row of data - set to column of non-blank data
ListeEnde3 = ThisWorkbook.Sheets("import").Cells(Rows.Count, 5).End(xlUp).Row
Dim ws As Worksheet
Set ws = ThisWorkbook.Sheets("import")
Dim startCell As Range
'set to whatever cell is the upper left corner of data
Set startCell = ThisWorkbook.Sheets("import").Range("E1")
Dim arr As Variant, arrToPrint() As Variant
'Get rightmost column of data instead of hardcoding to "Z"
'write dataset into an array
arr = ws.Range(startCell, ws.Range("Z" & ListeEnde3)).Value
x = UBound(arr) - LBound(arr) + 1 'num of rows of data
y = UBound(arr, 2) - LBound(arr, 2) + 1 'num of columns of data
ReDim arrToPrint(1 To x, 1 To y) 'array to hold valid/undeleted data
Dim i As Long, j As Long, printCounter As Long, arrayColumnToCheck as Long
arrayColumnToCheck = 14 - startCell.Column + 1 '14 is column N
For i = 1 To x
If arr(i, arrayColumnToCheck ) <> 0 And arr(i, arrayColumnToCheck ) <> vbNullString Then
printCounter = printCounter + 1
For j = 1 To y
'put rows to keep in arrToPrint
arrToPrint(printCounter, j) = arr(i, j)
Next j
End If
Next i
'Print valid rows to keep - only values will print - no formulas
startCell.Resize(printCounter, y).Value = arrToPrint
'Delete the rows with zero & empty cells off the sheet
startCell.Offset(printCounter).Resize(ListeEnde3 - printCounter, y).Delete xlShiftUp
Application.ScreenUpdating = True
Application.EnableEvents = True
End Sub
You can add IsEmpty to your code to check the cells filling
Sub Reinigung()
Application.ScreenUpdating = False
Application.EnableEvents = False
ListeEnde3 = ThisWorkbook.Sheets("input").Cells(Rows.Count, 1).End(xlUp).Row
For Zeile1 = 2 To ListeEnde3
Set rngX = ThisWorkbook.Sheets("input").Cells(Zeile1, 14)
Set rngY = ThisWorkbook.Sheets("2018").Cells(Zeile1, 14)
If (rngX = "0" And (Not IsEmpty(rngX))) Or (rngY = "") Then
ThisWorkbook.Sheets("input").Rows(Zeile1).Delete
Zeile1 = Zeile1 - 1
End If
Next
' ThisWorkbook.Sheets("import").Columns(14).SpecialCells(xlCellTypeBlanks).EntireRow.Delete
Application.ScreenUpdating = True
Application.EnableEvents = True
End Sub
NEVER a good idea to alter a loop counter: Zeile1 = Zeile1 - 1
Instead start at the end and use Step -1 in your loop to work backward.
You are in a infinite loop because the loop doesnt move forward. If Zeile=3 and there is a "" in row3 in the '2018' sheet, then it will always be stuck on the Zeile1 = 3 line. You will always be coming back to that "" on row 3 in '2018'sheet.
For Zeile1 = ListeEnde3 To 2 Step -1
Set rngX = ThisWorkbook.Sheets("input").Cells(Zeile1, 14)
Set rngY = ThisWorkbook.Sheets("2018").Cells(Zeile1, 14)
If (rngX = "0" Or rngY = "") Then 'or rngY = vbNullString
ThisWorkbook.Sheets("input").Rows(Zeile1).Delete
End If
Next Zeile1
I've included the base code that currently runs to essentially pull out info for a specific product category based on a larger master listing (approx. 4000 lines by 36 columns). Previously this was not an issue, as the only codes listed and pulled out to individual sheets, were all is use; over time though, some of the older assigned product numbers are being discontinued and no longer in use. All I'm trying to do is modify the existing structure so that it first does a sweep through the master listing to verify whether or not any lines match the c.Value and d.Value - if there are no lines that meet the matching c.Value and d.Value criteria then it should just perform the action in the If statement inside the loop (ie. delete the old sheet, make a new one, and populate "G2" with a generic "item code not located" value); if any lines are found that meet the c and d.value criteria then it goes through the normal process.
Option Explicit
Sub Item()
CreateDeptReport "Item"
End Sub
Sub CreateDeptReport(Item As String)
Dim shtRpt As Excel.Worksheet, shtMaster As Excel.Worksheet, shtPrevious As Excel.Worksheet
Dim LCopyToRow As Long
Dim LCopyToCol As Long
Dim LastRow As Long
Dim arrColsToCopy
Dim c As Range, d As Range, e As Range, x As Integer
On Error GoTo Err_Execute
Application.ScreenUpdating = False
arrColsToCopy = Array(1, 8, 3, 7, 9, 10, 39, 19, 24, 25, 27, 29, 33, 34, 35)
Set shtMaster = ThisWorkbook.Sheets("CurrentMaster")
Set shtPrevious = ThisWorkbook.Sheets("PreviousMaster")
Set c = shtMaster.Range("AI5")
Set d = shtMaster.Range("H5")
Set e = shtMaster.Range("X5")
LCopyToRow = 11
Do
If c.Value = 2516 And d.Value = "37A" And Not e.Value = "T1" And Not e.Value = "T3" Then
If shtRpt Is Nothing Then
On Error Resume Next
Application.DisplayAlerts = False
ThisWorkbook.Sheets("Item").Delete
Application.DisplayAlerts = True
On Error GoTo 0
ThisWorkbook.Sheets("Template").Visible = xlSheetVisible
ThisWorkbook.Sheets("Template").Copy After:=shtPrevious
Set shtRpt = ThisWorkbook.Sheets(shtPrevious.Index + 1)
shtRpt.Name = Item
Range("G2").Value = "Item"
Range("C3").Value = Date
ThisWorkbook.Sheets("Template").Visible = xlSheetVeryHidden
End If
LCopyToCol = 1
shtRpt.Cells(LCopyToRow, LCopyToCol).EntireRow.Insert Shift:=xlDown
For x = LBound(arrColsToCopy) To UBound(arrColsToCopy)
shtRpt.Cells(LCopyToRow, LCopyToCol).Value = c.EntireRow.Cells(arrColsToCopy(x)).Value
LCopyToCol = LCopyToCol + 1
Next x
LCopyToRow = LCopyToRow + 1
End If
Set c = c.Offset(1, 0)
Set d = d.Offset(1, 0)
Set e = e.Offset(1, 0)
Loop Until IsEmpty(c.Offset(0, -1))
ThisWorkbook.Worksheets("Item").Rows("10:10").Delete
LastRow = Cells(Rows.Count, "A").End(xlUp).Row + 1
If LastRow <> 0 Then
Rows(LastRow).EntireRow.Delete
End If
Range("A9").Select
Application.ScreenUpdating = True
Exit Sub
Err_Execute:
MsgBox "An error occurred."
End Sub
It seems to me that you always want a new Worksheet for the Item.
So create the new worksheet first, then run the routine to find and fill the new worksheet with the records from the Master worksheet and use a variable (Dim blItmFound As Boolean) to flag when any record is found and at the end if there where no records found then enter in the new worksheet at G2 the generic string you want (see Rem Validate Records).
Please note that I changed "Item" for the value of the Variable Item and also changed this line:
Loop Until IsEmpty(c.Offset(0, -1))
for this:
Loop Until c.Value = Empty
for more details see IsEmpty Function
This is your code adjusted:
Sub CreateDeptReport(Item As String)
Dim shtRpt As Excel.Worksheet, shtMaster As Excel.Worksheet, shtPrevious As Excel.Worksheet
Dim LCopyToRow As Long
Dim LCopyToCol As Long
Dim LastRow As Long
Dim arrColsToCopy
Dim c As Range, d As Range, e As Range, x As Integer
Dim blItmFound As Boolean
arrColsToCopy = Array(1, 8, 3, 7, 9, 10, 39, 19, 24, 25, 27, 29, 33, 34, 35)
Application.ScreenUpdating = False
Set shtMaster = ThisWorkbook.Sheets("CurrentMaster")
Set shtPrevious = ThisWorkbook.Sheets("PreviousMaster")
Set c = shtMaster.Range("AI5")
Set d = shtMaster.Range("H5")
Set e = shtMaster.Range("X5")
Rem Delete Item Worksheet
On Error Resume Next
Application.DisplayAlerts = False
ThisWorkbook.Sheets(Item).Delete
Application.DisplayAlerts = True
On Error GoTo Err_Execute
Rem Add New Item Worksheet
ThisWorkbook.Sheets("Template").Visible = xlSheetVisible
ThisWorkbook.Sheets("Template").Copy After:=shtPrevious
Set shtRpt = ThisWorkbook.Sheets(shtPrevious.Index + 1)
shtRpt.Name = Item
Range("G2").Value = Item
Range("C3").Value = Date
ThisWorkbook.Sheets("Template").Visible = xlSheetVeryHidden
Rem Get Records from Master
LCopyToRow = 11
blItmFound = False
Do
If c.Value = 2516 _
And d.Value = "37A" _
And Not e.Value = "T1" _
And Not e.Value = "T3" Then
blItmFound = True
LCopyToCol = 1
shtRpt.Cells(LCopyToRow, LCopyToCol).EntireRow.Insert Shift:=xlDown
For x = LBound(arrColsToCopy) To UBound(arrColsToCopy)
shtRpt.Cells(LCopyToRow, LCopyToCol).Value = c.EntireRow.Cells(arrColsToCopy(x)).Value
LCopyToCol = LCopyToCol + 1
Next x
LCopyToRow = LCopyToRow + 1
End If
Set c = c.Offset(1, 0)
Set d = d.Offset(1, 0)
Set e = e.Offset(1, 0)
Loop Until c.Value = Empty
Rem Validate Records
Select Case blItmFound
Case True
ThisWorkbook.Worksheets(Item).Rows("10:10").Delete
LastRow = Cells(Rows.Count, "A").End(xlUp).Row + 1
If LastRow <> 0 Then
Rows(LastRow).EntireRow.Delete
End If
Case False
ThisWorkbook.Worksheets(Item).Range("G2").Value = "Item: [" & Item & "] code not located"
End Select
Range("A9").Select
Application.ScreenUpdating = True
Exit Sub
Err_Execute:
MsgBox "An error occurred."
End Sub
Based on what I've read, it sounds like you should just search for the values in their respective columns beforehand. This is also assuming that if one of those conditions is false, you'll enter your new code. So you could do something like:
Set cRange = shtMaster.Columns("AI:AI")
Set dRange = shtMaster.Columns("H:H")
If cRange.Find(2516) Is Nothing Or dRange.Find("37A") Is Nothing Then
'do code when either one of these conditions is false
Else
'both values are found in their respective columns
'do existing code
EDIT:
Set rng = Range("AI:AI")
Set origCell = rng.Find(2516)
Set currCell = origCell
Do
Set currCell = rng.FindNext(currCell)
If shtMaster.Range("H" & currCell.Row).Value = "37A" Then
boolMatchingPair = True
Exit Do
End If
Loop While currCell.Row <> origCell.Row
If boolMatchingPair = True
'found match
Else
'no match
I apologize if already exist a similar question, but if yes, I not found.
I'm new to programming in VBA and still do not know much of it, now I'm trying to run a function that will verify if in a column "B" are repeated velores and if exist will check in a column "C" where the highest value, copying the lowest to another table and deleting it.
The code already does all this however need to run in tables of 65 000 lines and it takes a long time, never got for running these tables, because even when I run in tables with 5000 or 10000 lines takes approximately 6 to 15 minutes.
My question is if there is any way to optimize the cycle that I'm using, it will be better to use a For Each or maintain the Do While Loop?
Here is the code I am using:
Function Copy()
Worksheets("Sheet1").Range("A1:AQ1").Copy _
Destination:=Worksheets("Sheet2").Range("A1")
Dim lRow As Long
Dim lRow2 As Long
Dim Row As Long
Dim countA As Long
Dim countB As Long
Dim t As Double
lRow = 5000
Row = 2
countA = 0
countB = 0
Application.ScreenUpdating = False
ViewMode = ActiveWindow.View
ActiveWindow.View = xlNormalView
Application.EnableEvents = False
Application.DisplayStatusBar = False
ActiveSheet.DisplayPageBreaks = False
lRow2 = lRow - 1
t = Timer
Do While lRow > 2
If (Cells.Item(lRow, "B") <> Cells.Item(lRow2, "B")) Then
lRow = lRow - 1
lRow2 = lRow - 1
Else
If (Cells.Item(lRow, "C") > Cells.Item(lRow2, "C")) Then
Sheets("Sheet1").Rows(lRow2).Copy Sheets("Sheet2").Rows(Row)
Rows(lRow2).Delete
lRow = lRow - 1
Row = Row + 1
countA = countA + 1
Else
Sheets("Sheet1").Rows(lRow).Copy Sheets("Sheet2").Rows(Row)
Rows(lRow).Delete
lRow = lRow - 1
Row = Row + 1
countB = countB + 1
End If
lRow2 = lRow2 - 1
End If
Loop
Application.DisplayStatusBar = True
ActiveWindow.View = ViewMode
Application.ScreenUpdating = False
MsgBox "A = " & countA & " B = " & countB & "Time (minutes): " & (Timer - t) / 60
End Function
As long as you've entered the VBA environment for a solution, there seems little point in not continuing that avenue toward the best route possible. The following uses a pair of Scripting.Dictionaries to build two sets of data from the original matrix in Sheet1. In addition to the main sub procedure, there are two short 'helper' functions that breach the 65536 barrier that Application.Index and Application.Transpose suffer from. These are necessary to peel out a row from a large two-dimensioned array and flip the orientation of the results while simultaneously splitting the stored records.
Sub Keep_Highest_BC()
Dim d As Long, dHIGHs As Object, dDUPEs As Object
Dim v As Long, vTMPs() As Variant, iCOLs As Long
Debug.Print Timer
'On Error GoTo bm_Safe_Exit
Set dHIGHs = CreateObject("Scripting.Dictionary")
Set dDUPEs = CreateObject("Scripting.Dictionary")
With Worksheets("Sheet1")
iCOLs = .Columns("AQ").Column
.Cells(1, 1).Resize(2, iCOLs).Copy _
Destination:=Worksheets("Sheet2").Cells(1, 1)
With .Cells(2, 1).Resize(.Cells(Rows.Count, 2).End(xlUp).Row - 1, iCOLs)
vTMPs = .Value2
End With
End With
For v = LBound(vTMPs, 1) To UBound(vTMPs, 1)
If dHIGHs.exists(vTMPs(v, 2)) Then
If CDbl(Split(dHIGHs.Item(vTMPs(v, 2)), ChrW(8203))(2)) < vTMPs(v, 3) Then
dDUPEs.Add Key:=vTMPs(v, 2) & v, Item:=dHIGHs.Item(vTMPs(v, 2))
dHIGHs.Item(vTMPs(v, 2)) = joinAtoAQ(vTMPs, v)
Else
dDUPEs.Add Key:=vTMPs(v, 2) & v, Item:=joinAtoAQ(vTMPs, v)
End If
Else
dHIGHs.Add Key:=vTMPs(v, 2), Item:=joinAtoAQ(vTMPs, v)
End If
Next v
With Worksheets("Sheet1")
With .Cells(2, 1).Resize(.Cells(Rows.Count, 2).End(xlUp).Row - 1, iCOLs)
.ClearContents
With .Resize(dHIGHs.Count, iCOLs)
.Value = transposeSplitLargeItemArray(dHIGHs.items)
End With
End With
End With
With Worksheets("Sheet2")
With .Cells(1, 1).CurrentRegion.Offset(1, 0)
.ClearContents
With .Resize(dDUPEs.Count, iCOLs)
.Value = transposeSplitLargeItemArray(dDUPEs.items)
.Rows(1).Copy
.PasteSpecial Paste:=xlPasteFormats
Application.CutCopyMode = False
End With
End With
End With
bm_Safe_Exit:
dHIGHs.RemoveAll: Set dHIGHs = Nothing
dDUPEs.RemoveAll: Set dDUPEs = Nothing
Debug.Print Timer
End Sub
Function joinAtoAQ(vTMP As Variant, ndx As Long)
Dim sTMP As String, v As Long
For v = LBound(vTMP, 2) To UBound(vTMP, 2)
sTMP = sTMP & vTMP(ndx, v) & ChrW(8203)
Next v
joinAtoAQ = Left$(sTMP, Len(sTMP) - 1)
End Function
Function transposeSplitLargeItemArray(vITMs As Variant)
Dim v As Long, w As Long, vTMPs As Variant, vITM As Variant
ReDim vTMPs(LBound(vITMs) To UBound(vITMs), LBound(vITMs) To UBound(Split(vITMs(LBound(vITMs)), ChrW(8203))))
For v = LBound(vITMs) To UBound(vITMs)
vITM = Split(vITMs(v), ChrW(8203))
For w = LBound(vITM) To UBound(vITM)
vTMPs(v, w) = vITM(w)
Next w
Next v
transposeSplitLargeItemArray = vTMPs
End Function
Once the two dictionaries have been filled with maximum values and duplicate lesser values, the arrays are returned to the two worksheets en masse and subsequently split back into the 43 columns. One final effort is made to restore the original formatting from Sheet1 into Sheet2's data area.
I tested this on 75,000 rows of columns A through column AQ containing random sample data first with predominantly duplicate values in column B and then with roughly half duplicate values in column B. The first single pass was processed in 13.19 seconds; the second in 14.22. While your own results will depend on the machine you are running it on, I would expect a significant improvement over your original code. Post your own timed results (start and stop in seconds within the VBE's Immediate window, Ctrl+G) into the comments if you can.
Everything i could think of has already been mentioned above, however this code snippet might help someone out, it's the least you could do to make a macro faster (in case no interaction is required during runtime of the macro)
Run Optimize(True) at the start of your code, Optimize(False) at the end.
'Toggles unnecessary excel features
Sub Optimize(start As Boolean)
On Error Resume Next
With Application
.ScreenUpdating = Not (start)
.DisplayStatusBar = Not (start)
.EnableEvents = Not (start)
If start Then
.Calculation = xlCalculationManual
Else
.Calculation = xlCalculationAutomatic
End If
End With
On Error GoTo 0
End Sub
Typically it's faster to perform a single delete at the end of the loop.
Untested:
Function Copy()
Dim shtSrc As Worksheet, shtDest As Worksheet
Dim lRow As Long, Row As Long, viewmode
Dim countA As Long, countB As Long
Dim t As Double, rw As Range, rngDel As Range
lRow = 5000
Row = 2
countA = 0
countB = 0
Set shtSrc = Worksheets("Sheet1")
Set shtDest = Worksheets("Sheet2")
shtSrc.Range("A1:AQ1").Copy Destination:=shtDest.Range("A1")
Application.ScreenUpdating = False
viewmode = ActiveWindow.View
ActiveWindow.View = xlNormalView
Application.EnableEvents = False
Application.DisplayStatusBar = False
ActiveSheet.DisplayPageBreaks = False
t = Timer
Do While lRow > 2
Set rw = shtSrc.Rows(lRow)
If (rw.Cells(2) = rw.Cells(2).Offset(-1, 0)) Then
If (rw.Cells(3) > rw.Cells(3).Offset(-1, 0)) Then
rw.Offset(-1, 0).Copy shtDest.Rows(Row)
AddToRange rngDel, rw.Offset(-1, 0)
countA = countA + 1
Else
rw.Copy shtDest.Rows(Row)
AddToRange rngDel, rw
countB = countB + 1
End If
Row = Row + 1
End If
lRow = lRow - 1
Loop
'anything to delete?
If Not rngDel Is Nothing Then
rngDel.Delete
End If
Application.DisplayStatusBar = True
ActiveWindow.View = viewmode
Application.ScreenUpdating = False
MsgBox "A = " & countA & " B = " & countB & "Time (minutes): " & (Timer - t) / 60
End Function
'utility sub for building up a range
Sub AddToRange(rngTot, rng)
If rngTot Is Nothing Then
Set rngTot = rng
Else
Set rngTot = Application.Union(rng, rngTot)
End If
End Sub