Reputation: 107
I have an SQL final exam in college in a few days and I have a query that's driving me crazy! I know it's a dumb query, but I'm just getting started and can't figure it out.
So, there's basicaly 2 tables, Client and Orders.
Client Orders
--------- ---------
PK Client_Id PK Order_Id
Name Client_Id FK
Order_Total
Date
Now, they ask me to "List the name of the client that bought the most in 2011"
So, for what I thought, this requires on one side, that I SUM all the Order_Total and Group by Client from 2011, then from that table, select the client with the MAX() sum of order totals, and then show only the name of that client. The problem is that I can't figure how to put all that in one query.
Hope somebody can help!
Thank you all for your very quick responses! I'm really impressed!
Now, I don't mean to be picky or anything, but just in case my teacher doesn't accept the "Limit" or "Select top" statement, is there any way to do this query without those?
SELECT
C.NAME
FROM
CLIENTS C,
ORDERS O
WHERE
O.CLIENT_ID = C.CLIENT_ID
AND O.DATE BETWEEN '1/1/2011 00:00:00.000' and '12/31/2011 23:59:59.999'
HAVING SUM(O.ORDER_TOTAL) >= ALL (SELECT SUM (O2.ORDER_TOTAL) FROM ORDER O2 GROUP BY O2.CLIENT_ID)
Upvotes: 6
Views: 31254
Reputation: 131
SELECT Client.Name
FROM Client LEFT JOIN Orders ON Orders.Client_Id = Client.Client_Id
WHERE YEAR(Orders.Date) = 2011
GROUP BY Client.Client_Id
ORDER BY SUM(Order.Order_Total) DESC
LIMIT 1
Upvotes: 1
Reputation: 1043
Is7aq's answer is correct according to your question but will only work on MySQL. Nor does it take into account the possibility that more than one client bought the most in the given year although I admit that wasn't a constraint listed in the original question. Also it's a huge performance hit to cross joins by simply using commas once your databases get sufficiently large so it's almost always better to use INNER or OUTER joins and specify the condition of the join. Anyways, did this as an exercise and here's what I had. It's could probably be better optimized:
CREATE TABLE #Client (
Client_Id int not null,
Name varchar(100) not null
)
INSERT INTO #Client VALUES (1, 'Client 1')
INSERT INTO #Client VALUES (2, 'Client 2')
INSERT INTO #Client VALUES (3, 'Client 3')
CREATE TABLE #Orders (
Order_Id int not null,
Client_Id int not null,
Order_Total int not null,
Date datetime not null
)
-- Customer 1: total=105
INSERT INTO #Orders VALUES (1, 1, 55, '1/1/2011')
INSERT INTO #Orders VALUES (2, 1, 50, '1/1/2011')
INSERT INTO #Orders VALUES (3, 1, 45, '1/1/2010') -- test removal of invalid dates
-- Customer 2: total=120
INSERT INTO #Orders VALUES (4, 2, 40, '1/1/2011')
INSERT INTO #Orders VALUES (5, 2, 40, '1/1/2011')
INSERT INTO #Orders VALUES (6, 2, 40, '1/1/2011')
-- Customer 3: total=120
INSERT INTO #Orders VALUES (7, 3, 40, '1/1/2011')
INSERT INTO #Orders VALUES (8, 3, 40, '1/1/2011')
INSERT INTO #Orders VALUES (9, 3, 40, '1/1/2011')
-- control customer to test hi/lo scenarios: total=40
INSERT INTO #Orders VALUES (10, 4, 10, '1/1/2011')
INSERT INTO #Orders VALUES (11, 4, 10, '1/1/2011')
INSERT INTO #Orders VALUES (12, 4, 10, '1/1/2011')
INSERT INTO #Orders VALUES (13, 4, 10, '1/1/2011')
SELECT T.NAME,
T.OrderTotal
FROM (SELECT C.NAME,
SUM(O.ORDER_TOTAL) OrderTotal
FROM #CLIENT C
INNER JOIN #ORDERS O
ON c.CLIENT_ID = o.CLIENT_ID
WHERE YEAR(O.DATE) = 2011
GROUP BY C.NAME) as T
WHERE T.OrderTotal = (SELECT MAX(T2.OrderTotal2)
FROM (SELECT C2.NAME,
SUM(O2.ORDER_TOTAL) OrderTotal2
FROM #CLIENT C2
INNER JOIN #ORDERS O2
ON c2.CLIENT_ID = o2.CLIENT_ID
WHERE YEAR(O2.DATE) = 2011
GROUP BY C2.NAME) as T2)
GROUP BY T.Name, T.OrderTotal
DROP TABLE #Client
DROP TABLE #Orders
-- Output
-- Client 2: 120
-- Client 3: 120
Upvotes: 1
Reputation: 2721
SELECT T.X
(SELECT C.NAME X, SUM(O.ORDER_TOTAL)
FROM CLIENT C, ORDERS O
WHERE C.CLIENT_ID = O.CLIENT_ID
AND YEAR(O.DATE) = 2011
GROUP BY O.CLIENT_ID
ORDER BY 2 DESC
LIMIT 1) T;
Upvotes: 4
Reputation: 38506
There are many ways to skin this cat... this is how I'd normally do it:
select top 1 c.Client_Id, c.Name, o.Order_Total
from Client c
join (select Client_Id, SUM(Order_Total) as Order_Total
from Orders
where `Date` between '1/1/2011 00:00:00.000' and '12/31/2011 23:59:59.999'
group by Client_Id) o
on c.Client_Id = o.Client_Id
order by o.Order_Total desc
Basically you're pulling the order total list, joining against that, sorting it descending by order total, and limiting the query to 1 result.
Upvotes: 4
Reputation: 23228
You're almost there, you just need to select from your aggregate query. It's called a derived table.
So you have this:
select c.client_id, c.name, sum(order_total) ordersum
from client c
inner join orders o on c.client_id = o.client_id
where year(o.date) = 2011
group by c.client_id, c.name
That gives you your sums. Now you want the first one. There are several ways to do this and it's also dependent on what DBMS you're using (mysql, mssql, etc.) The easiest method, though, is this:
select top 1 *
from (
select c.client_id, c.name, sum(order_total) ordersum
from client c
inner join orders o on c.client_id = o.client_id
where year(o.date) = 2011
group by c.client_id, c.name
) a
order by ordersum desc
There may be something simpler on a given platform, but that query should be generic enough to work on whatever DBMS you're using.
Upvotes: 1