Excel VBA FOR LOOP Crashing on 1000th Time - excel

I am trying to use some VBA code to copy a range of cells and paste its values in the next empty rows 2111 times.
This pastes successfully up to the 754507 row where after this it crashes.
I can see in the debug that it stops at the 1000th loop.
Option Explicit
Sub Paste_APIROWS()
Application.ScreenUpdating = False
Dim i As Long
Range("A2:H754").Copy
For i = 1 To 2111
Range("A2:H754" & i).PasteSpecial Paste:=xlPasteValues
Debug.Print i
Next i
Application.CutCopyMode = False
End Sub
I expect in the end to have about 1589583 rows but instead appear to be only getting about half of this.
The error message I get is "Run-time error '1004': Method 'Range' of object'_Global' failed"
Any advice would be greatly appreciated.
Many Thanks.

Run the loop in your head:
When i = 1, then the range is "A2:H7541" (Rows 2 through 7,541)
When i = 2, then the range is "A2:H7542" (Rows 2 through 7,542)
When i = 9, then the range is "A2:H7549" (Rows 2 through 7,549)
When i = 10, then the range is "A2:H75410" (Rows 2 through 75,410)
When i = 99, then the range is "A2:H75499" (Rows 2 through 75,499)
When i = 100, then the range is "A2:H754100" (Rows 2 through 754,100)
When i = 900, then the range is "A2:H754900" (Rows 2 through 754,900)
When i = 999, then the range is "A2:H754999" (Rows 2 through 754,999)
When i = 1000, then the range is "A2:H7541000" (Rows 2 through 7,541,000)
Notice as each value of i crosses each 10th power the row number increases by an order of magnitude:
From i = 9 to i = 10 you go from row 7,549 to 75,410
From i = 99 to i = 100 you go from row 75,499 to 754,100
From i = 999 to i = 1000 you go from row 754,100 to 7,541,000
Also note that your destination range row is always 2 - so on each iteration you're always overwriting yourself.
It crashes because Excel spreadsheets (since Excel 2007) cannot exceed 1,048,576 rows, hence the crash. The limit is 65,355 prior to Excel 2007 or when using a non-OOXML spreadsheet in modern versions of Excel).
I expect in the end to have about 1,589,583 rows but instead appear to be only getting about half of this.
Two things:
Excel does not support 1,589,583 rows anyway (as said, the maximum is 1,048,576).
Your logic does not compute copy destination ranges correctly, as per my explanation above.
The cause of your bug is the use of string concatenation (i.e. the & operator) instead of numerical addition.
You want to copy cells in the range A2:H754 some 2111 1930 times - that means you actually want to do this:
Const sourceRowLB = 2
Const sourceRowUB = 755 ' 755 not 754 because exclusive upper-bounds are easier to work with
Dim sourceRowCount = sourceRowUB - sourceRowLB
Dim lastCopyUB = 755
Dim sourceRangeExpr = GetRangeExpr( "A", sourceRowLB, "H", sourceRowUB ) ' Will be "A2:H754"
Range( sourceRangeExpr ).Copy
Const loopCount As Integer = 1389 ' This cannot be 2111 because ( 2111 * 754 ) exceeds the maximum row count
For i = 1 ToloopCount ' Loop 1389 times
' Recompute the destination range:
Dim destRowLB As Integer
destRowLB = lastCopyUB
Dim destRowUB As Integer
destRowUB = destRowLB + sourceRowCount
Dim rangeExpression As String
rangeExpression = GetRangeExpr( "A", destRowLB, "H" & destRowUB )
Range( rangeExpression ).PasteSpecial Paste:=xlPasteValues
lastCopyUB = destRowUB
Next i
Function GetRangeExpr(startCol As String, startRow As Integer, endCol As String, endRowExclUB As Integer) As String
GetRangeExpr = startCol & CStr( destRowLB ) & ":" & endCol & CStr( endRowExclUB - 1 ) ' We convert endRowExclUB to an inclusive upper-bound here
End Function

Here are some hints:
There is no need to do string math like Range("A2:H754" & i). A better solution is starting from the top left cell use .Cells(row, column) method to access a specific cell.
Expand a cell into a table using the .Resize(row_count, column_count) method.
Finally, there is no need to use the clipboard with the .Copy or .Paste methods are this is slow and memory intensive. Use direct assignment into the .Value property.
For example, to copy the 178th row from a table of 1000×8 cells located under A2 into the first row of the sheet, use the following
Range("A1").Resize(1,8).Value = Range("A2").Cells(178,1).Resize(1,8).Value
Note that the .Resize() values much match on both sides of the assignment.

Related

Count and CountIf Confusion VBA

I have a for loop that iterates through each sheet in my workbook. Each sheet is identical for most purposes. I am making an overview sheet in my workbook to express the results from the data in the other sheets(the ones that are identical).
There are 11 vehicles, each with their own sheet that has data from a test from each day. On any given day there can be no tests, or all the way to 30,000 tests. The header of each column in row 47 states the date in a "06/01/2021 ... 06/30/2021" format. The data from each iteration of the test will be pasted in the column of the correct date starting at row 49.
So what my overview page needs to display is the data from the previous day. In one cell on my overview there is a formula for obtaining just the number of the day of the month like 20 or 1 etc. Using this number, the number of the day is the same as the column index that the previous day's data will be in conveniently. So in my for loop I want to have a table that has the name of each sheet in column B, in column C I want to display the total number of tests done in that day(not the sum of all the data), in column D I need the number of tests with a result of 0, in column E I need the number of tests that have a result above the upper tolerance, and in column F I need the number of tests that have a result below the lower tolerance minus the result in column D.
I've been playing with the Application.WorksheetFunction.Count and CountIf functions but I keep getting 0 for every single cell. I made two arrays for the upper and lower tolerance values which are type Longs called UTol and LTol respectively. TabList is a public array that has each of the sheet names for the vehicles stored as strings. finddate is an integer that reads in Today's day number which is yesterday's column index. I've included a picture of the table in question and my for loop code:
finddate = Worksheets("Overview").Range("A18").Value
For TabNRow = 1 To 11
startcol = 3
Worksheets(TabList(TabNRow)).Activate
Debug.Print (TabList(TabNRow))
'Total number of tests done that day
Worksheets("Overview").Cells(startrow, startcol).Value = Application.WorksheetFunction.Count(Range(Cells(49, finddate), Cells(30000, finddate)))
startcol = 4
'Total number of 0 results, this used to get the number of 0's from each sheet but that row has since been deleted
Worksheets("Overview").Cells(startrow, startcol).Value = Worksheets(TabList(TabNRow)).Cells(48, finddate).Value
startcol = 5
'Total number of results above tolerance
Worksheets("Overview").Cells(startrow, startcol).Value = Application.WorksheetFunction.CountIf(Range(Cells(49, finddate), Cells(30000, finddate)), ">" & UTol(TabNRow))
startcol = 6
'Total number of results below tolerance
Worksheets("Overview").Cells(startrow, startcol).Value = Application.WorksheetFunction.CountIf(Range(Cells(49, finddate), Cells(30000, finddate)), "<" & LTol(TabNRow))
startrow = startrow + 1
Next TabNRow
```
No need to select/activate sheets when referencing them. See: How to avoid using Select in Excel VBA
Something like this should work:
Dim wsOv As Worksheet, wsData As Worksheet, rngData As Range
Set wsOv = ThisWorkbook.Worksheets("Overview")
finddate = wsOv.Range("A18").Value
For TabNRow = 1 To 11
Set wsData = ThisWorkbook.Worksheets(TabList(TabNRow))
Set rngData = wsData.Range(wsData.Cells(49, finddate), _
wsData.Cells(Rows.Count, finddate).End(xlUp))
Debug.Print wsData.Name, rngData.Address 'edited
With wsOv.Rows(2 + TabNRow)
.Columns("C").Value = Application.Count(rngData)
.Columns("D").Value = Application.CountIf(rngData, 0)
.Columns("E").Value = Application.CountIf(rngData, ">" & UTol(TabNRow))
.Columns("F").Value = Application.CountIf(rngData, "<" & LTol(TabNRow)) _
- .Columns("D").Value
End With
Next TabNRow

Excel VBA Sum up/Add values together, even those with commas/decimal

I have a problem where I can't add numbers with decimals. Only numbers with no decimals.
I have written a code to sum up values from different cells. This work fine as long as the numbers are without decimals.
Here is my code:
Sub SumValues()
'This makro is made to add values together depending on
'x amount of Sheets in the workbook:
Application.ScreenUpdating = False
'A will sum up the values from each cell,
'depending on the amount of Sheets in the this Workbook:
A = 0
For I = 1 To ThisWorkbook.Sheets.Count
'Adding the values from cell E5 to Cells(5, 5 + (I - 1) * 3),
'with the distance 3 between each cell:
A = A + Cells(5, 5 + (I - 1) * 3)
Next I
'The values A that is added togheter from the cells, is now put in a cell:
Worksheets("Sheet1").Cells(1, 1).Formula = "=" & A & ""
Application.ScreenUpdating = True
End Sub
So for 3 number of sheets, "I" goes from 1 to 3.
So if my cells contain these numbers:
Cell(5,5) = 2
Cell(5,8) = 3
Cell(5,11) = 8
I get the sum in Cell(1,1) = 13
But if I have these values:
Cell(5,5) = 2,2
Cell(5,8) = 3
Cell(5,11) = 8
I get the "run-time error '9': Subscript out of range" Error message when running script.
Any suggestions?
Another question is if it is possible to get the formula into the cell I am adding up the values?
For Examlpe if I have 3 Sheets in my Workbook, it will sum up the values from Cell(5,5) , Cell(5,8) and Cell(5,11).
The sum is shown in Cell(1,1).
But all I get is the number, not the formula.
Is it possible to make the Cell show the formula "=E5+H5+K5"?
This last question might be a "fix" for the first question, if it is the separator "," that is making trouble, maybe?
Thanks
GingerBoy
Tested and working fine
Declare your variables
You need to qualify your objects with a worksheet
No need to toggle off Screen Updating here. You are just modifying one cell
This code will place the Value in A1 and the Formula in B1
Disclaimer:
Your code, and the code below, is subject to a potential Type Mismatch Error if you feed any cell with a non-numerical value into your loop. If there is some chance of any non-numerical cell being in the sum range, you can avoid the error by nesting something like the following inside your loop: If ISNUMERIC(Range) Then.
Sub SumValues()
Dim ws As Worksheet: Set ws = ThisWorkbook.Sheets("Sheet1")
Dim A As Double, i As Long, Loc As String
For i = 1 To ThisWorkbook.Sheets.Count
A = A + ws.Cells(5, (5 + (i - 1) * 3))
Loc = ws.Cells(5, (5 + (i - 1) * 3)).Address(False, False) & "+" & Loc
Next i
ws.Range("A1") = A
ws.Range("B1").Formula = "=" & Mid(Loc, 1, Len(Loc) - 1)
End Sub

In Excel VBA, extract range text and sum data

I have a spreadsheet in which there are multiple rows that have three columns (K, L M) that contain text (inserted manually from a dropdown). The inserted text includes a 'score'. For the row shown in the image that score is 3 + 2 + 2 = 7.
What I'd like to be able to do is to have that score automatically calculated and shown in column N. I'm happy to do the score extraction given the text, but I'm completey unfamiliar with Excel's object model, and how to write a VBA macro that can be triggered across all of the rows. I assume it would be passed a range somehow, or a string designating a range, but how to do that is beyond me just now. Perhaps I just need a formula? But one that calls a function to strip non-numerical data from the cell?
Any help appreciated.
Put this formula in N2 cell and drag it all the way down.
=LEFT(K2, FIND("-", K2) - 2) + LEFT(L2, FIND("-", L2) - 2) + LEFT(M2, FIND("-", M2) - 2)
For more information see reference. It sum all numbers, that are present before the hyphen (-) in a cell.
Try:
N2 = LEFT(TRIM(K2),1) + LEFT(TRIM(L2),1) + LEFT(TRIM(M2),1)
As I said in comments, this solution does not scale so well if it is more than three columns and / or the scores are more than single digit [0-9]
A VBA solution to do all of your rows and enter the values into Column N:
Sub foo()
Dim ws As Worksheet: Set ws = Sheets("Sheet1")
'declare and set your worksheet, amend as required
LastRow = ws.Cells(ws.Rows.Count, "K").End(xlUp).Row
'get the last row with data on Column A
For rownumber = 1 To LastRow 'loop through rows
For i = 11 To 13 'loop through columns
strValue = ws.Cells(rownumber, i).Value 'get text string from cell
pos = InStr(strValue, " -") 'find the dash - in cell
If pos > 0 Then 'if dash found
Value = Value + Val(Left(ws.Cells(rownumber, i).Value, pos - 1)) 'remove everything after number
End If
Next i
ws.Cells(rownumber, 14).Value = Value 'write value to column N
Value = 0
Next rownumber
End Sub

Excel Split Single Column Into Multiple Columns Based on Header Rows

I have an excel file that contains data exported from LTSpice simulations. There are 280 different runs however the data is exported as two columns (time and voltage) with a run cell at the start of a new run. The number of data points in each run varies. Looks something like this:
Run 1/280
Time1 Voltage1
Time2 Voltage2
Run 2/280
Time1 Voltage1
Time2 Voltage2
Time3 Voltage3
Run 3/280
I would like to have the run cells as row and the time and voltage columns beneath them.
Run 1/280 Run 2/280 Run 3/280
Time1 Voltage1 Time1 Voltage1
Time2 Voltage2 Time2 Voltage2
Time3 Voltage3
I haven't found an easy way to do this yet, so any help would be appreciated.
Thanks
Without VBA...
For each row of your input list, you need to identify its type (Run x/xxx header or terminal, voltage pair) and the row and pair of columns in the output where this input row belongs.
In the picture below, columns A and B perform this task. Column A identifies the output column pair and B the output row, where row 0 indicates the header row of the output.
The header row of the output utilises the fact that if an array is sorted in ascending order and has repeated values then MATCH(x,array,0) finds the index of the first element in array equal to x. The cumbersome repetition of the SUMPRODUCT term in the formulae for the other rows is necesssary for the following reason. If there is no matching pair in columns A and B to the current output row and column pair number then the SUMPRODUCT delivers 0 and, unfortunately, the INDEX(array,SUMPRODUCT()) term evaluates to INDEX(array,0) which delivers the first element of array (*) - which is not what is wanted.
You obviously need sufficient helper values in row 1 and column E of the worksheet in the output area - the maximum values of columns B and A, respectively determine the requirements. Oversizing the output (as the picture has done) is not a problem as the formulae in any redundant positions simply evaluate to "".
(*) In fact, for a single column array the formula =INDEX(array,0) evaluates to the array itself. When used as cell formula (rather than being used as an array formula across a range of cells) formula simply picks the first value from array.
Please try this code.
Sub SplitToColumns()
' 16 Sep 2017
Dim WsS As Worksheet ' S = "Source"
Dim WsD As Worksheet ' D = "Destination"
Dim WsDName As String
Dim RunId As String ' first word in "Run 1/280"
Dim RowId As Variant ' value in WsS.Column(A)
Dim Rl As Long ' last row (WsS)
Dim Rs As Long, Rd As Long ' row numbers
Dim Cd As Long ' column (WsD)
WsDName = "RemoteMan" ' change to a valid tab name
Application.ScreenUpdating = False
On Error Resume Next
Set WsD = Worksheets(WsDName)
If Err Then
' create WsD if it doesn't exist:
Set WsD = Worksheets.Add(After:=Worksheets(Worksheets.Count))
WsD.Name = WsDName
Cd = -1
Else
' continue adding new data to the right of existing,
With WsD.UsedRange
Cd = .Columns.Count - 1
If Cd = 1 And .Rows.Count = 1 Then Cd = -1
End With
End If
Set WsS = Worksheets("Remote") ' change to a valid tab name
With WsS
' presume "Run" & Time in column A, Voltage in Column B
' presume: no blank rows
Rl = .Cells(Rows.Count, "A").End(xlUp).Row
RunId = .Cells(2, 1).Value ' row 2 must have the RunId
RunId = Left(RunId, InStr(RunId, " ") - 1)
For Rs = 2 To Rl ' assume data start in row 2 (A1 may not be blank!)
RowId = .Cells(Rs, "A").Value
If InStr(1, RowId, RunId, vbTextCompare) = 1 Then
Rd = 1 ' first row to use in WsD
Cd = Cd + 2 ' determine next columns
End If
WsD.Cells(Rd, Cd).Value = RowId
WsD.Cells(Rd, Cd + 1).Value = .Cells(Rs, "B").Value
Rd = Rd + 1 ' next row to use
Next Rs
End With
Application.ScreenUpdating = True
End Sub

Why does Excel take so long to calculate and producing inaccurate results?

I have a spreadsheet, BO2009, that is 300k rows long. Only one column contains a formula The others are all pasted values so only one formula needs to be calculated in the entire workbook. Here is the formula: =IFERROR(INDEX('RE2009'!H:H,MATCH('BO2009'!A2,'RE2009'!A:A,0)),1) This formula is copied down to the bottom of the sheet, so 300k times.
RE2009 sheet has 180k rows. 'RE2009'!H:H contains decimal numbers and 'RE2009'!A:A, 'BO2009'!A:A contain ID codes--an 8 character combination of numbers and letters. Both 'RE2009'!A:A, 'BO2009'!A:A are formatted as general.
I use INDEX/MATCH all the time and while most of my spreadsheets are not 300k long, 60k-100k is typical. Right now it takes a couple minutes of my CPU devoting 99% to Excel in order to finish the calculation.
Is that normal? Is there any way to improve Excel's performance?
On top of that I am getting inaccurate results: instead of 0.3 the lookup produces an error.
As suggested, I have filtered the BO2009 sheet down to 80k rows, but still have the same issues. I decided to look at a single formula in particular: =IFERROR(INDEX('RE2009'!H:H,MATCH('BO2009'!A108661,'RE2009'!A:A,0)),1) to see if it worked correctly. The ID that it is looking for with the MATCH function is the 3rd entry in the lookup array, but it still isn't able to produce the correct value (0.3)
It seems that you've found a satisfactory solution to your problem(s) but as a matter of curiosity, you may wish to time this against your current formula based solution to see if there is a measurable increase in speed.
Sub index_match_mem()
Dim v As Long, vVALs As Variant, vTMP As Variant
Dim dRE2009 As Object
Debug.Print Timer
Application.ScreenUpdating = False
With Worksheets("RE2009")
With .Cells(1, 1).CurrentRegion
With .Resize(.Rows.Count, 8)
vTMP = .Cells.Value2
End With
End With
End With
Set dRE2009 = CreateObject("Scripting.Dictionary")
dRE2009.CompareMode = vbTextCompare
For v = LBound(vTMP, 1) To UBound(vTMP, 1)
If Not dRE2009.exists(vTMP(v, 1)) Then _
dRE2009.Add Key:=vTMP(v, 1), Item:=vTMP(v, 8)
Next v
With Worksheets("BO2009")
With .Cells(1, 1).CurrentRegion
With .Resize(.Rows.Count - 1, 2).Offset(1, 0)
vVALs = .Cells.Value2
For v = UBound(vVALs, 1) To LBound(vVALs, 1) Step -1
If dRE2009.exists(vVALs(v, 1)) Then
vVALs(v, 2) = dRE2009.Item(vVALs(v, 1))
Else
vVALs(v, 2) = 1
End If
Next v
.Cells = vVALs
End With
End With
End With
dRE2009.RemoveAll: Set dRE2009 = Nothing
Application.ScreenUpdating = True
Debug.Print Timer
End Sub
This will produce static values in column B of the BO2009 worksheet. The elapsed start and stop in seconds will be in the VBE's Immediate window (Ctrl+G)

Resources