I am sorry if this sort of questions has already been asked, I couldn't find the answer I needed and I am quite new to VBA.
I am trying to match some values from one table to the other via an Index Match which is moving between 2 Workbooks. To do this properly I have used two loops For To. But my Code is really slow when it comes to a few thousands lines. Can I improve it with an Array or something else?
Dim mainWB As Workbook
Dim mainWS As Worksheet
Set mainWB = ActiveWorkbook
Set mainWS = mainWB.Sheets(1)
Dim RowsToProcess As Long
RowsToProcess = mainWS.Range("C" & Rows.Count).End(xlUp).Row
Dim lastCol As Long
lastCol = mainWS.Cells(10, Columns.Count).End(xlToLeft).Column
Range(Cells(11, 8), Cells(RowsToProcess, lastCol)).Select
Selection.NumberFormat = "General"
For i = 1 To lastCol
For a = 1 To RowsToProcess
If Workbooks("template.xls").Sheets("#").Cells(10 + a, 7 + i).Value <> vbNullString Then
Cells(10 + a, 7 + i).Select
ActiveCell.FormulaR1C1 = _
"= "Long Formula" "
End If
Next
Next
For my Long formula. It is basically doing 2 Index(Match. between this Workbook and 2 others, but I thought I would take it out here to keep it clearer
Thank you very much for the help!
If you are effectively defining a range you can use the first and last columns and rows addresses and create a range object.
See examples here: Dynamic ranges
You can then do:
Range(myRange).SpecialCells(xlCellTypeBlanks).FormulaR1C1 = "LongFormula"
which effectively replaces all blank cells with your formula without having to do all the loops.
Combine those steps with the items mentioned in the comments such as avoiding .select.
Summary:
Avoid the .select
Use WITH where possible when working with objects
Get the first cell address and last cell address and create your range object
Use the special cells method to set blanks in this range to your formula.
Related
I have a macro generating a new workbook, pasting a selection of data in it, making it an Excel table (listobject), adding data from another table, etc
Now I'm trying to loop through the entire table (working) looking in each column for identical cells to merge them
Dim tableName As String
Dim tblcofin As Listobject
Dim v As Long, w As Long
Dim Rg1 As Range, Rg2 As Range
tableName = "CO_FIN"
Set tblcofin = ActiveSheet.ListObjects(tableName)
For v = 1 To Range("CO_FIN").Columns.Count
For w = 1 To Range("CO_FIN").Rows.Count
Set Rg1 = tblcofin.DataBodyRange.Cells(w, v)
Set Rg2 = tblcofin.DataBodyRange.Cells(w + 1, v)
If Rg1 = Rg2 And Rg1 <> "" Then
tblcofin.Range(Rg1, Rg2).Merge
End If
Next w
Next v
Using Debug.Print lines I was able to confirm that it loops through my whole table, that it identified when 2 cells in the same columns are identical, but I always get an error message "Application-defined or object-defined error" (or another depending on the alternative I tried) for "Range(Rg1, Rg2).Merge"
I tried to:
- declare Rg1 & Rg2 as Variant rather than Range (setting them with or without adding .Address)
- use "Cells(Rg1, Rg2).Resize.Merge"
- and a multitude or other variations
I'm sure it's something very stupid, but though I usually make the point to find the solution by myself, after hours of trying and going through forums, I would really appreciate some advice!
(not sure how to upload a sample file, in case it may help)
I also tried this (adapted from J.A. Gomez) for just my 1st column, to no avail :( (still the same issue on the ".Merge" line...)
Dim myFirstRow As Long
Dim myLastRow As Long
Dim myFirstColumn As Long
Dim myLastColumn As Long
Dim myWorksheet As Worksheet
Dim iCounter As Long
Dim iCounter2 As Long
myFirstRow = 6
myFirstColumn = 2
myLastColumn = 5
myLastRow = 21
Set myWorksheet = Worksheets("Fin_conso")
With myWorksheet
For iCounter = myLastRow To myFirstRow Step -1
iCounter2 = iCounter - 1
If .Cells(iCounter, myFirstColumn).Value = Cells(iCounter - 1, myFirstColumn).Value Then
Debug.Print .Cells(iCounter, myFirstColumn).Address
.Range(.Cells(iCounter, myFirstColumn), .Cells(iCounter2, myFirstColumn)).Merge
End If
Next iCounter
End With
After hours spent looking for a solution, it came to me in a shocking revelation: it's just NOT POSSIBLE to merge cells in a Excel table (listobject), which must have values for filters etc.
Hope this experience can at least help others wasting time looking like me in the wrong direction!
So I had to unlist the table to make it a normal range, and just had to insert the excellent code from Pk found here:
Dim RgTable As Range
Dim FirstRow As Long, LastRow As Long, FirstCol As Long, LastCol As Long
Set RgTable = tblcofin.DataBodyRange 'To have a clear range to work from
'Unlist the tblcofin table to make it just a normal table (not Listobject)
tblcofin.Unlist
'Select the range where to merge identical cells
RgTable.Select
'Merge identical cells
Application.DisplayAlerts = False
Dim RgM As Range
MergeCells:
For Each RgM In Selection
If RgM.Value = RgM.Offset(1, 0).Value And RgM.Value <> "" Then
Range(RgM, RgM.Offset(1, 0)).Merge
Range(RgM, RgM.Offset(1, 0)).HorizontalAlignment = xlCenter
Range(RgM, RgM.Offset(1, 0)).VerticalAlignment = xlCenter
GoTo MergeCells
End If
Next
Application.DisplayAlerts = True
Thanks to the community: I've been learning so much ffrom stackoverflow forum over time. Appreciated!
I have a massive Excel template that's having performance issues. I'd like to optimize the VBA code I'm using to modify cells to be more of an "all at once" approach. I have several basic functions using loops to modify values. Here's an example:
Dim aCell as Range
For Each aCell In Range("A1:A9999").Cells
'appends prefix to value of each cell
aCell.Value = "CC_" & aCell.Value
Next aCell
While this works, the drawback of this is that it causes several recalculations and updates that slows down the template. I'm familiar with turning calculations/screen updating on and off, but for reasons I won't go into, that's not an option.
This code below does NOT work, but it's the approach I'm looking for. Is there a way to make such a change using an array or some other tool I'm not thinking of that would minimize the templates calculation updates?
Range("A1:A9999").Value = "CC_" & Range("A1:A9999").Value
Thank you!
Reading/writing to/from the worksheet takes a lot of time. Do the modifications within a VBA array, then write it back.
Dim myRange As Range, myArr As Variant
Set myRange = Range("A1:A9999")
myArr = myRange
For i = 1 To UBound(myArr, 1)
myArr(i, 1) = "CC_" & myArr(i, 1)
Next i
myRange = myArr
You could temporarily create a column full of functions, then paste those values over the column A values:
Range("XFD1:xfd9999").Formula = "=""CC_""&A1"
Calculate
Range("A1:a9999").Value = Range("XFD1:XFD8").Value
Range("XFD1:XFD9999").ClearContents
I'm operating on the assumption here that you are not using column XFD for anything else. If you are, you could use a different column for this purpose.
FWIW, you can do it without a loop using Evaluate like this:
Sub addText()
With Range("A1:A9999")
.Value2 = .Worksheet.Evaluate("INDEX(""CC_""&" & .Address & ",)")
End With
End Sub
I was revisiting this (trying to still make it faster) and now that I have a little better understanding, I would recommend an approach shown below. The accepted answer is hard-coded for one column, which was my example asked, but the below approach is more dynamic.
Sub sampleArraySheetEdit()
Dim aRange As Range: Set aRange = Range("A1:B9999") ' or whatever your range is...
Dim vRng() As Variant: vRng = aRange
Dim r As Long, c As Long
For r = LBound(vRng, 1) To UBound(vRng, 1) 'this ensures all cells always accounted for
For c = LBound(vRng, 2) To UBound(vRng, 2)
'perform you operation here....
vRng(r, c) = "CC_" & vRng(r, c)
Next c
Next r
aRange = vRng
End Sub
I have a macro which performs a vlookup by taking the vendor name in column J and looks for the vendor number in my table array of my vlookup in column C and D. However when I run the macro, something goes visibly wrong with my vlookup. Please see the formula inside the picture attached. Apparently, the part of my table array in my vlookup does not work properly. Actually, I would like that my vlookup returns me a fixed table array (I mean with absolute reference and dollar) from point of origin C5 and as limit point the last row in column D (I mean the limit of my table array should be the last row of column D).
Please see my VBA code below, it seems that this part of my VBA code inside my vlookup is wrong
: C4" & LastRow & "
Thanks a lot for your help.
Xavi
Sub insertvlookuptogetmyvendornumber()
Dim LastRow As Integer
LastRow = Range("D" & Rows.Count).End(xlUp).Row
PenultimateLastRow = Range("J" & Rows.Count).End(xlUp).Offset(-1, 0).Row
Range("I4").Select
ActiveCell.FormulaR1C1 = "Vendor number"
Range("I5").Select
ActiveCell.FormulaR1C1 = "=VLOOKUP(RC[1],R5C3:C4" & LastRow & ",2,0)"
Selection.AutoFill Destination:=Range("I5:I" & PenultimateLastRow), Type:=xlFillDefault
End Sub
As per my comment I would maintain a historic table of names and numbers. I would initially read this into a dictionary and then loop the appropriate columns of the pivottable updating the dictionary value if the name exists. If the name doesn't exist then add the name and number to the dictionary. At the end write it all back out the historic table.
The historic table is your current table where you are trying to do VLookup. In this case, that table would only contain matched pairs which have new values added to it from pivottable, or existing values updated.
To re-iterate, your table on the right, columns I & J should only have matched pairs in it to start with. Hardcoded.
This assumes no subtotal/total rows within pivottable body, though these can be excluded, if present, with an update to the code.
Option Explicit
Public Sub UpdateReferenceTable()
Dim lastRow As Long, dict As Object, ws As Worksheet, pvt As PivotTable, i As Long
Set ws = ThisWorkbook.Worksheets("Sheet1")
Set pvt = ws.PivotTables("PivotTable1")
Set dict = CreateObject("Scripting.Dictionary")
With ws
lastRow = .Cells(.Rows.Count, "I").End(xlUp).Row
End With
Dim initialDictData(), pivotTableUpdates()
initialDictData = ws.Range("I9:J" & lastRow).Value
For i = LBound(initialDictData, 1) To UBound(initialDictData, 1)
dict(initialDictData(i, 2)) = initialDictData(i, 1)
Next
Dim names(), vendorNumbers()
names = Application.Transpose(pvt.PivotFields("Name 1").DataRange.Value)
vendorNumbers = Application.Transpose(pvt.PivotFields("Vendor Number").DataRange.Value)
For i = LBound(names) To UBound(names)
If names(i) <> vbNullString Then
If dict.exists(names(i)) Then
dict(names(i)) = vendorNumbers(i)
Else
dict.Add names(i), vendorNumbers(i)
End If
End If
Next
ws.Range("I9").Resize(dict.Count, 1) = Application.Transpose(dict.items)
ws.Range("J9").Resize(dict.Count, 1) = Application.Transpose(dict.Keys)
End Sub
Data:
I have 6 worksheets, each has a subcategory of data (it is important they are in separate worksheets). I am loading up the data into arrays because there are thousands of rows, then printing them out in a specific format to a .txt file.
Sub ExcelToXML()
Dim headers(), data(), attributes1(), attributes2(), attr$, r&, c&
Dim rowCount As Long
Dim columnCount As Long
Dim FF As Worksheet
Dim FOPR As Worksheet
Dim R1 As Long
Dim C1 As Long
Set FF = Worksheets("Fairy")
Set FOPR = Worksheets("Opera")
rowCount = (FF.Range("A1048576").End(xlUp).Row) 'Only one defined as rowCount should be consistent
ffcolumncount = (FF.Range("XFD1").End(xlToLeft).Column)
FOPRcolumnCount = FOPR.Range("XFD1").End(xlToLeft).Column
' load the headers and data to an array '
FFheaders = Cells(1, 1).Resize(1, ffcolumncount).Value
FFdata = Cells(1, 1).Resize(rowCount, ffcolumncount).Value
FOPRheaders = Cells(1, 1).Resize(1, FOPRcolumnCount).Value
FOPRdata = Cells(1, 1).Resize(rowCount, FOPRcolumnCount).Value
' set the size for the attributes based on the columns per child, dynamic
ReDim attributes1(1 To ffcolumncount)
ReDim attributes2(1 To FOPRcolumnCount)
' open file and print the header two main parents
Open "C:\desktop\ToGroup.xml" For Output As #1 'file path is here, going to change to save prompt
Print #1, "<Parent>"
Print #1, " <Child>"
' iterate each row non inclusive of headers
For r = 2 To UBound(FFdata)
' iterate each column '
For c = 1 To UBound(FFdata, 2)
' build each attribute '
attr = FFheaders(1, c) & "=""" & FFdata(r, c) & """"
attributes1(c) = FFheaders(1, c) & "=""" & FFdata(r, c) & """"
Next
For R1 = 2 To UBound(FOPRdata)
For C1 = 1 To UBound(FOPRdata, 2)
attr = FOPRheaders(1, c) & "=""" & FOPRdata(r, c) & """"
attributes2(c) = FOPRheaders(1, c) & "=""" & FOPRdata(r, c) & """"
Next
I cut it off at the prining and at 2 for next loops. (Not actually sure if the for..next loops are structured properly). Anyways, my question is, am I redimensioning wrong? It gives me 'subscript out of range' error on the second attribute. Is the line
ReDim attributes2(1 To FOPRcolumnCount)
the issue? As it may be dimensioning the array in the original worksheet. Perhaps I should define the arrays in separate or worksheet models? Can I and how would I reference them? Is there a way to make the array specifically refer to a worksheet?
Appreciate any input. It's really hard not having anyone around who can provide a second opinion.
Try replacing 'FOPRcolumnCount' with an actual value. If it solves your problem then the issue is with how 'FOPRcolumnCount' is calculated, which I think is where your problem lies. It's hard to tell from your example, but it appears you are trying to find the right most column on row#1 ; there are easier ways of doing that. Same with rowCount.
I notice you haven't declared it as a variable. Always declare your variables; put "Option Explicit" at the top of your module to force you to declare all variables.
I have 2 sheets. I am using a user-defined function in sheet 1, in which I want to use an array to compare some strings. The array is comprised of the contents of a column of cells in the second sheet (which is named "phrases.").
So (looking at it another way) in "phrases" I have 100 strings typed into column P, cells 3 to 102. And I want to put all of them into an array that i can use later.
Now, let me complicate it a little - my intent is that users of the spreadsheet will be able to add new content to column P, so that it may eventually be 500 cells or more. So I really want to populate that array dynamically.
Here's where i am - and it doesn't seem to be working:
Dim newarray() As String
Dim i As Long
Dim counter As Long
counter = 0
For i = 0 To 5000
If Worksheets("phrases").Cells(i + 3, 16).Value <> 0 Then
newarray(counter) = Worksheets("phrases").Range(i + 3, 16).Value
counter = counter + 1
End If
Next
Where am i going wrong?
Please note - I've tried this without .Value - didn't seem to work.
I've tried this with .Text instead of .Value - didn't seem to work.
I've tried CStr(Worksheets("phrases").Range(i + 3, 16).Value) and several variations - and it didn't seem to work.
I expect there is something simple I am missing here - but i have no idea what.
Dim newarray() As String
Dim i As Long
Dim lr AS Long
Dim counter As Long
lr = ActiveSheet.Range("P" & Rows.Count).End(xlUp).Row
counter = 0
For i = 1 To lr
If Worksheets("phrases").Range("P" & i).value <> 0 Then
Redim Preserve newarray(counter)
newarray(counter) = Worksheets("phrases").Range("P" & i).value
counter = counter + 1
End If
Next
First construct a comma-separated string. Then convert the string into an array using the Split() function.
You can make the array directly from the cells without having to loop at all using a single line of code, this will also capture anything that the user adds to the bottom of column P by using the Resize() method:
Sub SO()
stringArray = [P3:P102].Resize(Range("P" & Rows.Count).End(xlUp).Row - 2, 1)
'// Print each value in the array
For Each x In stringArray
Debug.Print CStr(x)
Next x
End Sub