简体   繁体   中英

How do I select the max value after a SUM() + Group By?

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?

Edit: Original code attempt ported from comments:

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)
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;

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.

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
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

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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM