VBA Error: Compile error in hidden module - arrays

I have an Excel File which I've designed to simplify a process at my workplace (with our customers)
One of the macro's is matching data between two different tabs, performing some calculations, and highlighting cells pink where an error has been found to highlight this to the end user. Most of this is done inside an array for speed (there can be quite a large volume of data)
This macro as well as all the other macros on this workbook are working perfectly well on my PC as well as my colleagues.
However, upon sending this file to some of our customers - they have reported having an issue upon running the macro I mentioned above. They have shared the below message (link below):
'Compile Error in hidden module: .............
This error commonly occurs when code is incompatible with the version, platform, or architecture of this application.
I've read this probably has to do with 32 bit vs 64 bit versions of Windows and that I might need to alter my code to account for this. However, I'm not using any declare statements which call an API, and I'm also not using any Long variables which refer to Pointers or Handlers - so I'm a bit confused what might be causing this issue.
Please could somebody help me figure out what might be causing this error message on other PC's when the code works okay for me?
Sub RefreshData()
'Set Variables
Dim fd As Worksheet
Dim ld As Worksheet
Dim OrN As Worksheet
Dim RF As Worksheet
Set fd = ThisWorkbook.Sheets("Feeder Data")
Set ld = ThisWorkbook.Sheets("Live Data")
Set OrN = ThisWorkbook.Sheets("Order Numbers")
Set RF = ThisWorkbook.Sheets("reference")
Dim PeriodReturnRange As Range
Dim YearReturnRange As Range
Dim DateLookUpRange As Range
Set PeriodReturnRange = RF.Range("K3:K200")
Set YearReturnRange = RF.Range("I3:I200")
Set DateLookUpRange = RF.Range("J3:J200")
Dim fdArray() As String
Dim ldArray() As String
Dim OrNArray() As String
Dim ldArray2() As String
'' 1) Set the size of the Feeder Data Array
On Error GoTo ErrorMsgfd
ReDim Preserve fdArray(6 To fd.Range("C" & Rows.Count).End(xlUp).Row, 3 To 14)
On Error GoTo 0
'' 2) Set size of Live Data Array
On Error GoTo ErrorMsgld
ReDim Preserve ldArray(10 To ld.Range("B" & Rows.Count).End(xlUp).Row, 2 To 28)
On Error GoTo 0
'' 3) Set Size of Order Number Array
On Error GoTo ErrorMsgOrN
ReDim Preserve OrNArray(8 To OrN.Range("J" & Rows.Count).End(xlUp).Row, 8 To 10)
On Error GoTo 0
''4) Set size of second Order Number to get info needed for saving calculation
On Error GoTo ErrorMsgld
ReDim Preserve ldArray2(10 To ld.Range("B" & Rows.Count).End(xlUp).Row, 14 To 22)
On Error GoTo 0
On Error GoTo PasswordErrorMsg
ld.Unprotect "password1234"
fd.Unprotect "password1234"
On Error GoTo 0
With Application
.ScreenUpdating = False
.EnableEvents = False
.Calculation = xlCalculationManual
End With
On Error Resume Next
ld.ShowAllData
fd.ShowAllData
On Error GoTo 0
''''Perform Worksheet Clearing and copy formatting down to row 5500
With ld
.Range("E4").Copy
.Range("B10:B5500").PasteSpecial xlPasteFormats
.Range("G10:G5500").PasteSpecial xlPasteFormats
.Range("J10:J5500").PasteSpecial xlPasteFormats
.Range("O10:P5500").PasteSpecial xlPasteFormats
.Range("S10:S5500").PasteSpecial xlPasteFormats
.Range("Z10:Z5500").PasteSpecial xlPasteFormats
.Range("AA10:AA5500").PasteSpecial xlPasteFormats
.Range("F4").Copy
.Range("C10:F5500").PasteSpecial xlPasteFormats
.Range("I10:I5500").PasteSpecial xlPasteFormats
.Range("K10:N5500").PasteSpecial xlPasteFormats
.Range("Q10:R5500").PasteSpecial xlPasteFormats
.Range("T10:V5500").PasteSpecial xlPasteFormats
.Range("AA10:AB5500").PasteSpecial xlPasteFormats
.Range("G4").Copy
.Range("H10:H5500").PasteSpecial xlPasteFormats
.Range("W10:Y5500").PasteSpecial xlPasteFormats
.Range("C10:F5500").ClearContents
.Range("I10:I5500").ClearContents
.Range("K10:N5500").ClearContents
.Range("Q10:R5500").ClearContents
.Range("T10:V5500").ClearContents
.Range("AA10:AB5500").ClearContents
End With
'''''''Populate Arrays
''4) Populate Feeder Data Array with the data
For A = 6 To fd.Range("C" & Rows.Count).End(xlUp).Row
For B = 3 To 14
fdArray(A, B) = Trim(fd.Cells(A, B))
Next B
Next A
''5) Populate Live Data Array
For A = 10 To ld.Range("B" & Rows.Count).End(xlUp).Row
For B = 2 To 28
ldArray(A, B) = Trim(ld.Cells(A, B))
Next B
Next A
'' 6) Populate Order Number Array
For A = 8 To OrN.Range("J" & Rows.Count).End(xlUp).Row
For B = 8 To 10
OrNArray(A, B) = Trim(OrN.Cells(A, B))
Next B
Next A
''''''''' Match the values between Live Data and Feeder Data arrays (still not transferring back to worksheet)
Dim LookUp1 As String
Dim LookUp2 As String
Dim LookUp3 As String
For A = 10 To UBound(ldArray)
LookUp1 = ldArray(A, 2)
LookUp2 = ldArray(A, 7)
On Error Resume Next
ldArray(A, 11) = Application.WorksheetFunction.Lookup(CLng(CDate(ld.Range("J" & A).Value)), DateLookUpRange, PeriodReturnRange)
ldArray(A, 22) = Application.WorksheetFunction.Lookup(CLng(CDate(ld.Range("J" & A).Value)), DateLookUpRange, YearReturnRange)
On Error GoTo 0
For B = 6 To UBound(fdArray)
If fdArray(B, 3) = LookUp1 And fdArray(B, 9) = LookUp2 Then
On Error Resume Next
''Calculation 1
If fd.Range("H" & B).Value = "ABC" Then
ldArray(A, 28) = fd.Range("N" & B).Value * (ld.Range("Z" & A).Value / 1000)
Else
ldArray(A, 28) = fd.Range("N" & B).Value * (ld.Range("Z" & A).Value / 100)
End If
''Calculation 2
ldArray(A, 17) = fd.Range("K" & B).Value - (ld.Range("O" & A).Value * fd.Range("L" & B).Value) - (ld.Range("P" & A).Value * fd.Range("M" & B).Value)
''Calculation 3
ldArray(A, 18) = (ld.Range("O" & A).Value * fd.Range("L" & B).Value) + (ld.Range("P" & A).Value * fd.Range("M" & B).Value)
On Error GoTo 0
''''''''Lookup results
'Result 1
ldArray(A, 3) = fdArray(B, 4)
'Result 2
ldArray(A, 4) = fdArray(B, 5)
'Result 3
ldArray(A, 5) = fdArray(B, 6)
'Result 4
ldArray(A, 6) = fdArray(B, 7)
'Result 5
ldArray(A, 9) = fdArray(B, 8)
'Result 6
ldArray(A, 13) = fdArray(B, 10)
'Result 7
ldArray(A, 14) = fdArray(B, 11)
'Result 8
ldArray(A, 20) = fdArray(B, 12)
'Result 9
ldArray(A, 21) = fdArray(B, 13)
'Result 10
ldArray(A, 27) = fdArray(B, 14)
Exit For
End If
Next B
''Check for blanks, highlight that there has been an error if found
If ldArray(A, 4) = "" Or ldArray(A, 5) = "" Then
ld.Range("B" & A).Interior.Color = RGB(253, 211, 211)
ld.Range("G" & A).Interior.Color = RGB(253, 211, 211)
End If
''Check for blanks, highlight that there has been an error if found
If ldArray(A, 11) = "" Or ldArray(A, 22) = "" Then
ld.Range("J" & A).Interior.Color = RGB(253, 211, 211)
End If
Next A
''''Run a second loop between live data and order numbers, to match relevant order numbers
For A = 10 To UBound(ldArray)
LookUp1 = ldArray(A, 7)
LookUp2 = ldArray(A, 5)
For C = 8 To UBound(OrNArray)
''check if a particular special value was found and return matched result to array if it was, and ignore second lookup value
If LookUp1 = "xxx" And OrNArray(C, 9) = "xxx" Then
ldArray(A, 12) = OrNArray(C, 10)
''IF special value not found, test against both lookup values''
Else
If OrNArray(C, 9) = LookUp1 And OrNArray(C, 8) = LookUp2 Then
ldArray(A, 12) = OrNArray(C, 10)
Exit For
End If
End If
Next C
If ldArray(A, 12) = "" Then
ld.Range("L" & A).Interior.Color = RGB(253, 211, 211)
End If
Next A
''7) Transfer the matched arrays to the Worksheet
ld.Range("B10", ActiveCell.Offset(UBound(ldArray, 1) - 10, UBound(ldArray, 2) - 23)).Value = ldArray
''''''''''Load second live data Array containing only relevant columns'''''''
For A = 10 To ld.Range("B" & Rows.Count).End(xlUp).Row
For B = 14 To 22
ldArray2(A, B) = ld.Cells(A, B)
Next B
Next A
''''''''''''Loop to check if a special condition has been met and color if needed'''''''''''''''''''''''''''''
For A = 10 To UBound(ldArray2)
With ld.Rows(A)
If ldArray2(A, 19) = "" And ldArray2(A, 14) = ldArray2(A, 17) And _
Application.CountIfs(ld.Range("D10:D" & A), .Columns("D").Value, ld.Range("V10:V" & A), .Columns("V").Value, ld.Range("K10:K" & A), .Columns("K").Value) > 1 Then
.Columns("S").Interior.Color = RGB(253, 211, 211)
End If
End With
Next A
Erase fdArray
Erase ldArray
Erase OrNArray
Erase ldArray2
'''reset formatting of columns correctly, and unlock editable columns
With ld
.Range("B10:B5500").Locked = False
.Range("G10:H5500").Locked = False
.Range("J10:J5500").Locked = False
.Range("W10:Z5500").Locked = False
.Range("S10:S5500").Locked = False
.Range("O10:P5500").Locked = False
.Range("L10:L10000").NumberFormat = "#"
.Range("E10:E10000").NumberFormat = "#"
.Range("Q10:Q10000").NumberFormat = "0.00"
.Range("Z10:AB10000").NumberFormat = "0.00"
End With
'''array is being sent as text - this line required to make data format correctly - not sure why!
ld.Range("B10:AB5500").Select
Selection.Value = Selection.Value
ld.Range("B10").Select
On Error GoTo PasswordErrorMsg
ld.Protect "password1234", AllowFiltering:=True
fd.Protect "password1234", AllowFiltering:=True
On Error GoTo 0
With Application
.ScreenUpdating = True
.EnableEvents = True
.Calculation = xlCalculationAutomatic
End With
Exit Sub
ErrorMsgfd:
MsgBox "No Data has been entered into the Feeder Data!", vbCritical, "Cannot Update"
Exit Sub
ErrorMsgld:
MsgBox "No Data has been entered into the Live Data!", vbCritical, "Cannot Update"
Exit Sub
ErrorMsgOrN:
MsgBox "No Data has been entered into the Order Numbers!", vbCritical, "Cannot Update"
Exit Sub
PasswordErrorMsg:
MsgBox "An incorrect password has been entered for this worksheet. Please change the password to the agreed text to continue!", vbCritical, "Incorrect Password!!"
End Sub

Related

Using CountA or Equivelant on a range of columns contained within a larger array of columns

I am currently reading a range into an array to perform a few calculations before outputting into another worksheet. My reason for using the array is speed as I am often dealing with thousands of rows.
I have one particular calculation that I am struggling with for some reason.
This is the part I am struggling with (rest of sample of this code is further down):
For i = non_rev_rows To 2 Step -1.
**' Remove Blank Rows from array
If data_range(i, 2) = "No WBS/CC" Then
If Application.WorksheetFunction.CountA(Range("C" & i & ":M" & i)) = 0 Then
Rows(i).Delete
End If
So basically when a row in column 2 is equal to "No WBS/CC" then I need to run a CountA or any other method you can recommend to calcuate the total value of columns C to M on that row. I am essentially looking for any row that = "No WBS/CC" and where columns C:M have no value. If so, then delete the entire row. If there is a value in columns C:M then I would not wish to delete the row.
'Row Count
With Sheets("array")
non_rev_rows = .Range("E" & .Rows.Count).End(xlUp).Row
End With
' Remove Blank Rows from array
' Replace "NO WBS/CC" with Co Code Over-Ride if supplied
' Set Debit / Credit
' Round to 2 decimal places
Set data = array_sheet.Range("A1:M" & non_rev_rows)
data_range = data.Value
For i = non_rev_rows To 2 Step -1.
**' Remove Blank Rows from array
If data_range(i, 2) = "No WBS/CC" Then
If Application.WorksheetFunction.CountA(Range("C" & i & ":M" & i)) = 0 Then
Rows(i).Delete
End If
' Replace "NO WBS/CC" with Co Code Over-Ride if supplied
If data_range(i, 13) <> 0 Then
data_range(i, 2) = data_range(i, 13)
End If
End If**
' Set Debit / Credit
data_range(i, 3) = Replace(data_range(i, 3), "Debit", 41)
data_range(i, 3) = Replace(data_range(i, 3), "Credit", 51)
' Round to 2 decimal places
data_range(i, 5) = WorksheetFunction.Round(data_range(i, 5), 2)
' If data_range(i, 3) = "Debit" Then
' data_range(i, 3).Value = 41
' ElseIf data_range(i, 3) = "Credit" Then
' data_range(i, 3).Value = 51
' End If
'data_range(i, 5).Value = Application.WorksheetFunction.Round(Range(data_range(i, 5)).Value, 2)
'Range("E" & i).Value = Application.WorksheetFunction.Round(Range("E" & i).Value, 2)
Next i
**' Remove Blank Rows from array
If data_range(i, 2) = "No WBS/CC" Then
If Application.WorksheetFunction.CountA(Range("C" & i & ":M" & i)) = 0 Then
Rows(i).Delete
End If
This code does not result in an error but it also does not have the desired impact. I have several rows in my test data that contain "No WBS/CC" in column 2 and zero values in columns C:M but the code is not deleting those rows.
If you want learning/understanding how an array row can be deleted (adapted for your case), please test the next way. It will return the array without deleted rows starting from "O2" of the same sheet, so the range after M:M column must be empty. You can easily adapt last code line to return wherever you need (in other sheet, other workbook...):
Sub DeleteArrayRows()
Dim array_sheet As Worksheet, non_rev_rows As Long, Data As Range, count2 As Long, data_range, arrRow, i As Long
Set array_sheet = ActiveSheet 'worksheets("array")
non_rev_rows = array_sheet.Range("E" & array_sheet.rows.count).End(xlUp).row
Set Data = array_sheet.Range("A1:M" & non_rev_rows)
data_range = Data.Value
For i = 1 To UBound(data_range)
count2 = 0
If data_range(i, 2) = "No WBS/CC" Then
With Application
arrRow = .Transpose(.Transpose(.Index(data_range, i, 0))) 'extract a slice of the row array
End With
Debug.Print Join(arrRow, ""): Stop 'just to see the joinned respecitve slice In Immediate Window
'comment it after seeing what it represents and press F5
If data_range(i, 1) <> "" Then count2 = Len(data_range(i, 1))
If Len(Join(arrRow, "")) - count2 = Len(data_range(i, 2)) Then
data_range = DeleteArrayRow_(data_range, i): i = i - 1
End If
End If
If i = UBound(data_range) Then Exit For
Next i
'drop the array (without deleted rows) in a range:
array_sheet.Range("O1").Resize(UBound(data_range), UBound(data_range, 2)).Value = data_range
End Sub
Private Function DeleteArrayRow_(arr As Variant, RowToDelete As Long) As Variant 'interesting...
'It does not work to eliminate the first array row...
Dim Rws As Long, cols As String
Rws = UBound(arr) - LBound(arr)
cols = "A:" & Split(Columns(UBound(arr, 2) - LBound(arr, 2) + 1).address(, 0), ":")(0)
DeleteArrayRow_ = Application.Index(arr, Application.Transpose(Split(Join(Application.Transpose(Evaluate("Row(1:" & _
(RowToDelete - 1) & ")"))) & " " & Join(Application.Transpose(Evaluate("Row(" & _
(RowToDelete + 1) & ":" & UBound(arr) & ")"))))), Evaluate("COLUMN(" & cols & ")"))
End Function
It is not extremely fast, I tried showing it only for didactic purpose. To see that it is and how it is possible...
Note: I did not pay attention to all at the code lines after deletion. It can be easily adapted to include that part...
You can do both tests on the array rather than partially in array and partially in the worksheet.
Only delete the row in the worksheet when you find a full match.
Public Sub Test2()
Dim data_range As Variant
Dim lRows As Long
Dim lColumns As Long
Dim lCounter As Long
data_range = Sheet1.Range("A1:M6")
' Add the data to an array
For lRows = UBound(data_range) To LBound(data_range) Step -1
'Step through the array in reverse
If data_range(lRows, 2) = "No WBS/CC" Then
'Check for the "No WBS/CC" value in the second column of the array
lCounter = 0
'Reset the counter
For lColumns = 3 To 13
If Not IsEmpty(data_range(lRows, lColumns)) Then
lCounter = lCounter + 1
End If
Next lColumns
'Check columns in the array row to see if they have data
'Add to the counter for each cell having value
If lCounter = 0 Then
Sheet1.Rows(lRows).EntireRow.Delete
End If
'If the counter is zero delete the current row in the Workbook
End If
Next lRows
End Sub
Sample data before the macro is run. The row we expected to be removed highlighted in green.
Sample data after the macro is run. The expected row has been removed.
An alternate option is to write the valid rows to a new array.
Clear the data on the worksheet, then write the new array to the worksheet.
Remove Rows
Sub DoStuff()
Dim wb As Workbook: Set wb = ThisWorkbook
Dim ws As Worksheet: Set ws = wb.Worksheets("Array")
Dim LastRow As Long: LastRow = ws.Cells(ws.Rows.Count, "E").End(xlUp).Row
Dim rg As Range: Set rg = ws.Range("A2", ws.Cells(LastRow, "M"))
Dim rCount As Long: rCount = rg.Rows.Count
Dim cCount As Long: cCount = rg.Columns.Count
Dim Data() As Variant: Data = rg.Value
Dim sr As Long
Dim dr As Long
Dim c As Long
For sr = 1 To rCount
If Not IsRowBlank(Data, sr, 3, 13) Then ' is not blank
' Replace "NO WBS/CC" with Co Code Over-Ride if supplied
If CStr(Data(sr, 1)) = "No WBS/CC" Then
If Data(sr, 13) <> 0 Then
Data(sr, 2) = Data(sr, 13)
End If
End If
' Set Debit / Credit
Data(sr, 3) = Replace(Data(sr, 3), "Debit", 41)
Data(sr, 3) = Replace(Data(sr, 3), "Credit", 51)
' Round to 2 decimal places
Data(sr, 5) = Application.Round(Data(sr, 5), 2)
' Copy source row to destination row.
dr = dr + 1
For c = 1 To cCount
Data(dr, c) = Data(sr, c)
Next c
'Else ' is blank; do nothing
End If
Next sr
' Clear bottom source data.
If dr < rCount Then
For sr = dr + 1 To rCount
For c = 1 To cCount
Data(sr, c) = Empty
Next c
Next sr
End If
rg.Value = dData
End Sub
Function IsRowBlank( _
Data() As Variant, _
ByVal DataRow As Long, _
ByVal StartColumn As Long, _
ByVal EndColumn As Long) _
As Boolean
Dim c As Long
For c = StartColumn To EndColumn
If Len(CStr(Data(DataRow, c))) > 0 Then Exit For
Next c
IsRowBlank = c > EndColumn
End Function

Add items to an array rather than replacing

I have some code which uses arrays to get data from one worksheet (Cost Data) and essentially copy it to another worksheet (Compare Tool) in a side-by-side, comparison format. I got everything to work only to find out that in some cases, there are multiple lines of data on the Cost Data worksheet which meet the criteria. When these lines of data are assigned to my outarr array, it overwrites what was in there rather than adding to it.
I tried using the ReDim Preserve; however, I'm still getting an error. Any suggestions?
Sub Compare_Projects_Arrays()
Dim Toolary As Variant, Data_ary As Variant, PrjTitle_ary As Variant, CurrentAry As Variant, outarr As Variant
Dim r As Long, nr As Long, x As Long, c As Long, CurrentCostCod As Long
Dim Cl As Range
Dim Project1 As String, Project2 As String, Project3 As String, Project4 As String, Project5 As String, Project6 As String, Project7 As String, Project8 As String
Application.ScreenUpdating = False
On Error Resume Next
Sheets("Compare Tool").ShowAllData
Sheets("Cost Data").ShowAllData
Sheets("Compare Tool").Range("Clear_Cells").SpecialCells(xlConstants).ClearContents
Sheets("Compare Tool").Range("AD15:AD16,AD19,AQ15:AD16,AQ19,BD15:BD16,BD19,BQ15:BQ16,BQ19,CD15:CD16, CD19,CQ15:CQ16, CQ19,DD15:DD16, DD19,DQ15:DQ16,DQ19").ClearContents
Sheets("Compare Tool").Range("X23:DW24").ClearContents
On Error GoTo 0
With Sheets("Setup Page")
Typology = .Range("L18")
Project1 = .Range("U11").Value
Project2 = .Range("U12").Value
Project3 = .Range("U13").Value
Project4 = .Range("U14").Value
Project5 = .Range("U15").Value
Project6 = .Range("U16").Value
Project7 = .Range("U17").Value
Project8 = .Range("U18").Value
End With
With Sheets("Compare Tool")
Set SearchRangeTool = .Range("E:E").Find(What:="Last Row")
LastRowTool = SearchRangeTool.Row
If Project1 <> "" Then
.Range("X23") = Project1
.Range("X24") = "Typology: " & Typology
End If
If Project2 <> "" Then
.Range("AK23") = Project2
.Range("AK24") = "Typology: " & Typology
End If
If Project3 <> "" Then
.Range("AX23") = Project3
.Range("AX24") = "Typology: " & Typology
End If
If Project4 <> "" Then
.Range("BK23") = Project4
.Range("BK24") = "Typology: " & Typology
End If
If Project5 <> "" Then
.Range("BX23") = Project5
.Range("BX24") = "Typology: " & Typology
End If
If Project6 <> "" Then
.Range("CK23") = Project6
.Range("CK24") = "Typology: " & Typology
End If
If Project7 <> "" Then
.Range("CX23") = Project7
.Range("CX24") = "Typology: " & Typology
End If
If Project8 <> "" Then
.Range("DK23") = Project8
.Range("DK24") = "Typology: " & Typology
End If
End With
'Put data into the arrarys (Toolary & Data_ary)
Data_ary = Sheets("Cost Data").Range("A1").CurrentRegion.Value2
With Sheets("Compare Tool")
Toolary = .Range("A28:DV" & .Range("U" & Rows.Count).End(xlUp).Row).Value2
End With
'Project 1
'Check if Project field is blank
If Sheets("Setup Page").Range("U11") = "" Then GoTo Project2
With Sheets("Cost Data")
FirstRowDB = .Range("A:A").Find(What:=Project1, LookIn:=xlValues, SearchDirection:=xlNext).Row 'xlNext starts from top
GSFPrj = .Cells(FirstRowDB, 13)
GSFTypology = .Cells(FirstRowDB, 18)
End With
'Copy the GSF area & Total Project cost and paste into the top of the "Compare Tool" tab
Sheets("Prj Info").Select
FindPrj = Application.Match(Project1, Range("A:A"), 0)
Total_Prj_Cost = Sheets("Prj Info").Cells(FindPrj, 16)
Sheets("Compare Tool").Range("AD19") = GSFTypology
Sheets("Compare Tool").Range("AD16") = GSFPrj
Sheets("Compare Tool").Range("AD15") = Total_Prj_Cost
lastrow = UBound(Toolary)
outarr = Worksheets("Compare Tool").Range("X28:AI" & lastrow)
'The following will put the formulas from the subtotals lines into the "toolfrom" array and then put it into the "outarr" array
With Sheets("Compare Tool")
toolfrom = .Range("X28:AI" & lastrow).formula
End With
For i = 1 To UBound(outarr, 1)
For j = 1 To UBound(outarr, 2)
If Left(toolfrom(i, j), 1) = "=" Then 'erroring out at i=1 and j=10
outarr(i, j) = toolfrom(i, j)
End If
Next j
Next i
For r = 1 To lastrow
If Toolary(r, 5) = "Single" Or Toolary(r, 5) = "T2 Head" Then
CurrentCostCode = Toolary(r, 21)
CurrentT0 = Toolary(r, 9)
ReDim Preserve outarr(r) 'This is where the error happens
For x = 2 To UBound(Data_ary)
If Data_ary(x, 1) = Project1 And Data_ary(x, 34) = CurrentCostCode And Data_ary(x, 22) = CurrentT0 And Data_ary(x, 17) = Typology Then
outarr(r, 1) = Data_ary(x, 37) 'This is where the data is getting overwritten
outarr(r, 2) = Data_ary(x, 38) 'This is where the data is getting overwritten
outarr(r, 3) = Data_ary(x, 39) 'This is where the data is getting overwritten
outarr(r, 4) = Data_ary(x, 40) 'This is where the data is getting overwritten
outarr(r, 5) = Data_ary(x, 41) 'This is where the data is getting overwritten
outarr(r, 6) = Data_ary(x, 42) 'This is where the data is getting overwritten
outarr(r, 7) = Data_ary(x, 43) 'This is where the data is getting overwritten
If Data_ary(x, 44) <> "" Then
outarr(r, 8) = Data_ary(x, 44) 'This is where the data is getting overwritten
outarr(r, 9) = Data_ary(x, 45) 'This is where the data is getting overwritten
End If
End If
Next x
End If
Next r
Worksheets("Compare Tool").Range("X28:AF" & lastrow) = outarr
'Project 2
Project2:
Application.ScreenUpdating = True
End Sub

Match VBA Array values and overwrite duplicates

Welcome!
I have problem with preparing function or part of the code which provides operation on data in structure below (data in this format is already stored in Array):
ID Flag Company
33 AB 67345
33 ABC 53245
33 C 67345
33 AB 25897
33 A 89217
33 BC 81237
33 B 89217
33 C 89217
The purpose of the exercise is to obtain new array with combined records based on the key ID + Company. So basically output should be:
33 ABC 67345
33 ABC 53245
33 AB 25897
33 ABC 89217
33 BC 81237
I have tried several solution but still not getting final result. I used loops or comparing methods.
Can anyone provide vital solution? Performance is not a key at this point, the most important is solution that will solve this problem.
I have tried solution with moving values from Array to another but still I get duplicated rows for example:
33 ABC 89217
33 AB 89217
33 C 89217
Example of the code:
For i = 1 To UBound(Array1)
If Array1(i, 13) <> "Matched" Then
strTestCase = Array1(i, 1) & Array1(i, 9)
strLegalEntityType = EntityFlag(Array1(i, 5))
For j = 1 To UBound(Array1)
If Array1(j, 1) & Array1(j, 9) = strTestCase Then
Array1(i, 13) = "Matched"
End If
If EntityFlag(Array1(i, 5)) = EntityFlag(Array1(j, 5)) Then
arrTemporary1(i, 5) = EntityFlag(Array1(j, 5)) & strLegalEntityType
arrTemporary1(i, 5) = funcRemoveDuplicates(arrTemporary1(i, 5))
arrTemporary1(i, 1) = Array1(i, 1)
arrTemporary1(i, 2) = Array1(i, 2)
arrTemporary1(i, 3) = Array1(i, 3)
arrTemporary1(i, 4) = Array1(i, 4)
arrTemporary1(i, 6) = Array1(i, 6)
arrTemporary1(i, 7) = Array1(i, 7)
arrTemporary1(i, 8) = Array1(i, 8)
arrTemporary1(i, 9) = Array1(i, 9)
arrTemporary1(i, 10) = Array1(i, 10)
arrTemporary1(i, 11) = Array1(i, 11)
arrTemporary1(i, 12) = Array1(i, 12)
a = a + 1
End If
Next j
End If
Next i
This can be done in Power Query (aka Get&Transform in Excel 2016+)
Group the Rows by ID and Company with Operation = "All Rows"
Add a custom column to change the resultant table into a list:
Formula for custom column: Table.Column([Grouped],"Flag")
Select the double headed arrow at the top of the "Custom" column and"Extract" values from the list with "none" for the delimiter
The above can all be done from the user interface, (with manual entry of the formula for the custom column), but here is the resultant M-Code:
let
Source = Excel.CurrentWorkbook(){[Name="Table1"]}[Content],
#"Changed Type" = Table.TransformColumnTypes(Source,{{"ID", Int64.Type}, {"Flag", type text}, {"Company", Int64.Type}}),
#"Grouped Rows" = Table.Group(#"Changed Type", {"ID", "Company"}, {{"Grouped", each _, type table [ID=number, Flag=text, Company=number]}}),
#"Added Custom" = Table.AddColumn(#"Grouped Rows", "Custom", each Table.Column([Grouped],"Flag")),
#"Extracted Values" = Table.TransformColumns(#"Added Custom", {"Custom", each Text.Combine(List.Transform(_, Text.From)), type text})
in
#"Extracted Values"
You can achieve this by using a dictionary. To use dictionaries you will need to add a reference to Microsoft Scripting Runtime
Sub demo()
Dim dict As New Scripting.Dictionary
Dim arr As Variant
Dim i As Long
Dim tmpID As String
Dim k
Dim tmpFlag As String
' Set range to variant
' Update with your sheet reference and range location
With ActiveSheet
arr = .Range(.Cells(2, 1), .Cells(.Cells(.Rows.Count, 1).End(xlUp).Row, 3))
End With
' Loop through array
For i = LBound(arr, 1) To UBound(arr, 1)
' Create composite ID of ID and Company
tmpID = arr(i, 1) & "," & arr(i, 3)
' If it doesn't exist add to dictionary
If Not dict.Exists(tmpID) Then
dict.Add Key:=tmpID, Item:=arr(i, 2)
' If it does exist append value
Else
tmpFlag = StrConv(dict(tmpID) & arr(i, 2), vbUnicode)
tmpFlag = Join(SortArrayAtoZ(Split(tmpFlag, Chr$(0), Len(tmpFlag))), "")
dict(tmpID) = tmpFlag
End If
Next i
' Read back results
ReDim arr(1 To dict.Count, 1 To 3)
Dim arrCount As Long
' Debug.Print results can be viewed in the Immediate Window
Debug.Print "ID", "Flag", "Company"
For Each k In dict.Keys
arrCount = arrCount + 1
arr(arrCount, 1) = Split(k, ",")(0)
arr(arrCount, 2) = dict(k)
arr(arrCount, 3) = Split(k, ",")(1)
Debug.Print Split(k, ",")(0), dict(k), Split(k, ",")(1)
Next k
' Update with first cell of desired location of results
With ActiveSheet
.Cells(2, 5).Resize(UBound(arr, 1), UBound(arr, 2)) = arr
End With
End Sub
Function SortArrayAtoZ(myArray As Variant)
Dim i As Long
Dim j As Long
Dim Temp
'Sort the Array A-Z
For i = LBound(myArray) To UBound(myArray) - 1
For j = i + 1 To UBound(myArray)
If UCase(myArray(i)) > UCase(myArray(j)) Then
Temp = myArray(j)
myArray(j) = myArray(i)
myArray(i) = Temp
End If
Next j
Next i
SortArrayAtoZ = myArray
End Function

VBA Collections Increase Speed: Matching Two Lists, Find What Doesn't Match

I have to massive Excel sheets (rows 7500 and 16000). I need to see what items that are in list one are NOT in list two... and what items are in list two that are NOT in list one, and then paste those results on a third sheet.
I decided to store both lists in two Collections. So far that works well. When I try to loop through the Collections to find what doesn't match my computer freezes as the file is too big.
How can I change my code so that it is quicker? I feel like there must be a better way to do this instead of looping through every i in list one and every z in list two.
Thanks!
Sub FullListCompareFSvDF()
Worksheets("FundserveFL").Activate
'Open New Collection and define every variable
Dim FSTrades As New Collection
Dim c As Long
Dim i As Long
Dim z As Long
Dim searchFor As String
'enter the items into the list. There are blank rows and so the first IF Statement is to ignore these.
' The Else Statement shows an account number as the item and an account number & balance (FS.Offset(0,6).Value) as the key
Dim FS As Range
For Each FS In Sheet1.Range("L:L")
If FS = "" Then
Else: FSTrades.Add CStr(FS.Value & " " & FS.Offset(0, 6).Value)
End If
Next
Worksheets("DatafileFL").Activate
Dim DFTrades As New Collection
'enter the items into the list. There are blank rows as well as random numbers and so the first IF Statement is to ignore these (all account numbers are greater than 10000
'"Matching" is displayed for all errors - during an error read the account number from two columns over.
' The Else Statement shows an account number as the item and an account number & balance (FS.Offset(0,6).Value) as the key
Dim DF As Range
For Each DF In Sheet2.Range("H:H")
If DF = "" Or Not IsNumeric(DF.Offset(0, 2)) Or DF < 10000 Then
ElseIf DF.Offset(0, -4) = "MATCHING" Then
DFTrades.Add CStr(DF.Offset(0, 2).Value & " " & DF.Value)
Else:
DFTrades.Add CStr(DF.Value & " " & DF.Offset(0, -2).Value)
End If
Next
'loop through the first collection. Find the first item and try to match it with the items in the second collection.
'Collection 1 Item 1... is it in Collection 2 Item 1? No - then is it in Collection 2 Item 2? When a match is found, move on to Collection 1 Item 2... If no match is found send the item to "ForInvestigation" worksheet
For i = 1 To FSTrades.Count
searchFor = FSTrades(i)
z = 0
Do
z = z + 1
If z > DFTrades.Count Then
c = c + 1
Worksheets("ForInvestigation").Activate
Cells(c, 1).Value = DFTrades(i)
Exit Do
Else:
If DFTrades(z) = searchFor Then
Exit Do
End If
End If
Loop
Next
'Clear Collections
Set FSTrades = Nothing
Set DFTrades = Nothing
End Sub
Don't Activate
Read all the relevant cells into a variant array in one step. eg:
Dim V As Variant
With Worksheets("FundserveFL")
V = .Range("L1", .Cells(.Rows.Count, "L").End(xlUp)).Resize(columnsize:=6)
End With
Create a key for your collection that can be used to see if there is a duplicate.
On Error Resume Next
For i = 1 To UBound(V, 1)
If V(i, 1) <> "" Then
FSTrades.Add Item:=CStr(V(i, 1) & " " & V(i, 6)), Key:=CStr(V(i, 1) & " " & V(i, 6))
End If
Next i
On Error Resume Next
If you similarly handle the data on your second worksheet, creating an array, adding it to the same collection after creating a key which will "error" if you try to add a duplicate, you will wind up with a collection that contains no duplicates. Populate an array with that collection, and write it to your third worksheet.
I would guess that using the above technique will increase your speed by at least a factor of ten, if not more.
EDIT:
If you want to do something other than a unique list, it is merely a matter of understanding the logic. For example, if, as in your comment, you have two arrays 1,2,3,4 and 1,3,4,5, you could do something like the following. Understand, of course, that one assumption is that there are no duplicates within either array: (If there are, that can be handled also, would just require a different logic)
Sub foo()
Dim V1, V2
Dim COL As Collection
Dim I As Long
V1 = Array(1, 2, 3, 4)
V2 = Array(1, 3, 4, 5)
Set COL = New Collection
For I = 0 To UBound(V1)
COL.Add V1(I), CStr(V1(I))
Next I
On Error Resume Next
For I = 0 To UBound(V2)
COL.Add V2(I), CStr(V2(I))
Select Case Err.Number
Case 457 'This is a duplicate, so will remove
Err.Clear
COL.Remove CStr(V2(I))
Case Is <> 0
MsgBox "Error No. " & Err.Number & vbTab & Err.Description
End Select
Next I
Stop
End Sub
When the routine stops, if you examine COL you will see it only contains 2 and 5
I have a similarly sized list of stuff, and I frequently need to create a unique list of values. I'm not sure why you want to work with two collections at once though. It is much simpler to load the data from one sheet into the collection, then loop through the other sheet to see if it already exists in the collection. Here's some of my code to help you write yours.
Dim colUniqueSNs As New Collection
On Error Resume Next
For r = 2 To Sheets("Inventory").UsedRange.Rows.Count
strSN = Sheets("Inventory").Cells(r, 6).Text
strHost = Sheets("Inventory").Cells(r, 2).Text
If Not InCollection(colUniqueSNs, strSN) Then colUniqueSNs.Add strHost, strSN
Next
On Error GoTo 0
Public Function InCollection(col As Collection, key As Variant) As Boolean
Dim obj As Variant
On Error GoTo err
InCollection = True
obj = col(key)
Exit Function
err:
InCollection = False
End Function
You are starting with ranges and you are ending with them. How about skipping the Collections at all?
Pls try this:
Sub FullListCompareFSvDF()
Dim Ran1Val As Variant, Ran1ValOffset As Variant, Ran2Val As Variant
Ran1Val = Intersect(Sheet1.Columns(12), Sheet1.UsedRange).Value
Ran2Val = Intersect(Sheet1.Columns(18), Sheet1.UsedRange).Value
Dim i As Long, j As Long
For i = 1 To UBound(ranval1)
If Len(Ran1Val(i, 1)) Then Ran1Val(i, 1) = Ran1Val(i, 1) & " " & Ran2Val(i, 1)
Next
Ran2Val = Intersect(Sheet2.Range("D:J"), Sheet2.UsedRange).Value
Dim OutputVal() As Variant
ReDim OutputVal(1 To UBound(Ran1Val) + UBound(Ran2Val), 1 To 1)
For i = 1 To UBound(Ran2Val)
If Ran2Val(i, 5) <> "" And IsNumeric(Ran2Val(i, 7)) And Ran2Val(i, 5) > 10000 Then
If Ran2Val(i, 1) = "MATCHING" Then
Ran2Val(i, 1) = CStr(Ran2Val(i, 7) & " " & Ran2Val(i, 5))
Else
Ran2Val(i, 1) = CStr(Ran2Val(i, 5) & " " & Ran2Val(i, 3))
End If
If IsNumeric(Application.Match(Ran2Val(i, 1), Ran1Val, 0)) Then
j = j + 1
OutputVal(j, 1) = Ran2Val(i, 1)
End If
Else
Ran2Val(i, 1) = ""
End If
Next
ReDim Preserve Ran2Val(1 To UBound(Ran2Val), 1 To 1)
Dim runNer As Variant
For Each runNer In Ran1Val
If Len(runNer) Then
If IsNumeric(Application.Match(runNer, Ran2Val, 0)) Then
j = j + 1
OutputVal(j, 1) = runNer
End If
End If
Next
If j > 0 Then
Worksheets("ForInvestigation").Range("A1:A" & j).Value = OutputVal
End If
End Sub
I simply gets the Range.Value inside an array. Deleting all unused values and having one dimension as (1 To 1) allowes us to use Application.Match which is one of the fastest functions in excel.
when building up the second array, we already can check for the first one and push uniques directly to the output-array.
resizing the second array (with preserve) allowes us to use this with Match too.
Finally checking the entrys of the first array against the second one and push them also inside our output-array.
now we can directly copy the values to your destination (in one step)
Note:
- You may delete the "output-range" first (a smaller list later on will not overwrite oler values.)
- I'm not able to run real checks (you may need to report errors via comment I missed out)
- this code does not check for doubles inside one list (having 1 item 2 times in list 1 but not in list 2, will print it 2 times at the end / if you need this check, then just write a comment)
Thanks for all of your help! Here is my answer. It is mostly coming from Ron's answer - I have of course added some tweaks to it.
Sub MatchFSTradesDFTrades2()
Dim V1 As Variant
Dim V2 As Variant
Dim COL As New Collection
Dim I As Long
Worksheets("DatafileFL").Activate
With Worksheets("FundserveFL")
V1 = .Range("L1", .Cells(.Rows.Count, "L").End(xlUp)).Resize(columnsize:=7)
End With
With Worksheets("DatafileFL")
V2 = .Range("F1", .Cells(.Rows.Count, "D").End(xlUp)).Resize(columnsize:=12)
End With
For I = 1 To UBound(V1)
If V1(I, 1) = " " Or Not IsNumeric(V1(I, 1)) Or V1(I, 1) < 10000 Or V1(I, 1) = "***" Or Not IsNumeric(V1(I, 3)) Or (V1(I, 5)) = "Buy-EC" Or (V1(I, 5)) = "Sell-EC" Then
Else:
COL.Add (V1(I, 1)) & " " & (V1(I, 7)), CStr(V1(I, 1)) & " " & (V1(I, 7))
End If
Next I
For I = 1 To COL.Count
Sheet3.Cells(I + 1, 1).Value = COL.Item(I)
Next
On Error Resume Next
For I = 1 To UBound(V2)
If V2(I, 1) = "MATCHING" Then
If IsNumeric(V2(I, 5)) Then
COL.Add (V2(I, 7)) & " " & V2(I, 5), CStr(V2(I, 7)) & " " & V2(I, 5)
Select Case Err.Number
Case 457 'This is a duplicate, so will remove
Err.Clear
COL.Remove CStr(V2(I, 7)) & " " & V2(I, 5)
End Select
Else: V2(I, 12) = Right(V2(I, 5), Len(V2(I, 5)) - 1)
V2(I, 12) = Format(V2(I, 12), "General Number")
COL.Add (V2(I, 7)) & " " & V2(I, 12), CStr(V2(I, 7)) & " " & V2(I, 12)
Select Case Err.Number
Case 457 'This is a duplicate, so will remove
Err.Clear
COL.Remove CStr(V2(I, 7)) & " " & V2(I, 12)
End Select
End If
ElseIf V2(I, 5) = " " Or Not IsNumeric(V2(I, 5)) Or V2(I, 5) < 10000 Or V2(I, 5) = "***" Or V2(I, 1) = "BULK" Then
Else:
If IsNumeric(V2(I, 3)) Then
COL.Add (V2(I, 5)) & " " & V2(I, 3), CStr(V2(I, 5)) & " " & V2(I, 3)
Select Case Err.Number
Case 457 'This is a duplicate, so will remove
Err.Clear
COL.Remove CStr(V2(I, 5)) & " " & V2(I, 3)
End Select
Else: V2(I, 12) = Right(V2(I, 3), Len(V2(I, 3)) - 1)
V2(I, 12) = Format(V2(I, 12), "General Number")
COL.Add (V2(I, 5)) & " " & V2(I, 12), CStr(V2(I, 5)) & " " & V2(I, 12)
Select Case Err.Number
Case 457 'This is a duplicate, so will remove
Err.Clear
COL.Remove CStr(V2(I, 5)) & " " & V2(I, 12)
End Select
End If
End If
Next
Worksheets("ForInvestigation").Activate
Cells.Clear
For I = 1 To COL.Count
Sheet3.Cells(I + 1, 1).Value = COL.Item(I)
Next
Range("A:A").Select
Selection.TextToColumns DataType:=xlDelimited, Space:=True, Other:=True
Range("A1") = "Trade ID Number"
Range("A1").Font.Bold = True
Range("B1") = "Net Balanace On Trade"
Range("B1").Font.Bold = True
End Sub

Find multiple values, concatenate cooresponding values in other column, write to cell

Problem:
Nothing is being written into cells in column P. The line Cells(x, "P").Value = failingClasses should do this.
Description: (VBA script below)
I've got a column with ID numbers. There can be multiple rows with each ID number. What I need to do is concatenate all the corresponding values in another column and write this into a cell in the original row. This needs to be done for each row in the sheet.
Field 1 is where the IDs are, field 6 is where the information I want to concatenate is, I'm trying to write the concatenation into column P.
Right now, I think that the computation is being done correctly, but for what ever reason it isn't writing to the cell in P?
Macro takes for ever to run. Between 1k and 2k rows when run.
Thanks!
Worksheets("RAW GRADE DATA").Select
' Turn off auto calc update and screen update -- saves speed
Application.Calculation = xlCalculationManual
Application.ScreenUpdating = False
Dim x As Long, y As Long, totalGradeEntries As Long, failingClasses As String, failingClassesCell As Excel.Range
totalGradeEntries = Cells(Rows.Count, 1).End(xlUp).Row
For x = totalGradeEntries To 1 Step -1
failingClasses = ""
For y = totalGradeEntries To 1 Step -1
If Cells(y, 1).Value = Cells(x, 1).Value And Cells(x, 6) <> "02HR" Then
failingClasses = failingClasses & " " & Cells(y, 1).Value
End If
Cells(x, "P").Value = failingClasses
Next y
Next x
' Turn calc and screen update back on
Application.Calculation = xlCalculationAutomatic
Application.ScreenUpdating = True
I got the bones of a solution to this work, thanks to Ron Rosenfeld -- Here is the code, working on a test sheet with 3 columns of data, the Unique IDs being in column 1.
Sub CalcArrary()
'Declare variables
Dim numRows As Integer, calcArray() As Variant
'Set the number of rows in the sheet
numRows = ActiveSheet.Range("A1").Offset(Sheet1.Rows.Count - 1, 0).End(xlUp).Row
ReDim calcArray(numRows - 1, 4)
For i = 0 To numRows - 2
calcArray(i, 1) = Range("A" & i + 2)
calcArray(i, 2) = Range("B" & i + 2)
calcArray(i, 3) = Range("C" & i + 2)
Next i
For b = 0 To numRows - 2
For c = 0 To numRows - 2
If calcArray(c, 1) = calcArray(b, 1) And calcArray(c, 3) < 60 Then
calcArray(b, 4) = calcArray(b, 4) & calcArray(c, 2) & ", " & calcArray(c, 3) & "% "
End If
Next c
Next b
For d = 0 To numRows - 2
ActiveSheet.Range("D" & d + 2) = calcArray(d, 4)
Next d
End Sub

Resources