MySQL最佳/第一次得分差异查询优化

时间:2015-01-05 21:55:39

标签: php mysql sql

任何人都可以帮我优化此查询吗?我有下表:

cdu_user_progress:
--------------------------------------------------------------
|id    |uid     |lesson_id    |game_id    |date    |score    |
--------------------------------------------------------------

对于每个用户,我试图获取特定lesson_id的特定game_id的最佳得分和第一得分之间的差异,并根据该差异对结果进行排序('进展'在我的用户中查询):

SELECT ms.uid AS id, ms.max_score - fs.first_score AS progress
FROM (
    SELECT up.uid, MAX(CASE WHEN game_id = 3 THEN score ELSE NULL END) AS max_score
    FROM cdu_user_progress up
    WHERE  (up.uid IN  ('1671', '1672', '1673', '1674', '1675', '1676', '1679', '1716', '1725',         '1726', '1937', '1964', '1996', '2062', '2065', '2066', '2085', '2086')) AND (up.lesson_id = '65') AND (up.score > '-1')
GROUP BY up.uid
) ms
LEFT JOIN (
    SELECT up.uid, up.score AS first_score 
    FROM cdu_user_progress up
    INNER JOIN (
        SELECT up.uid, MIN(CASE WHEN game_id = 3 THEN date ELSE NULL END) AS first_date
        FROM cdu_user_progress up
        WHERE  (up.uid IN  ('1671', '1672', '1673', '1674', '1675', '1676', '1679', '1716', '1725', '1726', '1937', '1964', '1996', '2062', '2065', '2066', '2085', '2086')) AND (up.lesson_id = '65') AND (up.score > '-1') 
        GROUP BY up.uid
    ) fd ON fd.uid = up.uid AND fd.first_date = up.date
) fs ON fs.uid = ms.uid
ORDER BY progress DESC

非常感谢任何帮助!

1 个答案:

答案 0 :(得分:2)

如果没有任何EXPLAIN输出或索引定义,我们无法提出任何建议。 (我在评论中注意到,如果(uid,date)中的cdu_user_progress元组没有保证唯一性,看起来似乎缺少某些连接谓词...我们有可能获得行适用于不同的lesson_id或不大于'-1'的分数。

在查询文字中,紧接在 ) fs 之前,我要添加

        AND up.lesson_id = '65'
        AND up.score > '-1'
      GROUP BY up.uid

我还将up.score列(在fd视图的SELECT列表中)包装在聚合函数中,MIN()MAX(),以符合ANSI标准(即使SQL_MODE不包含ONLY_FULL_GROUP_BY时MySQL不需要它)


如果我没有定义合适的索引,我会考虑添加一个索引:

... ON cdu_user_progress (lesson_id, uid, score, game_id, date)

派生表有一些开销(实现内联视图),那些派生表不会有索引(在MySQL 5.5和更早版本中)。但是每个内联视图中的GROUP BY确保了我们将少于20行,所以这不会成为一个问题。

因此,如果存在性能问题,那么它就在视图查询中。同样,我们确实需要查看EXPLAIN的输出和索引定义以及一些基数估算,以便提出建议。


<强>后续

鉴于(uid,date)上没有唯一约束,我会在fs视图查询中添加这些谓词。我还在查询中使用唯一的表别名(对于cdu_user_progress的每个引用),以使语句和EXPLAIN输出更容易阅读。另外,在GROUP BY视图中添加fd子句和聚合函数...我会像这样编写查询:

SELECT ms.uid AS id
     , ms.max_score - fs.first_score AS progress
  FROM ( SELECT up.uid
              , MAX(CASE WHEN up.game_id = 3 THEN up.score ELSE NULL END) AS max_score
           FROM cdu_user_progress up
          WHERE up.uid IN ('1671','1672','1673','1674','1675','1676','1679','1716','1725','1726','1937','1964','1996','2062','2065','2066','2085','2086')
            AND up.lesson_id = '65'
            AND up.score > '-1'
          GROUP BY up.uid
       ) ms
  LEFT
  JOIN ( SELECT uo.uid
              , MIN(uo.score) AS first_score
           FROM ( SELECT un.uid
                       , MIN(CASE WHEN un.game_id = 3 THEN un.date ELSE NULL END) AS first_date
                    FROM cdu_user_progress un
                   WHERE un.uid IN ('1671','1672','1673','1674','1675','1676','1679','1716','1725','1726','1937','1964','1996','2062','2065','2066','2085','2086')
                     AND un.lesson_id = '65' 
                     AND un.score > '-1' 
                   GROUP BY un.uid
                ) fd
           JOIN cdu_user_progress uo
             ON uo.uid = fd.uid
            AND uo.date = fd.first_date
            AND uo.lesson_id = '65'
            AND uo.score > '-1'
          GROUP BY uo.uid
       ) fs
    ON fs.uid = ms.uid
 ORDER BY progress DESC

我相信这会使我上面推荐的索引适合所有cdu_user_progress的引用。