VBA: Speed up macro (for large file)

chosen

New Member
Joined
Oct 3, 2022
Messages
16
Office Version
  1. 2021
Platform
  1. Windows
Disables screen updating and calculation for faster execution.
Sets references to the first and second worksheets in the workbook.
Sets a reference to the currently selected range in the second worksheet.
Loops through each payment in the selected range and finds the corresponding name in the first worksheet.
If the name is found, it distributes the payments to premiums in the first worksheet until the remaining payment is zero or all premiums have been updated.
If the name is not found, it highlights the payment cell in red.
Enables screen updating again once the macro has finished

But the problem is that the macro inserts 500 installments in more than an hour
And I need to enter more than a thousand installments up to 9000 installments

This is the macro
VBA Code:
Sub DistributeInstallments()
    Dim ws1 As Worksheet, ws2 As Worksheet
    Dim nameColumn As Range, premiumColumn As Range
    Dim paymentColumn As Range
    Dim nameCell As Range, paymentCell As Range
    Dim totalInstallments As Double
    Dim currentName As String
    Dim remainingPayment As Double
    Dim amountToApply As Double
    Dim premium As Double
    
    ' Disable screen updating and calculation for faster execution
    Application.ScreenUpdating = False
    Application.Calculation = xlCalculationManual
    
    ' Set references to the first and second sheets
    Set ws1 = ThisWorkbook.Sheets("Sheet1") ' Update "Sheet1" with the name of your first sheet
    Set ws2 = ThisWorkbook.Sheets("Sheet2") ' Update "Sheet2" with the name of your second sheet
    
    ' Set reference to the currently selected range in the second sheet
    Set paymentColumn = Selection
    
    ' Loop through each payment in the selected range
    For Each paymentCell In paymentColumn
        ' Get the corresponding name in the first sheet
        currentName = paymentCell.Value
        
        ' Find the corresponding name in the name column of the first sheet
        Set nameCell = ws1.Columns("Q:Q").Find(What:=currentName, LookIn:=xlValues, LookAt:=xlWhole)
        
        ' If the name is found, distribute the payments to premiums in the first sheet
        If Not nameCell Is Nothing Then
            ' Get the total installments for the current name
            totalInstallments = WorksheetFunction.SumIf(ws1.Range("Q:Q"), currentName, ws1.Range("R:R"))
            
            ' Get the remaining payment from the abbreviation
            remainingPayment = paymentCell.Offset(0, 1).Value
            
            ' Distribute the remaining payment to premiums in the first sheet
            Do While remainingPayment > 0
                ' Get the next premium for the current name
                premium = ws1.Cells(nameCell.Row, "R").Value
                
                ' Check if premium in column AE is less than premium in column R
                If ws1.Cells(nameCell.Row, "AE").Value < premium Then
                    premium = ws1.Cells(nameCell.Row, "AE").Value
                End If
                
                ' Calculate the amount to be applied to the current premium
                amountToApply = WorksheetFunction.Min(premium, remainingPayment)
                
                ' Subtract the amount applied from the remaining payment
                remainingPayment = remainingPayment - amountToApply
                
                ' Update the premium in the first sheet
                ws1.Cells(nameCell.Row, "AC").Value = ws1.Cells(nameCell.Row, "AC").Value + amountToApply
                
                ' Move to the next row in the first sheet
                Set nameCell = ws1.Columns("Q:Q").FindNext(nameCell)
                
                ' Exit the loop if all premiums have been updated
                If nameCell Is Nothing Then Exit Do
            Loop
        Else
            ' Highlight the name not found in red
            paymentCell.Interior.Color = RGB(255, 0, 0) ' Red color
        End If
    Next paymentCell
    
    ' Re-enable screen updating and calculation
    Application.ScreenUpdating = True
End Sub


Is there a solution to make the macro insert 1000 installments in less than 30 minutes?
 

Excel Facts

Select all contiguous cells
Pressing Ctrl+* (asterisk) will select the "current region" - all contiguous cells in all directions.
Probably using whole column references Q:Q and R:R doesn’t help, fixed ranges or use of .usedRange might help
i am not on my pc at the moment so can’t test things
 
Upvote 0
Generally, and when a lot of data is handled, it is convenient to work everything in memory, loading the information in matrix and arrays.

At the end of the 'Sub' what is calculated is carried to the cells (in one go) and that is what gives you efficiency.

Since you did not accompany your query with the data from the workbook and it is not possible to infer what you intend with your calculations, I can only mention these general ideas.
 
Upvote 0
Generally, and when a lot of data is handled, it is convenient to work everything in memory, loading the information in matrix and arrays.

At the end of the 'Sub' what is calculated is carried to the cells (in one go) and that is what gives you efficiency.

Since you did not accompany your query with the data from the workbook and it is not possible to infer what you intend with your calculations, I can only mention these general ideas.
Here is the problem
I couldn't convert this code to the way the matrix works
 
Upvote 0
Prepare a sample workbook with various data on how to work on it.
Upload it to a public and free server (MediaFire or equivalent) and explain what calculations you perform: the important thing is the method and not the amount of data.
 
Upvote 0
Prepare a sample workbook with various data on how to work on it.
Upload it to a public and free server (MediaFire or equivalent) and explain what calculations you perform: the important thing is the method and not the amount of data.
ok
 
Upvote 0
Hi @chosen .
Thanks for posting on MrExcel.​

I show you the macro converted to use a dictionary and arrays. This will certainly make it faster.

I'm honest, I did tests with a small sample of data to get a feel for it and develop the new macro.

Test with a sample of your data so that you can review the results, if the results are correct test with all of your data, the result should be in seconds.

Also, if you prefer to change instead of selecting the data from sheet2 to process, simply write in the macro the cell range of sheet2 to process.
Change this line:​
Set paymentColumn = Selection 'selection​
For this line and adjust the range of the cells​
Set paymentColumn = ws2.Range("B2:B7")

But if you don't prefer then the macro is designed as your macro, select the cells on sheet2 and run the macro.

VBA Code:
Sub DistributeInstallments_v1()
  Dim ws1 As Worksheet, ws2 As Worksheet
  Dim a As Variant, b As Variant, c As Variant, d As Variant, ky As Variant
  Dim dic As Object
  Dim i As Long, j As Long, y As Long
  Dim nRow As Long, nCol As Long, filA As Long, ini As Long, col As Long
  Dim rng As Range, paymentColumn As Range
  Dim kys As String
  Dim remainingPayment As Double, amountToApply As Double, premium As Double
  
  Set ws1 = ThisWorkbook.Sheets("Sheet1") ' Update "Sheet1" with the name of your first sheet
  Set ws2 = ThisWorkbook.Sheets("Sheet2") ' Update "Sheet2" with the name of your second sheet
  Set dic = CreateObject("Scripting.Dictionary")
  Set rng = ws2.Range("A1")
 
  a = ws1.Range("A1:AE" & ws1.Range("Q" & Rows.Count).End(3).Row).Value 'sheet1
  ReDim b(1 To UBound(a, 1), 1 To UBound(a, 1))                         'store rows
 
  Set paymentColumn = Selection                               'selection
  Set paymentColumn = paymentColumn.Resize(paymentColumn.Rows.Count, 2)
  c = paymentColumn.Value
  ini = paymentColumn.Cells(1).Row - 1
  col = paymentColumn.Cells(1).Column
  d = Application.Index(a, , 29)                                        'output
 
  For i = 1 To UBound(a, 1)
    kys = a(i, 17)
    If kys <> "" Then
      If Not dic.exists(kys) Then
        y = y + 1
        b(y, 1) = i
        dic(kys) = y & "|" & 1
      Else
        nRow = Split(dic(kys), "|")(0)
        nCol = Split(dic(kys), "|")(1)
        nCol = nCol + 1
        b(nRow, nCol) = i
        dic(kys) = nRow & "|" & nCol
      End If
    End If
  Next
 
  For i = 1 To UBound(c, 1)               'for each item in selection
    kys = c(i, 1)
    If dic.exists(kys) Then
      remainingPayment = c(i, 2)
      j = 1
      nRow = Split(dic(kys), "|")(0)
      nCol = Split(dic(kys), "|")(1)
    
      Do While remainingPayment > 0
        filA = b(nRow, j)
        premium = a(filA, 18)               '"R"
        If a(filA, 31) < premium Then
          premium = a(filA, 31)             '"AE"
        End If
        If premium < remainingPayment Then
          amountToApply = premium
        Else
          amountToApply = remainingPayment
        End If
        remainingPayment = remainingPayment - amountToApply
        d(filA, 1) = d(filA, 1) + amountToApply   '"AC"
        j = j + 1
        If j > nCol Then j = 1
      Loop
    Else
      Set rng = Union(rng, ws2.Cells(i + ini, col))
    End If
  Next
  ws1.Range("AC1").Resize(UBound(d)).Value = d    'Output
  rng.Interior.Color = RGB(255, 0, 0)             'Highlight
  ws2.Range("A1").Interior.Color = xlNone
End Sub

The code may look longer, but it's faster.

Just for the record, this is my test data sample from sheet1:
Dante Amor
APQRSABACADAE
1q1premiumAAA
2APQRSBCE
311617181928293031
4A4P4Q41S4AB483
5A5P5Q524S5AB519
6A6P6Q620S6AB620
7A7P7Q747S7AB742
8A8P8Q841S8AB844
9A9P9Q941S9AB936
10A10P10Q1027S10AB1022
11A11P11Q1174S11AB1169
12A12P12Q423S12AB1218
13A13P13Q1386S13AB1381
14A14P14Q444S14AB1439
15A15P15Q675S15AB1570
Sheet1


And sheet2:
Dante Amor
BC
1SelectionpaymentCell.Offset(0, 1).Value
2Q4300
3Q220
4Q630
5Q840
6Q1050
7Q2525
Sheet2


--------------
Let me know the result and I'll get back to you as soon as I can.
Sincerely
Dante Amor
--------------
 
Last edited:
Upvote 0
Hi @chosen .
Thanks for posting on MrExcel.​

I show you the macro converted to use a dictionary and arrays. This will certainly make it faster.

I'm honest, I did tests with a small sample of data to get a feel for it and develop the new macro.

Test with a sample of your data so that you can review the results, if the results are correct test with all of your data, the result should be in seconds.

Also, if you prefer to change instead of selecting the data from sheet2 to process, simply write in the macro the cell range of sheet2 to process.
Change this line:​
Set paymentColumn = Selection 'selection​
For this line and adjust the range of the cells​
Set paymentColumn = ws2.Range("B2:B7")

But if you don't prefer then the macro is designed as your macro, select the cells on sheet2 and run the macro.

VBA Code:
Sub DistributeInstallments_v1()
  Dim ws1 As Worksheet, ws2 As Worksheet
  Dim a As Variant, b As Variant, c As Variant, d As Variant, ky As Variant
  Dim dic As Object
  Dim i As Long, j As Long, y As Long
  Dim nRow As Long, nCol As Long, filA As Long, ini As Long, col As Long
  Dim rng As Range, paymentColumn As Range
  Dim kys As String
  Dim remainingPayment As Double, amountToApply As Double, premium As Double
 
  Set ws1 = ThisWorkbook.Sheets("Sheet1") ' Update "Sheet1" with the name of your first sheet
  Set ws2 = ThisWorkbook.Sheets("Sheet2") ' Update "Sheet2" with the name of your second sheet
  Set dic = CreateObject("Scripting.Dictionary")
  Set rng = ws2.Range("A1")
 
  a = ws1.Range("A1:AE" & ws1.Range("Q" & Rows.Count).End(3).Row).Value 'sheet1
  ReDim b(1 To UBound(a, 1), 1 To UBound(a, 1))                         'store rows
 
  Set paymentColumn = Selection                               'selection
  Set paymentColumn = paymentColumn.Resize(paymentColumn.Rows.Count, 2)
  c = paymentColumn.Value
  ini = paymentColumn.Cells(1).Row - 1
  col = paymentColumn.Cells(1).Column
  d = Application.Index(a, , 29)                                        'output
 
  For i = 1 To UBound(a, 1)
    kys = a(i, 17)
    If kys <> "" Then
      If Not dic.exists(kys) Then
        y = y + 1
        b(y, 1) = i
        dic(kys) = y & "|" & 1
      Else
        nRow = Split(dic(kys), "|")(0)
        nCol = Split(dic(kys), "|")(1)
        nCol = nCol + 1
        b(nRow, nCol) = i
        dic(kys) = nRow & "|" & nCol
      End If
    End If
  Next
 
  For i = 1 To UBound(c, 1)               'for each item in selection
    kys = c(i, 1)
    If dic.exists(kys) Then
      remainingPayment = c(i, 2)
      j = 1
      nRow = Split(dic(kys), "|")(0)
      nCol = Split(dic(kys), "|")(1)
   
      Do While remainingPayment > 0
        filA = b(nRow, j)
        premium = a(filA, 18)               '"R"
        If a(filA, 31) < premium Then
          premium = a(filA, 31)             '"AE"
        End If
        If premium < remainingPayment Then
          amountToApply = premium
        Else
          amountToApply = remainingPayment
        End If
        remainingPayment = remainingPayment - amountToApply
        d(filA, 1) = d(filA, 1) + amountToApply   '"AC"
        j = j + 1
        If j > nCol Then j = 1
      Loop
    Else
      Set rng = Union(rng, ws2.Cells(i + ini, col))
    End If
  Next
  ws1.Range("AC1").Resize(UBound(d)).Value = d    'Output
  rng.Interior.Color = RGB(255, 0, 0)             'Highlight
  ws2.Range("A1").Interior.Color = xlNone
End Sub

The code may look longer, but it's faster.

Just for the record, this is my test data sample from sheet1:
Dante Amor
APQRSABACADAE
1q1premiumAAA
2APQRSBCE
311617181928293031
4A4P4Q41S4AB483
5A5P5Q524S5AB519
6A6P6Q620S6AB620
7A7P7Q747S7AB742
8A8P8Q841S8AB844
9A9P9Q941S9AB936
10A10P10Q1027S10AB1022
11A11P11Q1174S11AB1169
12A12P12Q423S12AB1218
13A13P13Q1386S13AB1381
14A14P14Q444S14AB1439
15A15P15Q675S15AB1570
Sheet1


And sheet2:
Dante Amor
BC
1SelectionpaymentCell.Offset(0, 1).Value
2Q4300
3Q220
4Q630
5Q840
6Q1050
7Q2525
Sheet2


--------------
Let me know the result and I'll get back to you as soon as I can.
Sincerely
Dante Amor
--------------

Unfortunately
Your edit didn't work
I will send you a thumbnail file similar to the original file
 
Upvote 0
Hi chosen.
I have several annotations, I hope you can read everything:

1) In your seetings you have that you use version 2021 but you shared a version 2003 file. Therefore I have to make a few small adjustments to my macro.​
2) I took the opportunity to adjust my macro, start in row 3 and finish before the row of totals that you have in row 32599 (But the macro gets it dynamically).​
3) I did some tests with your file with a selection of 30 items, I couldn't do tests with more records because Excel crashes.​
4) I did a test with my (updated) code with the same 30. I compared the results of your macro against the results of my macro and they are identical.​
5) In fact I carried out the execution of my macro, selecting all the items and the process took a second.

IMPORTANT:
1) The logic of your macro has a problem. If the column "AE" value is 0 (or less than 0), the macro enters an endless loop, since when​
remaining Payment subtracts 0, so the remaining Payment never decreases.​
2) If I am correct, if column AE has 0 or less than 0, it means that there is no remaining payment, therefore you should not process that record.​

Please try the following macro with all the records.
I currently put in the range "E8:E507". But you can extend it for all your data in this line:
Rich (BB code):
Set paymentColumn = ws2.Range("E8:E507")

Full macro.
VBA Code:
Sub DistributeInstallments_v1()
  Dim ws1 As Worksheet, ws2 As Worksheet
  Dim a As Variant, b As Variant, c As Variant, d As Variant, ky As Variant
  Dim dic As Object
  Dim i As Long, j As Long, y As Long
  Dim nRow As Long, nCol As Long, filA As Long, ini As Long, col As Long, lr As Long
  Dim rng As Range, paymentColumn As Range
  Dim kys As String
  Dim remainingPayment As Double, amountToApply As Double, premium As Double
 
  Set ws1 = ThisWorkbook.Sheets("Sheet1") ' Update "Sheet1" with the name of your first sheet
  Set ws2 = ThisWorkbook.Sheets("Sheet2") ' Update "Sheet2" with the name of your second sheet
  Set dic = CreateObject("Scripting.Dictionary")
  Set rng = ws2.Range("A1")
 
  lr = ws1.Range("Q" & Rows.Count).End(3).Row - 4
  a = ws1.Range("A3:AE" & lr).Value                            'sheet1
  ReDim b(1 To UBound(a, 1), 1 To 100)                         'store rows
 
  'Set paymentColumn = Selection                               'selection
  Set paymentColumn = ws2.Range("E8:E507")                               'selection
  Set paymentColumn = paymentColumn.Resize(paymentColumn.Rows.Count, 2)
  c = paymentColumn.Value
  ini = paymentColumn.Cells(1).Row - 1
  col = paymentColumn.Cells(1).Column
  d = Application.Index(a, , 29)                                        'output
 
  For i = 1 To UBound(a, 1)
    kys = a(i, 17)
    If kys <> "" Then
      If Not dic.exists(kys) Then
        y = y + 1
        b(y, 1) = i
        dic(kys) = y & "|" & 1
      Else
        nRow = Split(dic(kys), "|")(0)
        nCol = Split(dic(kys), "|")(1)
        nCol = nCol + 1
        b(nRow, nCol) = i
        dic(kys) = nRow & "|" & nCol
      End If
    End If
  Next
 
  For i = 1 To UBound(c, 1)               'for each item in selection
    kys = c(i, 1)
    If dic.exists(kys) Then
      remainingPayment = c(i, 2)
      j = 1
      nRow = Split(dic(kys), "|")(0)
      nCol = Split(dic(kys), "|")(1)
 
      Do While remainingPayment > 0
        filA = b(nRow, j)
     
        premium = a(filA, 18)               '"R"
        If a(filA, 31) < premium Then
          premium = a(filA, 31)             '"AE"
        End If
        If premium < remainingPayment Then
          amountToApply = premium
        Else
          amountToApply = remainingPayment
        End If
        remainingPayment = remainingPayment - amountToApply
        d(filA, 1) = d(filA, 1) + amountToApply   '"AC"
        j = j + 1
        If j > nCol Then
          If remainingPayment = c(i, 2) Then
            d(filA, 1) = 0
            Exit Do
          End If
          j = 1
        End If
      Loop
    Else
      Set rng = Union(rng, ws2.Cells(i + ini, col))
    End If
  Next
  ws1.Range("AC3").Resize(UBound(d)).Value = d    'Output
  rng.Interior.Color = RGB(255, 0, 0)             'Highlight
  ws2.Range("A1").Interior.Color = xlNone
End Sub

--------------
I hope to hear from you soon.
Respectfully
Dante Amor
--------------
 
Upvote 0

Forum statistics

Threads
1,224,824
Messages
6,181,187
Members
453,020
Latest member
Mohamed Magdi Tawfiq Emam

We've detected that you are using an adblocker.

We have a great community of people providing Excel help here, but the hosting costs are enormous. You can help keep this site running by allowing ads on MrExcel.com.
Allow Ads at MrExcel

Which adblocker are you using?

Disable AdBlock

Follow these easy steps to disable AdBlock

1)Click on the icon in the browser’s toolbar.
2)Click on the icon in the browser’s toolbar.
2)Click on the "Pause on this site" option.
Go back

Disable AdBlock Plus

Follow these easy steps to disable AdBlock Plus

1)Click on the icon in the browser’s toolbar.
2)Click on the toggle to disable it for "mrexcel.com".
Go back

Disable uBlock Origin

Follow these easy steps to disable uBlock Origin

1)Click on the icon in the browser’s toolbar.
2)Click on the "Power" button.
3)Click on the "Refresh" button.
Go back

Disable uBlock

Follow these easy steps to disable uBlock

1)Click on the icon in the browser’s toolbar.
2)Click on the "Power" button.
3)Click on the "Refresh" button.
Go back
Back
Top