how to match a value with SQL max(count) function? - sql

I have a orderLine table looks like this
I would like to know which pizza is the best seller, and the quantity of pizza sold.
I've tried query:
select sum(quantity), pizza_name from order_line group by pizza_name;
it returns
which is almost what I want, But when I start adding Max function, it could not match the pizza name with the total quantity of pizza sold
For example:
select MAX(sum(quantity)), pizza_name from order_line group by pizza_name;
it returns following error:
"not a single-group group function"
I guess I could achieve this by using a sub-query, but I have no idea how to do this.

You don't need max for this. If you only want one pizza, then you can use order by and fetch first 1 row only (or something similar such as limit or top):
select sum(quantity), pizza_name
from order_line
group by pizza_name
order by sum(quantity)
fetch first 1 row only;
Or, if you want all such pizzas, use rank():
select p.*
from (select sum(quantity) as quantity, pizza_name,
rank() over (order by sum(quantity) desc) as seqnum
from order_line
group by pizza_name
) p
where seqnum = 1;

Both of the queries give the same desired result
SELECT PIZZA_NAME,
SUM(QUANTITY) "Total Quant"
FROM Order_line
GROUP BY PIZZA_NAME
ORDER BY "Total Quant" DESC
FETCH FIRST 1 row only;
SELECT PIZZA_NAME, "Total Quantity" FROM (
SELECT PIZZA_NAME,SUM(QUANTITY) "Total Quantity", RANK() OVER (ORDER BY SUM(QUANTITY) DESC) T FROM Order_line GROUP BY PIZZA_NAME
) query1 where query1.T=1 ;

You group by pizza_name to get sum(quantity) per pizza_name.
Then you aggregate again by using MAX on the quantity sum, but you don't specify which of the three pizza names to have in the result. You need an aggregate function on pizza_name as well, which you don't have. Hence the error.
If you want to use your query, you must apply the appropriate aggregation function on pizza_name, which is KEEP DENSE_RANK FIRST/LAST.
select
max(sum(quantity)),
max(pizza_name) keep (dense_rank last order by sum(quantity))
from order_line
group by pizza_name;
But on one hand Gordon's queries are more readable in my opinion. And on the other this double aggregation is Oracle specific and not SQL standard. Unexperienced readers may be confused that the query produces one result row in spite of the GROUP BY clause.

Related

Using Max and Sum in the same query

I'm trying to get the the best selling product in all sales, and I'm using the following query:
SELECT Max(Sum(sold_mount)),
product_code
FROM sold_ items
GROUP BY product_code;
and it's returning "not a single-group group function"
it's possible to use max and sum in the same query?
I would recommend writing this as:
SELECT SUM(sold_mount), product_code
FROM sold_ items
GROUP BY product_code
ORDER BY SUM(sold_amount) DESC
FETCH FIRST 1 ROW ONLY;

How to use SUM and MAX on the same column?

So I'm having an SQL table where I will need find out the product that has been purchased the most meaning that I need to do a SUM and a group by on all the quantity of the products:
SELECT PRODUCT_ID, SUM(QUANTITY) FROM PURCHASE GROUP BY PRODUCT_ID
However when I try to find the product with the maximum amount of purchases it gives me an error:
SELECT MAX(QUANTITY) FROM(SELECT PRODUCT_ID, SUM(QUANTITY) FROM PURCHASE GROUP BY PRODUCT_ID)
Any ideas?
Just order by and keep the top record only:
SELECT PRODUCT_ID, SUM(QUANTITY) SUM_QUANTITY
FROM PURCHASE
GROUP BY PRODUCT_ID
ORDER BY SUM_QUANTITY DESC
LIMIT 1
The actual syntax might vary accross RDBMS. The above would work in MySQL and Postgres.
In SQL Server, you would use SELECT TOP (1) ... ORDER BY SUM_QUANTITY DESC.
In Oracle >= 12c, you would use SELECT ... ORDER BY SUM_QUANTITY DESC FETCH FIRST ROW ONLY.
You also have to consider the possibilty of ties in the first position, for which there are different strategies depending on your requirement and RDBMS.

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.

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.