Search code examples
sqlsql-serverranking

SQL Server get the rank without showing the full result set?


Say I have a Customer table and a Purchase table, with a one to zero or many relationship between Customer and Purchase.

I understand I can use the rank() function in SQL Server to get, for example, the top 10 customers based on the purchase amount. But I would also like to get a specific customer's rank within the entire system, but without returning the entire dataset of customers ranked ahead of that customer.

I'm a little rusty on SQL (Entity Framework's fault, haha) so I wasn't able to build an efficient statement for this query. Does anybody know if rank() or some other function can do this?


Solution

  • This sounds like a good candidate for a CTE and using a window function like you mention. You can declare the specific customer, use a CTE to get all customer's purchase amount, use a second cte to rank them all, then query the top 10 and union the specific customer info. The union will drop the 11th person if they are already in the top 10. UNION ALL will give you a duplicate record, but provide 11 rows.

    DECLARE @userID INT = xxxx
    
    ;WITH cte1 AS (
    SELECT 
    customerID
    , SUM(purchase_dollars) AS purchase_amount
    FROM customer_table
    
    GROUP BY customerID)
    
    , cte_rank AS (
    SELECT 
    customerID
    , RANK() OVER(ORDER BY purchase_amount DESC) AS purchase_amount_rank
    --, ROW_NUMBER() OVER(ORDER BY purchase_amount DESC) AS purchase_amount_rank --This will get you 10 records
    FROM cte1)
    
    SELECT 
    t1.CustomerID
    , t2.purchase_amount_rank
    
    FROM customer_table t1
    INNER JOIN cte_rank t2
    ON t1.customerid = t2.customerid
    WHERE t2.purchase_amount_rank <= 10
    
    UNION 
    
    SELECT t1.customerID
    , t2.purchase_amount_rank
    FROM customer_table t1
    INNER JOIN cte_rank t2
    ON t1.customerid = t2.customerid
    AND t1.customer_id = @userID
    
    ORDER BY t2.purchase_amount_rank