在一列上排名表,同时在另一列上排序

Posted

技术标签:

【中文标题】在一列上排名表,同时在另一列上排序【英文标题】:Ranking table on one column whilst sorting on another 【发布时间】:2018-05-03 12:38:07 【问题描述】:

我有一个 SQL Server 2008 R2 表的子集,如下所示:

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

我需要像这样对不同的价格周期进行排名:

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

以便它按cust_idprod_idday 升序排序,但在价格变化时增加排名。我曾尝试像这样使用DENSE_RANK()

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

这会返回类似:

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

显然,从排序中排除这一天会给我这些结果,但每当我在DENSE_RANK() 的按部分的顺序中包含这一天时 - 它只是将每个新的一天划分为一个新的 ID....

有人对这应该如何工作有任何想法吗?感谢任何建议,如果需要,可以提供更多信息

【问题讨论】:

作为说明,我尝试使用difference in row number method,但失败了,所以这可能不是一个好方法。 cust_id 或 prod_id 更改时的排名是多少?如果您在示例中也添加它会更好。 【参考方案1】:

LAGSUM 的第一个变体

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

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

递归 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)

【讨论】:

谢谢,它看起来可以工作,但不幸的是 LAG 在 sql 2008 R2 数据库上不起作用,我应该在最初的问题中提到这一点,我现在已经更新了 - 抱歉! 尝试第二种变体。我希望它适用于 SQLServer 2008。 感谢 Leran,我已经测试了后两个选项。选项 2 为新价格的第一行提供了 2,但随后又恢复为 1 - 使得很难按此 ID 分组并获得最小和最大天数来给出价格范围。选项 3 更好,因为它为每个新价格提供了一个递增的排名,但没有考虑不同的 cust_ids 我将 PARTITION BY cust_id 添加到所有三个变体中。 这似乎没有什么不同,但是,我可以对 cust_id 进行分组并进行排名以获得我需要的价格范围 - 所以这非常有效,谢谢!

以上是关于在一列上排名表,同时在另一列上排序的主要内容,如果未能解决你的问题,请参考以下文章

内连接恰好在一列上,而在另一列上模糊

在一个列上应用 distinct 并在另一列上按 count 排序

在按两列分组时选择最大值,并在另一列上排序

选择一列上的值在另一列上具有相同的一组值

熊猫在一列上分组,另一列上的最大日期python

如何在另一列上显示html表格的计算