如何根据行日差和分区对 SQL 中的列进行排名?

Posted

技术标签:

【中文标题】如何根据行日差和分区对 SQL 中的列进行排名?【英文标题】:How do I Rank column in SQL based on row day-difference and partition? 【发布时间】:2020-04-19 23:07:36 【问题描述】:

我正在尝试根据行差异

select hotel.*,
IFNULL(datediff(visit_date, lag(visit_date)
OVER (partition by hotel_id)), 0) as diff
from hotel;

我得到以下输出,

hotel_id customer_id  visit_date  diff
1            1        2020-01-01    0
1            2        2020-01-03    2
2            1        2020-01-01    0
2            2        2020-01-10    9
2            3        2020-01-14    4
3            1        2020-01-04    0
3            1        2020-01-11    7

我被 RANK() 部分卡住了。

预期输出: 如果 Day Difference 小于 3,则为 1,否则为 2。如果下一个大于 3 天,则为 3,依此类推

hotel_id customer_id  visit_date  rank
1            1        2020-01-01    1
1            2        2020-01-03    1
2            1        2020-01-01    1
2            2        2020-01-10    2
2            3        2020-01-14    3
3            1        2020-01-04    1
3            1        2020-01-11    2

【问题讨论】:

您说“如果日差小于 3,则为 1,否则为 2”,但表中的最后一个条目为 rank = 3,这是如何工作的? @Nick 这是我的 SQL。还有,是的!如果日差小于 3,则为 1,否则为 2、3、4,依此类推。只有当日差不小于 3 时,排名才会增加 那么如果 diff 回落到 @Nick 当 rank = 3,并且下一个 customer_id 天差 【参考方案1】:

您可以使用此查询来生成您的rank 值。它使用几个CTEs,第一个为每次访问生成行号(基于每个酒店),第二个(递归)CTE 生成rank 值,遍历从第一个 CTE 并且仅在日期差异超过 2 天时增加 rank

WITH RECURSIVE hotel_rows AS (
  SELECT hotel_id, customer_id, visit_date,
         ROW_NUMBER() OVER (PARTITION BY hotel_id ORDER BY visit_date) AS rn
  FROM hotel
  ORDER BY hotel_id, visit_date
),
ranks AS (
  SELECT hotel_id, customer_id, visit_date, rn, 1 AS `rank`
  FROM hotel_rows
  WHERE rn = 1
  UNION ALL
  SELECT h.hotel_id, h.customer_id, h.visit_date, h.rn,
         r.rank + (h.visit_date > r.visit_date + INTERVAL 2 DAY)
  FROM hotel_rows h
  JOIN ranks r ON h.hotel_id = r.hotel_id
              AND h.rn = r.rn + 1
)
SELECT SELECT hotel_id, customer_id, visit_date, `rank`
FROM ranks
ORDER BY hotel_id, visit_date

输出(用于我稍微扩展的演示):

hotel_id    customer_id     visit_date  rank
1           1               2020-01-01  1
1           2               2020-01-03  1
2           1               2020-01-01  1
2           2               2020-01-10  2
2           3               2020-01-14  3
2           1               2020-01-15  3
2           2               2020-01-20  4
3           1               2020-01-04  1
3           1               2020-01-11  2

Demo on dbfiddle

【讨论】:

更正。如果日差小于 3,则为 1,否则为 2、3、4,依此类推。只有当日差不小于 3 时,排名才会增加 @AmoghKatwe 请查看我的编辑。我认为这会做你想要的。 @AmoghKatwe 不用担心 - 我很高兴能提供帮助。【参考方案2】:

如果您希望根据给定条件获得结果,那么您可以在 SQL Server 中尝试以下操作。这是Demo

select
  hotel_id, 
  customer_id, 
  visit_date,
  case 
    when days < 3 then 1
    else 2
  end as rnk
from
(
  select
    *,
    datediff(day, n_date, visit_date) as days
  from
  (
      select
        *,
        coalesce(lag(visit_date) over (partition by hotel_id order by visit_date), visit_date) as n_date

      from hotel
  ) val
)days

【讨论】:

【参考方案3】:

我会这样表达:

select h.*,
       (case when lag(visit_date) over (partition by hotel_id order by visit_date) < visit_date - interval 3 day
             then 2 else 1
       end)
from hotel h;

编辑;

根据您的修改点,您想根据日期差异分配组,然后使用row_number()

select h.*,
       1 + sum( coalesce(visit_date > prev_vd + interval 3 day, 0) ) over (partition by hotel_id order by visit_date) as grp
from (select h.*,
             lag(visit_date) over (partition by hotel_id order by visit_date) as prev_vd
      from hotel h
     ) h;

Here 是一个 dbfiddle。

【讨论】:

更正。如果日差小于 3,则为 1,否则为 2、3、4,依此类推。只有当日差不小于 3 时,排名才会增加 @AmoghKatwe 。 . .这澄清了很多——我看到你编辑了这个问题。对于此类问题,我强烈推荐使用窗口函数而不是递归 CTE。 您的方法部分正确。除了,当值需要为 1 时,它是 0,当它应该是 2 时它是 1 @AmoghKatwe 。 . .我修复了它——答案比我想象的要简单——并添加了一个 dbfiddle. 绝对同意你关于“更简单”的部分。我对一些随机数据(10000 个条目)进行了一些测试,对于这个特定的示例,CTE 似乎实际上更快一些。可能在噪音中。

以上是关于如何根据行日差和分区对 SQL 中的列进行排名?的主要内容,如果未能解决你的问题,请参考以下文章

如何根据值 B 对 sql 中的列进行排序,其中字段中的值格式为 A-B-C。我

在 SQL Server 中使用 Dense_Rank 对具有排名的列进行排名组合

如何根据随机分布数据计算 C++ 中的样本均值、标准差和方差,并与原始均值和 sigma 进行比较

如何根据 SQL 中的分区对行求和?

如何根据sklearn中的预测概率对实例进行排名

如何根据列中的一组行对数据框进行排名?