SQL query to calculate interval discount - sql

I have trouble understanding how I can solve this problem with a T-SQL query.
I have a price column and a volume column. In another table a have discounts at different levels of volume. So my discount table could have values as
(StartLevel, DiscountFactor)
(0, 1);
(25, 0.95);
(50, 0.90);
(100, 0.75)
What I want is to calculate a total price. If Volume is 35, I want it to multiply
Price x ((35-25) x 0.95 + (25-0) x 1)
If the volume is 200, it should be
Price x ((200-100) x 0.75 + (100-50) x .9+(50-25) x .95+(25) x 1)
Can anybody help me with a query that solves this?

This can help:
DECLARE #products TABLE
(
id INT ,
price MONEY ,
volume INT
)
DECLARE #discounts TABLE
(
id INT ,
Level INT ,
Factor MONEY
)
INSERT INTO #products
VALUES ( 1, 10, 35 ),
( 2, 15, 200 )
INSERT INTO #discounts
VALUES ( 1, 0, 1 ),
( 2, 25, 0.95 ),
( 3, 50, 0.90 ),
( 4, 100, 0.75 )
SELECT p.id, p.price * SUM(ca.m)
FROM #products p
CROSS APPLY ( SELECT * ,
Factor * ( -Level + LEAD(Level) OVER ( PARTITION BY p.id ORDER BY Level, d ) ) AS m
FROM ( SELECT 1 AS d ,
Level ,
Factor
FROM #discounts
WHERE Level < p.volume
UNION ALL
SELECT 2 AS d ,
p.volume ,
0
) t
) ca
GROUP BY p.id, p.price
Without grouping it returns:
id price volume d Level Factor m
1 10.00 35 1 0 1.00 25.00
1 10.00 35 1 25 0.95 9.50
1 10.00 35 2 35 0.00 NULL
2 15.00 200 1 0 1.00 25.00
2 15.00 200 1 25 0.95 23.75
2 15.00 200 1 50 0.90 45.00
2 15.00 200 1 100 0.75 75.00
2 15.00 200 2 200 0.00 NULL
Then just group by product and sum of m results in:
id Total
1 345.00
2 2531.25

For a given Volume and Price you can get the discount based on interval using LEAD which is available in SQL Server 2012+ onwards.
Sample Data
DECLARE #PriceTable TABLE(Volume INT,Price DECIMAL(9,2) )
DECLARE #Discount TABLE(StartLevel int, DiscountFactor DECIMAL(9,2))
INSERT INTO #PriceTable
VALUES(75, 20.5),
(150, 20),
(250, 20.5),
(0, 15);
INSERT INTO #Discount
VALUES(0, 1),
(25, 0.95),
(50, 0.90),
(100, 0.75);
Query
SELECT Volume,Price,FinalPrice
FROM #PriceTable P
CROSS APPLY(
SELECT SUM(CASE WHEN (MaxLevel >=StartLevel) THEN (MaxLevel-StartLevel) ELSE 0 END *DiscountFactor)*P.Price as FinalPrice
FROM
(
SELECT CASE WHEN LEAD(StartLevel)OVER(ORDER BY StartLevel) < P.Volume THEN LEAD(StartLevel)OVER(ORDER BY StartLevel) ELSE P.Volume END MaxLevel,StartLevel, DiscountFactor
FROM #Discount
) IQ
)T
Output
Volume Price FinalPrice
75 20.50 1460.6250
150 20.00 2625.0000
250 20.50 4228.1250
0 15.00 0.0000

Related

How To Calculate Running balance using 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

Sum over partition reset when running total is 0

I have a query that is taking transactions (buying and selling of items) to calculate the gain/loss when the running total resets back to 0.
The fiddle is here: https://www.db-fiddle.com/f/974UVvE6id2rEiBPR78CKx/0
The units of each item can be added and subtracted and each time they come back to 0 for a account and item combination we want to calculate the net result of those transactions.
You can see it working in the fiddle for the first few (when open = 0), however it fails if there are multiple transactions before getting to 0 (eg 1 increment, 2 separate decrements of units).
From this data:
INSERT INTO t
(account, item, units, price, created_at)
VALUES
(2, 'A', -1, '$120.00', '2022-09-23 17:33:07'),
(2, 'A', 1, '$110.00', '2022-09-23 17:34:31'),
(1, 'B', -1, '$70.00', '2022-09-23 17:38:31'),
(1, 'B', 1, '$50.00', '2022-09-23 17:36:31'),
(1, 'B', 2, '$50.00', '2022-09-23 17:40:31'),
(1, 'B', -1, '$60.00', '2022-09-23 17:41:31'),
(1, 'B', -1, '$70.00', '2022-09-23 17:42:31'),
(1, 'B', 1, '$50.00', '2022-09-23 17:35:31'),
(1, 'B', -1, '$60.00', '2022-09-23 17:33:31'),
(2, 'B', 1, '$70.00', '2022-09-23 17:43:31'),
(2, 'B', 1, '$75.00', '2022-09-23 17:45:31'),
(2, 'B', -2, '$80.00', '2022-09-23 17:46:31')
;
I need to produce this result (net is the relevant column which we cannot get right in the fiddle, it shows incorrect values for the last two net values):
account
item
units
price
created_at
open
cost
net
2
A
-1
$120.00
2022-09-23T17:33:07.000Z
-1
$120.00
1
B
-1
$60.00
2022-09-23T17:33:31.000Z
-1
$60.00
2
A
1
$110.00
2022-09-23T17:34:31.000Z
0
-$110.00
$10.00
1
B
1
$50.00
2022-09-23T17:35:31.000Z
0
-$50.00
$10.00
1
B
1
$50.00
2022-09-23T17:36:31.000Z
1
-$50.00
1
B
-1
$70.00
2022-09-23T17:38:31.000Z
0
$70.00
$20.00
1
B
2
$50.00
2022-09-23T17:40:31.000Z
2
-$100.00
1
B
-1
$60.00
2022-09-23T17:41:31.000Z
1
$60.00
1
B
-1
$70.00
2022-09-23T17:42:31.000Z
0
$70.00
$30.00
2
B
1
$70.00
2022-09-23T17:43:31.000Z
1
-$70.00
2
B
1
$75.00
2022-09-23T17:45:31.000Z
2
-$75.00
2
B
-2
$80.00
2022-09-23T17:46:31.000Z
0
$160.00
$15.00
View on DB Fiddle
We start by establishing cost and every time the running total is 0. By using lag and count we make groups out of every run that leads to zero divided by account and item. We use the groups we just created and find the running total of cost, but only display the result when our original running_total = 0.
select account
,item
,units
,price
,created_at
,running_total as open
,cost
,case running_total when 0 then sum(cost) over(partition by account, item, grp order by created_at) end as net
from
(
select *
,count(mark_0) over(partition by account, item order by created_at) as grp
from (
select *
,case when lag(running_total) over(partition by account, item order by created_at) = 0 then 1 when lag(running_total) over(partition by account, item order by created_at) is null then 1 end as mark_0
from (
select *
,sum(units) over(partition by account, item order by created_at) as running_total
,price*units*-1 as cost
from t
) t
) t
) t
order by created_at
account
item
units
price
created_at
open
cost
net
2
A
-1
120.00
2022-09-23 17:33:07+01
-1
120.00
null
1
B
-1
60.00
2022-09-23 17:33:31+01
-1
60.00
null
2
A
1
110.00
2022-09-23 17:34:31+01
0
-110.00
10.00
1
B
1
50.00
2022-09-23 17:35:31+01
0
-50.00
10.00
1
B
1
50.00
2022-09-23 17:36:31+01
1
-50.00
null
1
B
-1
70.00
2022-09-23 17:38:31+01
0
70.00
20.00
1
B
2
50.00
2022-09-23 17:40:31+01
2
-100.00
null
1
B
-1
60.00
2022-09-23 17:41:31+01
1
60.00
null
1
B
-1
70.00
2022-09-23 17:42:31+01
0
70.00
30.00
2
B
1
70.00
2022-09-23 17:43:31+01
1
-70.00
null
2
B
1
75.00
2022-09-23 17:45:31+01
2
-75.00
null
2
B
-2
80.00
2022-09-23 17:46:31+01
0
160.00
15.00
Fiddle
You can use a recursive cte, building up the results row by row, using a JSON object to store running open and cost values for every unique item:
with recursive transactions as (
select row_number() over (order by t1.created_at) id, t1.* from t t1
order by t1.created_at
),
cte(id, account, item, unit, price, created_at, open, cost, net, p) as (
select t.*, t.unit, -1*t.price*t.unit, 0, (select jsonb_object_agg(t1.item,
jsonb_build_object('u', 0, 'c', 0)) from transactions t1)||jsonb_build_object(t.item,
jsonb_build_object('u', t.unit, 'c', -1*t.price*t.unit))
from transactions t where t.id = 1
union all
select t.*, (c.p -> t.item -> 'u')::int + t.unit, -1*t.price*t.unit,
case when (c.p -> t.item -> 'u')::int + t.unit = 0
then (c.p -> t.item -> 'c')::int + -1*t.price*t.unit else 0 end,
c.p || jsonb_build_object(t.item, jsonb_build_object('u', (c.p -> t.item -> 'u')::int + t.unit, 'c',
case when (c.p -> t.item -> 'u')::int + t.unit = 0 then 0
else (c.p -> t.item -> 'c')::int + -1*t.price*t.unit end))
from cte c join transactions t on t.id = c.id + 1
)
select account, item, unit, price, created_at,
open, cost, case when net > 0 then net end
from cte;

SQL Splitting Rows Into Multiple Rows Based on Condition

I have the below table:
Account
Credit
Debt
Net
ItemCount
InvoiceNumber
AAA
1300
150
1150
2
10
AAA
500
50
450
12
20
AAA
1800
650
1150
29
30
No record can have a higher count than 10 items so anything over 10 must be split into multiples of 10 until the last remaining value (which could be less than 10)
I am trying to automate this in SQL and have not been able to come up with an idea that works, I would like something to look at the above table and spit out the following:
Account
Credit
Debt
Net
ItemCount
InvoiceNumber
AAA
1300
150
1150
2
10
AAA
500
50
450
10
20
AAA
500
50
450
2
30
AAA
1800
650
1150
10
10
AAA
1800
650
1150
10
20
AAA
1800
650
1150
9
30
Any thoughts on how can this be accomplished?
I would try to use a cursor to iterate through the records and generate as many inserts as you need based on the value that you have
If you work with SQL Server, this should do the trick for you
CREATE TABLE #Table1
(
[Account] varchar(3),
[Credit] int,
[Debt] int,
[Net] int,
[ItemCount] int,
[InvoiceNumber] int
);
INSERT INTO #Table1 ([Account], [Credit], [Debt], [Net], [ItemCount], [InvoiceNumber])
VALUES
('AAA', 1300, 150, 1150, 2, 10),
('AAA', 500, 50, 450, 12, 20),
('AAA', 1800, 650, 1150, 29, 30)
;
WITH cte_numbers([Account] , [Credit] , [Debt] , [Net] , [ItemCount] , [InvoiceNumber] , itemcountzero, currentitmecount)
AS
(
SELECT
*,
(itemcount % 10) AS itemcountzero,
(itemcount) AS currentitmecount
FROM
#Table1
UNION ALL
SELECT
t.[Account] , t.[Credit] , t.[Debt] , t.[Net] , t.[ItemCount] , t.[InvoiceNumber] , c.itemcountzero, (c.currentitmecount-10)
FROM
#Table1 t
INNER JOIN
cte_numbers c ON t.[InvoiceNumber] = c.[InvoiceNumber]
AND (c.currentitmecount-10) >= c.itemcountzero
-- AND (currentitmecount-10) > 0
)
SELECT
[Account] , [Credit] , [Debt] , [Net] , IIF(currentitmecount > 10, 10 , currentitmecount) as [ItemCount] , [InvoiceNumber]
FROM
cte_numbers
ORDER BY
[InvoiceNumber], currentitmecount DESC;

Calculating new percentages over total in a "select distinct" query with multiple percentages rows

The title may be puzzling, but the situation should be really simple foranyone reading the following lines.
I have this table VAL:
customer month_1 month_2 month_3 value
ABC 0.5 0 0.50 200
ABC 0.25 0.25 0.50 200
XYZ 1 0 0 150
RST 0 0 1 200
RST 0 0.50 0.50 130
(This is the code to get it)
create table VAL (customer nvarchar(255), month_1 decimal(18,2), month_2 decimal(18,2), month_3 decimal(18,2), value decimal(18,2))
insert into VAL values ('ABC', 0.5, 0 , 0.50, 200)
insert into VAL values ('ABC',0.25 , 0.25, 0.50 , 200 )
insert into VAL values ('XYZ', 1 , 0 , 0 , 150 )
insert into VAL values ('RST', 0 , 0 , 1 , 200 )
insert into VAL values ('RST', 0 , 0.50, 0.50 , 130 )
This can be seen (transforming what is a percentage in actual values) as
customer value_month_1 value_month_2 value_month_3 value
ABC 100 0 100 200
ABC 50 50 100 200
XYZ 150 0 0 150
RST 0 0 200 200
RST 0 65 65 130
I need to transform everything in the following table, that is sort of a collapsed version of the first one:
customer month_1 month_2 month_3 value
ABC 0.375 0.125 0.50 400
XYZ 1.0 0 0.0 150
RST 0.0 0.197 0.793 330
So far, I'm able to do this customer by customer with the following code:
select distinct customer
,sum(month_1) as month_1
,sum(month_2) as month_2
,sum(month_3) as month_3
,sum(value) as value
from (
select distinct customer
,month_1 * sum(value)/(select sum(value) from VAL where customer='ABC' group by customer) as month_1
,month_2 * sum(value)/(select sum(value) from VAL where customer='ABC' group by customer) as month_2
,month_3 * sum(value)/(select sum(value) from VAL where customer='ABC' group by customer) as month_3
,sum(value) as value from VAL where customer='ABC' group by customer, month_1, month_2,month_3) as NEW
group by customer
that gives me the following result:
customer month_1 month_2 month_3 value
ABC 0.375 0.125 0.50 400
I'm pretty sure there are better ways to do this, probably with some command I don't know very well how to use.
Anyone able to help?
You just need a weighted average:
select customer,
sum(month_1 * value) / sum(value) as month_1,
sum(month_2 * value) / sum(value) as month_2,
sum(month_3 * value) / sum(value) as month_3,
sum(value)
from val
group by customer;

Multiple joins on same table giving duplicate data

Here is the sale_order table which contains CCNID
sale_orderid client_channel_nameid
1 1
2 1
3 2
4 2
5 2
6 2
7 1
8 1
sale_order_item Table has sale_orderid as a foreign key
sale_order_itemid sale_orderid order_date selling_price
42219 1 2018-03-21 00:00:00 200
28948 2 2018-03-21 16:17:55 100
42220 3 2018-03-21 00:00:00 300
13194 4 2018-03-21 13:33:58 400
42839 5 2018-03-20 07:54:29 550
42840 6 2018-03-20 07:58:20 600
42086 7 2018-03-20 00:00:00 700
11691 8 2018-03-20 05:32:31 500
And I want to get the sum of price of soid of 21 and 20 dates in different columns grouped by CCNID
client_channel_nameid 21 20
1 300 1200
2 700 1150
I am joining Sale order twice which is giving me wrong results
select ccn.client_channel_nameid,
round (sum(soi.selling_price)),
round(sum(soi1.selling_price))
from app.client_channel_name ccn
join app.sale_order so on so.client_channel_nameid = ccn.client_channel_nameid
inner join app.sale_order_item soi on soi.sale_orderid = so.sale_orderid
join app.sale_order so1 on so1.client_channel_nameid = ccn.client_channel_nameid
inner join app.sale_order_item soi1 on soi1.sale_orderid = so1.sale_orderid
where ccn.clientid = 1
and to_char(soi.order_date, 'DD-MM-YYYY') = '20-03-2018'
and to_char(soi1.order_date, 'DD-MM-YYYY') = '21-03-2018'
group by client_channel_nameid;
You can group the data by CCNID and then only sum selling_price when the order_date day is the 21 or 20.
SELECT client_channel_nameid
, SUM(CASE
WHEN EXTRACT(day FROM order_date) = 21 THEN selling_price
ELSE 0
END) AS "21"
, SUM(CASE
WHEN EXTRACT(day FROM order_date) = 20 THEN selling_price
ELSE 0
END) AS "20"
FROM sale_order so
JOIN sale_order_item soi
ON soi.sale_orderid = so.sale_orderid
GROUP BY so.client_channel_nameid
Below query produce the desired result. From your above tried solution and question you asked I think you are looking for exactly 21 and 20 dates. The below will need slight changes with extra filters for add more dates ex.22,23,24...
with sale_order(
sale_orderid, client_channel_nameid
) as (
select
*
from
(
values
(1, 1),
(2, 1),
(3, 2),
(4, 2),
(5, 2),
(6, 2),
(7, 1),
(8, 1)
) as x(
sale_orderid, client_channel_nameid
)
),
sale_order_item(
sale_order_itemid, sale_orderid,
order_date, selling_price
) as (
select
*
from
(
values
(
42219, 1, '2018-03-21 00:00:00' :: timestamp,
200
),
(
28948, 2, '2018-03-21 16:17:55' :: timestamp,
100
),
(
42220, 3, '2018-03-21 00:00:00' :: timestamp,
300
),
(
13194, 4, '2018-03-21 13:33:58' :: timestamp,
400
),
(
42839, 5, '2018-03-20 07:54:29' :: timestamp,
550
),
(
42840, 6, '2018-03-20 07:58:20' :: timestamp,
600
),
(
42086, 7, '2018-03-20 00:00:00' :: timestamp,
700
),
(
11691, 8, '2018-03-20 05:32:31' :: timestamp,
500
)
) as x(
sale_order_itemid, sale_orderid,
order_date, selling_price
)
)
select
client_channel_nameid,
sum(selling_price) filter (where to_char(order_date, 'dd-mm-yy') = '21-03-2018') as date_21,
sum(selling_price) filter (where to_char(order_date, 'dd-mm-yy') = '20-03-2018') as date_20
from
sale_order so
join sale_order_item soi on soi.sale_orderid = so.sale_orderid
group by
so.client_channel_nameid