数据库系统原理与应用教程(050)—— MySQL 查询(十二):SELECT 命令的执行过程分析


目录

  • 数据库系统原理与应用教程(050)—— MySQL 查询(十二):SELECT 命令的执行过程分析
  • 一、SELECT 语句的书写顺序
  • 二、SELECT 语句的执行顺序
  • 三、在 SELECT 语句中使用变量
  • 1、在 FROM 子句中为变量赋值
  • 2、在 SELECT 子句中为变量赋值
  • 3、统计查询结果的行数
  • 四、列的别名使用规则
  • 五、WHERE 和 GROUP BY ... HAVING 子句


SELECT 语句由多个部分组成,在书写时需要遵循一定的顺序,否则会提示语法错误。在执行过程中,各个部分又有相应的执行顺序,在使用列的别名时需要注意各部分的执行顺序,否则会出现找不到别名的情况。

一、SELECT 语句的书写顺序

SELECT 语句各组成部分的书写顺序如下:

SELECT DISTINCT <列名或表达式列表>
FROM <table_name> [INNER|LEFT|RIGHT] JOIN <table_name>
ON <连接条件>
WHERE <筛选条件>
GROUP BY <分组字段或表达式>
HAVING <分组筛选条件>
WITH ROLLUP
ORDER BY <排序字段或表达式>
LIMIT [m,]n

书写顺序错误 SELECT 语句在执行时会提示语法错误(错误码:1064)。

二、SELECT 语句的执行顺序

SELECT 语句各组成部分的执行顺序如下:

--(1)指定查询所使用的表
FROM <table_name> [INNER|LEFT|RIGHT] JOIN <table_name>
ON <连接条件>
--(2)指定筛选条件(WHERE 后面不能使用 SELECT 所指定的列的别名,因为此时 select 子句还没有执行)
WHERE <筛选条件>
--(3)对数据分组
GROUP BY <分组字段或表达式>
WITH ROLLUP
HAVING <分组筛选条件>
--(4)生成需要显示的列和表达式
SELECT <字段或表达式列表>
--(5)消除重复的数据行
DISTINCT
--(6)对查询结果排序
ORDER BY <排序字段或表达式>
LIMIT [m,]n

关于列和表达式的别名使用说明:

(1)WHERE 子句不能使用列和表达式的别名。

(2)GROUP BY 和 ORDER BY 子句可以使用列和表达式的别名。

三、在 SELECT 语句中使用变量

1、在 FROM 子句中为变量赋值

在整个查询过程中,FROM 子句只执行一次,因此 FROM 子句中为变量赋值的操作只执行一次。

验证如下:

/*
SELECT @var := 0;
SELECT s.* FROM student s,(SELECT @var := @var + 1) a;
SELECT @var;
*/
mysql> SELECT @var := 0;
+-----------+
| @var := 0 |
+-----------+
|         0 |
+-----------+
1 row in set (0.00 sec)

mysql> SELECT s.* FROM student s,(SELECT @var := @var + 1) a;
+-------+-----------+--------+---------------------+-------------+-----------+
| s_id  | s_name    | gender | birth               | phone       | addr      |
+-------+-----------+--------+---------------------+-------------+-----------+
| S2011 | 张晓刚    | 男     | 1999-12-03 00:00:00 | 13163735775 | 信阳市    |
| S2012 | 刘小青    | 女     | 1999-10-11 00:00:00 | 13603732255 | 新乡市    |
| S2013 | 曹梦德    | 男     | 1998-02-13 00:00:00 | 13853735522 | 郑州市    |
| S2014 | 刘艳      | 女     | 1998-06-24 00:00:00 | 13623735335 | 郑州市    |
| S2015 | 刘艳      | 女     | 1999-07-06 00:00:00 | 13813735225 | 信阳市    |
| S2016 | 刘若非    | 女     | 2000-08-31 00:00:00 | 13683735533 | 开封市    |
| S2021 | 董雯花    | 女     | 2000-07-30 00:00:00 | 13533735564 | 开封市    |
| S2022 | 周华建    | 男     | 1999-05-25 00:00:00 | 13243735578 | 郑州市    |
| S2023 | 特朗普    | 男     | 1999-06-21 00:00:00 | 13343735588 | 新乡市    |
| S2024 | 奥巴马    | 男     | 2000-10-17 00:00:00 | 13843735885 | 信阳市    |
| S2025 | 周健华    | 男     | 2000-08-22 00:00:00 | 13788736655 | 开封市    |
| S2026 | 张学有    | 男     | 1998-07-06 00:00:00 | 13743735566 | 郑州市    |
| S2031 | 李明博    | 女     | 1999-10-26 00:00:00 | 13643732222 | 郑州市    |
| S2032 | 达芬奇    | 男     | 1999-12-31 00:00:00 | 13043731234 | 郑州市    |
+-------+-----------+--------+---------------------+-------------+-----------+
14 rows in set (0.00 sec)

mysql> SELECT @var;
+------+
| @var |
+------+
|    1 |
+------+
1 row in set (0.00 sec)
2、在 SELECT 子句中为变量赋值

在整个查询过程中,SELECT 子句执行的次数取决于查询结果中记录的行数。因此,查询结果有多少行,则 SELECT 子句中为变量赋值的操作就执行几次。根据这一特点,可以使用 COUNT(1) 统计查询结果的行数。

验证如下:

/*
SELECT s.*,@var := @var + 1 FROM student s,(SELECT @var := 0) a;
SELECT @var;
*/
mysql> SELECT s.*,@var := @var + 1 FROM student s,(SELECT @var := 0) a;
+-------+-----------+--------+---------------------+-------------+-----------+------------------+
| s_id  | s_name    | gender | birth               | phone       | addr      | @var := @var + 1 |
+-------+-----------+--------+---------------------+-------------+-----------+------------------+
| S2011 | 张晓刚    | 男     | 1999-12-03 00:00:00 | 13163735775 | 信阳市    |                1 |
| S2012 | 刘小青    | 女     | 1999-10-11 00:00:00 | 13603732255 | 新乡市    |                2 |
| S2013 | 曹梦德    | 男     | 1998-02-13 00:00:00 | 13853735522 | 郑州市    |                3 |
| S2014 | 刘艳      | 女     | 1998-06-24 00:00:00 | 13623735335 | 郑州市    |                4 |
| S2015 | 刘艳      | 女     | 1999-07-06 00:00:00 | 13813735225 | 信阳市    |                5 |
| S2016 | 刘若非    | 女     | 2000-08-31 00:00:00 | 13683735533 | 开封市    |                6 |
| S2021 | 董雯花    | 女     | 2000-07-30 00:00:00 | 13533735564 | 开封市    |                7 |
| S2022 | 周华建    | 男     | 1999-05-25 00:00:00 | 13243735578 | 郑州市    |                8 |
| S2023 | 特朗普    | 男     | 1999-06-21 00:00:00 | 13343735588 | 新乡市    |                9 |
| S2024 | 奥巴马    | 男     | 2000-10-17 00:00:00 | 13843735885 | 信阳市    |               10 |
| S2025 | 周健华    | 男     | 2000-08-22 00:00:00 | 13788736655 | 开封市    |               11 |
| S2026 | 张学有    | 男     | 1998-07-06 00:00:00 | 13743735566 | 郑州市    |               12 |
| S2031 | 李明博    | 女     | 1999-10-26 00:00:00 | 13643732222 | 郑州市    |               13 |
| S2032 | 达芬奇    | 男     | 1999-12-31 00:00:00 | 13043731234 | 郑州市    |               14 |
+-------+-----------+--------+---------------------+-------------+-----------+------------------+
14 rows in set (0.00 sec)

mysql> SELECT @var;
+------+
| @var |
+------+
|   14 |
+------+
1 row in set (0.00 sec)
3、统计查询结果的行数
mysql> select count(1) from student where addr='郑州市';
+----------+
| count(1) |
+----------+
|        6 |
+----------+
1 row in set (0.04 sec)

mysql> select sum(1) from student where addr='郑州市';
+--------+
| sum(1) |
+--------+
|      6 |
+--------+
1 row in set (0.02 sec)

四、列的别名使用规则

1、WHERE 子句不能使用列和表达式的别名

/*
select s_id,s_name,birth,year(now())-year(birth) age 
from student where age > 20;
*/
-- 出错:无法识别别名 age
mysql> select s_id,s_name,birth,year(now())-year(birth) age 
    -> from student where age > 20;
ERROR 1054 (42S22): Unknown column 'age' in 'where clause'

2、GROUP BY 和 ORDER BY 子句可以使用列和表达式的别名

(1)GROUP BY 选项使用别名

/*
select year(now())-year(birth) age, count(*) 
from student group by age;
*/
mysql> select year(now())-year(birth) age, count(*) 
    -> from student group by age;
+------+----------+
| age  | count(*) |
+------+----------+
|   22 |        4 |
|   23 |        7 |
|   24 |        3 |
+------+----------+
3 rows in set (0.00 sec)

(2)ORDER BY 选项使用别名

/*
select s_id,s_name,birth,year(now())-year(birth) age 
from student order by age desc;
*/
mysql> select s_id,s_name,birth,year(now())-year(birth) age 
    -> from student order by age desc;
+-------+-----------+---------------------+------+
| s_id  | s_name    | birth               | age  |
+-------+-----------+---------------------+------+
| S2013 | 曹梦德    | 1998-02-13 00:00:00 |   24 |
| S2014 | 刘艳      | 1998-06-24 00:00:00 |   24 |
| S2026 | 张学有    | 1998-07-06 00:00:00 |   24 |
| S2011 | 张晓刚    | 1999-12-03 00:00:00 |   23 |
| S2012 | 刘小青    | 1999-10-11 00:00:00 |   23 |
| S2015 | 刘艳      | 1999-07-06 00:00:00 |   23 |
| S2022 | 周华建    | 1999-05-25 00:00:00 |   23 |
| S2023 | 特朗普    | 1999-06-21 00:00:00 |   23 |
| S2031 | 李明博    | 1999-10-26 00:00:00 |   23 |
| S2032 | 达芬奇    | 1999-12-31 00:00:00 |   23 |
| S2016 | 刘若非    | 2000-08-31 00:00:00 |   22 |
| S2021 | 董雯花    | 2000-07-30 00:00:00 |   22 |
| S2024 | 奥巴马    | 2000-10-17 00:00:00 |   22 |
| S2025 | 周健华    | 2000-08-22 00:00:00 |   22 |
+-------+-----------+---------------------+------+
14 rows in set (0.00 sec)

五、WHERE 和 GROUP BY … HAVING 子句

当 WHERE 和 GROUP BY … HAVING 子句同时使用时,执行过程如下:

(1)执行 WHERE 子句对表中记录进行筛选,得到满足给定条件的行。

(2)对满足查询条件的记录进行分组,然后使用 HAVING 子句对分组进行筛选。

例如:根据 student 表查询各个地址的【男】生人数,并显示超过 2 人的行。

命令如下:

/*
select addr,count(*) cnt from student
where gender = '男'
group by addr having cnt>2;
*/

mysql> select addr,count(*) cnt from student
    -> where gender = '男'
    -> group by addr having cnt>2;
+-----------+-----+
| addr      | cnt |
+-----------+-----+
| 郑州市    |   4 |
+-----------+-----+
1 row in set (0.00 sec)

以上查询可以这样分步执行:

(1)查询 student 表中的【男】生信息

mysql> select * from student where gender = '男';
+-------+-----------+--------+---------------------+-------------+-----------+
| s_id  | s_name    | gender | birth               | phone       | addr      |
+-------+-----------+--------+---------------------+-------------+-----------+
| S2011 | 张晓刚    | 男     | 1999-12-03 00:00:00 | 13163735775 | 信阳市    |
| S2013 | 曹梦德    | 男     | 1998-02-13 00:00:00 | 13853735522 | 郑州市    |
| S2022 | 周华建    | 男     | 1999-05-25 00:00:00 | 13243735578 | 郑州市    |
| S2023 | 特朗普    | 男     | 1999-06-21 00:00:00 | 13343735588 | 新乡市    |
| S2024 | 奥巴马    | 男     | 2000-10-17 00:00:00 | 13843735885 | 信阳市    |
| S2025 | 周健华    | 男     | 2000-08-22 00:00:00 | 13788736655 | 开封市    |
| S2026 | 张学有    | 男     | 1998-07-06 00:00:00 | 13743735566 | 郑州市    |
| S2032 | 达芬奇    | 男     | 1999-12-31 00:00:00 | 13043731234 | 郑州市    |
+-------+-----------+--------+---------------------+-------------+-----------+
8 rows in set (0.00 sec)

(2)按 addr 进行分组,统计每个地址包含的学生数

/*
select addr, count(*) cnt from student where gender = '男'
group by addr;
*/

mysql> select addr, count(*) cnt from student where gender = '男'
    -> group by addr;
+-----------+-----+
| addr      | cnt |
+-----------+-----+
| 信阳市    |   2 |
| 开封市    |   1 |
| 新乡市    |   1 |
| 郑州市    |   4 |
+-----------+-----+
4 rows in set (0.00 sec)

(3)使用 HAVING 子句对分组进行筛选

/*
select addr, count(*) cnt from student where gender = '男'
group by addr having cnt > 2;
*/
mysql> select addr, count(*) cnt from student where gender = '男'
    -> group by addr having cnt > 2;
+-----------+-----+
| addr      | cnt |
+-----------+-----+
| 郑州市    |   4 |
+-----------+-----+
1 row in set (0.00 sec)