MySQL 连胜一队

Posted

技术标签:

【中文标题】MySQL 连胜一队【英文标题】:MySQL Winning Streak for one team 【发布时间】:2012-05-07 07:30:15 【问题描述】:

我有一个 SQL 查询,它返回有关单个团队的下表:

date         gameid     pointsfor     pointsagainst

2011-03-20   15         1             10
2011-03-27   17         7             3
2011-04-03   23         6             5
2011-04-10   30         5             4
2011-04-17   35         4             8
2011-05-01   38         8             1
2011-05-08   43         3             7
2011-05-15   48         6             2
2011-05-22   56         10            2
2011-05-29   59         4             5
2011-06-05   65         2             3
2011-06-19   71         5             6
2011-06-19   74         12            2
2011-06-19   77         5             2
2011-06-19   80         5             4

从这张表中,谁能帮我计算一下最长的连胜和连败是多少?

我在这里查看了其他一些示例,但由于它们与我的不太一样,因此很难遵循它们。任何帮助将不胜感激。谢谢!

【问题讨论】:

能否提供架构和数据? 架构相当复杂,所以我认为我当前的选择查询返回了所有需要的数据(如上所示)。还有其他有用的数据吗?我想要的输出是: - 这个特定球队的最长连胜纪录,例如这支球队赢了,其中 gameid = 17、23 和 30,所以三个将是连胜纪录 - 最长的连胜纪录我不介意这些是否是单独的查询 我要+9999 @johntotetwoo 评论ツ 我想要的输出最初只是一些非常基本的东西,例如:`winstreak 6' 但现在我已经看到你的询问 Michael,我真的很喜欢它提供了日期和游戏列表。 【参考方案1】:

你必须实现一些 mysql 变量来帮助处理这个比多个查询/加入/分组更有效。这一次通过所有记录,然后再次获得每种类型的赢/输(或平局)的最大值。我假设您提供的数据是单次的,日期是明显的比赛顺序……积分是您感兴趣的球队,而反对的对手是谁。也就是说,我的“别名”名称将是“YourResultSingleTeam”。

内部查询将预先确定游戏状态为“W”in 或“L”oss。然后,查看该值是否与团队的前一个实例相同。如果是这样,将 1 添加到现有的赢/输计数器。如果不是,则将计数器设置回 1。然后,将当前游戏的状态保留回“LastStatus”值,以便与下一个游戏进行比较。

完成后,就是一个简单的游戏结果,按游戏结果状态分组的max()

select
      StreakSet.GameResult,
      MAX( StreakSet.WinLossStreak ) as MaxStreak
   from
      ( select YR.Date,
               @CurStatus := if( YR.PointsFor > YR.PointsAgainst, 'W', 'L' ) as GameResult,
               @WinLossSeq := if( @CurStatus = @LastStatus, @WinLossSeq +1, 1 ) as WinLossStreak,
               @LastStatus := @CurStatus as carryOverForNextRecord
            from 
               YourResultSingleTeam YR,
               ( select @CurStatus := '',
                        @LastStatus := '',
                        @WinLossSeq := 0 ) sqlvars
            order by
               YR.Date ) StreakSet
   group by
      StreakSet.GameResult

正如 Nikola 所提供的,如果您想考虑“平局”游戏,我们可以通过将 @CurStatus 更改为 case/when 条件来进行调整

@CurStatus := case when YR.PointsFor > YR.PointsAgainst then 'W'
                   when YR.PointsFor < YR.PointsAgainst then 'L'
                   else 'T' end as GameResult,

【讨论】:

感谢您帮助 DRapp。不幸的是,当我使用您的查询时,结果表始终显示为: L 1 W 1 我唯一更改的是将 YourResultSingleTeam 替换为返回我在问题中指定的表的查询。 (选择日期,gameid,pointsfor,pointsagainst FROM result WHERE teamid = 6 AND bye = 0 AND COMPLETED = 1 AND seasonid > 7 AND roundwd = 0 AND (pointsfor != 0 OR pointsagainst != 0) AND gametype != 'Friendly' ORDER BY date ASC) AS YR 我做错了什么吗?再次感谢! @GraemeCowbeller,我的错……忘记了 WinLossStreak 上 IF() 的 +1……虽然我早先 +1,但效率肯定更高 :)【参考方案2】:

有一个解决方案,但我认为您不会喜欢它,因为它需要自联接,而且您的表不是表而是查询。

内部查询会将日期转换为范围 - 对于表中的每个日期,它会找到具有不同结果的第一个日期,或者,如果是上一场比赛,则查找本场比赛的日期。该数据将按不同条纹的第一个日期汇总,以平整和计数条纹;外部查询然后根据结果查找极端情况。

select case Outcome 
            when -1 then 'Losses'
            when 1 then 'Wins'
            else 'Undecided'
        end Title
      , max(Streak) Streak
from
(
  select min(date) date, date_to, Outcome, count(*) Streak
  from
  (
    select t1.date, 
           sign (t1.pointsfor - t1.pointsagainst) Outcome, 
           ifnull (min(t2.date), t1.date) date_to
     from table1 t1
     left join table1 t2
       on t1.date < t2.date
      and sign (t1.pointsfor - t1.pointsagainst) 
       <> sign (t2.pointsfor - t2.pointsagainst)
    group by t1.date, sign (t1.pointsfor - t1.pointsagainst)
  ) a
  group by date_to, Outcome
) a
group by Outcome

为了避免将 table1 替换为 - 可能 - 繁琐的查询,您可能会使用临时表,或者在辅助表中已经有适当格式的数据。 Sql fiddle 进行了实时测试,以及另一个可能表现更好的子查询驱动版本 - 您应该同时尝试它们。

【讨论】:

天哪,我认为这就是 DBA 保证工作安全的方式。 @Len,尊敬的,查询确实有效......但是,对于 MySQL,很难遵循...... 对自身进行连接将实现处理的重复,比较 1 到 ? ???,然后 2 到 ??????,然后 3 到 ??????,等等...一旦完成,再次选择 group by,只是为了获得最大连胜。 您的查询类似于运行总计,有些问题不适用于基于集合的方法sqlblog.com/blogs/adam_machanic/archive/2006/07/12/… @MichaelBuen 是的,我知道。这就是我们拥有窗口函数的原因,特别是在 Oracle 中滞后和领先。我不使用 MySql,但不得不像其他人一样回答了四个小时,所以我提供了纯 SQL 解决方案。但感谢您的关注。 只是一个温和的提醒 :-) 这个查询并不完全适合生产;如果表中有 1,000 行,您的查询将遍历行 500,500 次。就像 Adam Machanic 用运行总数解释的那样【参考方案3】:

MySQL 没有 CTE 或窗口函数(例如 SUM OVER、ROW_NUMBER OVER 等)。但它有一个赎回因素。变量!

使用这个:

select 
   min(date) as start_date,
   max(date) as end_date,
   count(date) as streak,
   group_concat(gameid) as gameid_list
from
( 
  select *,      
    IF(
        pointsfor > pointsagainst 
        and 
        @pointsfor > @pointsagainst, 
           @gn, @gn := @gn + 1)                
    as group_number,

    @date as old_date, @gameid as old_gameid, 
    @pointsfor as old_pointsfor,
    @pointsagainst as old_pointsagainst,

    @date := date, @gameid := gameid, 
    @pointsfor := pointsfor, @pointsagainst := pointsagainst      
  from tbl
  cross join 
  (
    select 
      @date := CAST(null as date) as xa,
      @gameid := null + 0 as xb, -- why CAST(NULL AS INT) doesn't work?
      @pointsfor := null + 0 as xc, @pointsagainst := null + 0 as xd, @gn := 0
  ) x
  order by date
) as y
group by group_number
order by streak desc;

输出:

START_DATE                    END_DATE                      STREAK  GAMEID_LIST
March, 27 2011 08:00:00-0700  April, 10 2011 08:00:00-0700  3       17,23,30
June, 19 2011 08:00:00-0700   June, 19 2011 08:00:00-0700   3       74,77,80
May, 15 2011 08:00:00-0700    May, 22 2011 08:00:00-0700    2       48,56
March, 20 2011 08:00:00-0700  March, 20 2011 08:00:00-0700  1       15
April, 17 2011 08:00:00-0700  April, 17 2011 08:00:00-0700  1       35
May, 01 2011 08:00:00-0700    May, 01 2011 08:00:00-0700    1       38
May, 08 2011 08:00:00-0700    May, 08 2011 08:00:00-0700    1       43
May, 29 2011 08:00:00-0700    May, 29 2011 08:00:00-0700    1       59
June, 05 2011 08:00:00-0700   June, 05 2011 08:00:00-0700   1       65
June, 19 2011 08:00:00-0700   June, 19 2011 08:00:00-0700   1       71

现场测试:http://www.sqlfiddle.com/#!2/bbe78/8

请注意我在 sqlfiddle 上的解决方案,它有两个查询。 1. 模拟在上面。 2.下面的最终查询

【讨论】:

以防万一您仍然想知道为什么 CAST(NULL AS INT) 不起作用:Nested CAST not working。 我无法得到它:D 无论如何,它适用于其他数据库,例如Postgres,Oracle,SQL Server。可悲的是,MySQL 不能。幸运的是有一个解决方法:select null+0 as x 简单地说,MySQL 中的CASTCONVERT 函数接受的类型名称集与用于声明变量和列的类型名称略有不同。这对我来说似乎完全违反直觉,但事实就是如此。 嗨,Micahel,我正在我的查询浏览器中运行您的查询,但遇到了问题。由于某种原因,当我运行一次时,它会返回不正确的数据,但如果我再次单击运行,它会返回正确的数据。你会碰巧知道为什么会这样吗?再次感谢! 另外,以防万一,我已将“tbl”替换为返回表的查询:(SELECT date, gameid, pointsfor, pointsagainst FROM result WHERE teamid = 6 AND bye = 0 AND COMPLETED = 1 AND seasonid &gt; 7 AND roundwd = 0 AND (pointsfor != 0 OR pointsagainst != 0) AND gametype != 'Friendly' ORDER BY date ASC) AS tbl【参考方案4】:

最新版本的 MySQL 具有 CTE 并且支持窗口化。

这里有一个解决方案。

第一步,通过分配他们自己的streak_group编号来分组输赢:

with t as 
(
    select
        *,      
        pointsfor - pointsagainst > 0 is_winner,
        case when pointsfor - pointsagainst > 0 
            and lag(pointsfor) over(order by date, pointsfor - pointsagainst desc) 
                - lag(pointsagainst) over(order by date, pointsfor - pointsagainst desc) > 0 
        then
            0
        else
            1
        end as is_new_group
    from tbl
)
select *, sum(is_new_group) over(order by date, pointsfor - pointsagainst desc) as streak_group
from t

输出:

date                |gameid |pointsfor |pointsagainst |is_winner |is_new_group |streak_group |
--------------------|-------|----------|--------------|----------|-------------|-------------|
2011-03-20 15:00:00 |15     |1         |10            |0         |1            |1            |
2011-03-27 15:00:00 |17     |7         |3             |1         |1            |2            |
2011-04-03 15:00:00 |23     |6         |5             |1         |0            |2            |
2011-04-10 15:00:00 |30     |5         |4             |1         |0            |2            |
2011-04-17 15:00:00 |35     |4         |8             |0         |1            |3            |
2011-05-01 15:00:00 |38     |8         |1             |1         |1            |4            |
2011-05-08 15:00:00 |43     |3         |7             |0         |1            |5            |
2011-05-15 15:00:00 |48     |6         |2             |1         |1            |6            |
2011-05-22 15:00:00 |56     |10        |2             |1         |0            |6            |
2011-05-29 15:00:00 |59     |4         |5             |0         |1            |7            |
2011-06-05 15:00:00 |65     |2         |3             |0         |1            |8            |
2011-06-19 15:00:00 |74     |12        |2             |1         |1            |9            |
2011-06-19 15:00:00 |77     |5         |2             |1         |0            |9            |
2011-06-19 15:00:00 |80     |5         |4             |1         |0            |9            |
2011-06-19 15:00:00 |71     |5         |6             |0         |1            |10           |

最终查询。计算连胜次数:

with t as 
(
    select
        *,      
        pointsfor - pointsagainst > 0 is_winner,
        case when pointsfor - pointsagainst > 0 
            and lag(pointsfor) over(order by date, pointsfor - pointsagainst desc) 
                - lag(pointsagainst) over(order by date, pointsfor - pointsagainst desc) > 0 
        then
            0
        else
            1
        end as is_new_group
    from tbl
)
, streak_grouping as
(
    select
        *, sum(is_new_group) over(order by date, pointsfor - pointsagainst desc) as streak_group
    from t
)
select 
    min(date) as start_date,
    max(date) as end_date,
    count(*) as streak,
    group_concat(gameid order by gameid) as gameid_list
from streak_grouping
group by streak_group
order by streak desc, start_date

输出:

start_date          |end_date            |streak |gameid_list |
--------------------|--------------------|-------|------------|
2011-03-27 15:00:00 |2011-04-10 15:00:00 |3      |17,23,30    |
2011-06-19 15:00:00 |2011-06-19 15:00:00 |3      |74,77,80    |
2011-05-15 15:00:00 |2011-05-22 15:00:00 |2      |48,56       |
2011-03-20 15:00:00 |2011-03-20 15:00:00 |1      |15          |
2011-04-17 15:00:00 |2011-04-17 15:00:00 |1      |35          |
2011-05-01 15:00:00 |2011-05-01 15:00:00 |1      |38          |
2011-05-08 15:00:00 |2011-05-08 15:00:00 |1      |43          |
2011-05-29 15:00:00 |2011-05-29 15:00:00 |1      |59          |
2011-06-05 15:00:00 |2011-06-05 15:00:00 |1      |65          |
2011-06-19 15:00:00 |2011-06-19 15:00:00 |1      |71          |

【讨论】:

【参考方案5】:

您在这里处理的是跟踪胜负趋势,这需要使用某种带有运行计数器的循环来计算,而不是在 SQL 中。 SQL 查询处理单个行、分组、排序等;您正在尝试使用一种并非旨在解决此类问题的语言。

【讨论】:

不再。 ANSI SQL 2008 兼容的数据库现在同样能够解决这个问题,这可以通过 CTE 和窗口函数组合来解决。查看此问题的另一个示例:***.com/questions/10448024/…【参考方案6】:

你必须创建一个游标,读取所有行,计算数据......每次你想获得最长的条纹......

我建议一种解决方法,可以使事情变得更容易。 您在表“streakFor”中添加一列。每次插入一行:

//pseudo code
if pointsFor > pointsAgainst
    if last_streakFor > 0 
        then streakFor++ 
        else streakFor = 1

else
    if last_streakFor > 0 
        then streakFor = -1 
        else streakFor--

last_streakFor 是最后插入的行中的 streakFor 然后插入带有 streakFor 列的行

现在可以

select max(streakFor) from yourTable where yourConditions 这将为您提供“pointsFor”的最长连胜和“pointsAgainst”的最长连败 select min(streakFor) from yourTable where yourConditions 将为您提供“pointsAgainst”的最长连胜和“pointsFor”的最长连败

【讨论】:

【参考方案7】:

感谢所有帮助的家伙。我最终按照建议使用 php 循环。如果有人想知道,这是我的代码:

$streakSQL = "SELECT date, gameid, pointsfor, pointsagainst FROM result WHERE teamid = ".$_GET['teamid']." AND bye = 0 AND COMPLETED = 1 AND seasonid > 7 AND roundwd = 0 AND (pointsfor != 0 OR pointsagainst != 0)";
            $streak = mysql_query($streakSQL);

            $winstreak = 0;
            $maxwinstreak = 0;
            $losestreak = 0;
            $maxlosestreak = 0;
            while($streakRow = mysql_fetch_array($streak))
            
                //calculate winning streak
                if($streakRow['pointsfor'] > $streakRow['pointsagainst'])
                 
                    $winstreak++; 
                    if($winstreak > $maxwinstreak)
                    
                        $maxwinstreak = $winstreak;
                    
                
                else $winstreak = 0; 
                //calculate losing streak
                if($streakRow['pointsfor'] < $streakRow['pointsagainst'])
                 
                    $losestreak++; 
                    if($losestreak > $maxlosestreak)
                    
                        $maxlosestreak = $losestreak;
                    
                
                else $losestreak = 0; 
            
            echo "Biggest Winning Streak: ".$maxwinstreak;
            echo "<br />Biggest Losing Streak: ".$maxlosestreak;

【讨论】:

以上是关于MySQL 连胜一队的主要内容,如果未能解决你的问题,请参考以下文章

如何计算每位球员的最长连胜纪录

按团队分组的基于赢、平和输的连续连胜/非连胜

在 Postgres 中的连胜纪录

玩家平均连胜时长的 SQL 查询

javascript 两连胜Promise链

javascript 简单的鞅重复下注X后连续连胜