如何根据行日差和分区对 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
值。它使用几个CTE
s,第一个为每次访问生成行号(基于每个酒店),第二个(递归)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 对具有排名的列进行排名组合