SQL语句逻辑执行过程和相关语法详解(8)

MariaDB [test]> select * from Student group by class;
+------+---------+------+--------+
| sid  | name    | age  | class  |
+------+---------+------+--------+
|    7 | qianqi  |  22 | C      |
|    4 | lisi    |  20 | C#    |
|    8 | sunba  |  20 | C++    |
|    1 | chenyi  |  22 | Java  |
|    2 | huanger |  23 | Python |
+------+---------+------+--------+

上一小节分析了标准SQL的group by的特性,select_list中本无法使用非分组列,但这里却能使用,为什么呢?仍然使用上一小节加工后的数据结构来说明:

SQL语句逻辑执行过程和相关语法详解

标准SQL中之所以不能使用sid、name和age列,是因为group by的每个分组都是单行(标量)结果,如果使用了这些列,会违反关系模型的范式要求(一行对多行)。而MySQL、mariadb之所以允许,是因为它们会从重复的分组列中挑出一个随机行(注意随机这个字眼),将它和分组列的单行组成一行,这样就满足范式要求了。

例如上图中的Java组对应了4行记录,MySQL可能会挑sid=1(按照物理存储顺序挑,因此结果是随机的)的那行和Java组构成一行,Python组对应了2行记录,MySQL可能会挑sid=2的那行和Python构成一行。于是得到结果:

+------+---------+------+--------+
| sid  | name    | age  | class  |
+------+---------+------+--------+
|    7 | qianqi  |  22 | C      |
|    4 | lisi    |  20 | C#    |
|    8 | sunba  |  20 | C++    |
|    1 | chenyi  |  22 | Java  |
|    2 | huanger |  23 | Python |
+------+---------+------+--------+ 

MySQL和MariaDB用了一种不是办法的办法解决了关系模型的范式要求问题,使得select_list中能够使用非分组列。但因为挑选数据的时候具有随机性,因此不太建议如此使用。除非你知道自己在做什么,或者额外使用了ORDER BY子句保证挑选的数据是意料之中的。

MariaDB [test]> select * from Student1 group by class order by sid desc;
+------+----------+------+--------+
| sid  | name    | age  | class  |
+------+----------+------+--------+
|    8 | sunba    |  20 | C++    |
|    7 | qianqi  |  22 | C      |
|    4 | lisi    |  20 | C#    |
|    3 | zhangsan |  21 | Java  |
|    2 | huanger  |  23 | Python |
+------+----------+------+--------+

1.11 关于OVER( )

想必写过GROUP BY子句的人都很恼火选择列中不能使用非分组列,明明很想查看分组后所有行的结果,GROUP BY却阻止了这样的行为。

万幸,还有一个OVER()子句供我们实现目标。不过MySQL中不支持OVER()子句,ms sql、Oracle和mariaDB(MariaDB 10.2.0开始引入该功能)都支持,之所以MySQL不支持,我想是因为它的GROUP BY本就允许select_list中使用非分组列。

over()子句常被称为窗口函数或开窗函数,其实它就是进行分组,分组后也能进行聚合运算。只不过在over()的世界里,组称为窗口。

例如,以下是按照StudentID列进行分组。

SQL语句逻辑执行过程和相关语法详解

其实从上面的分组形式上看,它和GROUP BY分组的不同之处在于GROUP BY要求每个分组必须返回单行,而开窗则可以将单行数据同时分配给多个行,从而构成一个窗口。group by的侧重点是组,而开窗的侧重点在于组中的每行。

窗口函数很强大,强大到仅仅这一个专题就可以写成一本书。本文不会对其多做描述,而是围绕本文的主题"语句的逻辑执行顺序"稍作分析。

over()子句是对数据行按照指定列进行开窗(划分窗口),开窗后可以围绕每一组中的行进行操作,例如排序、聚合等等。

假如先执行DISTINCT去重再执行OVER,那么去重后再对具有唯一值的列(或多列)进行开窗就没有任何意义。例如上图中,如果先对StudentID去重,那么去重后将只有3行,这3行都是唯一值,没必要再去开窗,而且这也不符合开窗的目的。

因此OVER()是在DISTINCT之前完成开窗的。

另外,建议DISTINCT不要和OVER()一起使用,因为这时候的DISTINCT根本没有任何作用,但却会消耗额外的资源。

如果真的想对某些列去重后再开窗,可以借助GROUP BY。因为DISTINCT的功能基本等价于GROUP BY,但GROUP BY却先执行。

1.12 总结

虽然SQL语句的逻辑处理过程和真正的执行计划在有些地方会有所不同。但是理解逻辑处理过程,对学习SQL很有帮助。

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/90c3b57f9b6508b170ad70d479052a49.html