Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Ranking table on one column whilst sorting on another

I have a subset of a SQL Server 2008 R2 table like this:

cust_id | prod_id | day | price
--------+---------+-----+-------
137656    194528   42373   9.11
137656    194528   42374   9.11
137656    194528   42375   9.61
137656    194528   42376   9.61
137656    194528   42377   9.11
137656    194528   42378   9.11

I need to rank the different price periods like so:

cust_id | prod_id | day | price | rank
--------+---------+-----+-------+------
137656    194528   42373   9.11     1
137656    194528   42374   9.11     1
137656    194528   42375   9.61     2
137656    194528   42376   9.61     2
137656    194528   42377   9.11     3
137656    194528   42378   9.11     3

so that it sorts by cust_id, prod_id and day ascending but increments the rank when the price changes. I have tried to use DENSE_RANK() like this:

SELECT 
    cust_id, prod_id, [day], price, 
    DENSE_RANK() OVER (ORDER BY cust_id, prod_id, price)
FROM
    @prices 

This returns something like:

cust_id | prod_id | day | price | rank
--------+---------+-----+-------+------
137656    194528   42373   9.11     1
137656    194528   42374   9.11     1
137656    194528   42377   9.11     1
137656    194528   42378   9.11     1
137656    194528   42375   9.61     2
137656    194528   42376   9.61     2

Obviously excluding the day from the sort will give me these results but whenever I include the day in the order by section of the DENSE_RANK() - it just partitions each new day as a new ID....

Does anyone have any ideas on how this should work? Appreciate any advice and can give more info if required

like image 496
samil90 Avatar asked Nov 08 '22 14:11

samil90


1 Answers

The first variant with LAG and SUM

SELECT
  *,
  1+SUM(IncCount)OVER(PARTITION BY cust_id ORDER BY [day]) [rank]
  --1+SUM(IncCount)OVER(PARTITION BY cust_id ORDER BY [day] ROWS BETWEEN unbounded preceding AND current row) [rank]
FROM
  (
    SELECT
      *,
      IIF(LAG(price)OVER(PARTITION BY cust_id ORDER BY [day])<>price,1,0) IncCount
      --CASE WHEN LAG(price)OVER(PARTITION BY cust_id ORDER BY [day])<>price THEN 1 ELSE 0 END IncCount
    FROM Test
  ) q

The second variant without LAG

WITH numCTE AS(
  SELECT *,ROW_NUMBER()OVER(PARTITION BY cust_id ORDER BY [day]) RowNum
  FROM Test
)
SELECT
  t1.*,
  1+SUM(CASE WHEN t2.price<>t1.price THEN 1 ELSE 0 END)OVER(PARTITION BY t1.cust_id ORDER BY t1.[day]) [rank]
  --1+SUM(CASE WHEN t2.price<>t1.price THEN 1 ELSE 0 END)OVER(PARTITION BY t1.cust_id ORDER BY t1.[day] ROWS BETWEEN unbounded preceding AND current row) [rank]
FROM numCTE t1
LEFT JOIN numCTE t2 ON t2.RowNum+1=t1.RowNum AND t2.cust_id=t1.cust_id

The third variant with recursive CTE

WITH numCTE AS(
  SELECT *,ROW_NUMBER()OVER(PARTITION BY cust_id ORDER BY [day]) RowNum
  FROM Test
),
rankCTE AS(
  SELECT RowNum,cust_id,prod_id,[day],price,1 [rank]
  FROM numCTE
  WHERE RowNum=1

  UNION ALL

  SELECT
    n.RowNum,n.cust_id,n.prod_id,n.[day],n.price,
    r.[rank]+CASE WHEN n.price<>r.price THEN 1 ELSE 0 END [rank]
  FROM numCTE n
  JOIN rankCTE r ON n.RowNum=r.RowNum+1 AND n.cust_id=r.cust_id
)
SELECT *
FROM rankCTE
OPTION(MAXRECURSION 0)
like image 187
Sergey Menshov Avatar answered Nov 15 '22 08:11

Sergey Menshov