How To Calculate Running balance using SQL - sql

If I have total qty = 100. and it has been shipped in 4 phases line 40, 10, 25, 25 that equals to 100. when I am running this query:
Someone Helped me with this query. I want the same runnable for DB2.
SET totalQty = -1;
SELECT
IF(#totalQty<0, pl.quantity, #totalQty) AS totalQty,
pr.invoiceqty,
#totalQty:=(#totalQty - pr.invoiceqty) AS balance
FROM
purchaseorderline pl, replenishmentrequisition pr
I am getting result like this :
--total qty-- --invoice qty-- --balance qty--
100 40 60
100 10 90
100 25 75
100 25 70
The result I want :
--total qty-- --invoice qty-- --balance qty--
100 40 60
60 10 50
50 25 25
25 25 00

It would be good enough, if you provided some sample data in a table form and not just what you get on it.
WITH MYTAB (PHASE_ID, QTY) AS
(
-- Your initial data as the result of
-- your base SELECT statement
VALUES
(1, 40)
, (2, 10)
, (3, 25)
, (4, 25)
)
SELECT
QTY + QTY_TOT - QTY_RTOT AS "total qty"
, QTY AS "invoice qty"
, QTY_TOT - QTY_RTOT AS "balance qty"
FROM
(
SELECT
PHASE_ID
, QTY
-- Running total sum
, SUM (QTY) OVER (ORDER BY PHASE_ID) AS QTY_RTOT
-- Total sum
, SUM (QTY) OVER () AS QTY_TOT
FROM MYTAB
)
ORDER BY PHASE_ID
total qty
invoice qty
balance qty
100
40
60
60
10
50
50
25
25
25
25
0

A variation of Marks answer is:
WITH MYTAB (PHASE_ID, QTY) AS
(
-- Your initial data as the result of
-- your base SELECT statement
VALUES (1, 40)
, (2, 10)
, (3, 25)
, (4, 25)
)
SELECT QTY_TOT AS "total qty"
, QTY AS "invoice qty"
, coalesce(lead(QTY_TOT) over (order by phase_id),0) AS "balance qty"
FROM
( SELECT PHASE_ID
, QTY
-- Running total sum
, SUM (QTY) OVER (ORDER BY PHASE_ID desc) AS qty_tot
FROM MYTAB
)
ORDER BY PHASE_ID
It uses lead at the outer level instead of sum over the entire window at the inner level
Fiddle

Related

How to distribute sales with partitions

I have 2 tables:
1st table columns: ItemCode int, Amount float (I have over 1000 ItemCodes)
2nd table columns: ItemCode int, SoldAmount float, Price float (I have over 10000 sale rows for different items)
Example:
ItemId 1528's Amount in 1st table is 244. That items sales in the 2nd table is as below:
Sale 1 Amount = 120, Price = 10
Sale 2 Amount = 120, Price = 30
Sale 3 Amount = 100, Price = 20
Sale 4 Amount = 10, Price = 25
ItemCode
Amount
1528
244
1530
150
ItemCode
Date
Amount
Price
1528
2021.11.01
120
10
1530
2021.10.01
120
30
1528
2021.09.01
100
20
1530
2021.08.01
10
25
Tried cursor and loop , but no desired output.
The desired outcome is to distribute that 100 amount with the sales above like following:
Sale 1 Amount 60: 100 - 60 = 40 with price 5 --- So we continue to the next row and subtract whatever is left
Sale 2 Amount 30: 40 - 30 = 10 with price 6 --- So we continue to the next row and subtract whatever is left
Sale 3 Amount 20: 10 - 20 = -10 with price 7 --- So we stop here as the amount is equal to 0 or below .
As the result we should get this:
60 * 5 = 300
30 * 6 = 180
10 * 7 = 70 (that 10 is derived from whatever could be subtracted before it hits 0)
Desired table as below
ItemCode
Date
Amount
Price
1528
2021.11.01
120
10
1528
2021.10.01
120
30
1528
2021.09.01
4
20
My last attempt was as below
WITH CTE AS (
SELECT ItemCode, SUM(Amount) AS Amount
FROM table 1
GROUP BY STOCKREF )
SELECT *,
IIF(LAG(table1.Amount - table2.amount) OVER (PARTITION BY table1.Amount ORDER BY Date DESC) IS NULL, table1.Amount - table2.amount,
LAG(table1.Amount - table2.amount) OVER (PARTITION BY CTE.ItemCode ORDER BY Date DESC) - table2.AMOUNT) AS COL
FROM CTE JOIN (SELECT ItemCode, DATE_, AMOUNT, PRICE FROM table2) table 2 ON table1.ItemCode = table2.Amount
Hopefully this addresses the right question - if you're trying to create a running total per item_code, deducting the sale quantity from starting inventory from first-to-last sale, maybe this would work:
CREATE TABLE #items (item_code INT,
item_amount INT);
INSERT INTO #items (item_code, item_amount)
VALUES (1528, 244);
INSERT INTO #items (item_code, item_amount)
VALUES (1529, 240);
CREATE TABLE #sales (item_code INT,
sale_date DATE,
sale_amount INT,
sale_price DECIMAL(12,2));
INSERT INTO #sales (item_code, sale_date, sale_amount, sale_price)
VALUES (1528, '2021-12-01', 50, 5);
INSERT INTO #sales (item_code, sale_date, sale_amount, sale_price)
VALUES (1528, '2021-11-29', 120, 6.76292);
INSERT INTO #sales (item_code, sale_date, sale_amount, sale_price)
VALUES (1528, '2021-11-15', 120, 6.6453);
INSERT INTO #sales (item_code, sale_date, sale_amount, sale_price)
VALUES (1528, '2021-11-01', 100, 6.96875);
INSERT INTO #sales (item_code, sale_date, sale_amount, sale_price)
VALUES (1529, '2021-11-30', 48, 7.2);
INSERT INTO #sales (item_code, sale_date, sale_amount, sale_price)
VALUES (1529, '2021-11-18', 48, 3.5);
INSERT INTO #sales (item_code, sale_date, sale_amount, sale_price)
VALUES (1529, '2021-11-09', 96, 3.9);
INSERT INTO #sales (item_code, sale_date, sale_amount, sale_price)
VALUES (1529, '2021-11-05', 96, 3.75);
;WITH all_sales_with_running_totals AS ( --Calculate the running total of each item, deducting sale amount from total starting amount, in order of first sale to last
SELECT s.item_code,
s.sale_date,
s.sale_price,
i.item_amount AS starting_amount,
s.sale_amount,
i.item_amount - SUM(sale_amount) OVER(PARTITION BY s.item_code
ORDER BY s.sale_date
ROWS UNBOUNDED PRECEDING
) AS running_sale_amount
FROM #sales AS s
JOIN #items AS i ON s.item_code = i.item_code
),
sales_with_prev_running_total AS ( --Add the previous rows' running total, to assist with the final calculation
SELECT item_code,
sale_date,
sale_price,
starting_amount,
sale_amount,
running_sale_amount,
LAG(running_sale_amount, 1, NULL) OVER(PARTITION BY item_code
ORDER BY sale_date
)AS prev_running_sale_amount
FROM all_sales_with_running_totals
)
SELECT item_code, --Return the final running sale amount for each sale - if the inventory has already run out, return null. If there is insufficient inventory to fill the order, fill it with the qty remaining. Otherwise, fill the entire order.
sale_date,
sale_price,
starting_amount,
sale_amount,
running_sale_amount,
prev_running_sale_amount,
CASE WHEN prev_running_sale_amount <= 0
THEN NULL
WHEN running_sale_amount < 0
THEN prev_running_sale_amount
ELSE sale_amount
END AS result_sale_amount
FROM sales_with_prev_running_total;

Use oracle LAG function data in calculations

I want get STOCK and weighted average unit cost WAUC.
TABLE T1:
ROW
ITEM
IN
OUT
PRICE
STOCK
WAUC
1
A
1000
-
20
1000
20
2
A
2000
-
25
-
-
3
A
1500
-
15
-
-
4
A
500
-
20
-
-
I have the first 5 columns and the first two records of last two columns, and want to get the rest of last two columns STOCK and WAUC.
WAUC = ((PREVIOUS_PRICE * PREVIOUS_STOCK) + (CURRENT_IN * CURRENT_PRICE)) / CURRENT_STOCK
So, I write the query:
SELECT ROW,
SUM(IN - OUT) OVER(ORDER BY ROW) STOCK,
((LAG(STOCK * WAUC) OVER (ORDER BY ROW)) + (IN * PRICE)) / STOCK AS WAUC
FROM T1
What I want is :
ROW
ITEM
IN
OUT
PRICE
STOCK
WAUC
1
A
1000
-
20
1000
20
2
A
2000
-
25
3000
23.33
3
A
1500
-
15
4500
20.55
4
A
500
-
20
5000
20.49
In other words, I want to use LAG results in calculation data.
Your formula should be:
WAUC = (
PREVIOUS_WAUC * PREVIOUS_STOCK
+ (CURRENT_IN - CURRENT_OUT) * CURRENT_PRICE
)
/ CURRENT_STOCK
You can use a MODEL clause (with some extra measurements to make the calculation simpler):
SELECT "ROW", item, "IN", "OUT", price, stock, wauc
FROM t1
MODEL
DIMENSION BY ("ROW")
MEASURES (item, "IN", "OUT", price, 0 AS change, 0 AS stock, 0 AS total, 0 AS wauc)
RULES (
change["ROW"] = COALESCE("IN"[cv()], 0) - COALESCE("OUT"[cv()], 0),
stock["ROW"] = change[cv()] + COALESCE(stock[cv()-1], 0),
total["ROW"] = change[cv()] * price[cv()] + COALESCE(total[cv()-1], 0),
wauc["ROW"] = total[cv()] / stock[cv()]
);
Or, from Oracle 12, using MATCH_RECOGNIZE:
SELECT "ROW",
item,
"IN",
"OUT",
price,
total_stock AS stock,
total_cost / total_stock AS wauc
FROM t1
MATCH_RECOGNIZE(
ORDER BY "ROW"
MEASURES
SUM(COALESCE("IN", 0) - COALESCE("OUT", 0)) AS total_stock,
SUM((COALESCE("IN", 0) - COALESCE("OUT", 0))*price) AS total_cost
ALL ROWS PER MATCH
PATTERN (all_rows+)
DEFINE
all_rows AS 1 = 1
)
Or analytic functions:
SELECT "ROW",
item,
"IN",
"OUT",
price,
SUM(COALESCE("IN",0) - COALESCE("OUT", 0)) OVER (ORDER BY "ROW")
AS stock,
SUM((COALESCE("IN",0) - COALESCE("OUT", 0))*price) OVER (ORDER BY "ROW")
/ SUM(COALESCE("IN",0) - COALESCE("OUT", 0)) OVER (ORDER BY "ROW")
AS wauc
FROM t1
Which, for the sample data:
CREATE TABLE t1 ("ROW", ITEM, "IN", "OUT", PRICE, STOCK, WAUC) AS
SELECT 1, 'A', 1000, CAST(NULL AS NUMBER), 20, CAST(NULL AS NUMBER), CAST(NULL AS NUMBER) FROM DUAL UNION ALL
SELECT 2, 'A', 2000, NULL, 25, NULL, NULL FROM DUAL UNION ALL
SELECT 3, 'A', 1500, NULL, 15, NULL, NULL FROM DUAL UNION ALL
SELECT 4, 'A', 500, NULL, 20, NULL, NULL FROM DUAL;
All output:
ROW
ITEM
IN
OUT
PRICE
STOCK
WAUC
1
A
1000
20
1000
20
2
A
2000
25
3000
23.33333333333333333333333333333333333333
3
A
1500
15
4500
20.55555555555555555555555555555555555556
4
A
500
20
5000
20.5
Note: ROW, IN and OUT are keywords and you should not use them as identifiers as you would have to use quoted identifiers everywhere they occur.
db<>fiddle here
Your problem has nothing to do with "lag".
Using MT0's sample data:
select "ROW", item, "IN", "OUT", price,
sum(nvl("IN", 0) - nvl("OUT", 0))
over (partition by item order by "ROW") as stock,
round(sum((nvl("IN", 0) - nvl("OUT", 0)) * price)
over (partition by item order by "ROW")
/ sum(nvl("IN", 0) - nvl("OUT", 0))
over (partition by item order by "ROW"), 2 ) as wauc
from t1
;
ROW ITEM IN OUT PRICE STOCK WAUC
------ ---- ------ ------ ------ ------ ------
1 A 1000 20 1000 20
2 A 2000 25 3000 23.33
3 A 1500 15 4500 20.56
4 A 500 20 5000 20.5

I need to get the percentage of sum of grouped rows using SQL DB2

Below is the table :
AccountNo
Symbol
Amount
A1
IBM
10
A1
CSCO
20
A1
GOOG
30
A2
IBM
40
A2
FB
10
I need to get the Percentage of IBM on Account A1. i.e. 10 * 100 / 60 = 16.6%
I need to get the Percentage of CSCO on Account A1.i.e. 20 * 100 / 60 = 33.33%
I need to get the Percentage of GOOG on Account A1.i.e. 30 * 100 / 60 = 50 %
I need to get the Percentage of IBM on Account A2.i.e. 40 * 100 / 50 = 80%
I need to get the Percentage of FB on Account A2.i.e. 10 * 100 / 50 = 20%
I tried below query but does not execute:
select AccountNo, SYMBOL, Value, Float(cast((value(Amount,0)) as DECIMAL(18,2))) / FLOAT(cast((value(t2.total,0)) as DECIMAL (18,2))) * 100
from mytable
(select sum(Amount) as total from mytable group by AccountNo) as T2
where Amount > 0
group by AccountNo, SYMBOL, Value, T2.total;
You can use window functions:
select account, symbol,
amount * 100.0 / sum(amount) over (partition by account) as percentage
from t;
If amount is actually a calculation in an aggregation, you can incorporate this directly in the aggregation query. For example:
select account, symbol,
sum(amount) * 100.0 / sum(sum(amount)) over (partition by account) as percentage
from t
group by account, symbol;
There is a convenient function RATIO_TO_REPORT in Db2 for LUW:
WITH T (AccountNo, Symbol, Amount) AS
(
VALUES
('A1', 'IBM', 10)
, ('A1', 'CSCO', 20)
, ('A1', 'GOOG', 30)
, ('A2', 'IBM', 40)
, ('A2', 'FB', 10)
)
SELECT
T.*
, DEC (100 * RATIO_TO_REPORT (Amount) OVER (PARTITION BY AccountNo), 5, 2) R
FROM T;
dbfiddle link.

Cumulative sum of a column

I have a table that has the below data.
COUNTRY LEVEL NUM_OF_DUPLICATES
US 9 6
US 8 24
US 7 12
US 6 20
US 5 39
US 4 81
US 3 80
US 2 430
US 1 178
US 0 430
I wrote a query that will calculate the sum of cumulative rows and got the below output .
COUNTRY LEVEL NUM_OF_DUPLICATES POOL
US 9 6 6
US 8 24 30
US 7 12 42
US 6 20 62
US 5 39 101
US 4 81 182
US 3 80 262
US 2 130 392
US 1 178 570
US 0 254 824
Now I want to to filter the data and take only where the POOL <=300, if the POOL field does not have the value 300 then I should take the first value after 300. So, in the above example we do not have the value 300 in the field POOL, so we take the next immediate value after 300 which is 392. So I need a query so that I can pull the records POOL <= 392(as per the example above) which will yield me the output as
COUNTRY LEVEL NUM_OF_DUPLICATES POOL
US 9 6 6
US 8 24 30
US 7 12 42
US 6 20 62
US 5 39 101
US 4 81 182
US 3 80 262
US 2 130 392
Please let me know your thoughts. Thanks in advance.
declare #t table(Country varchar(5), Level int, Num_of_Duplicates int)
insert into #t(Country, Level, Num_of_Duplicates)
values
('US', 9, 6),
('US', 8, 24),
('US', 7, 12),
('US', 6, 20),
('US', 5, 39),
('US', 4, 81),
('US', 3, 80),
('US', 2, 130/*-92*/),
('US', 1, 178),
('US', 0, 430);
select *, sum(Num_of_Duplicates) over(partition by country order by Level desc),
(sum(Num_of_Duplicates) over(partition by country order by Level desc)-Num_of_Duplicates) / 300 as flag,--any row which starts before 300 will have flag=0
--or
case when sum(Num_of_Duplicates) over(partition by country order by Level desc)-Num_of_Duplicates < 300 then 1 else 0 end as startsbefore300
from #t;
select *
from
(
select *, sum(Num_of_Duplicates) over(partition by country order by Level desc) as Pool
from #t
) as t
where Pool - Num_of_Duplicates < 300 ;
The logic here is quite simple:
Calculate the running sum POOL value up to the current row.
Filter rows so that the previous row's total is < 300, you can either subtract the current row's value, or use a second sum
If the total up to the current row is exactly 300, the previous row will be less, so this row will be included
If the current row's total is more than 300, but the previous row is less then it will also be included
All higher rows are excluded
It's unclear what ordering you want. I've used NUM_OF_DUPLICATES column ascending, but you may want something else
SELECT
COUNTRY,
LEVEL,
NUM_OF_DUPLICATES,
POOL
FROM (
SELECT *,
POOL = SUM(NUM_OF_DUPLICATES) OVER (ORDER BY NUM_OF_DUPLICATES ROWS UNBOUNDED PRECEDING)
-- alternative calculation
-- ,POOLPrev = SUM(NUM_OF_DUPLICATES) OVER (ORDER BY NUM_OF_DUPLICATES ROWS UNBOUNDED PRECEDING AND 1 PRECEDING)
FROM YourTable
) t
WHERE POOL - NUM_OF_DUPLICATES < 300;
-- you could also use POOLPrev above
I used two temp tables to get the answer.
DECLARE #t TABLE(Country VARCHAR(5), [Level] INT, Num_of_Duplicates INT)
INSERT INTO #t(Country, Level, Num_of_Duplicates)
VALUES ('US', 9, 6),
('US', 8, 24),
('US', 7, 12),
('US', 6, 20),
('US', 5, 39),
('US', 4, 81),
('US', 3, 80),
('US', 2, 130),
('US', 1, 178),
('US', 0, 254);
SELECT
Country
,Level
, Num_of_Duplicates
, SUM (Num_of_Duplicates) OVER (ORDER BY id) AS [POOL]
INTO #temp_table
FROM
(
SELECT
Country,
level,
Num_of_Duplicates,
ROW_NUMBER() OVER (ORDER BY country) AS id
FROM #t
) AS A
SELECT
[POOL],
ROW_NUMBER() OVER (ORDER BY [POOL] ) AS [rank]
INTO #Temp_2
FROM #temp_table
WHERE [POOL] >= 300
SELECT *
FROM #temp_table WHERE
[POOL] <= (SELECT [POOL] FROM #Temp_2 WHERE [rank] = 1 )
DROP TABLE #temp_table
DROP TABLE #Temp_2

SQL - Same Table join to calculate profit from last entry

I have a table of transactions for various products. I want to calculate the profit made on each
Product Date Profit Incremental Profit
--------------------- --------------------------- -----------
Apple 2016-05-21 100
Banana 2016-05-21 60
Apple 2016-06-15 30
Apple 2016-08-20 10
Banana 2016-08-20 5
Can I create a SQL query that can group based on product and give me incremental profit on every date for each product. For example on 21-05-2015 since it is first date so incremental profit will be 0. But on 15-06-2016 it will be -70 (30-100).
The expected output is:
Product Date Profit Incremental Profit
--------------------- --------------------------- -----------
Apple 2016-05-21 100 0
Banana 2016-05-21 60 0
Apple 2016-06-15 30 -70
Apple 2016-08-20 10 -20
Banana 2016-08-20 5 -55
maybe u can use this.
select
a.product
,a.date
,a.profit
,isnull(a.profit - (select top 1 x.profit from profit x where x.product = a.product and x.date < a.date),0) as profit
from PROFIT a
order by product, date
Try this
DECLARE #Tbl TABLE (Product NVARCHAR(50), Date_ DATETIME, Profit INT)
INSERT INTO #Tbl
VALUES
('Apple' , '2016-05-21', 100),
('Banana', '2016-05-21', 60 ),
('Apple', '2016-06-15', 30 ),
('Apple', '2016-08-20', 10 ),
('Banana', '2016-08-20', 5 )
;WITH CTE
AS
(
SELECT
*,
ROW_NUMBER() OVER (PARTITION BY Product ORDER BY Date_) RowId
FROM #Tbl
)
SELECT
CurrentRow.Product ,
CurrentRow.Date_ ,
CurrentRow.Profit ,
CurrentRow.Profit - ISNULL(PrevRow.Profit, CurrentRow.Profit) 'Incremental Profit'
FROM
CTE CurrentRow LEFT JOIN
(SELECT CTE.Product ,CTE.Profit, CTE.RowId + 1 RowId FROM CTE) PrevRow ON CurrentRow.Product = PrevRow.product AND
CurrentRow.RowId = PrevRow.RowId
ORDER BY CurrentRow.Date_
Result:
Product Date_ Profit Incremental Profit
Apple 2016-05-21 100 0
Banana 2016-05-21 60 0
Apple 2016-06-15 30 -70
Apple 2016-08-20 10 -20
Banana 2016-08-20 5 -55
Edit:
UPDATE #Tbl
SET [Incremental Profit] = A.[Incremental Profit]
FROM
(
SELECT
CurrentRow.Product ,
CurrentRow.Date_ ,
CurrentRow.Profit ,
CurrentRow.Profit - ISNULL(PrevRow.Profit, CurrentRow.Profit) 'Incremental Profit'
FROM
(SELECT *, ROW_NUMBER() OVER (PARTITION BY Product ORDER BY Date_) RowId FROM #Tbl) CurrentRow LEFT JOIN
(SELECT *, ROW_NUMBER() OVER (PARTITION BY Product ORDER BY Date_) + 1 RowId FROM #Tbl) PrevRow ON CurrentRow.Product = PrevRow.Product AND
CurrentRow.RowId = PrevRow.RowId
) A
WHERE
[#Tbl].Product = A.Product AND
[#Tbl].Date_ = A.Date_