How to select Remain values after subtract with one Fixed value - sql-server-2005

Need To select Data From One Table After Minus With One Value
this is the question i already asked and this solution for one value input to table and result. but i need this with more input values for different categories and each categories output
for eg(based of previous question)
Table 1
SNo Amount categories
1 100 type1
2 500 type1
3 400 type1
4 100 type1
5 100 type2
6 200 type2
7 300 type2
8 500 type3
9 100 type3
and
values for type1 - 800
values for type2 - 200
values for type3 - 100
and the output need is
for type-1
800 - 100 (Record1) = 700
700 - 500 (record2) = 200
200 - 400 (record3) = -200
The table records starts from record 3 with Balance Values Balance 200
Table-Output
SNo Amount
1 200
2 100
that means if minus 800 in first table the first 2 records will be removed and in third record 200 is Balance
same operation for remain types also and how to do it?

SQLFiddle demo
with T1 as
(
select t.*,
SUM(Amount) OVER (PARTITION BY [Type] ORDER BY [SNo])
-
CASE WHEN Type='Type1' then 800
WHEN Type='Type2' then 200
WHEN Type='Type3' then 100
END as Total
from t
)select Sno,Type,
CASE WHEN Amount>Total then Total
Else Amount
end as Amount
from T1 where Total>0
order by Sno
UPD: If types are not fixed then you should create a table for them, for example:
CREATE TABLE types
([Type] varchar(5), [Value] int);
insert into types
values
('type1',800),
('type2',200),
('type3',100);
and use the following query:
with T1 as
(
select t.*,
SUM(Amount) OVER (PARTITION BY t.[Type] ORDER BY [SNo])
-
ISNULL(types.Value,0) as Total
from t
left join types on (t.type=types.type)
)select Sno,Type,
CASE WHEN Amount>Total then Total
Else Amount
end as Amount
from T1 where Total>0
order by Sno
SQLFiddle demo
UPDATE: For MSSQL 2005 just replace SUM(Amount) OVER (PARTITION BY t.[Type] ORDER BY [SNo]) with (select SUM(Amount) from t as t1
where t1.Type=t.Type
and t1.SNo<=t.SNo)
with T1 as
(
select t.*,
(select SUM(Amount) from t as t1
where t1.Type=t.Type
and t1.SNo<=t.SNo)
-
ISNULL(types.Value,0) as Total
from t
left join types on (t.type=types.type)
)select Sno,Type,
CASE WHEN Amount>Total then Total
Else Amount
end as Amount
from T1 where Total>0
order by Sno
SQLFiddle demo

Related

SQL Gap Fill by ID

I am attempting to gap fill in two scenarios. I can do it with one group but am uncertain with multiple
the data:
Order ID Amount
1 NULL NULL
2 A 500
3 NULL NULL
4 A 700
1 B 1000
2 NULL NULL
3 NULL NULL
4 B 1500
Target Result
Order ID Amount
1 A 500
2 A 500
3 A 700
4 A 700
1 B 1000
2 B 1500
3 B 1500
4 B 1500
Consider below approach
select * except(amount),
first_value(amount ignore nulls) over win as amount
from (select distinct `order` from your_table where not `order` is null),
(select distinct id from your_table where not id is null)
left join your_table using(`order`, id)
window win as (partition by id order by `order` rows between current row and unbounded following)
if applied to sample data in your question - output is

Agg Functions while Partitioning Data in SQL

I have a table that looks like this:
store_id industry_id cust_id amount gender
1 100 1000 1.00 M
2 100 1000 2.05 M
3 100 1000 3.15 M
4 100 1000 4.00 M
5 100 2000 5.00 F
6 200 2000 5.20 F
7 200 5000 6.05 F
8 200 6000 7.10 F
Here's the code to create this table:
CREATE TABLE t1(
store_id int,
industry_id int,
cust_id int,
amount float,
gender char
);
INSERT INTO t1 VALUES(1,100,1000,1.00, 'M');
INSERT INTO t1 VALUES(2,100,1000,2.05, 'M');
INSERT INTO t1 VALUES(3,100,1000,3.15, 'M');
INSERT INTO t1 VALUES(4,100,1000,4.00, 'M');
INSERT INTO t1 VALUES(5,100,2000,5.00, 'F');
INSERT INTO t1 VALUES(6,200,2000,5.20, 'F');
INSERT INTO t1 VALUES(7,200,5000,6.05, 'F');
INSERT INTO t1 VALUES(8,200,6000,7.10, 'F');
The question I'm trying to answer is: What is the avg. transaction amount for the top 20% of customers by industry?
This should yield these results:
store_id. industry_id avg_amt_top_20
1 100 4.80
2 100 4.80
3 100 4.80
4 100 4.80
5 100 4.80
6 200 7.10
7 200 7.10
8 200 7.10
Here's what I have so far:
SELECT
store_id, industry_id,
avg(CASE WHEN percentile>=0.80 THEN amount ELSE NULL END) OVER(PARTITION BY industry_id) as cust_avg
FROM(
SELECT store_id, industry_id, amount, cume_dist() OVER(
PARTITION BY industry_id
ORDER BY amount desc) AS percentile
FROM t1
) tmp
GROUP BY store_id, industry_id;
This fails on the GROUP BY (contains nonaggregated column 'amount'). What's the best way to do this?
What is the avg. transaction amount for the top 20% of customers by industry?
Based on this question, I don't see why store_id is in the results.
If I understand correctly, you need to aggregate to get the total by customer. Then you can use NTILE() to determine the top 20%. The final step is aggregating by industry:
SELECT industry_id, AVG(total)
FROM (SELECT customer_id, industry_id, SUM(amount) as total,
NTILE(5) OVER (PARTITION BY industry_id ORDER BY SUM(amount) DESC) as tile
FROM t
GROUP BY customer_id, industry_id
) t
WHERE tile = 1
GROUP BY industry_id

Running Total added to Next partition

declare #Temp table
(
Grp int,
Bal float,
[Value] float
)
declare #Amt float =1000;
Insert into #Temp(Grp,[Value])
Values(1,10),(1,5),(1,15)
,(2,20),(2,5),(2,15)
,(3,50),(3,50)
select Grp,#Amt as Amount,Value,Bal from #Temp
Required Output:
Grp Amount Value Bal
1 1000 10 1000
1 1000 5 1000
1 1000 15 1000
2 1000 20 1030 ---(10+5+15)
2 1000 5 1030
2 1000 15 1030
3 1000 50 1070 ---- (20+5+15)
3 1000 50 1070
Balance calculated based on running total of 'Value' of Group1 added to Group2 and running total of group 2 added to balance of Group3 and soon
I know how to calculate the running total but I can't as sums are added to next partition.
Please help to get required result efficiently. I am using SQL Server 2017
One method is outer apply:
select t.*, t.amount + coalesce(t2.value, 0)
from #temp t outer apply
(select sum(t2.value) as value
from #temp t2
where t2.grp < t.grp
) t2;
It is possibly more efficient to use aggregation and a running sum:
select t.*,
(1000 + tt.running_value)
from #temp t join
(select t.grp, sum(value) as value,
sum(sum(value)) over (order by grp) - sum(value) as running_value
from #temp t
group by grp
) tt
on t.grp = tt.grp;
Unfortunately, SQL Server doesn't fully support range window frames, so I don't think there is a convenient way to do this only with window functions. But the group by will probably have much better performance.

SQL Running Total Grouped By Limit

I am trying to determine how to group records together based the cumulative total of the Qty column so that the group size doesn't exceed 50. The desired group is given in the group column with sample data below.
Is there a way to accomplish this in SQL (specifically SQL Server 2012)?
Thank you for any assistance.
ID Qty Group
1 10 1
2 20 1
3 30 2 <- 60 greater than 50 so new group
4 40 3
5 2 3
6 3 3
7 10 4
8 25 4
9 15 4
10 5 5
You can use CTE to achieve the goal.
If one of the item exceeds Qty 50, a group still assign for it
DECLARE #Data TABLE (ID int identity(1,1) primary key, Qty int)
INSERT #Data VALUES (10), (20), (30), (40), (2), (3), (10), (25), (15), (5)
;WITH cte AS
(
SELECT ID, Qty, 1 AS [Group], Qty AS RunningTotal FROM #Data WHERE ID = 1
UNION ALL
SELECT data.ID, data.Qty,
-- The group limits to 50 Qty
CASE WHEN cte.RunningTotal + data.Qty > 50 THEN cte.[Group] + 1 ELSE cte.[Group] END,
-- Reset the running total for each new group
data.Qty + CASE WHEN cte.RunningTotal + data.Qty > 50 THEN 0 ELSE cte.RunningTotal END
FROM #Data data INNER JOIN cte ON data.ID = cte.ID + 1
)
SELECT ID, Qty, [Group] FROM cte
The following query gives you most of what you want. One more self-join of the result would compute the group sizes:
select a.ID, G, sum(b.Qty) as Total
from (
select max(ID) as ID, G
from (
select a.ID, sum(b.Qty) / 50 as G
from T as a join T as b
where a.ID >= b.ID
group by a.ID
) as A
group by G
) as a join T as b
where a.ID >= b.ID
group by a.ID
ID G Total
---------- ---------- ----------
2 0 30
3 1 60
8 2 140
10 3 160
The two important tricks:
Use a self-join with an inequality to get running totals
Use integer division to calculate group numbers.
I discuss this and other techniques on my canonical SQL page.
You need to create a stored procedure for this.
If you have Group column in your database then you have to take care about it while inserting a new record by fetching the max Group value and its sum of Qty column otherwise if you want Group column as computed in select statement then you have to code stored procedure accordingly.

How to exclude rows from sum but still show them?

I have a table itemsInShippment with the following data:
itemid shippmentid qty
10 1 100
20 1 200
10 2 300
10 3 1000
and table shippments
shippmentid date shippmentstatus supplierid
1 2015-01-12 OK 5000
2 2015-01-17 OK 5000
3 2015-01-17 Cancelled 5000
I need to write a query that shows this details about specific shippment say shipmentid 1. My given parameters are supplierid and date. together they related to one shipment (unique).
For supplierid=5000 and date=2015-01-12 I want to get:
itemid qty qtyInOtherShipments
10 100 300 //1000 is canceled.
20 200 0
My query works fine without considering the cancelled:
SELECT cte.*
FROM
(SELECT
a.itemid, b.date, a.qty,
(coalesce( SUM(a.qty) OVER (PARTITION BY a.itemid), 0) -
coalesce( SUM(a.qty) OVER (PARTITION BY a.itemid, a.shipmentid) ,0) ) AS qtyInOtherShipments,
FROM
itemsInShippment a
LEFT JOIN
shippments b using (shippmentid)
WHERE
b.supplierid = 5000) AS cte
WHERE
cte.date = '2015-01-12'
the cte must be this way as in qtyInOtherShipments I Sum the total qty and then remove my own qty. In order to sum the total qty I can't do WHERE d.date=... inside I must do that outside.
This query gives:
itemid qty qtyInOtherShipments
10 100 1300
20 200 0
I'm having trouble taking under consideration the cancelled shipments.
if I change the Where to :
where b.supplierid = 5000 and b.shippmentstatus not like 'cancelled'
it works... I will see:
itemid qty qtyInOtherShipments
10 100 300
20 200 0
but if I run the query on cancelled shipments (supplierid=5000 and date=2015-01-17) I will get:
itemid qty qtyInOtherShipments
nothing
what I should have get is:
itemid qty qtyInOtherShipments
10 1000 300
so my problem is that I don't want to sum itemid that is related to cancelled but I still want to see this rows.
How do I get the correct result?
You want to exclude canceled items only from sums. So, do not filter them with where, just filter them on sums:
SUM(case when b.shippmentstatus <> 'cancelled' then a.qty end) OVER (PARTITION BY ...
Sum does not take in consideration null, that's why the above works. (When status is canceled the case expression will return null.)
A more efficient variant of Florian's answer exists for PostgreSQL 9.4, the filter clause for an aggregate.
SUM (a.qty) FILTER (WHERE b.shippmentstatus <> 'cancelled') OVER (PARTITION BY ...
See FILTER in the docs for aggregates. It's basically a mini-WHERE clause that applies only for that aggregate.
Thanks to #a_horse_with_no_name for pointing it out earlier.
Try Below query
create table #itemsInShippment (itemid int, shippmentid int, qty int)
insert into #itemsInShippment (itemid, shippmentid, qty)
SELECT 10 as itemid, 1 as shippmentid, 100 as qty UNION
SELECT 20 , 1, 200 UNION
SELECT 10 , 2, 300 UNION
SELECT 10 , 3, 1000
CREATE TABLE #shippments (shippmentid int , dt date, shippmentstatus varchar(50), supplierid int)
insert into #shippments (shippmentid, dt, shippmentstatus,supplierid)
SELECT 1 as shippmentid, '2015-01-12' as dt, 'OK' as shippmentstatus , 5000 as supplierid UNION ALL
SELECT 2, '2015-01-17', 'OK' , 5000 UNION ALL
SELECt 3, '2015-01-17' , 'Cancelled' , 5000
SELECT cte.*
FROM (
select a.itemid,b.dt,a.qty,
(coalesce( SUM(case when shippmentstatus <> 'Cancelled' then a.qty else 0 end) OVER (PARTITION BY a.itemid) ,0) -
coalesce( SUM(case when shippmentstatus <> 'Cancelled' then a.qty else 0 end) OVER (PARTITION BY a.itemid,a.shippmentid) ,0) )
AS qtyInOtherShipments
from #itemsInShippment a
left join #shippments b on a.shippmentid = b.shippmentid
where b.supplierid = 5000 --and shippmentstatus = 'Cancelled'
) as cte
where cte.dt='2015-01-12'