Excel VB subscript out of range repairing macro - vba

Here is the declaration of navrec
Public navrec(1 To 100000, 100) As Variant
navrec(r,c) = Cells(r,c)
I encounter a subscript out of range error on the above for loop line within the below procedure
I am attempting to repair this macro with limited working knowledge of VB-Excel. I am assuming the preceding for loop ranges' navreclr & navreclc are referencing too large of a data area?
Sub import_navr()
EntityList = mywkb.Sheets("Source Files").Range("nrlist")
navreclr = 0
days = 0
fname = navrecloc
If Dir(fname) = "" Then
MsgBox ("Please save current PVAL. Macro will end")
End
End If
Workbooks.Open fname, ReadOnly:=True
Set tempbk = ActiveWorkbook
navreclr = Cells(1048576, 1).End(xlUp).Row
navreclc = Sheets(1).Cells(1, 1).End(xlToRight).Column
For r = 1 To navreclr
For c = 1 To navreclc
navrec(r, c) = Cells(r, c)
Next c
Next r
For c = 1 To navreclc
If navrec(1, c) = "ENTITY_ID" Then einr = c
If navrec(1, c) = "SHARE_CLASS" Then scnr = c
If navrec(1, c) = "LEDGER_ITEMS" Then linr = c
If navrec(1, c) = "BALANCE_CHANGE" Then bcnr = c
Next c
Set ofs = CreateObject("Scripting.FileSystemObject")
mywkb.Sheets("Source Files").Range("nrlist").Cells(1, 2) = ofs.GetFile(fname).Datelastmodified
tempbk.Close savechanges:=False
End Sub

The problem is that your array, navrec, is not the correct size to hold all of the cells. You can fix this by adding the following code directly above your loop (I've included the loop below):
ReDim navrec(navreclr, navreclc) As Variant
For r = 1 To navreclr
For c = 1 To navreclc
navrec(r, c) = Cells(r, c)
Next c
Next r
When you are determining the dimensions of your array dynamically, as in this example, it is generally best to declare the array dynamically as well, and not use a constant size. Try not to overutilize memory unless there is a good reason to do so.
You will also need to declare the variable dimensionless in the global module, or you will get an error stating "Array already dimensioned."
Replace:
Public navrec(1 To 100000, 100) As Variant
with
Public navrec() As Variant
to eliminate this error.

You can pick up the value of a rectangular array directly in one operation:
Set tempbk = Workbooks.Open(fname, ReadOnly:=True)
With tempbk.Sheets(1)
'get row/col counts
navreclr = .Cells(.Rows.Count, 1).End(xlUp).Row
navreclc = .Cells(1, .Columns.Count).End(xlToLeft).Column
'assign range value to array
nacrec = .cells(1).Resize(navreclr, navreclc).Value
End With

Related

Run time error 424, issues with defining variables

i am having troubles after fixing next variable issues. I am now getting run time errors 424. I believe this is because i am not naming my variables correctly as strings or Integers. I have two methods to the code and they are both resulting in the same error.
Columns 11 and 1 are text that need to match
Column 10 and columns(v) are dates that need to match
Column 2 will be text with a number at the end
Columns(V) will be a number with a number at the end
the logic of the loop makes sense to me but can't figure out what's causing the issue.
If cell.Value(J, 11) = Master.cell.Value(P, 1) And cell.Value(J, 10) = Master.cell.Value(P, V) Then
error Produced here "Run time '424' Object Required, What object am i missing? (error in bold)
Sub IndexInfo()
'
' Loops through data and finds matches and then indexs information
'
Dim J As Integer
Dim P As Integer
Dim V As Integer
Dim Master As Worksheet
Dim Gracie As Worksheet
IRowL = Cells(Rows.Count, 1).End(xlUp).Row
Set Master = Worksheets("Master")
Set Gracie = Worksheets("Gracie")
For J = 2 To IRowL
For V = 21 To 50
For P = 2 To IRowL
If Gracie.Cells(J, 11).Value = Master.Cells(P, 1).Value And Gracie.Cells.Value(J, 10) = Master.Cells(P, V).Values Then
Gracie.Cells(J, 30).Value = Master.Cells(P, 2).Value And Gracie.Cells(J, 31).Value = Master.Cells(1, V).Value
Else
End If
Next P
Next V
Next J
End Sub

Split action types into different sheets

Hi I have a excel sheet with different action types such as dividends, annual general meetings and so on..
Is there a way to write a vba macro that takes all the action types and puts them into separate sheet within the workbook ? Also the header such as date time should be included in all of the sheets. I am kind of struggling with this atm as I am new to VBA: I have a screen shot of the excel sheet..
Again thanks in advance.
I have the code which sorts for dividends however I am struggling to get the actions into a list and then go through the list and create new sheets.
Sub SortActions()
Dim i&, k&, s$, v, r As Range, ws As Worksheet
Set r = [index(a:a,match("###start",a:a,),):index(a:a,match("###end",a:a,),)].Offset(, 6)
k = r.Row - 1
v = r
For i = 1 To UBound(v)
If LCase$(v(i, 1)) = "dividend" Then
s = s & ", " & i + k & ":" & i + k
End If
Next
s = Mid$(s, 3)
If Len(s) Then
Set ws = ActiveSheet
With Sheets.Add(, ws)
ws.Range(s).Copy .[a1]
Rows("1:1").Select
Selection.Insert Shift:=xlDown, CopyOrigin:=xlFormatFromLeftOrAbove
Sheets("20140701_corporate_action_servi").Select
Rows("2:2").Select
Selection.Copy2
Range("C32").Select
Sheets("Sheet11").Select
ActiveSheet.Paste
End With
End If
End Sub
This should do it:
Public Sub CopyActionTypes()
Dim i&, k&, key, v, r As Range, ws As Worksheet, d As Object
On Error Resume Next
Set r = [index(a:a,match("###start",a:a,),):index(a:a,match("###end",a:a,),)].Offset(, 6)
If Err = 0 Then
On Error GoTo 0
k = r.Row + 1
v = r
Set d = CreateObject("scripting.dictionary")
d.CompareMode = 1
For i = 1 To UBound(v)
key = v(i, 1)
If Len(key) Then
If Not d.Exists(key) Then d.Add key, k & ":" & k
d(key) = d(key) & Replace(",.:.", ".", i)
End If
Next
Set ws = ActiveSheet
For Each key In d.Keys
If LCase$(key) <> "action_type" Then
With Sheets.Add(, ws.Parent.Sheets(ws.Parent.Sheets.Count))
.Name = key
GetRangeUnion(d(key), ws).Copy .[a1]
End With
End If
Next
End If
End Sub
Private Function GetRangeUnion(s As String, ws As Worksheet) As Range
Dim i&, v, r As Range
v = Split(s, ",")
Set r = ws.Range(v(0))
For i = 1 To UBound(v)
Set r = Union(r, ws.Range(v(i)))
Next
Set GetRangeUnion = r
End Function
As an aside, try to not select anything from code during your macros. This is a best practice and one of many ways to optimize code.

Improve redline comparison of cells

I am using Excel 2010.
I have some working VBA code that compares two cells (from text, to text) and generates the redlined text into a third cell with strikethroughs on removed words, underlines on added words. This is not a straight combination of the contents of the cells.
The code works, but I think it can be more efficient with the use of multidimensional arrays to store things instead of using additional cells and recombining. But I am stuck on how to implement it. I would also like to determine where the breaking point is, especially for newer versions of Excel that I don't have yet, since the number of characters allowed in a cell seems to continually grow with every new release.
Comments are also welcome.
The working code:
Sub main()
Cells(3, 3).Clear
Call Redline(3)
End Sub
Sub Redline(ByVal r As Long)
Dim t As String
Dim t1() As String
Dim t2() As String
Dim i As Integer
Dim j As Integer
Dim f As Boolean
Dim c As Integer
Dim wf As Integer
Dim ss As Integer
Application.ScreenUpdating = False
t1 = Split(Range("A" + CStr(r)).Value, " ", -1, vbTextCompare)
t2 = Split(Range("B" + CStr(r)).Value, " ", -1, vbTextCompare)
t = ""
f = False
c = 4
ss = 0
If (Range("A" + CStr(r)).Value <> "") Then
If (Range("B" + CStr(r)).Value <> "") Then
j = 1
For i = LBound(t1) To UBound(t1)
f = False
For j = ss To UBound(t2)
If (t1(i) = t2(j)) Then
f = True
wf = j
Exit For
End If
Next j
If (Not f) Then
Cells(r, c).Value = t1(i)
Cells(r, c).Font.Strikethrough = True ' strikethrough this cell
c = c + 1
Else
If (wf = i) Then
Cells(r, c).Value = t1(i) ' aka t2(wf)
c = c + 1
ss = i + 1
ElseIf (wf > i) Then
For j = ss To wf - 1
Cells(r, c).Value = t2(j)
Cells(r, c).Font.Underline = xlUnderlineStyleSingle ' underline this cell
c = c + 1
Next j
Cells(r, c).Value = t1(i)
c = c + 1
ss = wf + 1
End If
End If
Next i
If (UBound(t2) > UBound(t1)) Then
For i = ss To UBound(t2)
Cells(r, c).Value = t2(i)
Cells(r, c).Font.Underline = xlUnderlineStyleSingle ' underline this cell
c = c + 1
Next i
End If
Else
t = Range("A" + CStr(r)).Value
End If
Else
t = Range("B" + CStr(r)).Value
End If
lc = Range("XFD" + CStr(r)).End(xlToLeft).Column
Call Merge_Cells(r, 4, lc)
Application.ScreenUpdating = True
End Sub
Sub Merge_Cells(ByVal r As Long, ByVal fc As Integer, ByVal lc As Long)
Dim i As Integer, c As Integer, j As Integer
Dim rngFrom As Range
Dim rngTo As Range
Dim lenFrom As Integer
Dim lenTo As Integer
Set rngTo = Cells(r, 3)
' copy the text over
For c = fc To lc
lenTo = rngTo.Characters.Count
Set rngFrom = Cells(r, c)
lenFrom = rngFrom.Characters.Count
If (c = lc) Then
rngTo.Value = rngTo.Text & rngFrom.Text
Else
rngTo.Value = rngTo.Text & rngFrom.Text & " "
End If
Next c
' now copy the formatting
j = 0
For c = fc To lc
Set rngFrom = Cells(r, c)
lenFrom = rngFrom.Characters.Count + 1 ' add one for the space after each word
For i = 1 To lenFrom - 1
With rngTo.Characters(j + i, 1).Font
.Name = rngFrom.Characters(i, 1).Font.Name
.Underline = rngFrom.Characters(i, 1).Font.Underline
.Strikethrough = rngFrom.Characters(i, 1).Font.Strikethrough
.Bold = rngFrom.Characters(i, 1).Font.Bold
.Size = rngFrom.Characters(i, 1).Font.Size
.ColorIndex = rngFrom.Characters(i, 1).Font.ColorIndex
End With
Next i
j = j + lenFrom
Next c
' wipe out the temporary columns
For c = fc To lc
Cells(r, c).Clear
Next c
End Sub
You can directly assign Excel Range object to VBA 2d-array and perform all that business logic operations on that array. It will provide substantial performance boost vs range iteration. The result values then can be inserted back into Excel worksheet column from that 2d-array.
Sample code snippet follows:
Sub Range2Array()
Dim arr As Variant
arr = Range("A:B").Value
'alternatively
'arr = Range("A:B")
'test
Debug.Print (arr(1, 1))
End Sub
Another useful technique is to assign Excel's UsedRange to VBA Array:
arr = ActiveSheet.UsedRange
Hope this may help. Best regards,
Sample code not quite right
I've got a spreadsheet with the following "original" and "changed" content:
Tesla to Begin Trial for Allowing Other Vehicles from Other Electric Vehicle Automakers to Use Tesla Superchargers
Tesla to Begin Trial for Allowing Other Vehicles from Other EV Auto Makers to Use Tesla Superchargers
Running your code, I got not-quite-right results.
The "original" text that is missing from the "changed" version is correctly shown with strikethrough, but the new text in the "changed" version is just ... missing.
Alternative approach
Poking around, it looks like you're trying to re-create MS Word's Track Changes formatting.
Why not just leverage Word?
The following VBA code does just that. This requires that your Excel VBA project has a reference to the Word object library. You can add this from within the VBA editor by clicking Tools → References, and selecting Microsoft Word XX.Y Object Library, where XX.Y is whatever version you have installed.
Public Sub CompareCells()
' ####################
' Basic Flow
'
' 1. Get the text content of the two cells to compare.
' 2. Get an open instance of MS Word, or spin up a new one.
' 3. Use Word's text-comparison features to generate the tracked-changes markup.
' 4. Copy that markup to the clipboard.
' 5. Then just paste that into our target cell.
' ####################
Const Src As String = "A" ' Column containing the original source text
Const Tgt As String = "B" ' Column containing the targeted text to compare
Const Cmp As String = "C" ' Column where we will put the marked-up comparison
Const RowToUse As Integer = 8 ' Rejigger as appropriate to your use case.
' 1.
Dim ThisSheet As Excel.Worksheet: Set ThisSheet = Excel.ActiveSheet
Dim StrSrc As String, StrTgt As String
StrSrc = ThisSheet.Range(Src & RowToUse).Value
StrTgt = ThisSheet.Range(Tgt & RowToUse).Value
' 2.
Dim Wd As Word.Application: Set Wd = GetApp("Word")
' 3.
Dim DocOrig As Word.Document, DocChgd As Word.Document, DocMarkup As Word.Document
Set DocOrig = Wd.Documents.Add(Visible:=False)
DocOrig.Content = StrSrc
Set DocChgd = Wd.Documents.Add(Visible:=False)
DocChgd.Content = StrTgt
Set DocMarkup = Wd.CompareDocuments(DocOrig, DocChgd, wdCompareDestinationNew)
' 4.
DocMarkup.Content.Copy
' 5.
ThisSheet.Range(Cmp & RowToUse).Select
ThisSheet.Paste
' Cleanup
DocOrig.Close savechanges:=False
DocChgd.Close savechanges:=False
DocMarkup.Close savechanges:=False
End Sub
Public Function GetApp(AppName As String) As Object
Dim app As Object
On Error GoTo Handler
Set app = GetObject(, AppName & ".Application")
Set GetApp = app
Exit Function
On Error GoTo 0
Handler:
If Err.Number > 0 And Err.Number <> 429 Then ' Unknown error, so error out
Err.Raise Err.Number, Err.Source, Err.Description, Err.HelpFile, Err.HelpContext
Exit Function
End If
DoEvents
' If we get here, there's no open app by that name, so start a new instance.
Set app = CreateObject(AppName & ".Application")
Set GetApp = app
End Function
When run using the same sample texts, I get the following:
This time, we get both the removed text in strikethrough, and the added text in underlining, with color coding as well.

Macro to compare and copy data from one sheet to another taking a long time

I used this macro to copy contents from one Excel sheet to another, by comparing two columns and finding a matching cell. The problem is that this macro is taking a long time (close to three days) to complete. There are close to 4,00,000 records in both the sheets to compare against.
Can someone please help me to make things faster?
Option Explicit
Sub MatchAndCopy()
Dim sheet01 As Worksheet, sheet02 As Worksheet
Dim count As Range, matchingCell As Long
Dim RangeInSheet1 As Variant
Dim RangeInSheet2 As Variant
Application.ScreenUpdating = False
Application.DisplayStatusBar = True
Set sheet01 = Worksheets("Sheet1")
Set sheet02 = Worksheets("Sheet2")
Set RangeInSheet1 = sheet01.Columns(1)
Set RangeInSheet2 = sheet02.Range("A2", sheet02.Range("A" & Rows.count).End(xlUp))
For Each count In RangeInSheet2
matchingCell = 0
On Error Resume Next
matchingCell = Application.Match(count, RangeInSheet1, 0)
On Error GoTo 0
If matchingCell <> 0 Then
Application.StatusBar = "Please wait while data is being copied, Processing count : " & count
sheet01.Range("F" & matchingCell).Value = count.Offset(, 1)
sheet01.Range("G" & matchingCell).Value = count.Offset(, 2)
sheet01.Range("H" & matchingCell).Value = count.Offset(, 3)
sheet01.Range("I" & matchingCell).Value = count.Offset(, 4)
sheet01.Range("J" & matchingCell).Value = count.Offset(, 5)
End If
Next count
Application.StatusBar = False
Application.ScreenUpdating = True
End Sub
Should be faster:
Sub MatchAndCopy()
Dim sheet01 As Worksheet, sheet02 As Worksheet
Dim c As Range, matchingCell As Long
Dim RangeInSheet1 As Range
Dim RangeInSheet2 As Range
Dim dict As Object, tmp
Set dict = CreateObject("scripting.dictionary")
Application.ScreenUpdating = False
Application.DisplayStatusBar = True
Set sheet01 = Worksheets("Sheet1")
Set sheet02 = Worksheets("Sheet2")
Set RangeInSheet1 = sheet01.Range(sheet01.Range("A2"), _
sheet01.Cells(Rows.count, 1).End(xlUp))
Set RangeInSheet2 = sheet02.Range(sheet02.Range("A2"), _
sheet02.Cells(Rows.count, 1).End(xlUp))
'populate dictionary...
For Each c In RangeInSheet1.Cells
tmp = c.Value
If Not dict.exists(tmp) Then
dict.Add tmp, c.Row
End If
Next c
For Each c In RangeInSheet2.Cells
tmp = c.Value
If dict.exists(tmp) Then
Application.StatusBar = "Please wait while data is being copied," & _
" Processing count : " & c.Row
sheet01.Cells(dict(tmp), "F").Resize(1, 5).Value = _
c.Offset(0, 1).Resize(1, 5).Value
End If
Next c
Application.StatusBar = False
Application.ScreenUpdating = True
End Sub
For 4 million records between the two sheets, use a database. Excel is not a database.
If you insist on treating Excel as a database, I would suggest using ADODB. See this answer for a similar problem and solution.
Name each of the columns on Sheet1 that you want to write to, by putting the name in the first row of each column. For the example, let's call them F1,F2,F3,F4 and F5. Also, name the column with the shared data between Sheet1 and Sheet2; for the example we'll call it F0.
Then, if your version of Office allows it, you can issue this statement:
UPDATE [Sheet1$]
INNER JOIN [Sheet2$] ON [Sheet1$].F0 = [Sheet2$].F0
SET
[Sheet1$].F1 = [Sheet2$].F1,
[Sheet1$].F2 = [Sheet2$].F2,
[Sheet1$].F3 = [Sheet2$].F3,
[Sheet1$].F4 = [Sheet2$].F4,
[Sheet1$].F5 = [Sheet2$].F5
If not, you can use the CopyFromRecordset method with the recordset generated from the following SQL statement:
SELECT s1.F0,
Iif(s2.F0 Is Not Null, s2.F1, s1.F1),
Iif(s2.F0 Is Not Null, s2.F2, s1.F2),
Iif(s2.F0 Is Not Null, s2.F3, s1.F3),
Iif(s2.F0 Is Not Null, s2.F4, s1.F4),
Iif(s2.F0 Is Not Null, s2.F5, s1.F5)
FROM [Sheet1$] AS s1
LEFT JOIN [Sheet2$] AS s2 ON s1.F0 = s2.F0
Get the whole sheet at once using
var values = sheet.getDataRange().getValues();
and compare values locally
EDIT-1
Google Apps script documentation https://developers.google.com/apps-script/reference/spreadsheet/spreadsheet provides the following example for getDataRange()
Returns a Range corresponding to the dimensions in which data is present. This is functionally equivalent to creating a Range bounded by A1 and (Range.getLastColumn(), Range.getLastRow()).
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheets()[0];
// This represents ALL the data
var range = sheet.getDataRange();
var values = range.getValues();
// This logs the spreadsheet in CSV format with a trailing comma
for (var i = 0; i < values.length; i++) {
var row = "";
for (var j = 0; j < values[i].length; j++) {
if (values[i][j]) {
row = row + values[i][j];
}
row = row + ",";
}
Logger.log(row);
}
Instead of using a lot of ranges, data should be obtained in ONE call and processed locally

Excel UDF is returning #value! when changing worksheets and copying and pasting

I have a sheet that has a table on it, the following function will be used to search another sheet and return the number of times the agents name shows up;
The problem I'm having is that if I try to copy and paste the value it will change from a number to '#Value!'. Also, when switching worksheets and then switching back to the worksheet that has the UDF being called it changes all values to '#Value!'
Here is the function and how it is called. Any help would be greatly appreciated.
Public Function GetMatrixCount(AgentName As String) As Integer
Dim matrixSheet As Worksheet, mContainer() As String, c As Integer, m As Integer, y As Integer
Dim fullRange As Range, l As Range, lastRow As Integer
Dim firstThree As String, curAgent As String
'toDo
'return zero if the matrix updates worksheet doesn't exist or the input string is empty
On Error Resume Next
Set matrixSheet = Sheets("Matrix Updates")
On Error GoTo 0
If matrixSheet Is Nothing Or Not Trim(AgentName) <> "" Then
GetMatrixCount = 0
Exit Function
End If
'get month number user wants to input from the title at the top of the page - used to do value check on matrix updates data
mContainer() = Split(Range("B1").Value, " ")
m = month(DateValue(mContainer(UBound(mContainer) - 1) & " 1"))
y = mContainer(UBound(mContainer))
firstThree = Left(AgentName, 3)
lastRow = matrixSheet.Cells(Rows.Count, 1).End(xlUp).Row
c = 0
Set fullRange = matrixSheet.Range("B2:B" & lastRow)
For Each l In fullRange.Cells
curAgent = l.Offset(0, 1).Value
If month(l.Value) = m And year(l.Value) = y And Left(curAgent, 3) = firstThree And Mid(curAgent, InStrRev(curAgent, " ") + 1) = Mid(AgentName, InStrRev(AgentName, " ") + 1) Then
c = c + 1
End If
If l.Value = "" Then
Exit For
End If
Next
GetMatrixCount = c
End Function
Usage:
=GetMatrixCount(B4)
B4: John Doe
UPD:
Try to write following:
Set wb = ThisWorkbook
Set matrixSheet = wb.Sheets("Matrix Updates")
It should fix the problem when you switch workbooks.