SQL query to calculate totals - sql

I'm quite new to SQL querying so please go easy on me if what I've done so far is really odd :)
I have two tables - A for Income and B for Expenditure:
Business_ID Income_Desc Income_Amount
1 Income A 1000
1 Income B 3000
1 Income C 2000
Business_ID Expen_Amount
1 2500
I'd like to produce a table that shows each of the income amounts, the one expenditure amount, the total income, the total expenditure and a Grand Total of total income-total expenditure.
Something like this if possible
Business_ID Income Description Income Amount Expenditure Amount Total
1 Income A 1000 2500 -
1 Income B 3000 - -
1 Income C 2000 - -
1 All Amounts 6000 2500 3500
This is what I've tried so far
SELECT a. Business_ID, COALESCE (a.Income_Desc, 'All Amounts') AS 'Income Description', SUM(a.Income_Amount) AS 'Income Amount', SUM(b.Expen_Amount) AS Expenditure Amount', (sum(a.Income_Amount)-SUM(b.Expen_Amount)) AS 'Total'
FROM Income AS a LEFT JOIN Expenditure AS b ON a.Business_ID = b. Business_ID
GROUP BY a. Business_ID, a.Income_Desc WITH ROLLUP
The result I'm getting is this
Business_ID Income Description Income Amount Expenditure Amount Total
1 Income A 1000 2500 -1500
1 Income B 3000 2500 500
1 Income C 2000 2500 -500
1 All Amounts 6000 7500 -1500
All Amounts 6000 7500 -1500
Is it possible to get an output like the one I provided above? Could you show me how to achieve it (or something very close) please?
Thanks

You can use row_number() for the join:
with ie as (
select i.business_id, i.income_desc, i.income_amount,
e.expen_amount
from (select i.*,
row_number() over (partition by business_id order by income_desc) as seqnum
from income i
) i left join
(select e.*,
row_number() over (partition by business_id order by expen_amount) as seqnum
from expenditure e
) e
on i.business_id = e.business_id and i.seqnum = e.seqnum
)
select ie.*
from ie
union all
select business_id, 'Total', sum(income_amount), sum(expen_amount)
from ie
group by business_id;

You could make a sub query out of your original query and only select values where the business ID is not null. Furthermore, use CASE WHEN to identify those values < 0 and replace it with "-":
SELECT x.Business_ID
, x.`Income Description`
, x.`Income Amount`
, x.`Expenditure Amount`
, x.Total
FROM
(SELECT a. Business_ID
, COALESCE (a.Income_Desc, 'All Amounts') AS 'Income Description'
, SUM(a.Income_Amount) AS 'Income Amount'
, SUM(b.Expen_Amount) AS 'Expenditure Amount'
, CASE WHEN (sum(a.Income_Amount)-SUM(b.Expen_Amount)) < 0
THEN '-'
ELSE (sum(a.Income_Amount)-SUM(b.Expen_Amount))
END AS 'Total'
FROM Income AS a
LEFT JOIN Expenditure AS b ON a.Business_ID = b.Business_ID
WHERE a.Business_ID is not null and b.Business_ID is not null
GROUP BY a.Business_ID, a.Income_Desc WITH ROLLUP) as x
where x.Business_ID is not null
DB Fiddle

Related

PIVOT using JOIN in SQL

I was asked to pivot this data using basic SQL and wasn't sure how to answer it. I googled some answers and realized you can use MAX or SUM with CASE expressions, but at the end of the interview I asked how you would solve the question and the interviewer said by using joins. Can anyone show me how it's done using joins?
BEGINNING TABLE
emp_id
col_id
col_desc
attvalue
month
1
1
salary
2000
2010-05-09
1
2
bonus
0
2010-05-09
1
3
compensation
2000
2010-05-09
1
1
salary
2000
2010-05-10
1
2
bonus
500
2010-05-10
1
3
compensation
2500
2010-05-10
2
1
salary
1000
2010-05-09
2
2
bonus
500
2010-05-09
2
3
compensation
1500
2010-05-09
Code to create the beginning table
CREATE TABLE Employees(emp_id INT, col_id INT, col_desc NVARCHAR(MAX), attvalue INT, month DATE);
INSERT INTO Employees
VALUES
(1,1,'salary',2000,'2010-05-09'),
(1,2,'bonus',0,'2010-05-09'),
(1,3,'compensation',2000,'2010-05-09'),
(1,1,'salary',2000,'2010-05-10'),
(1,2,'bonus',500,'2010-05-10'),
(1,3,'compensation',2500,'2010-05-10'),
(2,1,'salary',1000,'2010-05-09'),
(2,2,'bonus',500,'2010-05-09'),
(2,3,'compensation',1500,'2010-05-09');
RESULTING TABLE
emp_id
month
salary
bonus
compensation
1
2010-05-09
2000
0
2000
1
2010-05-10
2000
500
2500
2
2010-05-09
1000
500
1500
Below are the self join, case expression and pivot way
-- Self Join way
select s.emp_id, s.month,
s.attvalue as salary,
b.attvalue as bonus,
c.attvalue as compensation
from Employees s
inner join Employees b on s.emp_id = b.emp_id
and s.month = b.month
inner join Employees c on s.emp_id = c.emp_id
and s.month = c.month
where s.col_desc = 'salary'
and b.col_desc = 'bonus'
and c.col_desc = 'compensation'
order by s.emp_id, s.month
-- case expression way
select emp_id, month,
max(case when col_desc = 'salary' then attvalue else 0 end) as salary,
max(case when col_desc = 'bonus' then attvalue else 0 end) as bonus,
max(case when col_desc = 'compensation' then attvalue else 0 end) as compensation
from Employees
group by emp_id, month
order by emp_id, month
-- Pivot way
select *
from (
select emp_id, month, col_desc, attvalue
from Employees
) d
pivot
(
max(attvalue)
for col_desc in ([salary], [bonus], [compensation])
) p
order by emp_id, month

SQL Server price table weighted to buying price

I have 3 tables:
Buys
ID
Item
qty
price
1
1001
10
1.00
2
1001
10
2.00
3
1001
10
3.00
4
1002
10
2.00
5
1002
10
1.00
6
1003
10
1.00
7
1004
10
1.00
8
1004
10
2.00
Fallback
Item
price
1001
3.00
1002
3.00
1003
4.00
Stock
Item
stock
1001
15
1002
5
1003
25
1004
15
I have to calculate actually price each item. For that, I have to check the table "buys" each row from the biggest ID to smallest ID and take all prices as long as the stock is sufficient. If not enough buys in the table, I have to use the fallback prices for part of stock, I don't have price in first table.
So for item no. 1001, stock is 15. Price for 10 pcs found in ID 3 (3.00 USD); price for rest 5 pieces in row ID 2 (2.00 USD). So correct actually stockprice is 2.66 USD.
For item no. 1002, stock is 5. Price for latest buy is 1.00 USD in row ID 5 with quantity more than 5. So correct actually stockprice is 1.00 USD.
For item no. 1003, stock is 25. Only one entry in row ID 6 with 10 pcs for 1.00 USD each. so price for missing 15pcs have to take from fallback table 4.00 USD. So correct actually stockprice is 2.80 USD.
Result should be like this:
Item
stock
value
1001
15
2.66
1002
5
1.00
1003
25
2.80
But I have no idea how that works. Thank you very much for help.
Using conditional aggregation when comparring stock to buys runnig totals, finally apply the fallback
select t.item, (s + t.qf * f.price) s, stock, (s + t.qf * f.price) / stock price
from (
select s.Item, s.Stock,
sum(coalesce(case when b.qe <= Stock then b.qty else Stock - b.qs end * b.price, 0)) s,
-- qty for fallback
min(case when Stock > coalesce(b.qe,0) then Stock - coalesce(b.qe,0) else 0 end) qf
from Stock s
left join (
select Item, qty, price, ID,
sum(qty) over(partition by Item order by ID desc) - qty qs, -- starting runnig total
sum(qty) over(partition by Item order by ID desc) qe -- ending runnig total
from Buys
) b on s.Item = b.Item and s.Stock > b.qs
group by s.Item, s.Stock
) t
join Fallback f on f.Item = t.Item;
order by t.Item;
Provided a fallback can be missing for an item a minor tweak is requierd.
select t.item, (s + t.qf * coalesce(f.price, 0)) s, stock, (s + t.qf * coalesce(f.price, 0)) / stock price
from (
select s.Item, s.Stock,
sum(coalesce(case when b.qe <= Stock then b.qty else Stock - b.qs end * b.price, 0)) s,
-- qty for fallback
min(case when Stock > coalesce(b.qe,0) then Stock - coalesce(b.qe,0) else 0 end) qf
from Stock s
left join (
select Item, qty, price, ID,
sum(qty) over(partition by Item order by ID desc) - qty qs, -- starting runnig total
sum(qty) over(partition by Item order by ID desc) qe -- ending runnig total
from Buys
) b on s.Item = b.Item and s.Stock > b.qs
group by s.Item, s.Stock
) t
left join Fallback f on f.Item = t.Item
where t.qf = 0 or f.item is not null
order by t.Item;
The query will not return a row if a fallback is required but is missing. Otherwise the row is returned.
db<>fiddle
You need to create a running sum of the quantity in Buys and calculate the price based off that.
This is somewhat complicated by the fact that you may have too many, or not enough, rows in Buys to fulfil the stock.
SELECT
s.Item,
s.stock,
(
ISNULL(b.FoundStockPrice, 0)
+ CASE WHEN s.stock > ISNULL(b.FoundStock, 0)
THEN s.stock - ISNULL(b.FoundStock, 0)
ELSE 0 END * f.price
) / s.stock
FROM Stock s
JOIN Fallback f ON f.Item = s.Item
OUTER APPLY (
SELECT
FoundStock = SUM(b.qty),
FoundStockPrice = SUM(
CASE WHEN b.FullStock > b.RunningSum THEN b.qty
ELSE b.FullStock - (b.RunningSum - b.qty) END
* b.price)
FROM (
SELECT *,
RunningSum = SUM(b.qty) OVER (PARTITION BY b.Item
ORDER BY b.ID DESC ROWS UNBOUNDED PRECEDING),
FullStock = s.stock
FROM Buys b
WHERE b.Item = s.Item
) b
WHERE b.RunningSum - b.qty < s.stock
) b;
Steps are as follows:
For every Stock take all relevant Buys rows.
Calculate a running sum of qty, and then filter to only rows where the running sum includes the final stock (in other words it must up to the previous running sum).
Sum these Buys rows multiplied by their price, taking into account that we need to net off anything over the necessary stock. Take also a total sum of the quantity.
The final price is: the previous calculated total, plus any remaining unfound stock multiplied by the fallback.price, all divided by the total stock.
db<>fiddle

SQL | How to sum over partition group of 3 items?

I'm trying to get the percent of a day's revenue for top 3 product categories but struggling with the percentage. I have already the revenue per product ranked 1 to 3 but cant wrap my head on how to get the percentage.
Any pointers will be appreciated.
SELECT * FROM (
SELECT date,
category_name,
revenue,
row_number() OVER(PARTITION BY DATE(date) ORDER BY revenue DESC) AS category_rank,
(revenue / (select sum(revenue) from a group by 1)) * 100 percentage AS percentage_of_daily_total -- this is the wrong one
FROM (
SELECT DATE(orders.order_timestamp) AS date,
products.product_cat AS category_name,
ROUND(SUM(payments.payment)) AS revenue
FROM table1.orders orders
JOIN table1.t_payments payments ON orders.order_id = payments.order_id
JOIN table1.t_items items ON orders.order_id = items.order_id
JOIN table1.t_products products ON items.product_id = products.product_id
GROUP BY 1 ,2) a) b
WHERE category_rank <= 3;
Sample data is as follow: date, category_name, revenue, category_rank
2016-10-05 jeans 20 1
2016-10-05 shirts 15 2
2016-10-05 shoes 10 3
2016-10-06 skirts 50 1
2016-10-06 sports_wear 30 2
2016-10-06 accesories 10 3
Desired outcome:date, category_name, revenue, category_rank, percentage_of_daily_total
2016-10-05 jeans 30 1 50
2016-10-05 shirts 20 2 33
2016-10-05 shoes 10 3 17
2016-10-06 skirts 20 1 50
2016-10-06 sports_wear 16 2 40
2016-10-06 accessories 4 3 10
Use CTEs
WITH a AS (
SELECT DATE(orders.order_timestamp) AS date,
products.product_cat AS category_name,
ROUND(SUM(payments.payment)) AS revenue
FROM table1.orders orders
JOIN table1.t_payments payments ON orders.order_id = payments.order_id
JOIN table1.t_items items ON orders.order_id = items.order_id
JOIN table1.t_products products ON items.product_id = products.product_id
GROUP BY 1 ,2
)
SELECT * FROM (
SELECT a.date,
a.category_name,
a.revenue,
row_number() OVER(PARTITION BY DATE(a.date) ORDER BY a.revenue DESC) AS category_rank,
(a.revenue / b.revenue_sum) * 100 percentage AS percentage_of_daily_total
FROM a
JOIN (SELECT date, sum(revenue) AS revenue_sum FROM a GROUP BY 1) AS b
ON a.date = b.date)
WHERE category_rank <= 3;
Your original query is very close. Calculate the percent in the OUTERMOST sql. So drop your percentage calc and then in the outer Select:
Select *, 100*revenue/(sum(revenue) Over (Partition by Date)) as percentage_of_daily_total
Remember that by the time you get to calculating the windowing functions the Where clause has already been executed and you're down to 3 records per day so any total will only be based on the top 3.

calculating percentage of sales profit in SQL

Product Group Product ID Sales Profit
A 6797 1,000 200
A 6745 500 90
B 1278 200 60
B 1245 1,500 350
C 7890 650 80
D 4587 350 50
Q1). Filter out product IDs that contribute to top 80% of the total profit of their respective group.
Not sure what rdbms you are using you can get the output in SQL server in this way. You can get profit for a group and use aggregate function to compare and filter the rows.
select 'A' as Product_group, 6797 as ProductID, 1000 as Sales , 200 as Profit into #temp1 union all
select 'A' as Product_group, 6745 as ProductID, 500 as Sales , 90 as Profit union all
select 'B' as Product_group, 1278 as ProductID, 200 as Sales , 60 as Profit union all
select 'B' as Product_group, 1245 as ProductID, 1500 as Sales , 350 as Profit union all
select 'C' as Product_group, 7890 as ProductID, 650 as Sales , 80 as Profit union all
select 'D' as Product_group, 4587 as ProductID, 350 as Sales , 50 as Profit
select t.Product_group, t.ProductID, sum(t.sales) totalsles, sum(t.profit) totalProfit, sum(Profit_grp) Groupprofit from #temp1 t
join (select Product_group, sum(sales) totalsles_group, sum(profit) Profit_grp from #temp1 t1 group by Product_group) t1 on t1.Product_group = t.Product_group
group by t.Product_group, t.ProductID
having sum(t.profit) *1.0/ sum(t1.Profit_grp) *1.0 >= 0.8
Output: I added group profit just to compare. You can remove the aggregate and add in group by if you would like
Product_group ProductID totalsles totalProfit Groupprofit
B 1245 1500 350 410
C 7890 650 80 80
D 4587 350 50 50
I think this may works out:
with CTE as(
select [Product Group], sum([Sales]) as Tolsum from Table
group by [Product Group]
select prod.*,
sum(prod.[Profit]/cte.[Tolsum]) over (Partition by prod.[Product Group] Order by prod.[Product ID]) as contribution
from CTE cte
inner join
Table prod
on
cte.[Product Group] = prod.[Product Group]
having
sum(prod.[Profit]/cte.[Tolsum]) over (Partition by prod.[Product Group] Order by prod.[Product ID]) < 0.8

T-SQL calculate the percent increase or decrease between the earliest and latest for each project

I have a table like below, I am trying to run a query in T-SQL to get the earliest and latest costs for each project_id according to the date column and calculate the percent cost increase or decrease and return the data-set show in the second table (I have simplified the table in this question).
project_id date cost
-------------------------------
123 7/1/17 5000
123 8/1/17 6000
123 9/1/17 7000
123 10/1/17 8000
123 11/1/17 9000
456 7/1/17 10000
456 8/1/17 9000
456 9/1/17 8000
876 1/1/17 8000
876 6/1/17 5000
876 8/1/17 10000
876 11/1/17 8000
Result:
(Edit: Fixed the result)
project_id "cost incr/decr pct"
------------------------------------------------
123 80% which is (9000-5000)/5000
456 -20%
876 0%
Whatever query I run I get duplicates.
This is what I tried:
select distinct
p1.Proj_ID, p1.date, p2.[cost], p3.cost,
(nullif(p2.cost, 0) / nullif(p1.cost, 0)) * 100 as 'OVER UNDER'
from
[PROJECT] p1
inner join
(select
[Proj_ID], [cost], min([date]) min_date
from
[PROJECT]
group by
[Proj_ID], [cost]) p2 on p1.Proj_ID = p2.Proj_ID
inner join
(select
[Proj_ID], [cost], max([date]) max_date
from
[PROJECT]
group by
[Proj_ID], [cost]) p3 on p1.Proj_ID = p3.Proj_ID
where
p1.date in (p2.min_date, p3.max_date)
Unfortunately, SQL Server does not have a first_value() aggregation function. It does have an analytic function, though. So, you can do:
select distinct project_id,
first_value(cost) over (partition by project_id order by date asc) as first_cost,
first_value(cost) over (partition by project_id order by date desc) as last_cost,
(first_value(cost) over (partition by project_id order by date desc) /
first_value(cost) over (partition by project_id order by date asc)
) - 1 as ratio
from project;
If cost is an integer, you may need to convert to a representation with decimal places.
You can use row_number and OUTER APPLY over top 1 ... prior to SQL 2012
select
min_.projectid,
latest_.cost - min_.cost [Calculation]
from
(select
row_number() over (partition by projectid order by date) rn
,projectid
,cost
from projectable) min_ -- get the first dates per project
outer apply (
select
top 1
cost
from projectable
where
projectid = min_.projectid -- get the latest cost for each project
order by date desc
) latest_
where min_.rn = 1
This might perform a little better
;with costs as (
select *,
ROW_NUMBER() over (PARTITION BY project_id ORDER BY date) mincost,
ROW_NUMBER() over (PARTITION BY project_id ORDER BY date desc) maxcost
from table1
)
select project_id,
min(case when mincost = 1 then cost end) as cost1,
max(case when maxcost = 1 then cost end) as cost2,
(max(case when maxcost = 1 then cost end) - min(case when mincost = 1 then cost end)) * 100 / min(case when mincost = 1 then cost end) as [OVER UNDER]
from costs a
group by project_id