views:

1106

answers:

3

In MS Transact SQL, let's say I have a table (Orders) like this:

 Order Date       Order Total     Customer #
 09/30/2008       8.00            1
 09/15/2008       6.00            1
 09/01/2008       9.50            1
 09/01/2008       1.45            2
 09/16/2008       4.50            2
 09/17/2008       8.75            3
 09/18/2008       2.50            3

What I need out of this is: for each customer the average order amount for the most recent two orders. So for Customer #1, I should get 7.00 (and not 7.83).

I've been staring at this for an hour now (inside a larger problem, which I've solved) and I think my brain has frozen. Help for a simple problem?

+5  A: 

This should make it

select avg(total), customer 
from orders o1 
where orderdate in 
  ( select top 2 date 
    from orders o2 
    where o2.customer = o1.customer 
    order by date desc )
group by customer
AlbertEin
May not work if there is more then 1 order on a given day e.g. if for a customer you have 3 orders on one day. the sub-query will give you 2 records of the same date, and the top query will take into account all 3 orders - but perhaps it is not real life scenario in the context.
kristof
In a real life scenario the date column would store the time too, so it wouldn't be a problem
AlbertEin
You could also target the TOP 2 on the CreatedDate of the record, if your OrderDate has a "day resolution" only. If you have a CreatedDate, that is. ;-)
Tomalak
And if you don't, well, you don't have the data needed to get the last two sales, so you data schema is wrong
AlbertEin
Actually, the Real Life version of this table has the resolution problem (dates, but not times) but it's close enough for the kind of information I need.
clintp
A: 

In SQL Server 2005 you have the RANK function, used with partition:

USE AdventureWorks;
GO
SELECT i.ProductID, p.Name, i.LocationID, i.Quantity
    ,RANK() OVER 
    (PARTITION BY i.LocationID ORDER BY i.Quantity DESC) AS 'RANK'
FROM Production.ProductInventory i 
    INNER JOIN Production.Product p 
        ON i.ProductID = p.ProductID
ORDER BY p.Name;
GO

Link

Sklivvz
This has a touch of overkill. Is there a good reason to do it like that, performance-wise?
Tomalak
Yes, works much better than a subquery.
Sklivvz
In terms of? There must be a damn good reason to divert from what a few lines less of standard SQL can do with an equal result.
Tomalak
Microsoft has provided a function (RANK with PARTITION) exactly to handle cases like these. The more specific a function, the better the execution plan.So IMHO it's better in term of both future readability and performance.
Sklivvz
The rank method is easier and much cleaner if there are multiple criteria that define the "rank" and the partition. See /questions/150891/sql-query-remove-duplicates-with-caveats#151410 for an example where rank makes the query much simpler
Darrel Miller
A: 

One option would be for you to use a cursor to loop through all the customer Id's, then do the averages as several subqueries.

Fair warning though, for large datasets, queries are not very efficient and can take a long time to process.

Ian Jacobs