MySQL 在特定条件下返回最新和最长的连续行
Posted
技术标签:
【中文标题】MySQL 在特定条件下返回最新和最长的连续行【英文标题】:MySQL Return latest and longest streak of rows with certain conditions 【发布时间】:2021-02-23 00:48:46 【问题描述】:请看看这个小提琴。
https://www.db-fiddle.com/f/71CxYHKkzwmXJnovzpFheV/7
我正在尝试完成两件事:
如何在没有任何跳过日期的情况下获得 LATEST STREAK OF CORRECT GUESSES(意思是 Result = Guess)的长度和日期?在这种情况下,它将是 4,从 2021-01-05 到 2021-01-08。 (虽然2021-01-03是正确的,但因为2021-01-04没有猜测,所以应该不包括在内)。
如何获得有史以来最长的连续正确猜测的长度和日期?再次表示结果 = 猜测,但可以在表中的任何位置。假设是 3 个月前的 10 个。
更复杂的是,可以由多个用户进行猜测,并且在同一天会有多个结果(例如针对不同的游戏类别)。所以上表是针对一个用户和一个游戏类别的。我认为如果我能就上述目标获得一些指导,我可以解决这个问题。
这超出了我的理解。任何和所有的帮助将不胜感激。
编辑:我更改了表格以显示日期并不总是连续的。另外,我被告知我应该使用 mysql 8.0 来完成这个任务,因为使用变量不是解决这个问题的好习惯。
编辑:使用窗口函数,开始到达某处:
请检查小提琴。它非常接近我想要达到的值,但总列中的“4”应该是 1。换句话说,“总和”应该重新开始。不知道如何实现这一点,因为很明显窗口函数将根据条件进行分组,从而打破顺序,从而打破连胜。
更新:我根据@The Impaler 的要求更新了小提琴。这里的表格更能代表我实际使用的内容(仍然不准确,但更接近)。
由于这个新提琴更具代表性,我也将解释我的最终目标。我还想获得每种游戏类型的连胜记录。我一直在将给定日期的 game_type 结果与“社区”(基本上是所有用户)的猜测进行比较的方式是将所有用户在该日期的每个 game_type 的所有 0 和 1 相加,然后使用更大的作为 '猜测'。通过这种方式,我可以了解“社区”作为一个整体是如何运作的。这适用于个别日期,但我不确定是否要连续运行。
更新 2 所以这就是我所得到的:
https://www.db-fiddle.com/f/71CxYHKkzwmXJnovzpFheV/11
我试图做一个嵌套窗口函数,但这是不允许的。当猜测=结果时,我有正确的分组和列。现在我需要帮助找出组内的连续性。
【问题讨论】:
如果你有 MySQL 8.x,你应该可以使用窗口函数来做到这一点。 *** 不是免费的编码服务。你应该try to solve the problem first。请更新您的问题以在minimal reproducible example 中显示您已经尝试过的内容。如需更多信息,请参阅How to Ask,并拨打tour :) 在过去的几个小时里,我实际上一直在做这件事。我已经尝试过分组、排序和限制,但无济于事。我已经搜索过,试图查看是否可以创建一个条件限制(例如“LIMIT WHEN 结果!= 猜测),这是不允许的。我没有发布任何尝试,因为没有什么能让我远程接近。我运行 MySQL 5.x,但我现在将研究 8.x。 您需要发布您的最佳尝试。我们不会为您编写它,但我们会帮助您了解您做错了什么以及如何解决它。 日期似乎总是连续的 【参考方案1】:这是一个典型的“差距和岛屿”问题。一旦你组装了这些岛屿,查询就变得容易了。
例如,对于单个用户,如小提琴中所述,您可以通过以下方式获得 LONGEST STREAK:
with
i as (
select
min(dayt) as starting_day,
max(dayt) as ending_day,
count(*) as streak_length
from (
select *, sum(beach) over(order by dayt) as island
from (
select *,
guess = result as inland,
case when (guess = result) <> (
lag(guess) over(order by dayt) = lag(result) over(order by dayt))
then 1 else 0 end as beach
from mytable
) x
where inland = 1
) y
group by island
)
select *
from i
order by streak_length desc
limit 1;
结果:
starting_day ending_day streak_length
------------- ----------- -------------
2021-01-06 2021-01-08 3
要获得最新的 STREAK,您只需更改末尾的 ORDER BY
子句,如下所示:
with
i as (
select
min(dayt) as starting_day,
max(dayt) as ending_day,
count(*) as streak_length
from (
select *, sum(beach) over(order by dayt) as island
from (
select *,
guess = result as inland,
case when (guess = result) <> (
lag(guess) over(order by dayt) = lag(result) over(order by dayt))
then 1 else 0 end as beach
from mytable
) x
where inland = 1
) y
group by island
)
select *
from i
order by ending_day desc
limit 1;
结果(与之前的结果相同):
starting_day ending_day streak_length
------------- ----------- -------------
2021-01-06 2021-01-08 3
请参阅DB Fiddle 的运行示例。
注意:您可以删除末尾的LIMIT
子句以查看所有岛屿,而不仅仅是选定的岛屿。
对于多用户,只需修改窗口(添加分区),其余查询保持不变。如果您为多用户提供小提琴,我也可以添加解决方案。
【讨论】:
首先,感谢您抽出宝贵时间回答这个问题。这太棒了!我不知道这是一个已知问题,并将对“间隙和岛屿”进行一些研究。我还没有完全解析你的答案来理解这一切,但我正在努力。我已根据您的要求更新了小提琴,并创建了一个更能代表我正在处理的内容的表格。我还更新了我的问题以添加我的最终目标(原始表格不可能)。我将利用你迄今为止所做的一切,尽我所能。再次感谢您! 用我最近的尝试再次更新了原版。多亏了你,肯定取得了进展,但仍然卡住了。我很想为此使用变量... 我想通了!你的回答给了我基础,所以我奖励你赏金。我会尽快发布完整的答案。 @MikelG 很高兴您找到了所需的PARTITION BY
子句。【参考方案2】:
所以,花了一段时间,但感谢@The Impaler 为我提供了基础和下面的链接,我能够解决问题。
https://www.red-gate.com/simple-talk/sql/t-sql-programming/efficient-solutions-to-gaps-and-islands-challenges/
这里是完整的解决方案:
with GAME_LOG as (
select
*,
guess = result as correct,
lag(case when (guess = result) then 1 else 0 end) over(partition by user_id, game_type) as previous_game_result,
lead(case when (guess = result) then 1 else 0 end) over(partition by user_id, game_type) as next_game_result,
row_number() over(partition by user_id, game_type order by dayt DESC) as ilocation
from mytable
),
CTE_ISLAND_START as (
select
*,
row_number() over(partition by user_id, game_type order by dayt DESC) as inumber,
dayt as island_start_time,
ilocation as island_start_location
from GAME_LOG
where correct = 1 AND
(previous_game_result <> 1 OR previous_game_result is null)
),
CTE_ISLAND_END as (
select
*,
row_number() over(partition by user_id, game_type order by dayt DESC) as inumber,
dayt as island_end_time,
ilocation as island_end_location
from GAME_LOG
where correct = 1 AND
(next_game_result <> 1 OR next_game_result is null)
)
select
CTE_ISLAND_START.user_id,
CTE_ISLAND_START.game_type,
CTE_ISLAND_START.island_start_time as streak_end,
CTE_ISLAND_END.island_end_time as streak_start,
cast(CTE_ISLAND_END.island_end_location as signed) -
cast(CTE_ISLAND_START.island_start_location as signed) + 1 as streak
from CTE_ISLAND_START
inner join CTE_ISLAND_END
on CTE_ISLAND_START.inumber = CTE_ISLAND_END.inumber AND
CTE_ISLAND_START.user_id = CTE_ISLAND_END.user_id AND
CTE_ISLAND_START.game_type = CTE_ISLAND_END.game_type
这将给出每个 user_id、每个 game_type 的所有连胜记录,以及连胜记录的开始和结束日期。
您可以简单地添加一个WHERE
子句来按game_type 和user_id 进行过滤。
这是稍微更新的数据集。
Fiddle
【讨论】:
以上是关于MySQL 在特定条件下返回最新和最长的连续行的主要内容,如果未能解决你的问题,请参考以下文章