数据库系统原理与应用教程(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)