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 中的CAST
和CONVERT
函数接受的类型名称集与用于声明变量和列的类型名称略有不同。这对我来说似乎完全违反直觉,但事实就是如此。
嗨,Micahel,我正在我的查询浏览器中运行您的查询,但遇到了问题。由于某种原因,当我运行一次时,它会返回不正确的数据,但如果我再次单击运行,它会返回正确的数据。你会碰巧知道为什么会这样吗?再次感谢!
另外,以防万一,我已将“tbl”替换为返回表的查询:(SELECT date, 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 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 连胜一队的主要内容,如果未能解决你的问题,请参考以下文章