1. 聚合函数  
聚合函数 ( Aggregate  Function ) :  是在数据库中进行数据处理和计算的常用函数 . 
它们可以对一组数据进行求和 ,  计数 ,  平均值 ,  最大值 ,  最小值等操作 ,  从而得到汇总结果 . 常见的聚合函数有以下几种 : 
SUM :  用于计算某一列的数值总和 ,  可以用于整数 ,  小数或者日期类型的列 . 
AVG :  用于计算某一列的数值平均值 ,  通常用于整数或者小数类型的列 . 
MAX :  用于找出某一列的最大值 ,  可以对任意数据类型的数据使用 . 
MIN :  用于找出某一列的最小值 ,  同样可以对任意数据类型的数据使用 . 
COUNT :  用于计算表中记录的总数或者某一列中非空值的数量 . COUNT ( * ) 会返回表中记录的总数 ,  适用于任意数据类型 . 注意事项 : 
*  1.  聚合函数通常都会忽略空值 ( COUNT比较特后续详细说明 ) . 例如 ,  如果一列中有 5 个数值和 3 个空值 ,  SUM函数只会对那 5 个数值进行求和 ,  而忽略那 3 个空值 . 同样地 ,  AVG函数也只会根据那 5 个数值来计算平均值 . *  2.  聚合函数经常与SELECT语句的GROUP  BY子句一同使用 ,  以便按照某个或多个列对结果进行分组 . 此外 ,  还可以使用HAVING子句对聚合结果进行过滤 .  例如 ,  可以找出平均薪水超过某个值的部门 . *  3.  聚合函数不能嵌套调用 . 比如不能出现类似 'AVG(SUM(字段名称))' 形式的调用 . *  4.  聚合函数通常不能与普通字段 ( 即非聚合字段 ) 直接一起出现在SELECT语句的列列表中 . ( 除非这些普通字段也包含在GROUP  BY子句中 ,  否则数据库会报错 . ) 聚合函数与GROUP  BY子句一同使用时作用于一组数据 ,  并对每一组数据返回一个值 . 聚合函数单独使用时作用于一列数据 ,  并返回一个值 . 如果在SELECT语句中同时包含聚合函数和普通字段 ,  但没有使用GROUP  BY子句 ,  数据库不知道如何将普通字段的值与聚合函数的结果对应起来 . 
  
 1.1 求数值总数  
基本语法 :  SUM ( column_name ) ;  用于计算指定列的总和 . 
例 :  SELECT  SUM ( salary )  AS  total_salary  FROM  employees ;  
  
mysql>  SELECT  SUM ( salary)  FROM  employees; 
+ 
|  SUM ( salary)  | 
+ 
|    691400.00  |   
+ 
1  row  in  set  ( 0.00  sec) 
  
 
 1.2 仅全分组模式  
ONLY_FULL_GROUP_BY :  是MySQL中的一种SQL模式 ,  其作用是约束SQL语句的执行 . 
当启用这个模式时 ,  MySQL要求SELECT语句中的GROUP  BY子句中的列必须是聚合函数 ( 如SUM、COUNT等 ) 或在GROUP  BY子句中出现的列 . 
换句话说 ,  如果在SELECT语句中存在既不在GROUP  BY子句中也不作为聚合函数的列 ,  MySQL将会抛出错误 . 这种限制的目的是为了确保查询结果的准确性 . 
因为在GROUP  BY语句中 ,  查询结果是按照分组列进行聚合的 . 
如果SELECT语句中的列既包含了聚合函数又包含了其他非分组列 ,  那么就会出现模糊性 ,  无法确切地确定每个分组中的非聚合列的值 . 
  
如果想要显示员工id ,  姓名等字段信息 ,  代码则为 :  SELECT  employee_id ,  first_name ,  SUM ( salary )  FROM  employees ; 
在MySQL  5.7 .5 之前的版本中代码是可以运行的 ,  ROUP  BY的行比较宽松 ,  
允许在SELECT列表中选择未在GROUP  BY子句中明确提及的列 ,  并且没有使用聚合函数 . 
然而 ,  从MySQL  5.7 .5 起 ,  由于默认的sql_mode包含了ONLY_FULL_GROUP_BY ,  MySQL对GROUP  BY的执行更加严格 . ONLY_FULL_GROUP_BY模式要求 ,  对于SELECT列表 ,  HAVING条件或ORDER  BY列表中的非聚合列 , 
它们必须明确地出现在  GROUP  BY  子句中 . 
如果没有 ,  MySQL  就会抛出一个错误 . 临时关闭ONLY_FULL_GROUP_BY模式 :  SET  SESSION  sql_mode = ( SELECT  REPLACE ( @ @ sql_mode , 'ONLY_FULL_GROUP_BY' , '' ) ) ; 
  
mysql>  SET  SESSION  sql_mode= ( SELECT  REPLACE ( @@sql_mode , 'ONLY_FULL_GROUP_BY' , '' ) ) ; 
Query OK,  0  rows  affected ( 0.00  sec) 
mysql>  SELECT  employee_id,  first_name,  SUM ( salary)  FROM  employees; 
+ 
|  employee_id |  first_name |  SUM ( salary)  | 
+ 
|          100  |  Steven     |    691400.00  | 
+ 
1  row  in  set  ( 0.00  sec) 
  
如果employees表中有多行数据 ,  且没有使用  GROUP  BY子句 ,  那么employee_id和first_name的值是从表中随机选择的单行数据 , 
而SUM ( salary ) 则是计算了表中所有行的薪水总和 . 
因此 ,  employee_id和first_name的值在这种情况下确实是没有意义的 ,  因为它们并不代表与薪水总和相对应的具体员工 . 
  
 1.3 求平均值  
基本语法 :  AVG ( column_name ) ;  用于计算指定列的平均值 . 
例 :  SELECT  AVG ( salary )  AS  average_salary  FROM  employees ; 
  
mysql>  SELECT  AVG ( salary)  FROM  employees; 
+ 
|  AVG ( salary)  | 
+ 
|  6461.682243  |   
+ 
1  row  in  set  ( 0.00  sec) 
mysql>  SELECT  SUM ( salary)  /  107  FROM  employees; 
+ 
|  SUM ( salary)  /  107  | 
+ 
|        6461.682243  | 
+ 
1  row  in  set  ( 0.00  sec) 
  
 
 1.4 求最大/小值  
基本语法 : 
MIN ( column_name ) ;  用于找出指定列的最小值 .  例 :  SELECT  MIN ( salary )  AS  minimum_salary  FROM  employees ; 
MAX ( column_name ) ;  用于找出指定列的最大值 .  例 :  SELECT  MAX ( salary )  AS  maximum_salary  FROM  employees ; 
  
-- 查询最高工资:
mysql> SELECT MAX(salary) FROM employees;
+-------------+
| MAX(salary) |
+-------------+
|    24000.00 |
+-------------+
1 row in set (0.00 sec)
  
 
mysql>  SELECT  MIN ( salary)  FROM  employees; 
+ 
|  MIN ( salary)  | 
+ 
|      2100.00  | 
+ 
1  row  in  set  ( 0.00  sec) 
  
 
mysql>  SELECT  MIN ( hire_date) ,  MAX ( hire_date)  FROM  employees; 
+ 
|  MIN ( hire_date)  |  MAX ( hire_date)  | 
+ 
|  1987 - 06 - 17      |  2000 - 04 - 21      | 
+ 
1  row  in  set  ( 0.00  sec) 
  
 1.5 统计  
基本语法 : 
COUNT ( * ) :  计算表中所有记录的数量 .        例 :  SELECT  COUNT ( * )  AS  total_employees  FROM  employees ;  
COUNT ( 常数 ) :  计算表中所有记录的数量 .      例 :  SELECT  COUNT ( 1 )  AS  total_employees  FROM  employees ;  
COUNT ( 字段 ) :  计算指定列中非NULL值的数量 .  例 :  SELECT  COUNT ( salary )  AS  salary_count  FROM  employees ;   COUNT函数在处理NULL值时的行为取决于其使用的具体形式 . 
使用COUNT ( 1 ) 或COUNT ( 2 ) 或COUNT ( * ) 时 ,  它们的功能实际上是相同的 . 
( 这里的常数仅仅是一个占位符 ,  用于表示对每一行进行计数 ,  可以被任何非NULL的常量表达式替换 . ) 
它们都会计算表中的行数 ,  不论列中的值是否为NULL . 
count ( * ) 是SQL92定义的标准统计行数的语法 ,  跟数据库无关 ,  跟NULL和非NULL无关 . 当使用COUNT ( 字段 ) 时 ,  它会统计该字段在表中出现的次数 ,  但会忽略字段值为NULL的情况 . 
也就是说 ,  它不会将null值计入总数 . 
  
mysql>  SELECT  COUNT ( * ) ,  COUNT ( 1 ) ,  COUNT ( 2 )  FROM  employees; 
+ 
|  COUNT ( * )  |  COUNT ( 1 )  |  COUNT ( 2 )  | 
+ 
|       107  |       107  |       107  |   
+ 
1  row  in  set  ( 0.00  sec) 
mysql>  SELECT  COUNT ( employee_id) ,  COUNT ( commission_pct)  FROM  employees; 
+ 
|  COUNT ( employee_id)  |  COUNT ( commission_pct)  | 
+ 
|                 107  |                     35  | 
+ 
1  row  in  set  ( 0.00  sec) 
  
 
mysql>  SELECT  SUM ( salary)  /  COUNT ( * )  FROM  employees; 
+ 
|  SUM ( salary)  /  COUNT ( * )  | 
+ 
|             6461.682243  | 
+ 
1  row  in  set  ( 0.00  sec) 
mysql>  SELECT  AVG ( commission_pct) ,  SUM ( commission_pct)  /  COUNT ( commission_pct)  FROM  employees; 
+ 
|  AVG ( commission_pct)  |  SUM ( commission_pct)  /  COUNT ( commission_pct)  | 
+ 
|             0.222857  |                                     0.222857  | 
+ 
1  row  in  set  ( 0.00  sec) 
mysql>  SELECT  AVG ( IFNULL( commission_pct,  0 ) ) ,  
SUM ( commission_pct)  /  COUNT ( IFNULL( commission_pct,  0 ) ) 
FROM  employees; 
+ 
|  AVG ( IFNULL( commission_pct,  0 ) )  |  SUM ( commission_pct)  /  COUNT ( IFNULL( commission_pct,  0 ) )  | 
+ 
|                        0.072897  |                                                0.072897  | 
+ 
1  row  in  set  ( 0.00  sec) 
  
问题 :  统计表中的记录数 ,  使用COUNT ( * ) ,  COUNT ( 1 ) ,  COUNT ( 具体字段 ) 哪个效率更高呢? 
答 :  如果使用的是MyISAM存储引擎 ,  则三者效率相同 ,  都是O ( 1 ) . 如果使用的是InnoDB  存储引擎 ,  则三者效率 : COUNT ( * )  =  COUNT ( 1 )  >  COUNT ( 字段 ) . 
  
 2. GROUP BY 分组  
 2.1 基本语法  
GROUP  BY是SQL中的一个子句 ,  用于对查询结果进行分组 . 
在SELECT语句中使用GROUP  BY子句时 ,  数据库会根据指定的列或表达式的值对查询结果进行分组 . 
所有具有相同值的行会被放到同一个分组中 ,  如果分组列中具有NULL值 ,  则NULL将作为一个分组返回 . 
然后 ,  你可以使用聚合函数 ( 如SUM ,  AVG ,  COUNT等 ) 对每个分组进行计算 ,  得到每个分组的摘要信息 . 基本语法如下 : 
SELECT  column_name ,  aggregate_function ( column_name )   
FROM  table_name   
WHERE  column_name  operator  value   
GROUP  BY  column_name ; 
其中 ,  column_name是你要根据其进行分组的列的名称 ,  aggregate_function是你要应用的聚合函数 . 
GROUP  BY声明在FROM后面 ,  WHERE后面 ,  ORDER  BY前面 ,  LIMIT前面 . 注意事项 : 
*  1.  除聚集计算语句外 ,  SELECT语句中的每个列都必须在GROUP  BY子句中给出 ,  否则报错 . 
*  2.  GROUP  BY子句可以包含任意数目的列 ,  这使得能对分组进行嵌套 ,  为数据分组提供更细致的控制 . 例如 ,  你可以同时按部门和职位对员工进行分组 ,  以获取更详细的数据 . 
*  3.  GROUP_CONCAT ( ) 函数是MySQL中的一个特殊函数 ,  它主要用于将同一个分组中的值连接起来 ,  并返回一个字符串结果 . 这个函数在配合GROUP  BY子句使用时非常有用 ,  因为它可以在分组后 ,  将指定字段的所有值连接成一个单独的字符串 . 
*  4.  使用GROUP  BY子句对多个列进行分组时 ,  列的顺序并不会影响分组的结果或聚合函数的结果 . 这是因为GROUP  BY子句是基于所有列的组合来定义分组的 ,  而不是单独基于每个列 . 
  
 2.2 基本使用  
mysql>  SELECT  department_id FROM  employees GROUP  BY  department_id; 
+ 
|  department_id | 
+ 
|           NULL  | 
|             10  | 
|             20  | 
|             30  | 
|             40  | 
|             50  | 
|             60  | 
|             70  | 
|             80  | 
|             90  | 
|            100  | 
|            110  | 
+ 
12  rows  in  set  ( 0.00  sec) 
SELECT  department_id,  GROUP_CONCAT( salary)  FROM  employees GROUP  BY  department_id; 
+ 
|  department_id |  GROUP_CONCAT( salary)                                            |                                    |           NULL  |  7000.00                                                         | 
|             10  |  4400.00                                                         |                                    |             20  |  13000.00 , 6000.00                                                | 
|             30  |  11000.00 , 3100.00 , 2900.00 , 2800.00 , 2600.00 , 2500.00                | 
|             40  |  6500.00                                                         | 
|            . . .  |  . . .                                                             |  
|            100  |  12000.00 , 9000.00 , 8200.00 , 7700.00 , 7800.00 , 6900.00                | 
|            110  |  12000.00 , 8300.00                                                |                                    + 
mysql>  SELECT  department_id,  salary FROM  employees GROUP  BY  department_id; 
ERROR 1055  ( 42000 ) : Expression 
this is  incompatible with  sql_mode= only_full_group_by
  
 2.3 搭配聚合函数  
使用GROUP  BY子句与聚合函数搭配是非常常见的做法 ,  
主要用于将多行数据按照一个或多个列进行分组 ,  并对每个分组应用聚合函数来计算汇总值 . 以下是一些常用的聚合函数及其与GROUP  BY子句搭配使用的示例 : 
*  1.  计算每个分组的某列的总和 :  SELECT  department_id ,  SUM ( salary )  AS  total_salary  FROM  employees  GROUP  BY  department_id ; 
*  2.  计算每个分组的某列的平均值 :  SELECT  job_id ,  AVG ( salary )  AS  average_salary  FROM  employees  GROUP  BY  job_id ; 
*  3.  找出每个分组的某列的最大值 :  SELECT  department_id ,  MAX ( salary )  AS  max_salary  FROM  employees  GROUP  BY  department_id ; 
*  4.  找出每个分组的某列的最小值 :  SELECT  job_id ,  MIN ( hire_date )  AS  earliest_hire_date  FROM  employees  GROUP  BY  job_id ; 
*  5.  计算每个分组的行数 :  SELECT  department_id ,  COUNT ( * )  AS  num_employees  FROM  employees  GROUP  BY  department_id ; 
  
mysql>  SELECT  department_id,  AVG ( salary)  FROM  employees GROUP  BY  department_id; 
+ 
|  department_id |  AVG ( salary)   | 
+ 
|           NULL  |   7000.000000  | 
|             10  |   4400.000000  | 
|             20  |   9500.000000  | 
|             30  |   4150.000000  | 
|             40  |   6500.000000  | 
|             50  |   3475.555556  | 
|             60  |   5760.000000  | 
|             70  |  10000.000000  | 
|             80  |   8955.882353  | 
|             90  |  19333.333333  | 
|            100  |   8600.000000  | 
|            110  |  10150.000000  | 
+ 
12  rows  in  set  ( 0.00  sec) 
  
 
mysql>  SELECT  department_id,  MAX ( salary)  FROM  employees GROUP  BY  department_id; 
+ 
|  department_id |  MAX ( salary)  | 
+ 
|           NULL  |      7000.00  | 
|             10  |      4400.00  | 
|             20  |     13000.00  | 
|             30  |     11000.00  | 
|             40  |      6500.00  | 
|             50  |      8200.00  | 
|             60  |      9000.00  | 
|             70  |     10000.00  | 
|             80  |     14000.00  | 
|             90  |     24000.00  | 
|            100  |     12000.00  | 
|            110  |     12000.00  | 
+ 
12  rows  in  set  ( 0.00  sec) 
mysql>  SELECT  job_id,  AVG ( salary)  FROM  employees GROUP  BY  job_id; 
+ 
|  job_id     |  AVG ( salary)   | 
+ 
|  AC_ACCOUNT |   8300.000000  | 
|  AC_MGR     |  12000.000000  | 
|  AD_ASST    |   4400.000000  | 
|  . . .         |   . . .          |  
|  SA_REP     |   8350.000000  | 
|  SH_CLERK   |   3215.000000  | 
|  ST_CLERK   |   2785.000000  | 
|  ST_MAN     |   7280.000000  | 
+ 
19  rows  in  set  ( 0.00  sec) 
  
 2.4 多列分组  
GROUP  BY子句对多个列进行分组时 ,  列的顺序不会影响分组的结果 . 
但SELECT子句中列的顺序可能会影响结果的显示顺序 . 
  
mysql>  SELECT  department_id,  job_id,  AVG ( salary)  FROM  employees GROUP  BY  department_id,  job_id; 
+ 
|  department_id |  job_id     |  AVG ( salary)   | 
+ 
|             90  |  AD_PRES    |  24000.000000  |  
|             90  |  AD_VP      |  17000.000000  |  
|             60  |  IT_PROG    |   5760.000000  | 
|           . . .   |  . . .         |  . . .           |  
|            110  |  AC_MGR     |  12000.000000  | 
|            110  |  AC_ACCOUNT |   8300.000000  | 
+ 
20  rows  in  set  ( 0.00  sec) 
mysql>  SELECT  job_id,  department_id,  AVG ( salary)  FROM  employees GROUP  BY  job_id,  department_id; 
+ 
|  job_id     |  department_id |  AVG ( salary)   | 
+ 
|  AD_PRES    |             90  |  24000.000000  |  
|  AD_VP      |             90  |  17000.000000  |  
|        . . .   |            . . .  |  . . .           |  
|  AC_MGR     |            110  |  12000.000000  | 
|  AC_ACCOUNT |            110  |   8300.000000  | 
+ 
20  rows  in  set  ( 0.00  sec) 
  
 
 2.5 分组后排序  
在SQL中 ,  分组后排序通常涉及两个关键步骤 : 
*  1.  首先使用GROUP  BY子句对数据进行分组 . 
*  2.  然后使用ORDER  BY子句对分组后的结果进行排序 . 基本语法 : 
SELECT  column1 ,  column2 ,  aggregate_function ( column3 )   
FROM  your_table   
GROUP  BY  column1 ,  column2   
ORDER  BY  column1 ,  column2 ; 
  
mysql>  SELECT  department_id,  AVG ( salary)  AS  ` avg_sal`   FROM  employees GROUP  BY  department_id ORDER  BY  avg_sal; 
+ 
|  department_id |  avg_sal      | 
+ 
|             50  |   3475.555556  | 
|             30  |   4150.000000  | 
|             10  |   4400.000000  | 
|             60  |   5760.000000  | 
|             40  |   6500.000000  | 
|           NULL  |   7000.000000  | 
|            100  |   8600.000000  | 
|             80  |   8955.882353  | 
|             20  |   9500.000000  | 
|             70  |  10000.000000  | 
|            110  |  10150.000000  | 
|             90  |  19333.333333  | 
+ 
12  rows  in  set  ( 0.00  sec) 
  
 2.6 WITH ROLLUP 子句  
WITH  ROLLUP是SQL中的一个子句 ,  主要用于与GROUP  BY子句结合使用 ,  以在查询结果中生成小计和总计 . 
当在查询中使用GROUP  BY对数据进行分组后 ,  WITH  ROLLUP会在结果集的末尾添加额外的行 ,  这些行显示了基于当前分组层次结构的汇总信息 . 语法 :  SELECT  department_id ,  AVG ( salary )  FROM  employees  GROUP  BY  department_id  WITH  ROLLUP ; 注意事项 : 
*  1.  汇总值通常是基于所选的聚合函数来计算的 ( 如汇总AVG ( ) 函数的平均值 ,  那么汇总的结果也就是平均值 ) . 
*  2.  当使用WITH  ROLLUP进行分组时 ,  如果在汇总时列的某个值为NULL ,  那么该列在汇总行中的值也会被设置为NULL . 
*  3.  使用多列进行分组时 ,  WITH  ROLLUP会按照你指定的列的顺序生成不同级别的汇总 . 例如 ,  如果按照列A和列B进行分组 ,  那么WITH  ROLLUP会首先为每一组 ( A ,  B ) 生成汇总行 , 然后为每一个唯一的A值生成汇总行 ( 此时B列的值为NULL ) ,  最后在结果集的末尾生成一个包含所有行汇总的单一行 ( 此时A和B列的值都为NULL ) . 
*  4.  老版本MySQL使用WITH  ROLLUP后不能使用ORDER  BY排序 . 
  
mysql>  SELECT  department_id,  AVG ( salary)  FROM  employees GROUP  BY  department_id WITH ROLLUP ; 
+ 
|  department_id |  AVG ( salary)   | 
+ 
|           NULL  |   7000.000000  | 
|             10  |   4400.000000  | 
|             20  |   9500.000000  | 
|             30  |   4150.000000  | 
|             40  |   6500.000000  | 
|             50  |   3475.555556  | 
|             60  |   5760.000000  | 
|             70  |  10000.000000  | 
|             80  |   8955.882353  | 
|             90  |  19333.333333  | 
|            100  |   8600.000000  | 
|            110  |  10150.000000  | 
|           NULL  |   6461.682243  |   
+ 
13  rows  in  set  ( 0.00  sec) 
mysql>  SELECT  department_id,  SUM ( salary)  FROM  employees GROUP  BY  department_id WITH ROLLUP ; 
+ 
|  department_id |  SUM ( salary)  | 
+ 
|           NULL  |      7000.00  | 
|             10  |      4400.00  | 
|             20  |     19000.00  | 
|             30  |     24900.00  | 
|             40  |      6500.00  | 
|             50  |    156400.00  | 
|             60  |     28800.00  | 
|             70  |     10000.00  | 
|             80  |    304500.00  | 
|             90  |     58000.00  | 
|            100  |     51600.00  | 
|            110  |     20300.00  | 
|           NULL  |    691400.00  |  
+ 
13  rows  in  set  ( 0.00  sec) 
  
mysql>  SELECT  department_id,  job_id,  AVG ( salary)  FROM  employees GROUP  BY  department_id,  job_id WITH ROLLUP ; 
+ 
|  department_id |  job_id     |  AVG ( salary)   | 
+ 
|           NULL  |  SA_REP     |   7000.000000  | 
|           NULL  |  NULL        |   7000.000000  |  
|             10  |  AD_ASST    |   4400.000000  | 
|             10  |  NULL        |   4400.000000  |  
|             20  |  MK_MAN     |  13000.000000  | 
|             20  |  MK_REP     |   6000.000000  | 
|             20  |  NULL        |   9500.000000  |  
|             30  |  PU_CLERK   |   2780.000000  | 
|             30  |  PU_MAN     |  11000.000000  | 
|             30  |  NULL        |   4150.000000  |  
|            . . .  |  . . .         |   . . .          | 
|           NULL  |  NULL        |   6461.682243  |  
+ 
33  rows  in  set  ( 0.00  sec) 
  
mysql>  SELECT  department_id,  AVG ( salary)  AS  ` avg_sal`   FROM  employees
GROUP  BY  department_id WITH ROLLUP  ORDER  BY  avg_sal ASC ; 
+ 
|  department_id |  avg_sal      | 
+ 
|             50  |   3475.555556  | 
|             30  |   4150.000000  | 
|             10  |   4400.000000  | 
|             60  |   5760.000000  | 
|           NULL  |   6461.682243  | 
|             40  |   6500.000000  | 
|           NULL  |   7000.000000  | 
|            100  |   8600.000000  | 
|             80  |   8955.882353  | 
|             20  |   9500.000000  | 
|             70  |  10000.000000  | 
|            110  |  10150.000000  | 
|             90  |  19333.333333  | 
+ 
13  rows  in  set  ( 0.00  sec) 
mysql>  SELECT  department_id,  AVG ( salary)  AS  ` avg_sal`   FROM  employees
GROUP  BY  department_id WITH ROLLUP  ORDER  BY  avg_sal ASC ; 
ERROR 1221  ( HY000) : Incorrect usage  of  CUBE/ ROLLUP and  ORDER  BY 
  
 3. HAVING过滤  
 3.1 基本语法  
在MySQL中 ,  HAVING关键字主要用于对分组后的数据进行过滤 . HAVING子句的基本语法如下 : 
HAVING  < 查询条件 > 
其中 ,  < 查询条件 > 指的是指定的过滤条件 ,  可以包含聚合函数 . 例如 ,  如果你想要查询各部门最高工资大于 10000 的部门信息 ,  可以使用以下查询 : 
SELECT  department ,  MAX ( salary )  as  max_salary   
FROM  employees   
GROUP  BY  department   
HAVING  max_salary  >  10000 ; 
在这个查询中 ,  首先使用GROUP  BY子句按部门对员工进行分组 ,  然后使用MAX ( salary ) 聚合函数计算每个部门的最高工资 . 
最后 ,  使用HAVING子句过滤出最高工资大于 10000 的部门 . 注意事项 : 
*  1.  当过滤条件中使用了聚合函数时 ,  必须使用HAVING而不是WHERE . 这是因为WHERE子句在数据分组前进行过滤 ,  并且不支持聚合函数 ,  而HAVING子句是在数据分组后进行过滤 ,  并支持使用聚合函数 . 
*  2.  HAVING子句通常与GROUP  BY子句一起使用 ,  且声明在GROUP  BY子句的后面 . 另外 ,  虽然HAVING可以单独使用 ,  但当过滤条件中没有聚合函数时 ,  推荐使用WHERE子句 ,  因为它的执行效率更高 . 
  
mysql>  SELECT  department_id,  MAX ( salary)  AS  ` max_salary`   FROM  employees
GROUP  BY  department_id HAVING  max_salary >  10000 ; 
+ 
|  department_id |  max_salary | 
+ 
|             20  |    13000.00  | 
|             30  |    11000.00  | 
|             80  |    14000.00  | 
|             90  |    24000.00  | 
|            100  |    12000.00  | 
|            110  |    12000.00  | 
+ 
6  rows  in  set  ( 0.00  sec) 
  
 
 3.2 基本使用  
mysql>  SELECT  department_id,  MAX ( salary)  AS  ` max_salary`   FROM  employees
WHERE  department_id IN  ( 10 ,  20 ,  30 ,  40 ) 
GROUP  BY  department_id HAVING  max_salary >  10000 ; 
+ 
|  department_id |  max_salary | 
+ 
|             20  |    13000.00  | 
|             30  |    11000.00  | 
+ 
2  rows  in  set  ( 0.00  sec) 
mysql>  SELECT  department_id,  MAX ( salary)  AS  ` max_salary`   FROM  employees
GROUP  BY  department_id HAVING  max_salary >  10000  AND  department_id IN  ( 10 ,  20 ,  30 ,  40 ) ; 
+ 
|  department_id |  max_salary | 
+ 
|             20  |    13000.00  | 
|             30  |    11000.00  | 
+ 
2  rows  in  set  ( 0.00  sec) 
  
 3.3 WHERE和HAVING的对比  
WHERE和HAVING的对比 : 
*  1.  WHERE可以直接使用表中的字段作为筛选条件 ,  但不能使用分组中的计算函数作为筛选条件 ; HAVING必须要与GROUP  BY配合使用 ,  可以把分组计算的函数和分组字段作为筛选条件 . 这决定了 ,  在需要对数据进行分组统计的时候 ,  HAVING可以完成WHERE不能完成的任务 . 这是因为 ,  在查询语法结构中 ,  WHERE在GROUP  BY之前 ,  所以无法对分组结果进行筛选 . HAVING  在  GROUP  BY  之后 ,  可以使用分组字段和分组中的计算函数 ,  对分组的结果集进行筛选 ,  这个功能是  WHERE  无法完成的 . 另外 ,  WHERE排除的记录不再包括在分组中 . *  2.  如果需要通过连接从关联表中获取需要的数据 ,  WHERE是先筛选后连接 ,  而HAVING是先连接后筛选 . 这一点 ,  就决定了在关联查询中 ,  WHERE比HAVING更高效 . 因为WHERE可以先筛选 ,  用一个筛选后的较小数据集和关联表进行连接 ,  这样占用的资源比较少 ,  执行效率也比较高 . HAVING则需要先把结果集准备好 ,  也就是用未被筛选的数据集进行关联 ,  然后对这个大的数据集进行筛选 ,  这样占用的资源就比较多 ,  执行效率也较低 . 
  
关键字优点缺点WHERE先筛选数据再关联, 执行效率高.不能使用分组中的计算函数进行筛选.HAVING可以使用分组中的计算函数.在最后的结果集中进行筛选, 执行效率较低.
 
开发中的选择 : 
WHERE和HAVING不是互相排斥的 ,  可以在一个查询里面同时使用WHERE和HAVING . 
包含分组统计函数的条件用HAVING ,  普通条件用WHERE . 
这样 ,  既利用了WHERE条件的高效快速,又发挥了HAVING可以使用包含分组统计函数的查询条件的优点 . 
当数据量特别大的时候 ,  运行效率会有很大的差别 . 
  
 4. SQL底层执行原理  
 4.1 查询的结构  
关键字顺序 :  SELECT  . . .  FROM  . . .  JOIN  . . .  ON  . . .  WHERE  . . .  GROUP  BY  . . .  HAVING  . . .  ORDER  BY  . . .  LIMIT . . . 
  
SELECT  DISTINCT   column1,      column2,    aggregate_function( column3)  AS  alias_name  
FROM    table1   
JOIN    table2 
ON  table1. id =  table2. table1_id  
WHERE   table1. column4 =  'some_value'  AND    table2. column5 >  100   
GROUP  BY   column1,    column2  
HAVING    aggregate_function( column3)  >  some_value  
ORDER  BY   column1 ASC ,    aggregate_function( column3)  DESC   
LIMIT   offset ,    number_of_rows; 
  
SELECT  DISTINCT   column1,      column2,   aggregate_function( column3)  AS  alias_name  
FROM   table1  
( LEFT  /  RIGHT )  JOIN   table2 
ON  table1. id =  table2. table1_id  
WHERE   table1. column4 =  'some_value'  AND   table2. column5 >  100   
GROUP  BY   column1,   column2  
HAVING    aggregate_function( column3)  >  some_value  
ORDER  BY   column1 ASC ,   alias_name DESC   
LIMIT   offset ,   number_of_rows; 
  
 4.2 SELECT执行顺序  
SQL语句的各个部分会按照以下顺序进行处理 : 
*  1.  FROM  . . .  JOIN  . . .  ON 首先 ,  数据库会根据FROM子句确定要查询的表 . 然后 ,  如果存在JOIN操作 ( 如INNER  JOIN ,  LEFT  JOIN ,  RIGHT  JOIN等 ) ,  数据库会根据ON子句中指定的条件将这些表连接起来 . *  2.  WHERE 在连接了所有必要的表之后 ,  WHERE子句会过滤出满足条件的记录 .  这一步会大大减少需要处理的记录数 . *  3.  GROUP  BY 接下来 ,  GROUP  BY子句会将结果集按照指定的列进行分组 . 这通常与聚合函数 ( 如SUM ( ) ,  COUNT ( ) ,  AVG ( ) 等 ) 一起使用 ,  以便对每个组进行计算 . *  4.  HAVING HAVING子句用于过滤分组后的结果 . 它类似于WHERE子句 ,  但作用于分组后的数据 ,  而不是原始的记录 . *  5.  SELECT 在此阶段 ,  数据库会选择要显示的列 .  这包括直接选择的列和通过聚合函数计算得到的列 . *  6.  DISTINCT 如果在SELECT子句中使用了DISTINCT关键字 ,  数据库会去除结果集中的重复行 . *  7.  ORDER  BY ORDER  BY子句会对结果集进行排序 . 可以按照一列或多列进行排序 ,  并指定升序 ( ASC ) 或降序 ( DESC ) . *  8.  LIMIT 最后 ,  LIMIT子句用于限制返回的记录数 .  这对于分页查询特别有用 ,  因为它允许你只获取结果集的一个子集 . 需要注意的是 ,  虽然上述顺序是SQL语句的逻辑执行顺序 ,  但实际的物理执行计划可能会因数据库优化器的决策而有所不同 . 
优化器会根据表的大小 ,  索引的存在与否 ,  统计信息等因素来决定如何最有效地执行查询 . 
  
执行顺序 :  FROM  . . .  JOIN  . . .  ON  - >  WHERE  - >  GROUP  BY  - >  HAVING  - >  SELECT  的字段  - >  DISTINCT  - >  ORDER  BY  - >  LIMIT 
  
SELECT  DISTINCT  player_id,  player_name,  count ( * )  as  num 
FROM  player JOIN  team ON  player. team_id =  team. team_id 
WHERE  height >  1.80  
GROUP  BY  player. team_id 
HAVING  num >  2  
ORDER  BY  num DESC  
LIMIT  2  
  
在SELECT语句执行这些步骤的时候 ,  每个步骤都会产生一个虚拟表 ,  然后将这个虚拟表传入下一个步骤中作为输入 . 
需要注意的是 ,  这些步骤隐含在SQL的执行过程中 ,  对于我们来说是不可见的 . 
  
 4.3 SQL 的执行原理  
SELECT是先执行FROM这一步的 . 
在这个阶段 ,  如果是多张表联查 ,  还会经历下面的几个步骤 : 
*  1.  首先先通过CROSS  JOIN求笛卡尔积 ,  相当于得到虚拟表  vt  ( virtual  table )  1 - 1 ; 
*  2.  通过ON进行筛选 ,  在虚拟表vt1- 1 的基础上进行筛选 ,  得到虚拟表  vt1- 2 ; 
*  3.  添加外部行 .  如果我们使用的是左连接 ,  右链接或者全连接 ,  就会涉及到外部行 ,  也就是在虚拟表  vt1- 2  的基础上增加外部行 ,  得到虚拟表  vt1- 3. 当然如果我们操作的是两张以上的表 ,  还会重复上面的步骤 ,  直到所有表都被处理完为止 . 
这个过程得到是我们的原始数据 . 当我们拿到了查询数据表的原始数据 ,  也就是最终的虚拟表vt1 ,  就可以在此基础上再进行WHERE阶段 . 
在这个阶段中 ,  会根据vt1表的结果进行筛选过滤 ,  得到虚拟表  vt2 . 然后进入第三步和第四步 ,  也就是GROUP  BY和HAVING阶段 . 
在这个阶段中 ,  实际上是在虚拟表vt2的基础上进行分组和分组过滤 ,  得到中间的虚拟表vt3和vt4 . 当我们完成了条件筛选部分之后 ,  就可以筛选表中提取的字段 ,  也就是进入到SELECT和DISTINCT阶段 . 
首先在SELECT阶段会提取想要的字段 ,  然后在DISTINCT阶段过滤掉重复的行 ,  分别得到中间的虚拟表vt5- 1 和vt5- 2. 当我们提取了想要的字段数据之后 ,  就可以按照指定的字段进行排序 ,  也就是ORDER  BY阶段 ,  得到虚拟表vt6 . 最后在vt6的基础上 ,  取出指定行的记录 ,  也就是LIMIT阶段 ,  得到最终的结果 ,  对应的是虚拟表vt7 . 当然我们在写SELECT语句的时候 ,  不一定存在所有的关键字 ,  相应的阶段就会省略 . 
  
 4.4 别名问题  
在SQL中 ,  字段别名主要在SELECT子句中使用 ,  以便为查询结果的列提供更具描述性或更简洁的名称 . 
然而 ,  别名在查询的其他部分 ( 如WHERE和GROUP  BY子句 ) 中的使用是有限制的 ,  这主要是出于查询逻辑和语法解析的考虑 . 为什么WHERE和GROUP  BY不能使用别名? 
逻辑顺序 :  SQL查询的执行顺序并不是按照你写的顺序来的 . 
虽然书写顺序是 :  FROM  - >  WHERE  - >  GROUP  BY  - >  HAVING  - >  SELECT ,  但实际执行时 ,  WHERE和GROUP  BY会在SELECT之前被处理 . 
因此 ,  在WHERE或GROUP  BY中使用别名时 ,  该别名可能尚未被定义或识别 . 解析顺序 :  SQL查询解析器在处理查询时 ,  需要按照特定的顺序来解析各个子句 . 
如果允许在WHERE或GROUP  BY中使用别名 ,  那么解析器就需要进行更复杂的处理 ,  以确保别名在正确的上下文中被解析 . 为什么HAVING可以使用别名? 
标准的SQL标准是不允许在having中使用select子句中的别名 ,  但是MySQL对这个地方进行了扩展 . 
官网中明确表示 :  可以在group  by ,  having子句中使用别名 ,  不可以在where中使用别名。   
地址 :  https : / / dev . mysql . com / doc / refman / 8.0 /en/problems-with-alias.html  . 
  
 
地址 :  https : / / dev . mysql . com / doc / refman / 8.0 /en/group-by-handling.html  . 
  
 
 5. 练习  
*  1.  where子句可否使用组函数进行过滤? 
答 :  where不支持组函数过滤 ,  组函数过滤需要使用having子句 . 
  
mysql>  SELECT  MAX ( salary) ,  MIN ( salary) ,  AVG ( salary) ,  SUM ( salary)  FROM  employees; 
+ 
|  MAX ( salary)  |  MIN ( salary)  |  AVG ( salary)  |  SUM ( salary)  | 
+ 
|     24000.00  |      2100.00  |  6461.682243  |    691400.00  | 
+ 
1  row  in  set  ( 0.00  sec) 
  
mysql>  SELECT  MAX ( salary) ,  MIN ( salary) ,  MAX ( salary)  -  MIN ( salary)  AS  ` difference`   FROM  employees; 
+ 
|  MAX ( salary)  |  MIN ( salary)  |  difference | 
+ 
|     24000.00  |      2100.00  |    21900.00  |  
+ 
1  row  in  set  ( 0.00  sec) 
  
mysql>  SELECT  job_id,  MAX ( salary) ,  MIN ( salary) ,  AVG ( salary) ,  SUM ( salary)  FROM  employees GROUP  BY  job_id; 
+ 
|  job_id     |  MAX ( salary)  |  MIN ( salary)  |  AVG ( salary)   |  SUM ( salary)  | 
+ 
|  AC_ACCOUNT |      8300.00  |      8300.00  |   8300.000000  |      8300.00  | 
|  AC_MGR     |     12000.00  |     12000.00  |  12000.000000  |     12000.00  | 
|  AD_ASST    |      4400.00  |      4400.00  |   4400.000000  |      4400.00  | 
|  AD_PRES    |     24000.00  |     24000.00  |  24000.000000  |     24000.00  | 
|  AD_VP      |     17000.00  |     17000.00  |  17000.000000  |     34000.00  | 
|  FI_ACCOUNT |      9000.00  |      6900.00  |   7920.000000  |     39600.00  | 
|  FI_MGR     |     12000.00  |     12000.00  |  12000.000000  |     12000.00  | 
|  HR_REP     |      6500.00  |      6500.00  |   6500.000000  |      6500.00  | 
|  IT_PROG    |      9000.00  |      4200.00  |   5760.000000  |     28800.00  | 
|  MK_MAN     |     13000.00  |     13000.00  |  13000.000000  |     13000.00  | 
|  MK_REP     |      6000.00  |      6000.00  |   6000.000000  |      6000.00  | 
|  PR_REP     |     10000.00  |     10000.00  |  10000.000000  |     10000.00  | 
|  PU_CLERK   |      3100.00  |      2500.00  |   2780.000000  |     13900.00  | 
|  PU_MAN     |     11000.00  |     11000.00  |  11000.000000  |     11000.00  | 
|  SA_MAN     |     14000.00  |     10500.00  |  12200.000000  |     61000.00  | 
|  SA_REP     |     11500.00  |      6100.00  |   8350.000000  |    250500.00  | 
|  SH_CLERK   |      4200.00  |      2500.00  |   3215.000000  |     64300.00  | 
|  ST_CLERK   |      3600.00  |      2100.00  |   2785.000000  |     55700.00  | 
|  ST_MAN     |      8200.00  |      5800.00  |   7280.000000  |     36400.00  | 
+ 
19  rows  in  set  ( 0.00  sec) 
  
mysql>  SELECT  job_id,  COUNT ( * )  FROM  employees GROUP  BY  job_id; 
+ 
|  job_id     |  COUNT ( * )  | 
+ 
|  AC_ACCOUNT |         1  | 
|  AC_MGR     |         1  | 
|  AD_ASST    |         1  | 
|  AD_PRES    |         1  | 
|  AD_VP      |         2  | 
|  FI_ACCOUNT |         5  | 
|  FI_MGR     |         1  | 
|  HR_REP     |         1  | 
|  IT_PROG    |         5  | 
|  MK_MAN     |         1  | 
|  MK_REP     |         1  | 
|  PR_REP     |         1  | 
|  PU_CLERK   |         5  | 
|  PU_MAN     |         1  | 
|  SA_MAN     |         5  | 
|  SA_REP     |        30  | 
|  SH_CLERK   |        20  | 
|  ST_CLERK   |        20  | 
|  ST_MAN     |         5  | 
+ 
19  rows  in  set  ( 0.00  sec) 
  
mysql>  SELECT  manager_id,  MIN ( salary)  AS  ` min_salary`   
FROM  employees 
WHERE  manager_id IS  NOT  NULL  
GROUP  BY  manager_id 
HAVING  min_salary >  6000 ; 
+ 
|  manager_id |  min_salary | 
+ 
|         102  |     9000.00  | 
|         108  |     6900.00  | 
|         145  |     7000.00  | 
|         146  |     7000.00  | 
|         147  |     6200.00  | 
|         148  |     6100.00  | 
|         149  |     6200.00  | 
|         205  |     8300.00  | 
+ 
8  rows  in  set  ( 0.00  sec) 
  
SELECT  department_name,  location_id,  COUNT ( * ) ,  AVG ( salary)  AS  ` avg_salary`  
FROM  employees AS  ` emp`  
RIGHT  JOIN  departments AS  ` dep`  
ON  emp. department_id =  dep. department_id
GROUP  BY  department_name,  location_id
ORDER  BY  avg_salary DESC ; 
+ 
|  department_name      |  location_id |  COUNT ( * )  |  avg_salary   | 
+ 
|  Executive            |         1700  |         3  |  19333.333333  | 
|  Accounting           |         1700  |         2  |  10150.000000  | 
|  Public  Relations     |         2700  |         1  |  10000.000000  | 
|  Marketing            |         1800  |         2  |   9500.000000  | 
|  Sales                |         2500  |        34  |   8955.882353  | 
|  Finance              |         1700  |         6  |   8600.000000  | 
|  Human Resources      |         2400  |         1  |   6500.000000  | 
|  IT                   |         1400  |         5  |   5760.000000  | 
|  Administration       |         1700  |         1  |   4400.000000  | 
|  Purchasing           |         1700  |         6  |   4150.000000  | 
|  Shipping             |         1500  |        45  |   3475.555556  | 
|  . . .                   |          . . .  |       . . .  |           . . .  |  
|  Retail Sales         |         1700  |         1  |          NULL  | 
|  Recruiting           |         1700  |         1  |          NULL  | 
|  Payroll              |         1700  |         1  |          NULL  | 
+ 
27  rows  in  set  ( 0.00  sec) 
  
SELECT  department_name,  job_id,  MIN ( salary)  AS  ` min_salary`  
FROM  employees AS  ` emp`  
RIGHT  JOIN  departments AS  ` dep`  
ON  emp. department_id =  dep. department_id
GROUP  BY  department_name,  job_id; 
+ 
|  department_name      |  job_id     |  min_salary | 
+ 
|  Administration       |  AD_ASST    |     4400.00  | 
|  Marketing            |  MK_MAN     |    13000.00  | 
|  Marketing            |  MK_REP     |     6000.00  | 
|  Purchasing           |  PU_MAN     |    11000.00  | 
|  Purchasing           |  PU_CLERK   |     2500.00  | 
|  Human Resources      |  HR_REP     |     6500.00  | 
|  Shipping             |  ST_MAN     |     5800.00  | 
|  . . .                   |  . . .         |         . . .  |  
|  IT Support           |  NULL        |        NULL  | 
|  NOC                  |  NULL        |        NULL  | 
|  IT Helpdesk          |  NULL        |        NULL  | 
|  Government Sales     |  NULL        |        NULL  | 
|  Retail Sales         |  NULL        |        NULL  | 
|  Recruiting           |  NULL        |        NULL  | 
|  Payroll              |  NULL        |        NULL  | 
+ 
35  rows  in  set  ( 0.00  sec)