Extended Quantity - excel

Good afternoon,
I'm wondering if anyone knows of a better approach for finding the extended quantity of a row (based on level.) The dataset looks similar to this:
Level
Component
Quantity
ExtQty (formula)
Purchase
1
complete component 1
2
2
M
2
component 2
4
8
P
3
component 3
4
0
M
2
component 4
4
8
M
3
component5
10
80
M
1
complete component 2
1
1
M
2
component2
4
4
P
3
component3
4
0
M
2
component8
4
4
M
3
component9
10
40
M
I'm using:
=#IF(A2-1<1,C2,IF(XLOOKUP(A2-1,$A$2:$A2,$E$2:$E2,"ERR",0,-1)="P",0,XLOOKUP(A2-1,$A$2:$A2,$D$2:$D2,"ERR",0,-1)*$C2))
Currently it's still inserting over 184,000 rows and it's been about 50 minutes, still going...
(edit: finished at 53 minutes)
The goal is to take the quantity and multiply it with the quantity of the levels above. If the parent is a purchase part, set quantity to 0.
Does anyone have any tips to make this a little faster, other than working with a smaller dataset?

It seems to me that the table does not express the same behavior in each row, for example, I understand that in Level 3 = 10 * 2 * 2 * 2 = 80, but the Level 2 = 4 * 2. It does not have a fixed rule. In case you want to use the first it's "=POWER(2;A6)*C6" and otherwise "=POWER(2;A5-1)*C5"

I've decided to go with VBA. Currently running 184000 rows in 20 seconds rather than 53 minutes.
I'm not great at VBA, but this is what I came up with:
Sub ExtQty()
Dim cell As Range
Dim sheet As Worksheet
Dim levelStack, stopStack As Object
Dim lvlCol, extQtyCol, qtyCol, procCol, bomCol, itemCol, compCol As Integer
Dim Progress As Double
Dim i, j As Long
Set sheet = Application.ActiveSheet
ProgressForm.Show (vbModeless)
lRow = lastRow()
qtyCol = GetColumn("Quantity")
extQtyCol = qtyCol + 1
sheet.Columns(extQtyCol).Insert shift:=xlToRight, CopyOrigin:=xlFormatFromLeftOrAbove
sheet.Cells(1, extQtyCol).Value = "ExtQty"
lvlCol = GetColumn("Level") 'after ExtQty insert for correct column
procCol = GetColumn("Procurement")
compCol = GetColumn("Component")
itemCol = GetColumn("Item")
Set levelStack = CreateObject("System.Collections.Stack")
sheet.Range(Cells(2, itemCol), Cells(lRow, itemCol)).Font.Bold = True
sheet.Range(Cells(2, compCol), Cells(lRow, compCol)).Font.Bold = True
sheet.Range(Cells(2, extQtyCol), Cells(lRow, extQtyCol)).Font.Bold = True
i = 1
For Each cell In sheet.Range(sheet.Cells(2, lvlCol), sheet.Cells(lRow, lvlCol))
Call ProgressForm.Progress(i, cell.Row, lRow, 1000) 'every 1000 loops; every loop hurts performance
If cell.Offset(1, 0).Value > cell.Value Then 'push parent level onto stack
If cell.Value = 1 Then
sheet.Cells(cell.Row(), extQtyCol).Value = sheet.Cells(cell.Row(), qtyCol).Value
Else
If levelStack.Peek()(1) = "F" Then
sheet.Cells(cell.Row(), extQtyCol).Value = 0
Else
sheet.Cells(cell.Row(), extQtyCol).Value = sheet.Cells(cell.Row(), qtyCol).Value * levelStack.Peek()(0)
End If
End If
levelStack.Push Array(sheet.Cells(cell.Row(), extQtyCol).Value, sheet.Cells(cell.Row(), procCol).Value)
ElseIf cell.Offset(1, 0).Value < cell.Value Then 'check if leaving current level and pop
If cell.Value = 1 Then
sheet.Cells(cell.Row(), extQtyCol).Value = sheet.Cells(cell.Row(), qtyCol).Value
If cell.Row >= lRow Or cell.Offset(1, 0).Value = "" Then 'fixed error if last row AND level 1
Exit For
End If
ElseIf levelStack.Peek()(1) = "F" Then
sheet.Cells(cell.Row(), extQtyCol).Value = 0
Else
sheet.Cells(cell.Row(), extQtyCol).Value = levelStack.Peek()(0) * sheet.Cells(cell.Row(), qtyCol).Value
End If
If cell.Value > cell.Offset(1, 0).Value Then
diff = cell.Value - cell.Offset(1, 0).Value
End If
For j = 1 To diff 'used for when level drops more than one level
If cell.Row() = lRow Then
topStack = levelStack.Clear()
Exit For
Else
topStack = levelStack.Pop()
End If
Next
Else 'All others - don't push or pop
If cell.Value = 1 Then
sheet.Cells(cell.Row(), extQtyCol).Value = sheet.Cells(cell.Row(), qtyCol).Value
ElseIf levelStack.Peek()(1) = "F" Then
sheet.Cells(cell.Row(), extQtyCol).Value = 0
Else
sheet.Cells(cell.Row(), extQtyCol).Value = levelStack.Peek()(0) * sheet.Cells(cell.Row(), qtyCol).Value
End If
End If
sheet.Cells(cell.Row(), extQtyCol) = Round(sheet.Cells(cell.Row(), extQtyCol), 3)
Next
ProgressForm.Hide
End Sub

Related

Running Count - Sum not summing when inputting less than expected quantity

I am using a form that takes an item and quantity. I'm trying to create a running count that consists of the quantity (denoted as qtytxt1, qtytxt2, etc) of each item. Each item has its own quantity input field denoted with the ending number (i.e., qtytxt1 applies to item 1).
I am trying to have a cell located in (emptyRow, 27) to output the sum of the total quantity of all items inputted into the form (i.e., Item 1 qty = 2,000; Item 2 qty = 3,000; Expected Output = 5,000).
There are a total of 10 input fields for "Item/Qty," however, not all 10 fields are expected to be used. I have created a code that seems to work as I prefer, however, I would receive a sum error message when entering less than 10 items.
Dim emptyRow As Long
Dim data As Worksheet
Dim runCount As Long
Worksheets("mining history").Activate
emptyRow = WorksheetFunction.CountA(Range("i:i")) + 6
Set data = Sheets("data")
runCount = 0
If qtytxt2.Value = "" Then
qtytxt2.Value = 0
ElseIf qtytxt3.Value = "" Then
qtytxt3.Value = 0
ElseIf qtytxt4.Value = "" Then
qtytxt4.Value = 0
ElseIf qtytxt5.Value = "" Then
qtytxt5.Value = 0
ElseIf qtytxt6.Value = "" Then
qtytxt6.Value = 0
ElseIf qtytxt7.Value = "" Then
qtytxt7.Value = 0
ElseIf qtytxt8.Value = "" Then
qtytxt8.Value = 0
ElseIf qtytxt9.Value = "" Then
qtytxt9.Value = 0
ElseIf qtytxt10.Value = "" Then
qtytxt10.Value = 0
End If
If IsEmpty(Range("E:E")) Then
'Is Empty
runCount = 0
Else
' Not Empty
runCount = WorksheetFunction.Sum(qtytxt1.Value, qtytxt2.Value, qtytxt3.Value, qtytxt4.Value, qtytxt5.Value, qtytxt6.Value, qtytxt7.Value, qtytxt8.Value)
Cells(emptyRow, 27).Value = runCount
End If
You can use a loop:
Dim emptyRow As Long
Dim data As Worksheet, wsMH As Worksheet
Dim runCount As Long, n As Long, v
Set data = Sheets("data")
Set wsMH = Worksheets("mining history")
'no need to Activate...
emptyRow = wsMH.Cells(Rows.count, "I").End(xlUp).row + 1
If Application.CountA(wsMH.Range("E:E")) = 0 Then
runCount = 0
Else
'loop all the entry textboxes
For n = 1 To 10
v = Me.Controls("qtytxt" & n).Value
If Len(v) > 0 And IsNumeric(v) Then runCount = runCount + v
Next n
wsMH.Cells(emptyRow, 27).Value = runCount
End If
I suspect you need this:
If qtytxt2.Value = "" Then
qtytxt2.Value = 0
End If
If qtytxt3.Value = "" Then
qtytxt3.Value = 0
End If
etc

Check values in columns, allowing for not all columns being present

I'm trying to clean up raw data exported from an online database.
There can be up to five columns. If all cells in a row have a value of 0, I want to delete that row.
When the user exports the data, they can choose to exclude columns, and the columns can be in any order.
For example, if the data contains only two of the possible five columns, I want to check just those two for 0s.
Could do a a big loop looking at every row and seeing if all 5 columns in that row are blank
Sub test()
Dim sh As Worksheet
Set sh = ThisWorkbook.Sheets("sheetname")
Dim LastRow As Integer
LastRow = sh.UsedRange.Rows.Count - 1
For i = 1 To LastRow
If (sh.Cells(i, 1).Value = "" And sh.Cells(i, 2).Value = "" And sh.Cells(i, 3).Value = "" And _
sh.Cells(i, 4).Value = "" And sh.Cells(i, 5).Value = "") Then
sh.Cells(i, 1).EntireRow.Delete
i = i - 1
Dim newLastRow As Integer
newLastRow = sh.UsedRange.Rows.Count - 1
If i = newLastRow Then
Exit For
End If
End If
Next i
MsgBox ("Done")
End Sub
#kyle campbell, thank you for your input! It didn't quite get me there, but it did get my wheels turning. Here is the solution I came up with, if anyone's curious:
I set a variable to represent the column number for each of the 5 possible columns using Range.Find. If the Find came up with nothing, I set the variable to 49, since the maximum number of columns this report can have is 48.
Then I did a nested If to test if the value in each cell was either 0 or null (because if the column number is 49, there won't be any data there). If all Ifs were true, I deleted the row. I also added a counter and message box, just to make sure this worked.
Sub DeleteRows()
Dim O As Long
Dim E As Long
Dim H As Long
Dim B As Long
Dim P As Long
lRow = Range("A1").CurrentRegion.Rows.Count
If Range("1:1").Find("SUM(OBLIGATIONS)") Is Nothing Then
O = 49
Else
O = Range("1:1").Find("SUM(OBLIGATIONS)").Column
End If
If Range("1:1").Find("SUM(EXPENDITURES)") Is Nothing Then
E = 49
Else
E = Range("1:1").Find("SUM(EXPENDITURES)").Column
End If
If Range("1:1").Find("SUM(HOURS)") Is Nothing Then
H = 49
Else
H = Range("1:1").Find("SUM(HOURS)").Column
End If
If Range("1:1").Find("SUM(BUDGET_RESOURCES)") Is Nothing Then
B = 49
Else
B = Range("1:1").Find("SUM(BUDGET_RESOURCES)").Column
End If
If Range("1:1").Find("SUM(PRIOR_YEAR_RECOVERY)") Is Nothing Then
P = 49
Else
P = Range("1:1").Find("SUM(PRIOR_YEAR_RECOVERY)").Column
End If
Dim j As Integer
j = 0
For i = lRow To 2 Step -1
If Cells(i, O) = 0 Or Cells(i, O) = "" Then
If Cells(i, E) = 0 Or Cells(i, E) = "" Then
If Cells(i, H) = 0 Or Cells(i, H) = "" Then
If Cells(i, B) = 0 Or Cells(i, B) = "" Then
If Cells(i, P) = 0 Or Cells(i, P) = "" Then
Rows(i).Delete
j = j + 1
End If
End If
End If
End If
End If
Next i
MsgBox "Macro complete, " & j & " lines deleted."
End Sub

Insert a row n times

I´ve an Excel file with 10 Columns. In columns 2, 3, 4 I have a number or a dash.
If the sum of these 3 cells is greater than 1, I need to replace that entire row with n rows that have only one of the columns with the value 1 but the other cells stay the same.
Example
1 - - #-> leave it as is
- 2 - #-> replace that row with 2 rows : - 1 - ; - 1 -
2 - 1 #-> replace that row with 3 rows : 1 - - ; 1 - - ; - - 1;
I managed to iterate from bottom up, but I´m having trouble storing a row in memory, manipulate it and insert below.
Sub Test()
Dim rng As Range
Dim count20, count40, count45, total, i As Integer
Set rng = Range("A3", Range("A3").End(xlDown))
For i = rng.Cells.count To 1 Step -1
count20 = 0
count40 = 0
count45 = 0
total = 0
count20 = Cells(rng.Item(i).Row, 10).Value
If count20 > 1 Then
total = total + count20
End If
count40 = Cells(rng.Item(i).Row, 11).Value
If count40 > 1 Then
total = total + count40
End If
count45 = Cells(rng.Item(i).Row, 12).Value
If count45 > 1 Then
total = total + count45
End If
If total <> 0 Then
MsgBox total
End If
Next i
End Sub
EDIT 2
I’ve provided alternative code based on your latest comment. It uses columns J-L (10-12) as the numeric cells to be changed, and columns A-I (1-9) and M-AD (13-30) as the cells with text to be preserved. As before, sheet 1 starting in row 3 is assumed, and you can change this to whatever you need.
Option Explicit
Sub testJtoL()
Dim LastRow As Long, i As Long, j As Long, c As Long, _
insertR As Long, TopRow As Long, BottomRow As Long
Dim b As Range
Dim ws As Worksheet
'*** This code is based your values being in Columns J-L (10-12) in sheet 1 ***
'Set sheet 1 as ws
Set ws = Sheet1
'Sheet1 column J is used here to get your last row
LastRow = ws.Cells(Rows.Count, 10).End(xlUp).Row
'*** This code is based your values starting in Row 3 ***
For c = LastRow To 3 Step -1
'Determine number of rows to insert based on sum of that row
insertR = Application.WorksheetFunction.Sum(Range(Cells(c, 10), Cells(c, 12))) - 1
If insertR = 0 Then GoTo skip
'STEP 1 insert the correct number of rows
With ws.Range(Cells(c + 1, 1), Cells(c + insertR, 30))
.Insert Shift:=xlDown, CopyOrigin:=xlFormatFromLeftOrAbove
End With
'STEP 2 fill the values into the correct number of rows
insertR = insertR + 1
With ws.Range(Cells(c, 1), Cells(c, 30))
.Resize(insertR, 30).Value = .Value
End With
TopRow = c
If insertR = 0 And c = 3 Then
BottomRow = c
Else
BottomRow = c + insertR - 1
End If
'STEP 3 replace all numbers with 1 or "-"
'Replace numbers in column J
If ws.Range(Cells(c, 10), Cells(c, 10)).Value = "-" Then GoTo SkipA
i = ws.Range(Cells(c, 10), Cells(c, 10)).Value
j = 1
For Each b In ws.Range(Cells(TopRow, 10), Cells(BottomRow, 10))
If j <= i Then
b.Value = 1
b.Offset(0, 1).Value = "-"
b.Offset(0, 2).Value = "-"
Else
b.Value = "-"
End If
j = j + 1
Next b
SkipA:
'Replace numbers in column K
j = 1
For Each b In ws.Range(Cells(TopRow, 11), Cells(BottomRow, 11))
If b.Value = "-" Then GoTo SkipB
i = b.Value
If j <= i Then
b.Value = 1
b.Offset(0, 1).Value = "-"
Else
b.Value = "-"
End If
j = j + 1
SkipB:
Next b
'Replace numbers in column L
j = 1
For Each b In ws.Range(Cells(TopRow, 12), Cells(BottomRow, 12))
If b.Value = "-" Then GoTo SkipC
i = b.Value
If j <= i Then
b.Value = 1
Else
b.Value = "-"
End If
j = j + 1
SkipC:
Next b
skip:
Next c
End Sub

FIFO using Excel Formula or VBA

Here is my "IN" List -
PN Qty Price
A 100 5
B 150 6
C 150 7
D 50 -9
E 100 5
F 5 9
G 20 6
I 5 7
J 15 7
J 30 10
K 100 10
K 50 10
A 20 8
Here is my "OUT" List -
PN Qty
A 120
B 10
C 110
D 60
E 100
J 20
J 10
Expected Results -
Manual Formula to calculate Price for PN = "A" = ((100*5)+(20*8))/120
PN Qty Price Total
A 120 5.5 660
B 10 6 60
C 110 7 770
D 60 -9 -540
E 100 5 500
J 20 7.75 155
J 10 10 100
I want to implement FIFO logic to calculate the Total Price in "OUT" List based on "Quantity" in "IN" List.
When you have tables like this, the outgoing button with following code:
Private Sub Outgoing_Click()
Dim pn As String
Dim ammout As Long
Dim current As Long
pn = InputBox("Which Item do you want to take out?")
ammount = InputBox("How Item do you want to take out?")
Dim cells As Long
Dim fifo As Double
counter = 1 //line where your table starts
current = 0
fifo = 0
Do Until IsEmpty(cells(counter, 13).Value Or current = ammount)
If cells(counter, 13).Value = pn Then
If cells(counter, 14).Value > (ammount - current) Then
fifo = fifo + (ammount - current) * cells(current, 15).value
current = ammount
Else
fifo = fifo + cells(counter, 14).Value * cells(counter, 15).Value
current = current + cells(counter, 14)
cells(counter, 14).Value = 0
End If
counter = counter + 1
Loop
fifo = fifo / ammount
End Sub
Should be work.
I did not match lie if you have enough in your current list and others so the validation part is missing.
your "Expected Results" doesn't match your "Manual Formula"
following this latter
Option Explicit
Sub main()
Dim cell As Range
Dim dictSum As Object
Dim dictRept As Object
Set dictSum = CreateObject("Scripting.Dictionary")
Set dictRept = CreateObject("Scripting.Dictionary")
With Worksheets("IN")
For Each cell In .Range("A2", .Cells(.Rows.Count, 1).End(xlUp))
dictSum(cell.Value) = dictSum(cell.Value) + cell.Offset(, 1).Value * cell.Offset(, 2).Value
dictRept(cell.Value) = dictRept(cell.Value) + cell.Offset(, 1).Value
Next
End With
With Worksheets("OUT")
For Each cell In .Range("A2", .Cells(.Rows.Count, 1).End(xlUp))
cell.Offset(, 2) = dictSum(cell.Value) / dictRept(cell.Value)
cell.Offset(, 3) = cell.Offset(, 1) * cell.Offset(, 2)
Next
End With
End Sub

IF, Else, ElseIF Loop within a certain Date

I want to set the following condition but i can only get 2 condition to be done
The three condition are less than 7 days multiple by one value, the range between 2 dates multiply by one value and more than 30 days multiply by another value.
Not able to get all to work
Not sure what went wrong
' To create the following condition
'If less than 7 days interest = 0%
' if 8 to 30 days interest = 7%
'if more than 31 days interest = 9%
Sub Workbook_Open()
For i = 1 To 3 'Rows.Count
xdate = Cells(i, 1)
nulldate = DateAdd("d", -7, Date)
irate7late = DateAdd("d", -8, Date)
irate7early = DateAdd("d", -30, Date)
If Day(nulldate) < Day(xdate) Then
result = Cells(i, 2) * 1
ElseIf Day(irate7early) <= Day(xdate) And Day(xdate) <= Day(irate7late) Then
'30/9/2015 20/10/2015 20/10/2015 22/10/2015
result = Cells(i, 2) * 1.07
ElseIf Day(irate7early) > Day(xdate) Then
result = Cells(i, 2) * 1.09
End If
Cells(i, 3).Value = result
Next i
End Sub
Reversing your test may simplify them sometimes:
Sub Workbook_Open()
Dim delta as Long
Dim xdate as Date
For i = 1 To 3 'Rows.Count
xdate = Cells(i, 1).Value
delta = DateDiff("d", xdate, Date)
If delta > 30 Then
Cells(i,3).Value = Cells(i,2).Value * 1.09
ElseIf delta > 7 Then
Cells(i,3).Value = Cells(i,2).Value * 1.07
Else 'delta <= 7
Cells(i,3).Value = Cells(i,2).Value
End If
Next i
End Sub
And don't forget Option Explicit, it may save you a lot of time in debugging.
Try this below code for your requirement
Sub Workbook_Open()
Dim diffdate As Variant
For i = 1 To 3 'Rows.Count
xdate = Cells(i, 1).Value
diffdate = (DateDiff("d", xdate, Now()))
If diffdate < 7 Then
result = Cells(i, 2) * 1
ElseIf diffdate < 31 And diffdate > 7 Then
result = Cells(i, 2) * 1.07
Else
result = Cells(i, 2) * 1.09
End If
Cells(i, 3).Value = result
Next
End Sub

Resources