2

我正在大学学习数据库,并有关于找到大学课程的最低平均考试成绩的任务。我已经提出了两个解决方案,但我希望这里的专家可以帮助我:

什么是最好/最有效的解决方案?

解决方案1:

SELECT courses.name , MIN(avg_grade)
FROM (SELECT courseCode, AVG(grade) as avg_grade
      FROM exams
      GROUP BY courseCode) avg_grades, courses
WHERE courses.code = avg_grades.courseCode

解决方案2:

SELECT name, min(avg_grade)
FROM (SELECT courses.name, AVG(grade) as avg_grade
      FROM courses
      LEFT JOIN exams on exams.courseCode = courses.code
      GROUP BY courseCode) mytable

而且我一直在考虑在这里使用 JOIN 或 LEFT JOIN 是否正确?

4

1 回答 1

5

您的两个查询不同,因此您无法真正比​​较效率,您的第二个查询将返回没有考试结果的课程记录。假设您将 LEFT JOIN 切换为 INNER 以使查询具有可比性,那么我希望第一个查询效率更高一些,因为它只有一个派生表,而第二个有两个:

解决方案1:

ID  SELECT_TYPE     TABLE   TYPE    POSSIBLE_KEYS   KEY KEY_LEN REF ROWS    FILTERED    EXTRA
1   PRIMARY         ALL                                             5       100 
1   PRIMARY courses ALL                                             5       100     Using where; Using join buffer
2   DERIVED exams   ALL                                             5       100     Using temporary; Using filesort

解决方案2:

ID  SELECT_TYPE     TABLE   TYPE    POSSIBLE_KEYS   KEY KEY_LEN REF ROWS    FILTERED    EXTRA
1   PRIMARY         ALL                                             5       100 
2   DERIVED courses ALL                                             5       100         Using temporary; Using filesort
2   DERIVED exams   ALL                                             5       100         Using where; Using join buffer

但是,我会根据您自己的执行计划检查这一点,因为我的只是SQL Fiddle上的一个简单示例。

我想借此机会建议不要使用 ANSI-89 隐式连接语法,它在 20 多年前被 ANSI-92 标准中的显式连接语法所取代。Aaron Bertrand 写了一篇关于为什么要切换的好文章,我不会在这里重复。

另一个更重要的一点是您的查询不是确定性的,也就是说,即使数据没有潜在的变化,您也可以运行两次相同的查询并获得 2 个不同的结果。

以您的第二个查询为例(尽管您会注意到 SQL-Fiddle 上的两个查询都是错误的),您有一个MyTable像这样的子查询:

SELECT courses.name, AVG(grade) as avg_grade
FROM courses
LEFT JOIN exams on exams.courseCode = courses.code
GROUP BY courseCode

这返回了一个像这样的表:

Name    |   avg_grade
--------+--------------
   A    |       10
   B    |       5
   C    |       6
   D    |       7
   E    |       2

您可能希望整个查询返回:

Name    |   avg_grade
--------+--------------
   E    |       2

因为 2 是最低平均成绩,而 E 是与之对应的名称。但是,您会错的,如此处所示,您可以看到它实际上返回了:

Name    |   avg_grade
--------+--------------
   A    |       2

本质上发生的是 MySQL 正在正确计算最小 avg_grade,但是由于您没有向组中添加任何列,因此您已经让 MySQL 全权委托选择Name它选择的任何值。

要获得您想要的输出,我认为您需要:

SELECT  courses.name , MIN(avg_grade)
FROM    (   SELECT  courseCode, AVG(grade) as avg_grade
            FROM    exams
            GROUP BY courseCode
        ) avg_grades
        INNER JOIN courses
            ON courses.code = avg_grades.courseCode
GROUP BY courses.Name;

或者,如果您只想学习平均成绩最低的课程,请使用:

SELECT  courseCode, AVG(grade) as avg_grade
FROM    exams
GROUP BY courseCode
ORDER BY avg_grade
LIMIT 1;

SQL Fiddle 上的示例

请原谅我即将做的事情的懒惰,但我之前已经解释了很多这个问题,现在我发布了一个标准回复来解释 MySQL 分组的问题。它比上面更详细,希望能进一步解释。


MySQL 隐式分组

我建议尽可能避免 MySQL 提供的隐式分组,我的意思是在选择列表中包含列,即使它们不包含在聚合函数或 group by 子句中。

想象一下下面的简单表(T):

ID  | Column1 | Column2  |
----|---------+----------|
1   |    A    |    X     |
2   |    A    |    Y     |

在 MySQL 中,您可以编写

SELECT  ID, Column1, Column2
FROM    T
GROUP BY Column1;

这实际上打破了 SQL 标准,但它在 MySQL 中有效,但问题是它是不确定的,结果:

ID  | Column1 | Column2  |
----|---------+----------|
1   |    A    |    X     |

不比正确或不正确

ID  | Column1 | Column2  |  
----|---------+----------|
2   |    A    |    Y     |

所以你说的是给我一个不同的值Column1,两个结果集都满足,那么你怎么知道你会得到哪一个?好吧,您不知道,这似乎是一个相当流行的误解,您可以添加和ORDER BY子句来影响结果,例如以下查询:

SELECT  ID, Column1, Column2
FROM    T
GROUP BY Column1
ORDER BY ID DESC;

将确保您获得以下结果:

ID  | Column1 | Column2  |  
----|---------+----------|
2   |    A    |    Y     |

由于ORDER BY ID DESC, 然而这不是真的(如此处所示)。

MySQL文档状态:

服务器可以从每个组中自由选择任何值,因此除非它们相同,否则选择的值是不确定的。此外,从每个组中选择值不会受到添加 ORDER BY 子句的影响。

因此,即使您有这样的订单,直到每组选择一行之后才适用,并且这一行是不确定的。

SQL 标准确实允许选择列表中的列不包含在 GROUP BY 或聚合函数中,但是这些列必须在功能上依赖于 GROUP BY 中的列。例如,示例表中的 ID 是 PRIMARY KEY,所以我们知道它在表中是唯一的,所以下面的查询符合 SQL 标准,在 MySQL 中运行,目前在许多 DBMS 中会失败(在编写 Postgresql 时)是我所知道的最接近正确实施标准的 DBMS):

SELECT  ID, Column1, Column2
FROM    T
GROUP BY ID;

由于每一行的 ID 是唯一的,因此每个 ID 只能有一个值Column1,一个值Column2对于每一行返回的内容没有歧义。

编辑

来自 SQL-2003-Standard (5WD-02-Foundation-2003-09 - 第 346 页) - http://www.wiscorp.com/sql_2003_standard.zip

  1. 如果 T 是一个分组表,则令 G 是 T 的分组列的集合。在每个包含在 中,每个引用 T 的列的列引用都应引用在功能上依赖于 G 或应包含在一个列中的某个列 C聚合查询为 QS 的 a 的聚合参数。
于 2013-09-09T17:02:36.930 回答