【问题标题】:User Guessing System for Sports - SQL Issue体育用户猜测系统 - SQL 问题
【发布时间】:2013-10-31 03:01:57
【问题描述】:

我正在用 PHP/MySQL 构建一个运动系统。在 StackOverFlow 的一些帮助下,我已经构建了它。

我有 OW_SPORTS_GAMES 表,其中包含有关使用团队 ID 和分数进行的每场比赛的所有详细信息。这是结构。

还有另一个表 OW_SPORTS_PREDICTIONS 存储用户对游戏结果的预测。用户可以预测哪支球队将赢得比赛。这是表结构。

我希望获得可以知道每个用户做出了多少正确和错误预测的结果。如果猜对了,用户将获得每场比赛的积分(积分栏)。输出应忽略双方得分均为 0 的任何比赛。

这是我当前的 SQL:

SELECT p.userId,
       SUM(IF(g.id IS NOT NULL, 1, 0)) correct,
       SUM(IF(g.id IS NULL, 1, 0)) wrong,
       SUM(IF(g.id IS NOT NULL, g.points, 0)) AS points
  FROM
       (SELECT * FROM ow_sports_games WHERE seasonId = 10 AND (homeTeamScore > 0 OR awayTeamScore > 0) ) g
  RIGHT JOIN ow_sports_predictions p
     ON g.id = p.gameId
    AND p.teamId = IF(g.homeTeamScore > g.awayTeamScore , g.homeTeam, IF(g.homeTeamScore < g.awayTeamScore , g.awayTeam, NULL))
  GROUP BY p.userId ORDER BY points DESC, correct DESC, wrong DESC;

使用此 SQL,我得到了错误的统计数据,其中还考虑了用户未预测的游戏和得分 0-0。

SQL 小提琴:http://sqlfiddle.com/#!2/f4c9ed/2

在同一个小提琴数据中,应该是 2 个正确和 2 个错误的预测。但它有 2 个正确和 4 个错误的预测。

【问题讨论】:

    标签: mysql sql


    【解决方案1】:

    您的问题是您将 JOIN 限制为仅返回预测正确的游戏;

    AND p.teamId = IF(g.homeTeamScore > g.awayTeamScore , g.homeTeam, IF(g.homeTeamScore < g.awayTeamScore , g.awayTeam, NULL))
    

    但是,您在计算错误预测数量时的假设是游戏 id 将为空:

    SUM(IF(g.id IS NULL, 1, 0)) wrong,
    

    但是,如果您试图排除的分数是0-0,那么游戏 ID 也将为空。我会简化这一点,首先通过简化连接条件,以便您可以切换到 INNER JOIN:

    SELECT  *
    FROM    ow_sports_predictions p
            INNER JOIN ow_sports_games g
                ON g.id = p.gameId
    WHERE   g.SeasonID = 10
    

    然后您可以确定预测是否正确的逻辑(这比我稍后将使用的更详细但更好地演示逻辑),

    SELECT  *,
            CASE WHEN g.homeTeamScore > g.awayTeamScore AND g.HomeTeam = p.TeamID THEN 'Correct - Home Win'
                WHEN g.awayTeamScore > g.homeTeamScore AND g.AwayTeam = p.Teamid THEN 'Correct - Away Win'
                WHEN g.homeTeamScore + g.awayTeamScore = 0 THEN 'Void (0-0)'
                ELSE 'Lose'
            END AS Result
    FROM    ow_sports_predictions p
            INNER JOIN ow_sports_games g
                ON g.id = p.gameId
    WHERE   g.SeasonID = 10;
    

    最后,我们可以总结一下:

    SELECT  p.userID,
            SUM(IF((g.homeTeamScore > g.awayTeamScore AND g.HomeTeam = p.TeamID) 
                OR (g.awayTeamScore > g.homeTeamScore AND g.AwayTeam = p.TeamID), 1, 0)) AS Correct,
            SUM(IF((g.homeTeamScore < g.awayTeamScore AND g.HomeTeam = p.TeamID) 
                OR (g.awayTeamScore < g.homeTeamScore AND g.AwayTeam = p.TeamID), 1, 0)) AS Wrong,
            SUM(IF((g.homeTeamScore > g.awayTeamScore AND g.HomeTeam = p.TeamID) 
                OR (g.awayTeamScore > g.homeTeamScore AND g.AwayTeam = p.TeamID), g.Points, 0)) AS Points,
            COUNT(*) AS TotalPredictions
    FROM    ow_sports_predictions p
            INNER JOIN ow_sports_games g
                ON g.id = p.gameId
    WHERE   g.SeasonID = 10
    GROUP BY p.UserID;
    

    Example on SQL Fiddle

    【讨论】:

    • 感谢您的详细解释。我不知道你们怎么有这样的大脑:) 看起来你解决了我的问题。
    • 只是为了添加进一步的解释。通过将逻辑移动到子查询可以使这看起来更简单,不幸的是,在人眼看来更简单,在 MySQL db 引擎看来更简单是两个不同的东西,当它遇到子查询时,MySQL 基本上将整个子查询转储到一个临时表,然后引用它,这意味着您会失去性能。这就是为什么我试图通过重复基本相同的逻辑并保留主表引用而不是派生来避免子查询。
    【解决方案2】:

    我认为您的问题的主要根源是在加入预测时使用 RIGHT 连接 - 这将为您提供您不想计算的游戏的行数据...尝试将其更改为 LEFT 连接并让我们知道。

    更新:因为指出 RIGHT 连接正在提取额外记录而被否决?还有其他问题,但我认为这是最大的问题。因为显然我应该提供一个完整的答案,所以这里有一个我会使用的查询,并附有小提琴:

    http://sqlfiddle.com/#!2/f4c9ed/49

    SELECT s.userId,
      SUM(IF(s.teamId = s.winningTeam, 1, 0)) correct,
      SUM(IF(s.teamId != s.winningTeam, 0, 1)) wrong,
      SUM(IF(s.teamId = s.winningTeam, s.points, 0)) points
    FROM
      (SELECT p.*, g.points,
       IF (g.homeTeamScore-g.awayTeamScore > 0, g.homeTeam, g.awayTeam) as winningTeam
       FROM ow_sports_predictions p
        INNER JOIN ow_sports_games g ON g.id = p.gameId AND g.homeTeamScore+g.awayTeamScore > 0) s
    GROUP BY s.userId;
    

    【讨论】:

    • 对不起,是我投了反对票,但没有时间写一个正确的解释为什么,所以根本没有解释(对或错)。它是RIGHT 连接的事实不是问题,而是连接条件,简单地将 JOIN 更改为 LEFT JOIN 并不能解决这个问题。无论如何,你已经提供了一个更好的答案,我随后删除了反对票。
    【解决方案3】:

    这是我的,它会给你答案。

    select sp.userid,
           sum(if ( (sp.teamid = sg.idwinner), 1, 0 )) correct,
           sum(if ( (sp.teamid != sg.idwinner), 0, 1)) wrong,
           sum(if ( (sp.teamid = sg.idwinner), sg.points, 0 )) points
      from (select if ( homeTeamScore>awayTeamScore, homeTeam, awayTeam ) idwinner,
                   sg.* 
             from ow_sports_games sg
            where homeTeamScore>0 and awayTeamScore>0 ) sg,
           ow_sports_predictions sp
     where sg.id = sp.gameid
     group by sp.userid
    

    Fiddle with your data 部分点有所改变。

    【讨论】:

    • 没关系,我提交的时候看到你已经选择了。我的错误是没有按 F5 :)
    【解决方案4】:

    这里是快速变体,它计算每个用户的所有数字,当然你只需要其中的两个,计算第三个就足够了:

    select
      b.userId,
      count(*) as total_predictions,
      sum(if(b.teamId=q.winteam,1,0)) as correct_predictions,
      sum(if(b.teamId<>q.winteam,1,0)) as wrong_predictions
    from ow_sports_predictions as b
    inner join
    (
    select
    a.id,
    if (a.hometeamscore>a.awayteamscore, a.hometeam, if(a.hometeamscore<a.awayteamscore, a.awayteam, -1)) as winteam
    from ow_sports_games as a
    )
    as q
    on (b.gameId=q.id)
    group by b.userId;
    

    这里是简化版:

    select
      b.userId,
      count(*) as total_predictions,
      sum(if(b.teamId=if (q.hometeamscore>q.awayteamscore, q.hometeam, if(q.hometeamscore<q.awayteamscore, q.awayteam, -1)),1,0)) as correct_predictions
    from ow_sports_predictions as b
    inner join ow_sports_games as q
    on (b.gameId=q.id)
    group by b.userId;
    

    要计算积分,需要将IF中的1改为points,如:

    select
      b.userId,
      count(*) as total_predictions,
      sum(if(b.teamId=if (q.hometeamscore>q.awayteamscore, q.hometeam, if(q.hometeamscore<q.awayteamscore, q.awayteam, -1)),q.points,0)) as correct_predictions_mul_points
    from ow_sports_predictions as b
    inner join ow_sports_games as q
    on (b.gameId=q.id)
    group by b.userId;
    

    【讨论】:

    • 感谢您的快速回复。但是您的 SQL 甚至会占用分数为 0-0 的游戏。我相信你也知道处理这个问题的最佳方法。
    • @Purus 再次检查 sql,即使它取 0-0 - 它计算正确
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-04-10
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-01-21
    • 1970-01-01
    相关资源
    最近更新 更多