my excelsheets look like this:
Then I tried it with the formula:
=SUMIF(B1:B200;B1:B200<>"#NV";B:B)
But it seems wrong. I want to summarize all numbers if is not #NV in each section (11111, 22222). Each numbers 2,3,4,6,9 belongs to the group 11111. I want to determine the sum of all the values which is allocated to the group 11111.
For 11111: Sums over the cell B2:B6, for 22222, sums over B8:B12. That is what I want to do.
Hope you can help me. If it is possible to have a macro, I would be very thankful, if you can share with your ideas.
Thanks
INDIRECT ft. ARRAY FORMULA
It is highly advised that you replace the occurrences of $B:$B with the actual ranges e.g. $B$1:$B$100
The following two formulas are to be copied to C1 and D1. The second is an array formula and you have to use CTRL+SHIFT+ENTER if you don't have 365.
It's a terrible solution, because the first formula uses indirect which is a volatile (updating after every calculation) function and the second is an array formula. The second should be put in the first instead of D1 but it doesn't work (I'm using Excel 2019). So you have to use an extra column for it. Maybe 365 could accept it.
Hopefully someone will use this information to create a more efficient solution.
The Formulas
These two are the ones you need in C1 and D1:
=IF($B2<>"#NV","",SUM(INDIRECT(ADDRESS(D1,2,4)&":"&ADDRESS(ROW(),2,4))))
=IF($B2<>"#NV","",SMALL(IF($B:$B="#NV",ROW($B:$B)-ROW(INDEX($B:$B,1,1))+2),COUNTIF($B$1:$B1,"#NV")))
This one counts the number of occurrences of "#NV" from B$1 to the current row.
=COUNTIF(B$1:B6,"#NV")
This one is an array formula and returns the row after the first occurrence of "#NV". Note the +2 instead of +1 is for the row below, and the 1 after it represents the first occurrence.
=IFERROR(SMALL(IF($B:$B="#NV",ROW($B:$B)-ROW(INDEX($B:$B,1,1))+2),1),"")
Similarly to the previous, this one is an array formula and returns the row after the last occurrence of "#NV".
=IFERROR(SMALL(IF($B:$B="#NV",ROW($B:$B)-ROW(INDEX($B:$B,1,1))+2),COUNTIF(B$1:B6,"#NV")),"")
This is the SUM/INDIRECT formula: Note the first number 2 is the number to be replaced by the array formula, and the 6 is to be replaced by the last ROW formula.
=SUM(INDIRECT(ADDRESS(2,2,4)&":"&ADDRESS(6,2,4)))
The ROW formula.
=ROW()
Try the next VBA code, please. It creates a range from the error cells of B:B filled column range and exploit the discontinuous range Areas addresses. You did not answer my question regarding the possibility to have two consecutive such errors. Anyhow, the following code is able to deal with such situations, too. If no possible consecutive error rows, the code can be simplified:
Sub testErrorRng()
Dim sh As Worksheet, rngErr As Range, lastRow As Long, Ar As Range
Dim startSum As String, endSum As String, i As Long
Set sh = ActiveSheet
lastRow = sh.Range("B" & rows.count).End(xlUp).row
Set rngErr = sh.Range("B1:B" & lastRow).SpecialCells(xlFormulas)
For Each Ar In rngErr.Areas
i = i + 1
If i = 1 Then
If Ar.cells.count = 1 Then
If Ar.Address = "$B$1" Then
startSum = Ar.Offset(1).Address
Else
Ar.Offset(-1, 1).Formula = "=Sum(B1:" & Ar.Offset(-1).Address & ")"
startSum = Ar.Offset(1).Address
End If
Else
startSum = Ar.cells(Ar.cells.count + 1).Address
End If
Else
If Ar.cells.count = 1 Then
endSum = Ar.Offset(-1).Address
Ar.Offset(-1, 1).Formula = "=Sum(" & startSum & ":" & endSum & ")"
startSum = Ar.Offset(1).Address
Else
endSum = Ar.cells(1).Offset(-1).Address
Ar.cells(1).Offset(-1, 1).Formula = "=Sum(" & startSum & ":" & endSum & ")"
startSum = Ar.cells(Ar.cells.count + 1).Address
End If
End If
Next
If rngErr.Areas(rngErr.Areas.count).row < lastRow Then
endSum = sh.Range("B" & lastRow).Address
sh.Range("C" & lastRow).Formula = "=Sum(" & startSum & ":" & endSum & ")"
End If
End Sub
Please, test it and send some feedback.
Edited:
Shorter version for no consecutive error rows:
Sub testErrorRngSimple()
Dim sh As Worksheet, rngErr As Range, lastRow As Long, Ar As Range
Dim startSum As String, endSum As String, i As Long
Set sh = ActiveSheet
lastRow = sh.Range("B" & rows.count).End(xlUp).row
Set rngErr = sh.Range("B1:B" & lastRow).SpecialCells(xlFormulas)
For Each Ar In rngErr.Areas
i = i + 1
If i = 1 Then
If Ar.Address = "$B$1" Then
startSum = Ar.Offset(1).Address
Else
Ar.Offset(-1, 1).Formula = "=Sum(B1:" & Ar.Offset(-1).Address & ")"
startSum = Ar.Offset(1).Address
End If
Else
endSum = Ar.Offset(-1).Address
Ar.Offset(-1, 1).Formula = "=Sum(" & startSum & ":" & endSum & ")"
startSum = Ar.Offset(1).Address
End If
Next
If rngErr.Areas(rngErr.Areas.count).row < lastRow Then
endSum = sh.Range("B" & lastRow).Address
sh.Range("C" & lastRow).Formula = "=Sum(" & startSum & ":" & endSum & ")"
End If
End Sub
Related
I would like to create a simple formula to calculate the net change on a certain row, and print it in the same row (column H). But what I plug into the formula will depend on the value at each row's column A.
This is the code that I tried:
Sub totalPnL()
Dim LR As Long
LR = Range("D" & Rows.Count).End(xlUp).Row
If Range("A2:A" & LR).Value = "WIN" Then
Range("H2:H" & LR).Formula = "=ABS(D2-F2)*G2"
ElseIf Range("A2:A" & LR).Value = "LOSS" Then
Range("H2:H" & LR).Formula = "=-ABS(D2-E2)*G2"
End If
Range("T2") = Application.WorksheetFunction.Sum(Range("H:H"))
End Sub
I feel something is wrong with the If statement, but I'm not sure how to edit it
Thanks for all the help
As written in the comment a formula will be easier and quicker but if you want to have your code "fixed" then the following code would do it.
Sub totalPnL()
Dim LR As Long, i As Long
LR = Range("D" & Rows.Count).End(xlUp).Row
' the original statement
' Range("A2:A" & LR).Value = "WIN"
' cannot work as Range("A2:A" & LR) will be an array in case LR > 2
' in this case you have to loop
' AGAIN: usage of a formula will be better
For i = 2 To LR
If Range("A" & i).Value = "WIN" Then
Range("H" & i).FormulaR1C1 = "=ABS(RC[-4]-RC[-2])*RC[-1]"
ElseIf Range("A" & i).Value = "LOSS" Then
Range("H" & i).FormulaR1C1 = "=ABS(RC[-4]-RC[-2])*RC[-1]"
End If
Next i
Range("T2") = Application.WorksheetFunction.Sum(Range("H:H"))
End Sub
To make the code easier to read you could use a Select statement
For i = 2 To LR
Select Case Range("A" & i).Value
Case "WIN"
Range("H" & i).FormulaR1C1 = "=ABS(RC[-4]-RC[-2])*RC[-1]"
Case "LOSS"
Range("H" & i).FormulaR1C1 = "=ABS(RC[-4]-RC[-2])*RC[-1]"
End Select
Next i
Another formula you could use would be
=IF(A2="WIN";1;(IF(A2="LOSS";-1)))*ABS(D2-F2)*G2
How to combine text of multiple cells for instance translate this excel formula to VBA code
Cell F3 should =Q3&"_"&A3&"_"&D3&"_"&(ROUND(M3/1000,1))&"k"
But I would Like it to repeat in every instant so in F99= Q99&"_"&A99&"_"&D99&"_"&(ROUND(M99/1000,1))&"k"
Using VBA Code:
For Each Cel In Range("A1:A100")
If Cel.Value <> "" Then Cel.Offset(0, 5).Value = *Excel Formula NEED* "Q3&"_"&A3&"_"&D3&"_"&(ROUND(M3/1000,1))&"k""*
Saying that if value in column A insert text in same row Column E
I also think that a formula (adapted a little to return "" in case of empty cells) would be the best. But, even if you did not answer my question and you want a solution in VBA, please, test the next code:
Sub testConcatenate()
Dim sh As Worksheet, cel As Range, lastRow As Long
Set sh = ActiveSheet 'please use your sheet here
lastRow = sh.Range("A" & Rows.count).End(xlUp).Row
For Each cel In Range("A1:A" & lastRow)
If cel.value <> "" Then
cel.Offset(0, 5).value = Range("Q" & cel.Row).value & "_" & cel.value & "_" & _
Range("D" & cel.Row).value & "_" & Round(Range("M" & cel.Row).value / 1000, 1) & "k"
End If
Next
End Sub
You maybe clarify here the meaning of "" in your formula, what "k" means and what the last character "*" is used for. I did not use it...
I am trying to replicate in VBA the simple function in excel which allows you to repeat a function through an entire column, and stops when the columns on the side are empty. Specifically, I want to repeat an if - else if function for the entire relevant part of the column
Here's an attempt which does not really work
Sub RepeatIfElseif
Range("A1").Select
If selection > 0 Then
Range("B1").Select
ActiveCell.FormulaR1C1 = "X"
Range("A1").Select
ElseIf selection <= 0 Then
Range("B1").Select
ActiveCell.FormulaR1C1 = "Y"
End If
Range("B1").Select
selection.AutoFill Destination:=Range("B1:B134")
Is there any way I can do it with a loop?
You do not need to loop to drop formulas in. You just need to know where the last row is!
Pick a column that is most likely to represent your last row (I am using Column A in my example) and then you can dynamically drop-down your equation in one line without the loop.
The below will fill in the equation A2 + 1 in Column B starting from 2nd row (assuming you have a header row) down to the last used row in Column A
Option Explicit
Sub Formula_Spill()
Dim ws As Worksheet: Set ws = ThisWorkbook.Sheets("Sheet1") '<-- Update sheet!
Dim LR As Long
LR = ws.Range("A" & ws.Rows.Count).End(xlUp).Row '<-- Update column!
ws.Range("B2:B" & LR).Formula = "=A2+1" '<-- Update formula!
End Sub
If you want to use a loop, you can use something like the code below:
For i = 1 To 134
If Range("A" & i).Value > 0 Then
Range("B" & i).FormulaR1C1 = "X"
Else
Range("B" & i").FormulaR1C1 = "Y"
End If
Next I
It can be done without a loop, something like:
Range("B1:B134").Formula = "=IF(A1>0," & Chr(34) & "X" & Chr(34) & "," & Chr(34) & "Y" & Chr(34) & ")"
Not sure what formula you are trying to achieve with .FormulaR1C1 = "Y" ?
I'm trying to improve my English, I swear...
I would do something like this:
dim row as long
dim last_row as Long
last_row = ActiveSheet.Range("A1048576").End(xlUp).Row
For row = 1 to last_row
If Range("A" & row).Value > 0 Then
ActiveSheet.Range("B" & row).Value = "X"
Else
ActiveSheet.Range("B" & row).Value = "Y"
End If
Next row
Hope this helps.
Goal: Populate F and G columns with proper formulas depending on total PROD-TIME for a block
This is another issue that has come up after one of my previous questions:
How to loop through "blocks" of rows in excel dataset instead of each row individually (VBA)?
I have been able to loop through blocks of rows and can now get the sum of the PROD-TIME for that particular block. This sum is necessary to determine which formula needs to be used in the F and G columns.
This is best illustrated in this workbook,
https://www.dropbox.com/s/vgnqi00h8xosja3/wip%20Gantt%20Template.xlsx?dl=0 , where I have shown how I want the formulas to end up in the F and G columns. But for some reason when I run the macro, it just completely breaks. Some of the formulas don't even use reference cells and use the cell value instead, or reference cells don't even appear. Are the blank F and G columns confusing the macro? How can I make sure that every F and G cell gets filled with something? Errors are fine
Sub getStartEndDate()
Dim WrkSht As Excel.Worksheet
Dim RngColumn As Range, RngBlock As Range
Dim totalHrs As Integer 'total PROD-TIME for the given RngBlock
Dim lastRow As Long
lastRow = ActiveSheet.Cells.SpecialCells(xlCellTypeLastCell).Row
Set WrkSht = ActiveWorkbook.Worksheets(1)
' Populate the last row by itself first since the With-statement below needs to reference rows below current row
Range("E" & lastRow).Formula = "=ROUNDUP(D" & lastRow & "/12,0)"
Range("G" & lastRow).Value = Range("C" & lastRow).Value
Range("F" & lastRow).Formula = "=WORKDAY(G" & lastRow & ", -E" & lastRow & ")"
Columns("F:F").NumberFormat = "yyyy-mm-dd"
With WrkSht
Set RngColumn = .Range("B2:B" & lastRow)
'Starts the first block with the first cell in a column.
Set RngBlock = RngColumn.Cells(1)
'Checks every cell in a column.
For Each rngcell In RngColumn
If rngcell.Offset(0,1).Value <> "" Then
'Checks whether a cell's value equals the cell below it.
If rngcell.Value = rngcell.Offset(1, 0).Value Then
'If equal, includes the cell below in the block.
Set RngBlock = Union(RngBlock, rngcell.Offset(1, 0))
Else
'If not equal, that means the block RngBlock ends
' totalHrs is the sum of the "PROD-TIME" for that particular block
totalHrs = WorksheetFunction.Sum(Range(CStr(Trim(Chr(64 + RngBlock.Column + 2))) _
& CStr(Trim(Str(RngBlock.Row))) & ":" _
& CStr(Trim(Chr(64 + 2 + RngBlock.Column + RngBlock.Columns.Count - 1))) _
& CStr(Trim(Str(RngBlock.Row + RngBlock.Rows.Count - 1)))))
If totalHrs < 12 Then
' If total production time (PROD-TIME) is less than 12 hours, then the start and end date should be the same for all rows in that block
rngcell.Offset(0, 4).Value = rngcell.Offset(0, 1).Value
rngcell.Offset(0, 5).Value = rngcell.Offset(0, 1).Value
Else
' If total production time is greater than 12 hours, then start and end dates are based on a different formula
' e.g. Given row 11, F column formula looks like: =WORKDAY(G11, -E11), G column looks like: =IF(B11=B12,F12,C11)
rngcell.Offset(0, 4).Formula = "=WORKDAY(" & rngcell.Offset(0, 5) & ", -" & rngcell.Offset(0, 3) & ")"
rngcell.Offset(0, 5).Formula = "=IF(" & rngcell & "=" & rngcell.Offset(1, 0) & "," & rngcell.Offset(1, 4) & "," & rngcell.Offset(0, 1) & ")"
End If
'Starts the next block with the cell below.
Set RngBlock = rngcell.Offset(1, 0)
End If
End If
Next rngcell
End With
End Sub
I am trying to compare cell A1 with B1 and if it is true populate cell F1 with the A1 value. But irrespective of my input values the if condition becomes true.
Sub Macro3()
Dim i As Integer
i = 1
For i = 1 To 10
If (Range("A" & i).Select = Range("B" & i).Select) Then
Range("A" & i).Select
Selection.Copy
Range("F" & i).Select
ActiveSheet.Paste
End If
Next i
End Sub
Instead of selecting, copying, and pasting, you can compare the Value property of the cells, then set the F column Value accordingly:
Dim i As Integer
For i = 1 To 10
If Range("A" & i).Value = Range("B" & i).Value Then
Range("F" & i).Value = Range("A" & i).Value
End If
Next
Consider this a compliment to Nick's answer (accept his if you find it to work, which you should). I wanted to help explain some of the things that are wrong in your code.
Before FIX:
Sub Macro3()
Dim i As Integer
i = 1
For i = 1 To 10
If (Range("A" & i).Select = Range("B" & i).Select) Then
Range("A" & i).Select
Selection.Copy
Range("F" & i).Select
ActiveSheet.Paste
End If
Next i
End Sub
AFTER FIX
Sub Macro4()
Dim i As Long
For i = 1 To 10
If Range("A" & i).Value = Range("B" & i).Value Then
Range("F" & i).Value = Range("A" & i).Value
End If
Next
End Sub
POINTS:
Use Long instead of Integer (small optimization since VBA will convert the int to a long anyway)
No need to declare i = 1 twice in a row
You should be comparing values, not simply selecting cells. There is rarely, if ever, a need to use the .Select keyword. You can access all object's properties directly.
Copy and paste is a heavy operation. Since you are in VBA, may as well just assign the value that is in A to the cell in column B. It's faster, and more effecient.
I hope this helps. BTW, you can simple enter:
=IF(A1=B1,A1,"")
in F1 and drag the formula down to get a similar result.
You can use a variant array to address your performance issue that you raise above. This code will run the same as Nicks except it will skip blanks cell, ie it will
update the F value if A and B are the same
skip updates if the A cell is blank
leave the existing F values in place if A<>B
It wasn't clear to me how you are comparing rows accross two sheets, can you expand on this?
Sub MyArray()
Dim X As Variant
Dim Y As Variant
Dim lngrow As Long
X = Range([a1], Cells(Rows.Count, "B").End(xlUp))
Y = Range([f1], [f1].Offset(UBound(X, 1) - 1, 0))
For lngrow = 1 To UBound(X, 1)
If Len(X(lngrow, 1)) > 0 Then
If X(lngrow, 1) = X(lngrow, 2) Then Y(lngrow, 1) = X(lngrow, 1)
End If
Next
Range([f1], [f1].Offset(UBound(X, 1) - 1, 0)) = Y
End Sub