Excel formula inserted with vba returning wrong result - excel

I developed a macro that loads some codes that I need to lookup and their associated int values into an array. After this, the macro will loop through the lookup array and convert all codes that compose a formula in one column (for now they are not stored as a formula but as a text in "Fórmula Calculo" column, e.g. dAGR99001/dAGR99002 with no = sign) into their associated int values using the Range.Replace method. Later the macro will load the formulas, which already have the codes with their associated int values, as text into an array and loop through the array to prepend an = sign to convert them into proper formulas and print the result in the desired columns.
Note that the results we can see here (2016) are returning the correct value (#DIV/0!) because the associated values for the codes in this year are 0's. The problem only occurs in 2019 for this formula.
The macro is working fine but some of the formulas are returning the wrong result in some situations.
e.g. in the above formula (dAGR99001/dAGR99002) both codes are associated with the following values for each month of 2019:
But instead of returning =2/2, for Jan/19, and the result of 1, it's returning =43498 and similar values for the following months of the year. It's weird because for the previous years the results are the expected.
I noticed that this error is only occurring when the formulas (divisions, sums, subtractions, etc.) deal with values below 10.
I did some research but haven't found any solution, did find some similar problems but not quite the same thing. I tried to change the values for a number format but that didn't work.
This is the vba code that I'm using:
Sub Looper()
Dim x As Integer
For x = 10 To 60
getformulas x
Next x
End Sub
Sub getformulas(MonthNum As Integer)
Dim wb As Workbook
Dim wsLookup As Worksheet
Dim wsData As Worksheet
Dim wsPaste As Worksheet
Dim aLookup() As Variant
Dim aData() As Variant
Dim lCodesLookupCol As Long
Dim lCodesConvertCol As Long
Dim i As Long
Dim lrow As Long
Call OptimizeCode_Begin
With Sheets("Variaveis")
lrow = .Range("A:BA").Find(What:="", after:=.Range("A2"), searchdirection:=xlPrevious).Row
End With
Set wb = ActiveWorkbook
Set wsData = wb.Worksheets("Indicadores")
Set wsLookup = wb.Worksheets("Variaveis")
Set wsPaste = wb.Worksheets("Formulas")
aLookup = wsLookup.Range("A2:BA" & lrow) '("A2:AO441")
lCodesLookupCol = LBound(aLookup, 2)
lCodesConvertCol = UBound(aLookup, 2)
With wsData.Range("H2", wsData.Cells(wsData.Rows.Count, "H").End(xlUp))
For i = 1 To UBound(aLookup, 1)
'.Replace aLookup(i, lCodesLookupCol), aLookup(i, lCodesConvertCol), xlPart, , False
.Replace aLookup(i, lCodesLookupCol), aLookup(i, MonthNum - 4), xlPart, , False
Next i
If .Cells.Count = 1 Then
ReDim aData(1 To 1, 1 To 1)
aData(1, 1) = .Formula
Else
aData = .Formula
End If
For i = 1 To UBound(aData, 1)
If Len(aData(i, 1)) > 0 And Left(aData(i, 1), 1) <> "=" Then aData(i, 1) = "=" & aData(i, 1)
wsData.Cells(i + 1, MonthNum) = aData(i, 1)
If Left(aData(i, 1), 1) <> "=" Then
wsData.Cells(i + 1, MonthNum).Value = "Error"
End If
On Error Resume Next
Next i
End If
Call OptimizeCode_End
Application.Calculation = xlCalculationAutomatic
Application.EnableEvents = True
End With
Worksheets("Formulas").Range("B2:B228").Copy
Worksheets("Indicadores").Range("H2:H228").PasteSpecial Paste:=xlPasteFormulas
Application.Goto Worksheets("Indicadores").Cells(2, 6)
End Sub

Related

Speed up and simplify

I cobbled together something that does work for me as is, but it runs very slowly and I'm sure the code can be simplified.
Sub CopyPasteValues()
Dim strSht1, strSht2 As String
Dim c, rng As Range
strSht1 = "Edit"
strSht2 = "LOB"
With ThisWorkbook.Sheets(strSht1)
Set rng = Range("J2:AJ37")
For Each c In rng
If Not c.Value = 0 Then
Cells(c.Row, 2).Copy
ThisWorkbook.Sheets(strSht2).Range("B" & Rows.Count).End(xlUp).Offset(1).PasteSpecial xlPasteValues
Range(Cells(c.Row, 4), Cells(c.Row, 5)).Copy
ThisWorkbook.Sheets(strSht2).Range("C" & Rows.Count).End(xlUp).Offset(1).PasteSpecial xlPasteValues
c.Copy
ThisWorkbook.Sheets(strSht2).Range("F" & Rows.Count).End(xlUp).Offset(1).PasteSpecial xlPasteValues
Cells(c.Column).Copy
ThisWorkbook.Sheets(strSht2).Range("G" & Rows.Count).End(xlUp).Offset(1).PasteSpecial xlPasteValues
End If
Next c
End With
End Sub
I appreciate any assistance.
As BigBen Mentioned, array method.
Super Fast.
Sub Move_Values_Array_Method()
Dim SourceSheet As Worksheet 'Source Worksheet
Dim DestinationSheet As Worksheet 'Destination Worksheet
Dim RG As Range 'Source Range
Dim InArr() 'Data In Array
Dim OutArr() 'Data Out Array
Dim X As Long 'Array X Position for purposes of iterating through array.
Dim Y As Long 'Array Y Position for purposes of iterating through array.
Dim Cnt As Long 'Found Value Count
Set SourceSheet = ThisWorkbook.Worksheets("Edit") 'Set Source Worksheet
Set DestinationSheet = ThisWorkbook.Worksheets("LOB") 'Set Dest Worksheet
Set RG = SourceSheet.Range("J2:AJ37") 'Set Source Range
ReDim OutArr(1 To RG.Cells.Count) 'Count Cells in Range, resize output array to be at least that big.
InArr = RG 'Transfer Range Data to Array
Cnt = 0
Debug.Print LBound(InArr, 1) & " - " & UBound(InArr, 1) 'Rows
Debug.Print LBound(InArr, 2) & " - " & UBound(InArr, 2) 'Columns
For Y = 1 To UBound(InArr, 1) 'For Each Row in Array (or each Y position)
For X = 1 To UBound(InArr, 2) 'For Each Column in Array (or each X position)
If InArr(Y, X) <> "" Then 'If not blank Value (you can change this to "If InArr(Y, X) <> 0 Then" if that works best for you.
Cnt = Cnt + 1 'Increment "found value count" by 1
OutArr(Cnt) = InArr(Y, X) 'Add found value to output array
End If
Next X
Next Y
'Output to Dest Sheet
DestinationSheet.Range("F2").Resize(UBound(OutArr, 1), 1).Value = Application.Transpose(OutArr())
End Sub
Based on the information in your previous comments, try these alternative solution using formulas and filters...
1) Array Formulas
To note:
I have put everything on one sheet for clarity, but it works just as well over multiple sheets, or even workbooks.
If you want to filter the entire sheet, with same column order, you only need to enter formula once and expand "Array" criteria in formula to encapsulate entire data set.
Formula used in cell "J4" = "=FILTER($I$4:$I$30,$C$4:$C$30>0)"
(filter range I4 to I30 to show rows where value in range C4 to C30 is greater than 0)
2) Directly Filter
Alternatively, you could (either manually or programmatically) copy all data to LOB sheet, (or selectively copy), then filter for Qty>0.

How to prevent format change with replace

I made a macro that calculates some formulas, that are stored as text at first in one column, by replacing some codes by their associated int values and later print the result in the desired column.
e.g. dAGR99001/dAGR99002 is replaced by 2/2 since their values for certain month/year are both 2, later this text formula converted into a proper formula by adding the equal sign at the beginning and prints the result of it in another column.
My problem is that when replacing the codes by their associated int values, Excel automatically converts it to a date. For example on the above formula, it should be replaced by 2/2 but instead, it's returning 2/Feb (2/Fev in Portuguese) as in 2/2/2019 and when later calculating it the final result is 43498 (days since 1/1/1900).
How can I prevent this from happening?
→
Note that both the column where the text formula is and the associated int values of the codes are stored has General format. I also tried to save them as Number or Text but the problem still persisted.
This is my code
Sub Looper()
Dim x As Integer
For x = 10 To 60
getformulas x
Next x
End Sub
Sub getformulas(MonthNum As Integer)
Dim wb As Workbook
Dim wsLookup As Worksheet
Dim wsData As Worksheet
Dim wsPaste As Worksheet
Dim aLookup() As Variant
Dim aData() As Variant
Dim lCodesLookupCol As Long
Dim lCodesConvertCol As Long
Dim i As Long
Dim lrow As Long
Call OptimizeCode_Begin
With Sheets("Variaveis")
lrow = .Range("A:BA").Find(What:="", after:=.Range("A2"), searchdirection:=xlPrevious).Row
End With
Set wb = ActiveWorkbook
Set wsData = wb.Worksheets("Indicadores")
Set wsLookup = wb.Worksheets("Variaveis")
Set wsPaste = wb.Worksheets("Formulas")
aLookup = wsLookup.Range("A2:BA" & lrow) '("A2:AO441")
lCodesLookupCol = LBound(aLookup, 2)
lCodesConvertCol = UBound(aLookup, 2)
With wsData.Range("H2", wsData.Cells(wsData.Rows.Count, "H").End(xlUp))
For i = 1 To UBound(aLookup, 1)
.Replace aLookup(i, lCodesLookupCol), aLookup(i, MonthNum - 4), xlPart, , False
Next i
If .Cells.Count = 1 Then
ReDim aData(1 To 1, 1 To 1)
aData(1, 1) = .Formula
Else
aData = .Formula
End If
For i = 1 To UBound(aData, 1)
If Len(aData(i, 1)) > 0 And Left(aData(i, 1), 1) <> "=" Then aData(i, 1) = "=" & aData(i, 1)
wsData.Cells(i + 1, MonthNum) = aData(i, 1)
If Left(aData(i, 1), 1) <> "=" Then
wsData.Cells(i + 1, MonthNum).Value = "Error"
End If
On Error Resume Next
Next i
End If
Call OptimizeCode_End
Application.Calculation = xlCalculationAutomatic
Application.EnableEvents = True
End With
Worksheets("Formulas").Range("B2:B228").Copy
Worksheets("Indicadores").Range("H2:H228").PasteSpecial Paste:=xlPasteFormulas
Application.Goto Worksheets("Indicadores").Cells(2, 6)
End Sub

How to get values from matching column in a different sheet

I have two worksheets (Variáveis and Indicadores). In Variáveis there are 14 columns (col A-Codes, col B-Description, cols C:N-months from January to December) and in Indicadores there are 17 columns (col A-ID, col B-Description, col C-Formula, col D-Measurement Unit, col E- Info, cols F:Q- months from January to December).
Variáveis contains the int monthly values for each code.
Indicadores has a text formula (column C) composed by the codes stored in Variáveis (e.g. (dAA11b+dAA12b)/dAA13b*100) that is converted (with the code below that was developed with the help of #tigeravatar) into a excel formula by replacing the codes for his correspondent int monthly value (e.g. (742+764)/125*100).
Portion of Sheet Variáveis
Portion of Sheet Indicadores with formula already converted and result in the first month
Note: this is just an example and values vary because I used the =RANDBETWEEN() formula to populate the worksheet with int values
Dim wb As Workbook
Dim wsLookup As Worksheet
Dim wsData As Worksheet
Dim aLookup() As Variant
Dim aData() As Variant
Dim lCodesLookupCol As Long
Dim lCodesConvertCol As Long
Dim i As Long
Set wb = ActiveWorkbook
Set wsData = wb.Worksheets("Indicadores")
Set wsLookup = wb.Worksheets("Variáveis")
'This line loads the codes we need to lookup and their associated Int Value into an array
aLookup = wsLookup.Range("A2:C1309")
lCodesLookupCol = LBound(aLookup, 2)
lCodesConvertCol = UBound(aLookup, 2)
'This is the range containing the Formulas stored as text in data worksheet
With wsData.Range("C2", wsData.Cells(wsData.Rows.Count, "C").End(xlUp))
'Loop through the lookup array and convert all codes into their associated Int Values using the Range.Replace method
For i = 1 To UBound(aLookup, 1)
.Replace aLookup(i, lCodesLookupCol), aLookup(i, lCodesConvertCol), xlPart, , False
Next i
'Now all of the Codes should have been replaced with their associated Int Values, but the formulas are still just text
'This block will load the formulas as text into an array
If .Cells.Count = 1 Then
ReDim aData(1 To 1, 1 To 1)
aData(1, 1) = .Formula
Else
aData = .Formula
End If
'Loop through the aData array to prepend an = sign to convert them into formulas
For i = 1 To UBound(aData, 1)
'Verifies if the cell isn't blank and that it's not already a formula
If Len(aData(i, 1)) > 0 And Left(aData(i, 1), 1) <> "=" Then aData(i, 1) = "=" & aData(i, 1)
wsData.Cells(i + 1, 6) = aData(i, 1)
If Left(aData(i, 1), 1) <> "=" Then
MsgBox "Error # row " & Str(i + 1)
End If
'On Error Resume Next
Next i
End With
End Sub
Right now the code converts the text formulas in Indicadores into excel formulas by replacing the codes with their values (stored in Variáveis) and placing the result in the column desired (in this case it was col F- Jan)
At the moment the macro can only do the job for one month at a time. What I'm trying to do now is to do the same thing but for all months simultaneously. All help is appreciated, thank you.
It's a little obfuscated and over-engineered, but I think this is the line that actually writes your values to the output sheet:
wsData.Cells(i + 1, 6) = aData(i, 1)
In which case the number "6" is the column being written to, which makes sense since that is the "January" column in your output sheet.
What I would do is enclose the entire sub in a For Loop that runs 12 times (once for each month), and have it increment that number on each loop. Something like this:
Sub Looper()
For x = 6 To 18
LookupMachine x
Next x
End Sub
Sub LookupMachine(MonthNum As Integer)
Dim wb As Workbook
Dim wsLookup As Worksheet
Dim wsData As Worksheet
Dim aLookup() As Variant
Dim aData() As Variant
Dim lCodesLookupCol As Long
Dim lCodesConvertCol As Long
Dim i As Long
Set wb = ActiveWorkbook
Set wsData = wb.Worksheets("Indicadores")
Set wsLookup = wb.Worksheets("Variáveis")
'This line loads the codes we need to lookup and their associated Int Value into an array
aLookup = wsLookup.Range("A2:N1309")
lCodesLookupCol = LBound(aLookup, MonthNum - 4)
lCodesConvertCol = UBound(aLookup, MonthNum - 4)
'This is the range containing the Formulas stored as text in data worksheet
With wsData.Range("C2", wsData.Cells(wsData.Rows.Count, "C").End(xlUp))
'Loop through the lookup array and convert all codes into their associated Int Values using the Range.Replace method
For i = 1 To UBound(aLookup, 1)
.Replace aLookup(i, lCodesLookupCol), aLookup(i, lCodesConvertCol), xlPart, , False
Next i
'Now all of the Codes should have been replaced with their associated Int Values, but the formulas are still just text
'This block will load the formulas as text into an array
If .Cells.Count = 1 Then
ReDim aData(1 To 1, 1 To 1)
aData(1, 1) = .Formula
Else
aData = .Formula
End If
'Loop through the aData array to prepend an = sign to convert them into formulas
For i = 1 To UBound(aData, 1)
'Verifies if the cell isn't blank and that it's not already a formula
If Len(aData(i, 1)) > 0 And Left(aData(i, 1), 1) <> "=" Then aData(i, 1) = "=" & aData(i, 1)
wsData.Cells(i + 1, MonthNum) = aData(i, 1)
If Left(aData(i, 1), 1) <> "=" Then
MsgBox "Error # row " & Str(i + 1)
End If
'On Error Resume Next
Next i
End With
End Sub
Note that I extended the aLookup array to cover up to column N, and changed the column number in the Array lookup from a hardcoded "2", to "MonthNum - 4", which should increment the column of data it's working with in the way that you want.

Adding additional rows under a row, depending on the amount of used cells in a range

basically I need to split a cell that has a few values, seperated by a comma into more cells. Then i need to create the exact amount of the cells under the new cells to be able to transpose this range later to have a new table.
In the picture you can see an example of what I have and what I need. I needed to anonymyze the data. Also I have hundreds of rows that need to changed like the 2 in the example.
Ths is my current code:
Sub texttocolumns()
Dim rng As Range
Dim x As Integer
x = ActiveSheet.UsedRange.Rows.Count
For i = x - 2 To 1
Cells(2 + i, 8).texttocolumns _
Destination:=Cells(2 + i, 9), _
Comma:=True
k = Application.WorksheetFunction.CountA("A" & "2 + i"" & "":" & "AT1")
Cells(2 + i, 1).Rows(k).Insert
Next i
End Sub
I can't find my mistake at the moment, could someone please help me out? thanks!
Since the output result is posted to a different location the expensive task of inserting rows can be avoided.
Try this procedure, which also avoids working with the source range by generating from it two Arrays:
An array containing the fixed fields
An array containing the field that needs to be split
The Procedure:
Sub Range_Split_A_Field()
Dim wsTrg As Worksheet, rgOutput As Range
Dim aFld_1To5 As Variant, aFld_6 As Variant
Dim aFld As Variant
Dim lRow As Long, L As Long
lRow = 3
Set wsTrg = ThisWorkbook.Sheets("Sht(2)")
Application.Goto wsTrg.Cells(1), 1
With wsTrg.Cells(lRow, 1).CurrentRegion
Set rgOutput = .Rows(1).Offset(0, 10)
.Rows(1).Copy
rgOutput.PasteSpecial
Application.CutCopyMode = False
aFld_1To5 = .Offset(1, 0).Resize(-1 + .Rows.Count, 5).Value2
aFld_6 = .Offset(1, 5).Resize(-1 + .Rows.Count, 1).Value2
End With
lRow = 1
For L = 1 To UBound(aFld_1To5)
aFld = aFld_6(L, 1)
If aFld = vbNullString Then
rgOutput.Offset(lRow).Resize(1, 5).Value = WorksheetFunction.Index(aFld_1To5, L, 0)
rgOutput.Offset(lRow, 5).Resize(1, 1).Value = aFld
lRow = 1 + lRow
Else
aFld = Split(aFld, Chr(44))
aFld = WorksheetFunction.Transpose(aFld)
rgOutput.Offset(lRow).Resize(UBound(aFld), 5).Value = WorksheetFunction.Index(aFld_1To5, L, 0)
rgOutput.Offset(lRow, 5).Resize(UBound(aFld), 1).Value = aFld
lRow = lRow + UBound(aFld)
End If: Next
End Sub
Please see the following pages for a better understanding of the resources used:
Application.Goto Method (Excel)
With Statement
Range Object (Excel)
Chr Function
UBound Function
WorksheetFunction Object (Excel)
Would something like this work:
'A1 = A,B,C,D,E,F,G
'A2 = 1,2,3,4,5,6,7
'A3 = A!B!C!D!E!F!G
'Test procedure will result in:
'A - G in cells A1:A7
'1,2,3,4,5,6,7 in cell A8.
'A - G in cells A9:A15
Sub Test()
TextToColumns Sheet1.Range("A1")
TextToColumns Sheet1.Range("A9"), "!"
End Sub
Public Sub TextToColumns(Target As Range, Optional Delimiter As String = ",")
Dim rng As Range
Dim lCount As Long
Dim x As Long
'How many delimiters in target string?
lCount = Len(Target) - Len(Replace(Target, Delimiter, ""))
'Add the blank rows.
For x = 1 To lCount + 1
Target.Offset(1).Insert Shift:=xlDown, CopyOrigin:=xlFormatFromLeftOrAbove
Next x
'Split the string.
Target.TextToColumns Target, xlDelimited, xlTextQualifierNone, , , , , , True, Delimiter
'Use TRANSPOSE formula to paste to rows and then remove formula.
With Target.Offset(1).Resize(lCount + 1, 1)
.FormulaArray = "=TRANSPOSE(R" & Target.Row & "C:R" & Target.Row & "C" & lCount + 1 & ")"
.Value = .Value
End With
'Delete the original text string.
Target.EntireRow.Delete
End Sub
Edit:
To use from the Macro dialog box you could add this small procedure:
Public Sub Test()
Dim y As Long
y = ActiveSheet.UsedRange.Rows.Count
With ActiveSheet
For y = 5 To 1 Step -1
TextToColumns .Cells(y, 1)
Next y
End With
End Sub
Note: ActiveSheet.UsedRange.Rows.Count is a terrible way to find the last row.
See this thread: Error in finding last used cell in VBA

How do I use a loop to paste my selected "text" into cells?

I'm trying to input text for example "code" into a column, using the Counta formula in two cells in my historic tab.
mystart and mystop both have Counta formulas in the cells and I want to paste the word "code" for example in all the cells between the two values, but I don't how to finish my code.
The text "code" will change thought out my code to something else
Sub MON_ImportHistory()
Sheets("Historic").Range("B2:AZ3000").ClearContents
'
If Worksheets("Help").Cells(18, 9) = "Data already present" Then
MsgBox ("Import aborted: Data for selected date already present")
Else
Dim myRange As Range
Dim NumRows As Integer
Dim myCopyrange As Range
Dim mystart As Range
Dim mystop As Range
m_import = Worksheets("Help").Cells(17, 9)
m_criteria = "=" & m_import
Workbooks.Open Filename:= _
"file on my data base"
Sheets("Historic").Select
If Application.WorksheetFunction.CountIf(Range("A:A"), m_import) > 0 Then
m_Filterrange = "$A$1:$AV$" & Trim(Str(Worksheets("Historic").Cells(1, 18) - 1))
ActiveSheet.Range(m_Filterrange).AutoFilter Field:=1, Criteria1:= _
m_criteria, Operator:=xlAnd
m_extractrange = "$A$90000:$AV$" & Trim(Str(Worksheets("Historic").Cells(1, 18) - 1))
Range(m_extractrange).Select
Selection.Copy
Windows("My File name").Activate
Sheets("Historic").Select
mystart = Worksheets("Historic").Cells(1, 19)
mystop = Worksheets("Historic").Cells(1, 20)
For x = mystart To mystop
myDestination = "B" & Trim(Str(Worksheets("Historic").Cells(1, 19)))
Range(myDestination).Select
ActiveSheet.Paste
Windows("My file name").Activate
There's a lot going on here...First, it looks like you are using CountA formulas in the worksheet to do what you should be doing in VBA. There is a lot of work around being done to make that work. You are probably getting an error on the second line because you are trying to clear the contents of a sheet that has not been opened yet. To find the number of occupied rows in a column you can use LastRow = Sheets("Historic").Cells(Rows.Count,1).End(xlUp).Row. You should try to minimize or eliminate the use of Select and Activate. If you try to clean that up it may make more sense to you and me. Don't forget to end the loop with Next x.
Sub MON_ImportHistory()
Dim LastRow, LastCol As Integer
Dim ws1, ws2 As Worksheet
Dim m_import As String
Dim x As Integer
Set ws1 = Worksheets("Help")
'
If ws1.Cells(18, 9) = "Data already present" Then
MsgBox ("Import aborted: Data for selected date already present")
Exit Sub
Else
Workbooks.Open "C:\Somefile.xlsx" 'Here is where you put your filename
Set ws2 = Workbooks("Somefile.xlsx").Worksheets("Historic")
LastRow = ws2.Cells(Rows.Count, 1).End(xlUp).Row
LastCol = ws2.Cells(1, Columns.Count).End(xlToLeft).Column
ws2.Range(ws2.Cells(2, 2), ws2.Cells(LastRow, LastCol)).ClearContents
m_import = ws1.Cells(17, 9)
For x = 2 To LastRow
ws2.Cells(x, 2) = m_import
Next x
End If
End Sub

Resources