MySQL第八章 基本SELECT语句

第八章 基本SELECT语句

8.1 基本的SELECT语句

8.1.1 SELECT ...

SELECT 1;
SELECT 1+3;
SELECT version();

8.1.2 SELECT ... FROM

SELECT 选择列 FROM 表
SELECT * FROM departments;
  • 一般情况下,除非需要使用表中所有的字段数据,最好不要使用通配符‘*’

    使用通配符虽然可以节省输入查询语句的时间,但是获取不需要的列数据通常会降低查询和所使用的应用程序的效率。

    通配符的优势是,当不知道所需要的列的名称时,可以通过它获取它们。

  • 在生产环境下,不推荐你直接使用SELECT * 进行查询。

选择特定的列:

SELECT department_id, location_id
FROM   departments;

MySQL中的SQL语句是不区分大小写的,因此SELECT和select的作用是相同的,但是,许多开发人员习惯将关键字大写、数据列和表名小写,自己写时也应该养成一个良好的编程习惯,这样写出来的代码更容易阅读和维护。

8.1.3 列的别名

  • 重命名一个列,便于计算
  • 紧跟列名,也可以在列名和别名之间加入关键字AS(AS是Alias的简称),别名使用双引号,以便在别名中包含空格或特殊的字符并区分大小写。
  • AS 可以省略
  • 建议别名简短,见名知意

举例

SELECT last_name AS name, commission_pct comm
FROM   employees;
SELECT last_name "Name", salary*12 "Annual Salary"
FROM   employees;

列的别名尽量使用双引号(" "),特别是别名中有空格的时候,而且不建议省略as

select id as "编号", `name` as "姓名" from t_stu; #起别名时,as都可以省略
select id as 编号, `name` as 姓名 from t_stu; #如果字段别名中没有空格,那么可以省略""
select id as 编 号, `name` as 姓 名 from t_stu; #错误,如果字段别名中有空格,那么不能省略""

8.1.4 DISTINCT关键字

在SELECT语句中使用关键字DISTINCT去除重复行

SELECT DISTINCT department_id
FROM   employees;

针对于:

SELECT DISTINCT department_id,salary 
FROM employees;

这里有两点需要注意:

  1. DISTINCT 需要放到所有列名的前面,如果写成SELECT salary, DISTINCT department_id FROM employees会报错。
  2. DISTINCT 其实是对后面所有列名的组合进行去重,你能看到最后的结果是 74 条,因为这 74 个部门id不同,都有 salary 这个属性值。如果你想要看都有哪些不同的部门(department_id),只需要写DISTINCT department_id即可,后面不需要再加其他的列名了。

8.1.6 着重号

  • 错误的
mysql> SELECT * FROM ORDER;
# ERROR 1064 (42000): You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'ORDER' at line 1
  • 正确的
mysql> SELECT * FROM `ORDER`;
+----------+------------+
| order_id | order_name |
+----------+------------+
|        1 | shkstart   |
|        2 | tomcat     |
|        3 | dubbo      |
+----------+------------+
3 rows in set (0.00 sec)

mysql> SELECT * FROM `order`;
+----------+------------+
| order_id | order_name |
+----------+------------+
|        1 | shkstart   |
|        2 | tomcat     |
|        3 | dubbo      |
+----------+------------+
3 rows in set (0.00 sec)
  • 结论

我们需要保证表中的字段、表名等没有和保留字、数据库系统或常用方法冲突。如果真的相同,请在SQL语句中使用一对``(着重号)引起来。

8.1.7 查询常数

  • SELECT 查询还可以对常数进行查询。对的,就是在 SELECT 查询结果中增加一列固定的常数列。这列的取值是我们指定的,而不是从数据表中动态取出的。
  • 你可能会问为什么我们还要对常数进行查询呢?
  • SQL 中的 SELECT 语法的确提供了这个功能,一般来说我们只从一个表中查询数据,通常不需要增加一个固定的常数列,但如果我们想整合不同的数据源,用常数列作为这个表的标记,就需要查询常数。
  • 比如说,我们想对 employees 数据表中的员工姓名进行查询,同时增加一列字段corporation,这个字段固定值为“Facebook”,可以这样写:
SELECT 'Facebook' as corporation, last_name FROM employees;

8.2 过滤WHERE

  • 语法:
SELECT 字段1,字段2
FROM 表名
WHERE 过滤条件
  • 使用WHERE 子句,将不满足条件的行过滤掉
  • WHERE子句紧随 FROM子句

举例

SELECT employee_id, last_name, job_id, department_id
FROM   employees
WHERE  department_id = 90 ;

8.3 分组GROUP BY

这个要搭配聚合函数

可以使用GROUP BY子句将表中的数据分成若干组

SELECT column, group_function(column)
FROM table
[WHERE	condition]
[GROUP BY	group_by_expression]
[ORDER BY	column];

明确:WHERE一定放在FROM后面

在SELECT列表中所有未包含在组函数中的列都应该包含在 GROUP BY子句中

(1) GROUP BY单列

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)

(2) GROUP BY多列

mysql> SELECT   department_id dept_id, job_id, SUM(salary)
    -> FROM     employees
    -> GROUP BY department_id, job_id ;
+---------+------------+-------------+
| dept_id | job_id     | SUM(salary) |
+---------+------------+-------------+
|      90 | AD_PRES    |    24000.00 |
|      90 | AD_VP      |    34000.00 |
|      60 | IT_PROG    |    28800.00 |
|     100 | FI_MGR     |    12000.00 |
|     100 | FI_ACCOUNT |    39600.00 |
|      30 | PU_MAN     |    11000.00 |
|      30 | PU_CLERK   |    13900.00 |
|      50 | ST_MAN     |    36400.00 |
|      50 | ST_CLERK   |    55700.00 |
|      80 | SA_MAN     |    61000.00 |
|      80 | SA_REP     |   243500.00 |
|    NULL | SA_REP     |     7000.00 |

(3) WITH ROLLUP

使用WITH ROLLUP关键字之后,在所有查询出的分组记录之后增加一条记录,该记录计算查询出的所有记录的总和,即统计记录数量。

注意:当使用ROL LUP时,不能同时使用ORDER BY子句进行结果排序,即ROLLUP和ORDER BY是互相排斥的。

mysql> SELECT department_id,AVG(salary)
    -> FROM employees
    -> WHERE department_id > 80
    -> GROUP BY department_id WITH ROLLUP;
+---------------+--------------+
| department_id | AVG(salary)  |
+---------------+--------------+
|            90 | 19333.333333 |
|           100 |  8600.000000 |
|           110 | 10150.000000 |
|          NULL | 11809.090909 | # 这里多出一行,是全部salary的平均
+---------------+--------------+
4 rows in set (0.00 sec)

mysql> SELECT department_id,AVG(salary)
    -> FROM employees
    -> WHERE department_id > 80
    -> GROUP BY department_id;
+---------------+--------------+
| department_id | AVG(salary)  |
+---------------+--------------+
|            90 | 19333.333333 |
|           100 |  8600.000000 |
|           110 | 10150.000000 |
+---------------+--------------+
3 rows in set (0.00 sec)

8.4 分组过滤HAVING

  1. 行已经被分组。
  2. 使用了聚合函数。
  3. 满足HAVING 子句中条件的分组将被显示。
  4. HAVING 不能单独使用,必须要跟 GROUP BY 一起使用。
mysql> SELECT   department_id, 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)

HAVING & WHERE

不能在 WHERE 子句中使用聚合函数

mysql> SELECT   department_id, AVG(salary)
    -> FROM     employees
    -> WHERE    AVG(salary) > 8000
    -> GROUP BY department_id;
# ERROR 1111 (HY000): Invalid use of group function

区别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 可以使用包含分组统计函数的查询条件的优点。
  • 当数据量特别大的时候,运行效率会有很大的差别。

8.5 排序ORDER BY

  • 使用 ORDER BY 子句排序
    • ASC(ascend): 升序(默认)
    • DESC(descend):降序
  • ORDER BY 子句在SELECT语句的结尾。

8.5.1 单列排序

SELECT   last_name, job_id, department_id, hire_date
FROM     employees
ORDER BY hire_date ;
mysql> SELECT   last_name, job_id, department_id, hire_date
    -> FROM     employees
    -> ORDER BY hire_date ;
+-------------+------------+---------------+------------+
| last_name   | job_id     | department_id | hire_date  |
+-------------+------------+---------------+------------+
| King        | AD_PRES    |            90 | 1987-06-17 |
| Whalen      | AD_ASST    |            10 | 1987-09-17 |
| Kochhar     | AD_VP      |            90 | 1989-09-21 |
| Hunold      | IT_PROG    |            60 | 1990-01-03 |
| Ernst       | IT_PROG    |            60 | 1991-05-21 |
| De Haan     | AD_VP      |            90 | 1993-01-13 |
... 
# 可以看到,hire_date是递增的
SELECT   last_name, job_id, department_id, hire_date
FROM     employees
ORDER BY hire_date DESC ;
mysql> SELECT   last_name, job_id, department_id, hire_date
    -> FROM     employees
    -> ORDER BY hire_date DESC ;
+-------------+------------+---------------+------------+
| last_name   | job_id     | department_id | hire_date  |
+-------------+------------+---------------+------------+
| Banda       | SA_REP     |            80 | 2000-04-21 |
| Kumar       | SA_REP     |            80 | 2000-04-21 |
| Ande        | SA_REP     |            80 | 2000-03-24 |
| Markle      | ST_CLERK   |            50 | 2000-03-08 |
| Lee         | SA_REP     |            80 | 2000-02-23 |
| Philtanker  | ST_CLERK   |            50 | 2000-02-06 |
...
# hire_date是递减的

即使是SELECT中不存在的列也可以作为排序依据

SELECT employee_id,salary
FROM employees
WHERE department_id IN(50,60,70)
ORDER BY department_id DESC;
SELECT employee_id,salary, department_id
FROM employees
WHERE department_id IN(50,60,70)
ORDER BY department_id DESC;

可以使用列的别名来进行排序

SELECT employee_id, last_name, salary*12 annsal
FROM   employees
ORDER BY annsal;

原因:SQL执行的时候不是按照从前往后的顺序执行的,实际上先从,FROM WHERE执行,然后SELECT才有了别名,然后是ORDER

8.5.2 多列排序

SELECT last_name, department_id, salary
FROM   employees
ORDER BY department_id, salary DESC;
  • 可以使用不在SELECT列表中的列排序。

  • 在对多列进行排序的时候,首先排序的第一列必须有相同的列值,才会对第二列进行排序。

    如果第一列数据中所有值都是唯一的,将不再对第二列进行排序。

mysql> SELECT last_name, department_id, salary
    -> FROM   employees
    -> ORDER BY department_id, salary DESC;
+-------------+---------------+----------+
| last_name   | department_id | salary   |
+-------------+---------------+----------+
| Grant       |          NULL |  7000.00 |
| Whalen      |            10 |  4400.00 |
| Hartstein   |            20 | 13000.00 |
| Fay         |            20 |  6000.00 |
| Raphaely    |            30 | 11000.00 |
| Khoo        |            30 |  3100.00 |
| Baida       |            30 |  2900.00 |
| Tobias      |            30 |  2800.00 |
| Himuro      |            30 |  2600.00 |

8.6 分页LIMIT

8.6.1 分页解决的问题

分页可以解决的问题:

  1. 查询返回的记录太多了,查看起来很不方便,怎么样能够实现分页查询呢?
  2. 表里有 4 条数据,我们只想要显示第 2、3 条数据怎么办呢?

分页原理:所谓分页显示,就是将数据库中的结果集,一段一段显示出来需要的条件。

  • MySQL中使用 LIMIT 实现分页

8.6.2 语法

格式:

LIMIT [位置偏移量,] 行数 # 位置偏移量如果没有就是0,偏移量为0,也就是第1条记录
  • 第一个“位置偏移量”参数指示MySQL从哪一行开始显示,是一个可选参数,如果不指定“位置偏移量”,将会从表中的第一条记录开始(第一条记录的位置偏移量是0,第二条记录的位置偏移量是1,以此类推);
  • 第二个参数“行数”指示返回的记录条数。

举例

-- 前10条记录:
SELECT * FROM 表名 LIMIT 0,10;
或者
SELECT * FROM 表名 LIMIT 10;

-- 第11至20条记录:第11条,偏移量是11
SELECT * FROM 表名 LIMIT 10,10;

-- 第21至30条记录: 
SELECT * FROM 表名 LIMIT 20,10;

练习:表里有107条数据,我们只想要显示第 32、33条数据

SELECT employee_id,last_name
FROM employees
LIMIT 31,2;

MySQL 8.0中可以使用“LIMIT 3 OFFSET 4”,意思是获取从第5条记录开始后面的3条记录,和“LIMIT 4,3;”返回的结果相同。


8.6.3 分页显示公式

  • 分页显式公式:(要显示的页数-1)*每页条数,每页条数
SELECT * FROM table 
LIMIT(PageNo - 1)*PageSize,PageSize;
  • 注意:LIMIT 子句必须放在整个SELECT语句的最后!

8.6.4 分页显示的好处

使用 LIMIT 的好处

  1. 约束返回结果的数量可以减少数据表的网络传输量,也可以提升查询效率

    如果我们知道返回结果只有 1 条,就可以使用LIMIT 1,告诉 SELECT 语句只需要返回一条记录即可。

    这样的好处就是 SELECT 不需要扫描完整的表,只需要检索到一条符合条件的记录即可返回。

如果只要有限的数据,使用LIMIT可以加快效率

8.6.5 其他SQL的分页

在不同的 DBMS 中使用的关键字可能不同。在 MySQL、PostgreSQL、MariaDB 和 SQLite 中使用 LIMIT 关键字,而且需要放到 SELECT 语句的最后面。

  • 如果是 SQL ServerAccess,需要使用 TOP 关键字,比如:
SELECT TOP 5 name, hp_max 
FROM heros 
ORDER BY hp_max DESC;
  • 如果是 DB2,使用FETCH FIRST 5 ROWS ONLY这样的关键字:
SELECT name, hp_max 
FROM heros 
ORDER BY hp_max DESC 
FETCH FIRST 5 ROWS ONLY;
  • 如果是 Oracle,你需要基于 ROWNUM 来统计行数:
SELECT rownum,last_name,salary 
FROM employees 
WHERE rownum < 5 ORDER BY salary DESC;

需要说明的是,这条语句是先取出来前 5 条数据行,然后再按照 hp_max 从高到低的顺序进行排序。但这样产生的结果和上述方法的并不一样。

应该使用子查询

SELECT rownum, last_name,salary
FROM (
    SELECT last_name,salary
    FROM employees
    ORDER BY salary DESC)
WHERE rownum < 10;

得到与上述方法一致的结果。

8.7 SELECT顺序

8.7.1 语法顺序

SELECT ... FROM ... WHERE ... GROUP BY ... HAVING ... ORDER BY ... LIMIT...

查询的结构

#方式1:
SELECT ...,....,...
FROM ...,...,....
WHERE 多表的连接条件
AND 不包含组函数的过滤条件
GROUP BY ...,...
HAVING 包含组函数的过滤条件
ORDER BY ... ASC/DESC
LIMIT ...,...
#方式2:
SELECT ...,....,...
FROM ... JOIN ... 
ON 多表的连接条件
JOIN ...
ON ...
WHERE 不包含组函数的过滤条件
AND/OR 不包含组函数的过滤条件
GROUP BY ...,...
HAVING 包含组函数的过滤条件
ORDER BY ... ASC/DESC
LIMIT ...,...

其中:

  1. from:从哪些表中筛选
  2. on:关联多表查询时,去除笛卡尔积
  3. where:从表中筛选的条件
  4. group by:分组依据
  5. having:在统计结果中再次筛选
  6. order by:排序
  7. limit:分页

8.7.2 执行顺序

你需要记住 SELECT 查询时的两个顺序:

SELECT 语句的执行顺序(在 MySQL 和 Oracle 中,SELECT 执行顺序基本相同):

FROM -> ON -> (LEFT/RIGHT JOIN) -> WHERE -> GROUP BY -> HAVING -> SELECT 的字段 -> DISTINCT -> ORDER BY -> LIMIT

由此可见,非聚合的写在WHERE中,就不用在GROUP BY中写了

比如你写了一个 SQL 语句,那么它的关键字顺序和执行顺序是下面这样的:

SELECT DISTINCT player_id, player_name, count(*) as num # 顺序 5

FROM player JOIN team ON player.team_id = team.team_id # 顺序 1
WHERE height > 1.80 # 顺序 2
GROUP BY player.team_id # 顺序 3
HAVING num > 2 # 顺序 4

ORDER BY num DESC # 顺序 6
LIMIT 2 # 顺序 7

在 SELECT 语句执行这些步骤的时候,每个步骤都会产生一个虚拟表,然后将这个虚拟表传入下一个步骤中作为输入。需要注意的是,这些步骤隐含在 SQL 的执行过程中,对于我们来说是不可见的。

8.7.3 SELECT执行原理

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 和 HAVING 阶段。在这个阶段中,实际上是在虚拟表 vt2 的基础上进行分组和分组过滤,得到中间的虚拟表 vt3vt4

当我们完成了条件筛选部分之后,就可以筛选表中提取的字段,也就是进入到 SELECT 和 DISTINCT 阶段

首先在 SELECT 阶段会提取想要的字段,然后在 DISTINCT 阶段过滤掉重复的行,分别得到中间的虚拟表 vt5-1vt5-2

当我们提取了想要的字段数据之后,就可以按照指定的字段进行排序,也就是 ORDER BY 阶段,得到虚拟表 vt6

最后在 vt6 的基础上,取出指定行的记录,也就是 LIMIT 阶段,得到最终的结果,对应的是虚拟表 vt7

当然我们在写 SELECT 语句的时候,不一定存在所有的关键字,相应的阶段就会省略。

同时因为 SQL 是一门类似英语的结构化查询语言,所以我们在写 SELECT 语句的时候,还要注意相应的关键字顺序,所谓底层运行的原理,就是我们刚才讲到的执行顺序。

posted @ 2023-09-17 19:21  Crispy·Candy  阅读(44)  评论(0编辑  收藏  举报