第四单元 · 概念、类型、改表、分页 · 第 11 / 21 节
先补分页的起始行,再做五题案例
建 lesson11_basic(8 行、城市与性别故意重复),先用别名把列和表达式改成能读的表头,再用 COUNT(*)/COUNT(列)/COUNT(DISTINCT 列) 与 SELECT DISTINCT(单列、多列)实测“几行、几个不同值、哪些组合”;然后建 lesson11_dql(12 行)把分页算清楚:LIMIT 偏移量,条数、起始索引 =(页码−1)×每页条数、CEIL 总页数、越界空集、分页为什么必须有能唯一确定顺序的 ORDER BY;最后用五道原创题把 IN、BETWEEN+三下划线 LIKE、按性别分组计数、多列排序、排序后 LIMIT 各用一次。
约 34 分钟。这一节补三件事:先给列起别名、把重复值去掉(DISTINCT),再算清 LIMIT 的“从第几行开始”,最后用五道题把条件、LIKE、分组、排序、分页各用一次。
$ / mysql> 提示符和输出。已完成 0 / 3 步
你现在在哪个提示符
应该在 mysql>。断了就先回到学习终端(PowerShell):
ssh root@8.148.204.152
mysql-learning
进去以后确认当前库:
SELECT DATABASE();
+------------+
| DATABASE() |
+------------+
| itcast |
+------------+
1 row in set (0.00 sec)
显示 itcast 就对了。如果是 NULL,执行 USE itcast;。
第一步:别名与去重:给列换个名字,数清有几行是重复的
第 3 节你查过单表,但有两件小事一直没正面讲:给列起别名,和把重复值去掉(DISTINCT)。它们看着小,实际每天都在用——报表要中文表头、算出来的数要有名字、统计“有几个不同的城市”时更离不开去重。
先建一张 8 行的小表,城市和性别故意有重复:
SHOW TABLES LIKE 'lesson11_basic';
Empty set (0.00 sec)
CREATE TABLE lesson11_basic (
id INT PRIMARY KEY COMMENT '工号',
name VARCHAR(20) NOT NULL COMMENT '姓名',
city VARCHAR(20) NOT NULL COMMENT '城市',
gender CHAR(1) NOT NULL COMMENT '性别',
salary DECIMAL(8,2) NOT NULL COMMENT '月薪'
) COMMENT='本教程第四单元练习表:别名与去重';
Query OK, 0 rows affected (0.03 sec)
INSERT INTO lesson11_basic (id, name, city, gender, salary) VALUES
(1, '张伟', '北京', '男', 8800.00),
(2, '李娜', '上海', '女', 7600.00),
(3, '王强', '北京', '男', 9200.00),
(4, '赵敏', '上海', '女', 8100.00),
(5, '陈晨', '北京', '男', 8700.00),
(6, '孙悦', '广州', '女', 10300.00),
(7, '周舟', '广州', '男', 8500.00),
(8, '吴迪', '上海', '男', 12000.00);
Query OK, 8 rows affected (0.00 sec)
Records: 8 Duplicates: 0 Warnings: 0
8 行、5 列。表里已经有这 8 行时再跑 INSERT 会得到 1062,原数据不受影响。
SELECT 后面写 * 表示“所有列”,先整体看一眼:
SELECT * FROM lesson11_basic ORDER BY id;
+----+--------+--------+--------+----------+
| id | name | city | gender | salary |
+----+--------+--------+--------+----------+
| 1 | 张伟 | 北京 | 男 | 8800.00 |
| 2 | 李娜 | 上海 | 女 | 7600.00 |
| 3 | 王强 | 北京 | 男 | 9200.00 |
| 4 | 赵敏 | 上海 | 女 | 8100.00 |
| 5 | 陈晨 | 北京 | 男 | 8700.00 |
| 6 | 孙悦 | 广州 | 女 | 10300.00 |
| 7 | 周舟 | 广州 | 男 | 8500.00 |
| 8 | 吴迪 | 上海 | 男 | 12000.00 |
+----+--------+--------+--------+----------+
8 rows in set (0.00 sec)
* 省事,但列的顺序和列数会随表结构变,正式写查询时更推荐把列名写全——下面都这么写。
别名:AS 只改显示的名字,不改数据
SELECT name AS 姓名, city AS 城市 FROM lesson11_basic ORDER BY id;
+--------+--------+
| 姓名 | 城市 |
+--------+--------+
| 张伟 | 北京 |
| 李娜 | 上海 |
| 王强 | 北京 |
| 赵敏 | 上海 |
| 陈晨 | 北京 |
| 孙悦 | 广州 |
| 周舟 | 广州 |
| 吴迪 | 上海 |
+--------+--------+
8 rows in set (0.00 sec)
AS 姓名 只是把结果集的表头换掉,表里的列名还是 name。AS 也可以省略(写 name 姓名),但建议留着,读起来更清楚。别名还能给表达式用:
SELECT name AS 姓名, salary AS 月薪, salary * 12 AS 年薪
FROM lesson11_basic
ORDER BY 年薪 DESC, id;
+--------+----------+-----------+
| 姓名 | 月薪 | 年薪 |
+--------+----------+-----------+
| 吴迪 | 12000.00 | 144000.00 |
| 孙悦 | 10300.00 | 123600.00 |
| 王强 | 9200.00 | 110400.00 |
| 张伟 | 8800.00 | 105600.00 |
| 陈晨 | 8700.00 | 104400.00 |
| 周舟 | 8500.00 | 102000.00 |
| 赵敏 | 8100.00 | 97200.00 |
| 李娜 | 7600.00 | 91200.00 |
+--------+----------+-----------+
8 rows in set (0.00 sec)
年薪 这一列在表里根本不存在,是 salary * 12 算出来的,别名让结果能读。注意 ORDER BY 里可以直接用别名——因为排序发生在 SELECT 之后。WHERE 里不行,下面「常见报错」会看到真实报错。
去重:DISTINCT 作用在整行结果上
8 行里城市有重复,先数清楚:
SELECT COUNT(*) AS 行数, COUNT(city) AS 有城市, COUNT(DISTINCT city) AS 不同城市
FROM lesson11_basic;
+--------+-----------+--------------+
| 行数 | 有城市 | 不同城市 |
+--------+-----------+--------------+
| 8 | 8 | 3 |
+--------+-----------+--------------+
1 row in set (0.00 sec)
COUNT(*) 是 8 行,COUNT(city) 也是 8(这一列没有 NULL),COUNT(DISTINCT city) 是 3——去重之后只剩 3 个城市。再看每个城市几个人:
SELECT city AS 城市, COUNT(*) AS 人数 FROM lesson11_basic
GROUP BY city ORDER BY 人数 DESC, 城市;
+--------+--------+
| 城市 | 人数 |
+--------+--------+
| 上海 | 3 |
| 北京 | 3 |
| 广州 | 2 |
+--------+--------+
3 rows in set (0.00 sec)
上海 3 人、北京 3 人、广州 2 人(人数相同时按城市名排,所以上海在前)。8 行里只有 3 行是各城市的第一次出现,另外 5 行是重复的城市值。想直接列出不重复的城市:
SELECT DISTINCT city FROM lesson11_basic ORDER BY city;
+--------+
| city |
+--------+
| 上海 |
| 北京 |
| 广州 |
+--------+
3 rows in set (0.00 sec)
3 行。DISTINCT 去掉的是整行结果的重复,不是某一列。多写一列就能看出区别:
SELECT DISTINCT city, gender FROM lesson11_basic ORDER BY city, gender;
+--------+--------+
| city | gender |
+--------+--------+
| 上海 | 女 |
| 上海 | 男 |
| 北京 | 男 |
| 广州 | 女 |
| 广州 | 男 |
+--------+--------+
5 rows in set (0.00 sec)
变成 5 行:(上海,女)、(上海,男)、(北京,男)、(广州,女)、(广州,男) 各算一种(顺序按城市、性别排)。所以“城市有 3 个”和“城市+性别有 5 种组合”并不矛盾——DISTINCT 看的是你 SELECT 出来的那一整行。
一句话收口:SELECT DISTINCT 列 去掉结果里的重复行;COUNT(DISTINCT 列) 数“这一列有几个不同的值”;COUNT(*) 数行、从不去重。
第二步:第 2 页从第几行开始——LIMIT 的偏移量
第 3 节你用过 LIMIT n 取前几行。真实的分页要多一个数:跳过多少行。这一节用一张 12 行的表把这件事算清楚。
SHOW TABLES LIKE 'lesson11_dql';
Empty set (0.00 sec)
CREATE TABLE lesson11_dql (
id INT PRIMARY KEY COMMENT '工号',
name VARCHAR(20) NOT NULL COMMENT '姓名',
gender CHAR(1) NOT NULL COMMENT '性别',
age TINYINT UNSIGNED NOT NULL COMMENT '年龄',
city VARCHAR(20) COMMENT '城市',
salary DECIMAL(8,2) COMMENT '月薪',
hired DATE NOT NULL COMMENT '入职日期'
) COMMENT='本教程第四单元练习表:分页与五题案例';
Query OK, 0 rows affected (0.03 sec)
INSERT INTO lesson11_dql (id, name, gender, age, city, salary, hired) VALUES
(1, '张伟', '男', 23, '北京', 8800.00, '2021-03-01'),
(2, '李娜', '女', 21, '上海', 7600.00, '2022-07-15'),
(3, '王小明', '男', 25, '广州', 9200.00, '2020-05-20'),
(4, '赵敏', '女', 22, '深圳', 8100.00, '2023-01-09'),
(5, '陈晨', '男', 24, '杭州', 8700.00, '2022-11-03'),
(6, '孙悦', '女', 28, '上海', 10300.00, '2019-08-12'),
(7, '周舟', '男', 23, '北京', 8500.00, '2021-09-27'),
(8, '吴迪', '男', 31, '成都', 12000.00, '2018-04-16'),
(9, '郑小雨', '女', 26, '广州', 9800.00, '2020-12-01'),
(10, '冯磊', '男', 35, '深圳', 13400.00, '2017-06-05'),
(11, '蒋文博', '男', 29, '杭州', 11200.00, '2019-02-18'),
(12, '沈静', '女', 20, '上海', 6900.00, '2023-06-30');
Query OK, 12 rows affected (0.01 sec)
Records: 12 Duplicates: 0 Warnings: 0
12 行,有男有女、年龄 20 到 35、六个城市,姓名两字三字都有。表里已经有这 12 行时再跑 INSERT 会得到 1062,原数据不受影响。
先确认行数,再取前 5 行:
SELECT COUNT(*) AS 总行数 FROM lesson11_dql;
+-----------+
| 总行数 |
+-----------+
| 12 |
+-----------+
1 row in set (0.00 sec)
SELECT id, name, age FROM lesson11_dql ORDER BY age, id LIMIT 5;
+----+--------+-----+
| id | name | age |
+----+--------+-----+
| 12 | 沈静 | 20 |
| 2 | 李娜 | 21 |
| 4 | 赵敏 | 22 |
| 1 | 张伟 | 23 |
| 7 | 周舟 | 23 |
+----+--------+-----+
5 rows in set (0.00 sec)
LIMIT 5 就是“只要前 5 行”。分页要的是第 2 页、第 3 页,写法是 LIMIT 偏移量, 条数:
SELECT id, name, age FROM lesson11_dql ORDER BY age, id LIMIT 5, 5;
+----+-----------+-----+
| id | name | age |
+----+-----------+-----+
| 5 | 陈晨 | 24 |
| 3 | 王小明 | 25 |
| 9 | 郑小雨 | 26 |
| 6 | 孙悦 | 28 |
| 11 | 蒋文博 | 29 |
+----+-----------+-----+
5 rows in set (0.00 sec)
LIMIT 5, 5 的意思是“跳过前 5 行,再取 5 行”,也就是每页 5 条时的第 2 页。第 3 页同理:
SELECT id, name, age FROM lesson11_dql ORDER BY age, id LIMIT 10, 5;
+----+--------+-----+
| id | name | age |
+----+--------+-----+
| 8 | 吴迪 | 31 |
| 10 | 冯磊 | 35 |
+----+--------+-----+
2 rows in set (0.00 sec)
只有 2 行——12 行按每页 5 条分,第 3 页就剩 2 条。起始索引不用背,算一次就明白:
SELECT (2 - 1) * 5 AS 第2页起始索引, (3 - 1) * 5 AS 第3页起始索引;
+---------------------+---------------------+
| 第2页起始索引 | 第3页起始索引 |
+---------------------+---------------------+
| 5 | 10 |
+---------------------+---------------------+
1 row in set (0.00 sec)
公式是 起始索引 = (页码 - 1) × 每页条数。所以“第 4 页、每页 5 条”要写 LIMIT 15, 5,可这一页已经没有数据了:
SELECT id, name, age FROM lesson11_dql ORDER BY age, id LIMIT 15, 5;
Empty set (0.00 sec)
总页数也得算出来,页面上的“下一页”才知道什么时候该消失:
SELECT COUNT(*) AS 总行数, CEIL(COUNT(*) / 5) AS 每页5条的总页数 FROM lesson11_dql;
+-----------+------------------------+
| 总行数 | 每页5条的总页数 |
+-----------+------------------------+
| 12 | 3 |
+-----------+------------------------+
1 row in set (0.00 sec)
12 / 5 = 2.4,向上取整是 3 页。CEIL 是向上取整函数,少了它会得到 2,最后一页的 2 行就永远看不到。
最后一条,也是最容易出错的地方:分页必须有确定的排序。12 行里 23 岁的有两个人(张伟、周舟),只按年龄排,这两行谁在前 MySQL 不保证:
SELECT id, name, age FROM lesson11_dql ORDER BY age LIMIT 5;
+----+--------+-----+
| id | name | age |
+----+--------+-----+
| 12 | 沈静 | 20 |
| 2 | 李娜 | 21 |
| 4 | 赵敏 | 22 |
| 1 | 张伟 | 23 |
| 7 | 周舟 | 23 |
+----+--------+-----+
5 rows in set (0.00 sec)
这次看起来是按主键排的,但那是“碰巧”,不是承诺。翻到第 2 页时,同一岁的两行可能换位置,于是第 1 页和第 2 页出现重复、或者漏掉一行。所以分页的 ORDER BY 一定要写到能唯一确定顺序,比如 ORDER BY age, id——上面的几条查询都是这么写的。
第三步:五道题,把 DQL 用一遍
这五道题对应讲义 2.6.8 的五种题型(条件、定长 LIKE、分组、排序、分页)。题干和数据都是本教程自己的,表还是上面那张 lesson11_dql。每题先自己想,再展开答案。
第 1 题:年龄正好是 20、21、22、23 岁的人有哪些?按年龄、工号排序。
提示 提示
四个“或”的关系,用 IN (…) 比写四遍 OR 清楚。
参考答案 答案
SELECT id, name, age FROM lesson11_dql
WHERE age IN (20, 21, 22, 23)
ORDER BY age, id;
+----+--------+-----+
| id | name | age |
+----+--------+-----+
| 12 | 沈静 | 20 |
| 2 | 李娜 | 21 |
| 4 | 赵敏 | 22 |
| 1 | 张伟 | 23 |
| 7 | 周舟 | 23 |
+----+--------+-----+
5 rows in set (0.00 sec)
5 行。IN 后面是候选值列表,等价于 age = 20 OR age = 21 OR ...,但短得多、也不容易漏括号。
第 2 题:男性、年龄在 20 到 40 岁之间、姓名恰好三个字的人是谁?
提示 提示
“之间”用 BETWEEN … AND …(含两端);“恰好三个字”用 LIKE,_ 匹配任意一个字符,所以三个下划线就是三个字。
参考答案 答案
SELECT id, name, gender, age FROM lesson11_dql
WHERE gender = '男'
AND age BETWEEN 20 AND 40
AND name LIKE '___';
+----+-----------+--------+-----+
| id | name | gender | age |
+----+-----------+--------+-----+
| 3 | 王小明 | 男 | 25 |
| 11 | 蒋文博 | 男 | 29 |
+----+-----------+--------+-----+
2 rows in set (0.00 sec)
2 行:王小明、蒋文博。三个条件同时成立才算,所以 张伟(两个字)和 沈静(女)都被排除。LIKE '___' 是三个下划线,别写成 %——% 是“任意多个字符”,LIKE '%' 会匹配所有名字。想数“三个字的有几个人”就写 COUNT(*) 配同样的条件。
第 3 题:60 岁以下的人里,男、女各有多少?
提示 提示
按性别分组,组内数行。
参考答案 答案
SELECT gender AS 性别, COUNT(*) AS 人数
FROM lesson11_dql
WHERE age < 60
GROUP BY gender
ORDER BY 性别;
+--------+--------+
| 性别 | 人数 |
+--------+--------+
| 女 | 5 |
| 男 | 7 |
+--------+--------+
2 rows in set (0.00 sec)
男 7 人、女 5 人,合计 12。执行顺序是先 WHERE 筛行、再 GROUP BY 分组、最后 SELECT 数每组的行数——所以 WHERE age < 60 是在分组之前生效的。
第 4 题:30 岁及以下的人,按年龄从小到大;年龄相同的,入职晚的排前面。
提示 提示
ORDER BY 可以写多个列,各自带方向:第一列相同时才看第二列。
参考答案 答案
SELECT id, name, age, hired FROM lesson11_dql
WHERE age <= 30
ORDER BY age ASC, hired DESC;
+----+-----------+-----+------------+
| id | name | age | hired |
+----+-----------+-----+------------+
| 12 | 沈静 | 20 | 2023-06-30 |
| 2 | 李娜 | 21 | 2022-07-15 |
| 4 | 赵敏 | 22 | 2023-01-09 |
| 7 | 周舟 | 23 | 2021-09-27 |
| 1 | 张伟 | 23 | 2021-03-01 |
| 5 | 陈晨 | 24 | 2022-11-03 |
| 3 | 王小明 | 25 | 2020-05-20 |
| 9 | 郑小雨 | 26 | 2020-12-01 |
| 6 | 孙悦 | 28 | 2019-08-12 |
| 11 | 蒋文博 | 29 | 2019-02-18 |
+----+-----------+-----+------------+
10 rows in set (0.00 sec)
10 行。23 岁的两个人里,周舟(2021-09-27)排在 张伟(2021-03-01)前面,因为第二排序键是 hired DESC。多列排序的方向是逐列写的,不会“继承”前一列。
第 5 题:男性、年龄 20 到 40 岁,按年龄从小到大、入职早的排前面,只取前 5 条。
提示 提示
先想清楚排序再截断——LIMIT 永远作用在排好序的结果上。
参考答案 答案
SELECT id, name, age, hired FROM lesson11_dql
WHERE gender = '男' AND age BETWEEN 20 AND 40
ORDER BY age ASC, hired ASC
LIMIT 5;
+----+-----------+-----+------------+
| id | name | age | hired |
+----+-----------+-----+------------+
| 1 | 张伟 | 23 | 2021-03-01 |
| 7 | 周舟 | 23 | 2021-09-27 |
| 5 | 陈晨 | 24 | 2022-11-03 |
| 3 | 王小明 | 25 | 2020-05-20 |
| 11 | 蒋文博 | 29 | 2019-02-18 |
+----+-----------+-----+------------+
5 rows in set (0.00 sec)
5 行。把 LIMIT 5 去掉会看到符合条件的 7 个人;加上 LIMIT 只留前 5 个。注意排序键里带了 hired:只写 ORDER BY age 时,23 岁的两个人谁进前 5 是不确定的。
常见报错怎么办
| 看到 | 意思 | 怎么办 |
|---|---|---|
1064 ... near 'LIMIT' | LIMIT 的写法不完整或有多余符号 | 只写 LIMIT 条数 或 LIMIT 偏移量, 条数,两个数都是非负整数 |
1140 ... incompatible with sql_mode=only_full_group_by | 没分组却把普通列和聚合函数混在一行 | 要么加 GROUP BY,要么把普通列套进聚合函数 |
1054 Unknown column | 列名写错,或 ORDER BY 用了不存在的别名 | 用 DESC 核对列名;别名可用在 GROUP BY/ORDER BY/HAVING,不能用在 WHERE |
1054 ... in 'where clause' | 把 SELECT 里起的别名写进了 WHERE | WHERE 比 SELECT 先执行,条件里写原始列名,别名只留给 GROUP BY/ORDER BY/HAVING |
1064 ... near '' | LIMIT 只给了偏移量、没给条数(LIMIT 2,) | 写 LIMIT 偏移量, 条数,两个数都不能省 |
说明 刻意写错的例子:LIMIT 后面多一个逗号
SELECT id FROM lesson11_dql LIMIT 2,;
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 '' at line 1
LIMIT 2, 只给了偏移量、没给条数,1064 直接拒绝。分页要写 LIMIT 2, 5 这样的两个数。
说明 刻意写错的例子:把普通列和 COUNT(*) 混在一行
SELECT id, COUNT(*) FROM lesson11_dql;
ERROR 1140 (42000): In aggregated query without GROUP BY, expression #1 of SELECT list contains nonaggregated column 'itcast.lesson11_dql.id'; this is incompatible with sql_mode=only_full_group_by
id 是普通列,COUNT(*) 是聚合值,两者放一行就变成“这一行的 id 代表哪一条记录?”——ONLY_FULL_GROUP_BY(本机 sql_mode 里就有)直接拒绝。要按性别数人,就 GROUP BY gender,SELECT 里只放分组列和聚合值。
说明 刻意写错的例子:在 WHERE 里用别名
SELECT name AS 姓名 FROM lesson11_basic WHERE 姓名 = '张伟';
ERROR 1054 (42S22): Unknown column '姓名' in 'where clause'
别名 姓名 是 SELECT 阶段才产生的名字,WHERE 在它之前执行,那时还不存在,所以是 1054。条件里写 WHERE name = '张伟',别名留给 ORDER BY。
练习
**1. 用别名把 name、salary 显示成「姓名」「月薪」,按「月薪」从高到低排;第一行是谁?**
提示 提示
别名用 AS,排序里直接写别名——ORDER BY 在 SELECT 之后执行,认得它。
参考答案 答案
SELECT name AS 姓名, salary AS 月薪 FROM lesson11_basic ORDER BY 月薪 DESC, id;
+--------+----------+
| 姓名 | 月薪 |
+--------+----------+
| 吴迪 | 12000.00 |
| 孙悦 | 10300.00 |
| 王强 | 9200.00 |
| 张伟 | 8800.00 |
| 陈晨 | 8700.00 |
| 周舟 | 8500.00 |
| 赵敏 | 8100.00 |
| 李娜 | 7600.00 |
+--------+----------+
8 rows in set (0.00 sec)
第一行是 吴迪,12000.00。ORDER BY 月薪 DESC 用的是别名;后面补 id 是为了让并列的薪水也有确定顺序。
说明 常见错
把条件写成 WHERE 月薪 > 9000:别名在 WHERE 阶段还不存在,会得到 1054 ... in 'where clause',要写原始列名 salary。
2. 用一条查询同时说出:一共几行、城市列有几个不同值、性别列有几个不同值。
提示 提示
数不同值用 COUNT(DISTINCT 列);数行还是 COUNT(*)。
参考答案 答案
SELECT COUNT(*) AS 行数, COUNT(DISTINCT city) AS 不同城市, COUNT(DISTINCT gender) AS 不同性别
FROM lesson11_basic;
+--------+--------------+--------------+
| 行数 | 不同城市 | 不同性别 |
+--------+--------------+--------------+
| 8 | 3 | 2 |
+--------+--------------+--------------+
1 row in set (0.00 sec)
8 行、3 个城市、2 种性别。COUNT(DISTINCT …) 只对括号里那一列去重,和 SELECT DISTINCT 去掉整行重复不是一回事。
说明 常见错
写成 COUNT(city):那只是“这一列有几个非空值”,8 行都有城市就是 8,完全没有去重效果。
**3. SELECT DISTINCT city, gender FROM lesson11_basic; 为什么是 5 行,而不是 3 行?**
提示 提示
DISTINCT 看的是 SELECT 出来的那一整行,不是单独某一列。
参考答案 答案
SELECT DISTINCT city, gender FROM lesson11_basic ORDER BY city, gender;
+--------+--------+
| city | gender |
+--------+--------+
| 上海 | 女 |
| 上海 | 男 |
| 北京 | 男 |
| 广州 | 女 |
| 广州 | 男 |
+--------+--------+
5 rows in set (0.00 sec)
3 个城市 × 2 种性别最多 6 种组合,实际出现 5 种:(上海,女)、(上海,男)、(北京,男)、(广州,女)、(广州,男)。想去掉的是“两列都相同”的重复行,所以比只数城市多。
说明 常见错
以为 DISTINCT city, gender 等于“先对 city 去重、再各自配一个 gender”——那样会得到 3 行且每行只代表一个随意挑出的性别,和 SQL 的语义不符。
4. 每页 4 条,取第 2 页(按年龄、工号排序)。
提示 提示
先把起始索引算出来,再写 LIMIT 起始索引, 条数。
参考答案 答案
SELECT id, name, age FROM lesson11_dql ORDER BY age, id LIMIT 4, 4;
+----+-----------+-----+
| id | name | age |
+----+-----------+-----+
| 7 | 周舟 | 23 |
| 5 | 陈晨 | 24 |
| 3 | 王小明 | 25 |
| 9 | 郑小雨 | 26 |
+----+-----------+-----+
4 rows in set (0.00 sec)
起始索引是 (2 - 1) × 4 = 4,所以 LIMIT 4, 4。第 1 页是 LIMIT 0, 4,两页合起来正好前 8 行。
说明 常见错
写成 LIMIT 2, 4:那是“跳过 2 行”,得到的是从第 3 行开始的 4 条,和第 2 页差两行。
5. 每页 4 条时一共几页?用一条查询算出来。
提示 提示
COUNT(*) 除以每页条数,向上取整用 CEIL。
参考答案 答案
SELECT COUNT(*) AS 总行数, CEIL(COUNT(*) / 4) AS 总页数 FROM lesson11_dql;
+-----------+-----------+
| 总行数 | 总页数 |
+-----------+-----------+
| 12 | 3 |
+-----------+-----------+
1 row in set (0.00 sec)
12 行、3 页,正好整除。如果哪天变成 13 行,CEIL 会给出 4 页——这就是它比 ROUND 靠谱的地方。
说明 常见错
用整数除法 COUNT(*) DIV 4:12 行时看着对,13 行时会算成 3 页,最后一页的 1 行永远翻不到。
6. 按城市统计人数,只要 2 人及以上的城市,按人数从多到少、城市名排序。
提示 提示
分组后筛“组”用 HAVING,不能用 WHERE。
参考答案 答案
SELECT city AS 城市, COUNT(*) AS 人数
FROM lesson11_dql
GROUP BY city
HAVING COUNT(*) >= 2
ORDER BY 人数 DESC, 城市;
+--------+--------+
| 城市 | 人数 |
+--------+--------+
| 上海 | 3 |
| 北京 | 2 |
| 广州 | 2 |
| 杭州 | 2 |
| 深圳 | 2 |
+--------+--------+
5 rows in set (0.00 sec)
5 个城市入选,上海 3 人排第一;成都只有 1 人,被 HAVING 筛掉。WHERE 筛行、HAVING 筛组,这一条是两者的分界线。
说明 常见错
把 HAVING COUNT(*) >= 2 写成 WHERE COUNT(*) >= 2:WHERE 在分组之前执行,那时还没有“每个城市几个人”这个数,会得到 1111 Invalid use of group function。
7. 姓名恰好三个字的人有几个?
提示 提示
LIKE '___' 配 COUNT(*)。
参考答案 答案
SELECT COUNT(*) AS 三字姓名人数 FROM lesson11_dql WHERE name LIKE '___';
+--------------------+
| 三字姓名人数 |
+--------------------+
| 3 |
+--------------------+
1 row in set (0.00 sec)
3 个人:王小明、郑小雨、蒋文博。_ 只匹配一个字符、不匹配“零个”,所以两字姓名不在里面。
说明 常见错
写成 LIKE '__%':下划线只要求前两个字,后面的 % 什么都能接,于是四个字的名字也会被算进来。定长用 _ 数够个数,不要混 %。
本单元验收
- 能用
AS给列和表达式起别名,并说出为什么别名能进ORDER BY却进不了WHERE。 - 能用
SELECT DISTINCT、COUNT(DISTINCT 列)、COUNT(*)三种写法分别回答“有哪些不同值”“几个不同值”“几行”,并解释DISTINCT city, gender为什么比DISTINCT city多。 - 能写出
LIMIT 偏移量, 条数,并解释(页码 - 1) × 每页条数这个起点公式。 - 能用
CEIL(COUNT(*) / 每页条数)算出总页数,并说出为什么不能用整数除法。 - 能说出分页为什么必须有一个能唯一确定顺序的
ORDER BY。 - 五道题能独立写出:
IN列表、BETWEEN+ 三下划线LIKE、按性别分组计数、多列排序、排序后LIMIT。 - 能说出
WHERE与HAVING在分组查询里的分工,并解释1140为什么会出现。
做到这里
做到这里今天可以停,第四单元也到此结束。你现在能:说清库/表/行/列和四类 SQL;按值选类型;安全地改表结构;给列起别名、用 DISTINCT 去掉重复行并数清“几个不同值”;把分页的起始行算准;用五道题把条件、LIKE、分组、排序、分页各走一遍。
下一单元的方向是账号与权限:你这条连接到底是谁、能做什么、哪些语句只能认识不能执行。
来源与边界
本节对应 BV1Kr4y1i7ru 平台 part 15(原标题「15. 基础-SQL-DQL-基础查询」)、part 20(「20. 基础-SQL-DQL-分页查询」)、part 21(「21. 基础-SQL-DQL-案例练习」),在 P01–P50 范围内;主题与同版讲义《MySQL-基础篇》2.6.1 基础查询与 2.6.2 别名/去重(p38–41)、2.6.7 分页查询(p47–48)、2.6.8 案例练习(p48–49)对应。其中 2.6.7 的分节号是按同章顺序推定的(P21 的画面证据显示 2.6.8 在 p48),标注为推断,未当已核实引用。
无逐字稿,本节未声称听过讲解。表名、lesson11_basic 的 8 行与 lesson11_dql 的 12 行数据、五道题的题干与全部查询均为本教程原创(题型对应讲义 2.6.8 的五类,未复制讲义题干与课堂 SQL);LIMIT 越界、总页数、并列年龄的排序不确定性、1140 报错都是在作者自己的隔离 MySQL 8.0.46 实例上实跑后写的。