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 在特定条件下返回最新和最长的连续行的主要内容,如果未能解决你的问题,请参考以下文章

在具有特定值的连续行上定义一个窗口

MySQL - 在匹配条件之后返回具有 n 个连续记录的所有记录

聚合 SQL 中的连续行

每个客户的连续行之间的Haversine距离

选择具有最长日期和其他条件的行

员工角色的连续行