Computed Column in Select Query? - sql

I Want To Create A %Share Column Whose Values Are Derived By Dividing The Sale Of Each Customer With The Total Sale. I'm Using The Below Query But Get Error That Column 'Sale' Cannot Be Found. Is there Is A Way Through Which I Can Get Total Of Sale Column i.e. 600 ? Please Help ...
Select IsNull([Customer].[FirstName],'Total') as Customer,
format(Sum([MY_DB].[dbo].[Order].[TotalAmount]),'0.00') [Sale],
FORMAT(sum([MY_DB].[dbo].[Order].[TotalAmount])/ sum([Sale]),'0.00%') as 'Share%'
From Customer
INNER JOIN [MY_DB].[dbo].[Order]
ON [Customer].[Id]=[MY_DB].[dbo].[Order].[CustomerId]
Group By [Customer].[FirstName] with Rollup
Having (Sum([MY_DB].[dbo].[Order].[TotalAmount]) > (Select AVG([MY_DB].[dbo].[Order].[TotalAmount]) From [MY_DB].[dbo].[Order]))
Order By [Customer].[FirstName] Desc;
-
Desired Result:
Customer Sale %Share
Zbyszek 100 16.66 %
Yvonne 200 33.33 %
Yoshi 300 50.00 %

You have to modify the existing query quite a bit. I don't think you need with rollup.
For getting the total sales use sum() over(). Then divide each sale amount by the total to get the percentage. In the same way, using avg() over() you can compute the average and find customers with sales >= avgamount.
Select Customer,[Sale],[Share%]
from (
Select Distinct
c.[FirstName] as Customer,
format(sum(o.[TotalAmount]) over(partition by o.[CustomerId]),'0.00') as [Sale],
format(100.0*sum(o.[TotalAmount]) over(partition by o.[CustomerId]) / sum(o.[TotalAmount]) over(),'0.00%') as 'Share%',
AVG(o.[TotalAmount]) over() as 'AvgAmount'
From Customer c
INNER JOIN [MY_DB].[dbo].[Order] o ON c.[Id]=o.[CustomerId]
) t
Where Sale >= AvgAmount
Order By Customer Desc;
Without computing the average you can just check for customers with share >= 50%.
Select Customer,[Sale],[Share%]
from (
Select Distinct
c.[FirstName] as Customer,
format(sum(o.[TotalAmount]) over(partition by o.[CustomerId]),'0.00') as [Sale],
format(100.0*sum(o.[TotalAmount]) over(partition by o.[CustomerId]) / sum(o.[TotalAmount]) over(),'0.00%') as 'Share%'
From Customer c
INNER JOIN [MY_DB].[dbo].[Order] o ON c.[Id]=o.[CustomerId]
) t
Where [Share%] >= 50
Order By Customer Desc;

You can't define [Sale] column and use it in a function in the same SQL.
Easiest way to achieve this is just to surround your SQL with an outer SQL statement, where you'll also calculate the total sum and make the division.
(My syntax might not be MS-SQL exact, but this is to illustrate the idea)
Select [data].[FirstName],
format([data].[Sale],'0.00'),
format([data].[Sale] / [total].[totalSum],'0.00') FROM
(Select IsNull([Customer].[FirstName],'Total') as Customer,
Sum([MY_DB].[dbo].[Order].[TotalAmount] [Sale],
From Customer
INNER JOIN [MY_DB].[dbo].[Order]
ON [Customer].[Id]=[MY_DB].[dbo].[Order].[CustomerId]
Group By [Customer].[FirstName] with Rollup
Having (Sum([MY_DB].[dbo].[Order].[TotalAmount]) > (Select AVG([MY_DB].[dbo].[Order].[TotalAmount]) From [MY_DB].[dbo].[Order])) ) data,
(Select sum([MY_DB].[dbo].[Order].[TotalAmount] as [totalSum]) from [MY_DB].[dbo].[Order]) total
Order By [data].[FirstName] Desc;

Related

SQL: Take 1 value per grouping

I have a very simplified table / view like below to illustrate the issue:
The stock column represents the current stock quantity of the style at the retailer. The reason the stock column is included is to avoid joins for reporting. (the table is created for reporting only)
I want to query the table to get what is currently in stock, grouped by stylenumber (across retailers). Like:
select stylenumber,sum(sold) as sold,Max(stock) as stockcount
from MGTest
I Expect to get Stylenumber, Total Sold, Most Recent Stock Total:
A, 6, 15
B, 1, 6
But using ...Max(Stock) I get 10, and with (Sum) I get 25....
I have tried with over(partition.....) also without any luck...
How do I solve this?
I would answer this using window functions:
SELECT Stylenumber, Date, TotalStock
FROM (SELECT M.Stylenumber, M.Date, SUM(M.Stock) as TotalStock,
ROW_NUMBER() OVER (PARTITION BY M.Stylenumber ORDER BY M.Date DESC) as seqnum
FROM MGTest M
GROUP BY M.Stylenumber, M.Date
) m
WHERE seqnum = 1;
The query is a bit tricky since you want a cumulative total of the Sold column, but only the total of the Stock column for the most recent date. I didn't actually try running this, but something like the query below should work. However, because of the shape of your schema this isn't the most performant query in the world since it is scanning your table multiple times to join all of the data together:
SELECT MDate.Stylenumber, MDate.TotalSold, MStock.TotalStock
FROM (SELECT M.Stylenumber, MAX(M.Date) MostRecentDate, SUM(M.Sold) TotalSold
FROM [MGTest] M
GROUP BY M.Stylenumber) MDate
INNER JOIN (SELECT M.Stylenumber, M.Date, SUM(M.Stock) TotalStock
FROM [MGTest] M
GROUP BY M.Stylenumber, M.Date) MStock ON MDate.Stylenumber = MStock.Stylenumber AND MDate.MostRecentDate = MStock.Date
You can do something like this
SELECT B.Stylenumber,SUM(B.Sold),SUM(B.Stock) FROM
(SELECT Stylenumber AS 'Stylenumber',SUM(Sold) AS 'Sold',MAX(Stock) AS 'Stock'
FROM MGTest A
GROUP BY RetailerId,Stylenumber) B
GROUP BY B.Stylenumber
if you don't want to use joins
My solution, like that of Gordon Linoff, will use the window functions. But in my case, everything will turn around the RANK window function.
SELECT stylenumber, sold, SUM(stock) totalstock
FROM (
SELECT
stylenumber,
SUM(sold) OVER(PARTITION BY stylenumber) sold,
RANK() OVER(PARTITION BY stylenumber ORDER BY [Date] DESC) r,
stock
FROM MGTest
) T
WHERE r = 1
GROUP BY stylenumber, sold

How do I proceed on this query

I want to know if there's a way to display more than one column on an aggregate result but without it affecting the group by.
I need to display the name alongside an aggregate result, but I have no idea what I am missing here.
This is the data I'm working with:
It is the result of the following query:
select * from Salesman, Sale,Buyer
where Salesman.ID = Buyer.Salesman_ID and Buyer.ID = sale.Buyer_ID
I need to find the salesman that sold the most stuff (total price) for a specific year.
This is what I have so far:
select DATEPART(year,sale.sale_date)'year', Salesman.First_Name,sum(sale.price)
from Salesman, Sale,Buyer
where Salesman.ID = Buyer.Salesman_ID and Buyer.ID = sale.Buyer_ID
group by DATEPART(year,sale.sale_date),Salesman.First_Name
This returns me the total sales made by each salesman.
How do I continue from here to get the top salesman of each year?
Maybe the query I am doing is completely wrong and there is a better way?
Any advice would be helpful.
Thanks.
This should work for you:
select *
from(
select DATEPART(year,s.sale_date) as SalesYear -- Avoid reserved words for object names
,sm.First_Name
,sum(s.price) as TotalSales
,row_number() over (partition by DATEPART(year,s.sale_date) -- Rank the data within the same year as this data row.
order by sum(s.price) desc -- Order by the sum total of sales price, with the largest first (Descending). This means that rank 1 is the highest amount.
) as SalesRank -- Orders your salesmen by the total sales within each year, with 1 as the best.
from Buyer b
inner join Sale s
on(b.ID = s.Buyer_ID)
inner join Salesman sm
on(sm.ID = b.Salesman_ID)
group by DATEPART(year,s.sale_date)
,sm.First_Name
) a
where SalesRank = 1 -- This means you only get the top salesman for each year.
First, never use commas in the FROM clause. Always use explicit JOIN syntax.
The answer to your question is to use window functions. If there is a tie and you wand all values, then RANK() or DENSE_RANK(). If you always want exactly one -- even if there are ties -- then ROW_NUMBER().
select ss.*
from (select year(s.sale_date) as yyyy, sm.First_Name, sum(s.price) as total_price,
row_number() over (partition by year(s.sale_date)
order by sum(s.price) desc
) as seqnum
from Salesman sm join
Sale s
on sm.ID = s.Salesman_ID
group by year(s.sale_date), sm.First_Name
) ss
where seqnum = 1;
Note that the Buyers table is unnecessary for this query.

SQL (DB2) query to get count of top 10% revenue contributing customers

I work for a telecom company and I need to run a scheme for top valued customers who contributed 10% of total company's revenue in the month. I want to know the count of customers who are eligible for this scheme? I am using SQL DB2.
Ex - In the below table, Sum of the Revenue is 5000 and its 10% is 500, and I want to know the count of minimum number of customers whose sum of revenue would be either 500 or just above 500
Customers Revenue
A 156
B 259
C 389
D 125
E 578
F 321
To find all customers where their total revenue is at least 10 percent of the overall revenue:
select customer
from the_table
group by customer
having sum(revenue) >= (select sum(revenue) * 0.1 from the_table);
Your sample data doesn't show this, but this also deals with multiple rows per each customer in the table (your example only has a single row per customer)
The get the count of that:
select count(*)
from (
select customer
from the_table
group by customer
having sum(revenue) >= (select sum(revenue) * 0.1 from the_table)
) t
I interpret the question as wanting the highest revenue customers whose sum is at least 10% of the total revenue.
You need a cumulative sum for this:
select count(*)
from (select t.*, sum(revenue) over (order by revenue desc) as cume_rev,
sum(revenue) over () as tot_rev
from t
) t
where cume_rev <= tot_rev * 0.1;
This assumes that there is one row per customer.
EDIT:
For "just above", the where clause should be:
where cume_rev - revenue < tot_rev * 0.1;

get row with max from group by results

I have sql such as:
select
c.customerID, sum(o.orderCost)
from customer c, order o
where c.customerID=o.customerID
group by c.customerID;
This returns a list of
customerID, orderCost
where orderCost is the total cost of all orders the customer has made. I want to select the customer who has paid us the most (who has the highest orderCost). Do I need to create a nested query for this?
You need a nested query, but you don't have to access the tables twice if you use analytic functions.
select customerID, sumOrderCost from
(
select customerID, sumOrderCost,
rank() over (order by sumOrderCost desc) as rn
from (
select c.customerID, sum(o.orderCost) as sumOrderCost
from customer c, orders o
where c.customerID=o.customerID
group by c.customerID
)
)
where rn = 1;
The rank() function ranks the results from your original query by the sum() value, then you only pick those with the highest rank - that is, the row(s) with the highest total order cost.
If more than one customer has the same total order cost, this will return both. If that isn't what you want you'll have to decide how to determine which single result to use. If you want the lowest customer ID, for example, add that to the ranking function:
select customerID, sumOrderCost,
rank() over (order by sumOrderCost desc, customerID) as rn
You can adjust you original query to return other data instead, just for the ordering, and not include it in the outer select.
You need to create nested query for this.
Two queries.

Find the highest number of occurences in a column in SQL

Given this table:
Order
custName description to_char(price)
A desa $14
B desb $14
C desc $21
D desd $65
E dese $21
F desf $78
G desg $14
H desh $21
I am trying to display the whole row where prices have the highest occurances, in this case $14 and $21
I believe there needs to be a subquery. So i started out with this:
select max(count(price))
from orders
group by price
which gives me 3.
after some time i didn't think that was helpful. i believe i needed the value 14 and 21 rather the the count so i can put that in the where clause. but I'm stuck how to display that. any help?
UPDATE: So I got it to query the 14 and 21 from this
select price
from orders
group by price
having (count(price)) in
(select max(count(price))
from orders
group by price)
but i need it to display the custname and description column which i get an error:
select custname, description, price
from orders
group by price
having (count(price)) in
(select max(count(price))
from orders
group by price)
SQL Error: ORA-00979: not a GROUP BY expression
any help on this?
I guess you are pretty close. Since HAVING operates on the GROUPed result set, try
HAVING COUNT(price) IN
or
HAVING COUNT(price) =
replacing your current line.
Since you tagged the question as oracle, you can use windowing functions to get aggregate and detail data within the same query.
SELECT COUNT (price) OVER (PARTITION BY price) count_at_this_price,
o.*
from orders o
order by 1 desc
select employee, count(employee)
from work
group by employee
having count(employee) =
( select max(cnt) from
( select employee, count(employee cnt
from work
group by employee
)
);
Reference
You could try something like
select * from orders where price in (select top 2 price from orders group by price order by price desc)
I'm not sure of limiting results in Oracle, in SQL Server is top, maybe you should use limit.