板凳-------Mysql cookbook学习 (八--2)
3.10 在用户程序中使用null作为比较参数
mysql> select * from taxpayer;
+---------+--------+
| name | id |
+---------+--------+
| bernina | 198-48 |
| bertha | NULL |
| ben | NULL |
| NULL | 475-83 |
| baidu | 111+55 |
+---------+--------+
5 rows in set (0.00 sec)
3.11 结果集排序
mysql> select * from mail where size > 100000 order by size;
+---------------------+---------+---------+---------+---------+---------+
| t | srcuser | srchost | dstuser | dsthost | size |
+---------------------+---------+---------+---------+---------+---------+
| 2006-05-12 12:48:13 | tricia | mars | gene | venus | 194925 |
| 2006-05-15 10:25:52 | gene | mars | tricia | saturn | 998532 |
| 2006-05-14 17:03:01 | tricia | saturn | phil | venus | 2394482 |
+---------------------+---------+---------+---------+---------+---------+
3 rows in set (0.00 sec)
mysql> select * from mail where dstuser = 'tricia'-> order by srchost, srcuser;
+---------------------+---------+---------+---------+---------+--------+
| t | srcuser | srchost | dstuser | dsthost | size |
+---------------------+---------+---------+---------+---------+--------+
| 2006-05-15 10:25:52 | gene | mars | tricia | saturn | 998532 |
| 2006-05-14 11:52:17 | phil | mars | tricia | saturn | 5781 |
| 2006-05-17 12:49:23 | phil | mars | tricia | saturn | 873 |
| 2006-05-11 10:15:08 | barb | saturn | tricia | mars | 58274 |
| 2006-05-13 13:59:18 | barb | saturn | tricia | venus | 271 |
+---------------------+---------+---------+---------+---------+--------+
5 rows in set (0.00 sec)
mysql> select * from mail where size > 50000 order by size desc;
+---------------------+---------+---------+---------+---------+---------+
| t | srcuser | srchost | dstuser | dsthost | size |
+---------------------+---------+---------+---------+---------+---------+
| 2006-05-14 17:03:01 | tricia | saturn | phil | venus | 2394482 |
| 2006-05-15 10:25:52 | gene | mars | tricia | saturn | 998532 |
| 2006-05-12 12:48:13 | tricia | mars | gene | venus | 194925 |
| 2006-05-14 14:42:21 | barb | venus | barb | venus | 98151 |
| 2006-05-11 10:15:08 | barb | saturn | tricia | mars | 58274 |
+---------------------+---------+---------+---------+---------+---------+
5 rows in set (0.00 sec)
3.12 使用视图来简化查询
mysql> select-> date_format(t, '%M %e %Y') as date_sent,-> concat(srcuser, '@', srchost) as sender,-> concat(dstuser, '@', dsthost) as recipient,-> size from mail;
+-------------+---------------+---------------+---------+
| date_sent | sender | recipient | size |
+-------------+---------------+---------------+---------+
| May 11 2006 | barb@saturn | tricia@mars | 58274 |
| May 12 2006 | tricia@mars | gene@venus | 194925 |
| May 12 2006 | phil@mars | phil@saturn | 1048 |
| May 13 2006 | barb@saturn | tricia@venus | 271 |
| May 14 2006 | gene@venus | barb@mars | 2291 |
| May 14 2006 | phil@mars | tricia@saturn | 5781 |
| May 14 2006 | barb@venus | barb@venus | 98151 |
| May 14 2006 | tricia@saturn | phil@venus | 2394482 |
| May 15 2006 | gene@mars | gene@saturn | 3824 |
| May 15 2006 | phil@venus | phil@venus | 978 |
| May 15 2006 | gene@mars | tricia@saturn | 998532 |
| May 15 2006 | gene@saturn | gene@mars | 3856 |
| May 16 2006 | gene@venus | barb@mars | 613 |
| May 16 2006 | phil@venus | barb@venus | 10294 |
| May 17 2006 | phil@mars | tricia@saturn | 873 |
| May 19 2006 | gene@saturn | gene@venus | 23992 |
+-------------+---------------+---------------+---------+
16 rows in set (0.00 sec)
mysql> select * from mail_view;
+--------------+---------------+---------------+---------+
| date_sent | sender | recipient | size |
+--------------+---------------+---------------+---------+
| May 11, 2006 | barb@saturn | tricia@mars | 58274 |
| May 12, 2006 | tricia@mars | gene@venus | 194925 |
| May 12, 2006 | phil@mars | phil@saturn | 1048 |
| May 13, 2006 | barb@saturn | tricia@venus | 271 |
| May 14, 2006 | gene@venus | barb@mars | 2291 |
| May 14, 2006 | phil@mars | tricia@saturn | 5781 |
| May 14, 2006 | barb@venus | barb@venus | 98151 |
| May 14, 2006 | tricia@saturn | phil@venus | 2394482 |
| May 15, 2006 | gene@mars | gene@saturn | 3824 |
| May 15, 2006 | phil@venus | phil@venus | 978 |
| May 15, 2006 | gene@mars | tricia@saturn | 998532 |
| May 15, 2006 | gene@saturn | gene@mars | 3856 |
| May 16, 2006 | gene@venus | barb@mars | 613 |
| May 16, 2006 | phil@venus | barb@venus | 10294 |
| May 17, 2006 | phil@mars | tricia@saturn | 873 |
| May 19, 2006 | gene@saturn | gene@venus | 23992 |
+--------------+---------------+---------------+---------+
16 rows in set (0.00 sec)
mysql> select date_sent, sender, size from mail_view-> where size > 100000 order by size;
+--------------+---------------+---------+
| date_sent | sender | size |
+--------------+---------------+---------+
| May 12, 2006 | tricia@mars | 194925 |
| May 15, 2006 | gene@mars | 998532 |
| May 14, 2006 | tricia@saturn | 2394482 |
+--------------+---------------+---------+
3 rows in set (0.00 sec)
3.13 多表查询
mysql> select * from profile;
+----+---------+------------+-------+-----------------------+------+
| id | name | birth | color | foods | cats |
+----+---------+------------+-------+-----------------------+------+
| 1 | Fred | 1970-04-13 | black | lutefisk,fadge,pizza | 1 |
| 2 | Mort | 1969-09-30 | white | burrito,curry,eggroll | 3 |
| 3 | Brit | 1957-12-01 | red | burrito,curry,pizza | 1 |
| 4 | Carl | 1973-11-02 | red | eggroll,pizza | 4 |
| 5 | Sean | 1963-07-04 | blue | burrito,curry | 5 |
| 6 | Alan | 1965-02-14 | red | curry,fadge | 1 |
| 7 | Mara | 1968-09-17 | green | lutefisk,fadge | 1 |
| 8 | Shepard | 1975-09-02 | black | curry,pizza | 2 |
| 9 | Dick | 1952-08-20 | green | lutefisk,fadge | 0 |
| 10 | Tony | 1960-05-01 | white | burrito,pizza | 0 |
| 11 | Alison | 1973-01-12 | blue | eggroll | 4 |
| 12 | De'Mont | 1973-01-12 | NULL | eggroll | 4 |
| 13 | De'Mont | 1973-01-12 | NULL | eggroll | 4 |
| 14 | De'Mont | 1973-01-12 | NULL | eggroll | 4 |
| 15 | De'Mont | 1973-01-12 | NULL | eggroll | 4 |
| 16 | De'Mont | 1973-01-12 | NULL | eggroll | 4 |
| 17 | Amabel | NULL | NULL | NULL | NULL |
| 18 | De'Mont | 1980-12-12 | NULL | eggroll | 8 |
| 19 | Juan | NULL | NULL | NULL | NULL |
+----+---------+------------+-------+-----------------------+------+
19 rows in set (0.00 sec)
mysql> select * from profile_contact order by profile_id, service;
+------------+---------+---------------+
| profile_id | service | contact_name |
+------------+---------+---------------+
| 1 | AIM | user1-aimid |
| 1 | MSN | user1-msnid |
| 2 | AIM | user2-aimid |
| 2 | MSN | user2-msnid |
| 2 | Yahoo | user2-yahooid |
| 4 | Yahoo | user4-yahooid |
+------------+---------+---------------+
6 rows in set (0.00 sec)
mysql> select id, name, service, contact_name-> from profile inner join profile_contact on id = profile_id;
+----+------+---------+---------------+
| id | name | service | contact_name |
+----+------+---------+---------------+
| 1 | Fred | AIM | user1-aimid |
| 1 | Fred | MSN | user1-msnid |
| 2 | Mort | AIM | user2-aimid |
| 2 | Mort | MSN | user2-msnid |
| 2 | Mort | Yahoo | user2-yahooid |
| 4 | Carl | Yahoo | user4-yahooid |
+----+------+---------+---------------+
6 rows in set (0.00 sec)
mysql> select * from profile_contact-> where profile_id = (select id from profile where name = 'Mort');
+------------+---------+---------------+
| profile_id | service | contact_name |
+------------+---------+---------------+
| 2 | AIM | user2-aimid |
| 2 | MSN | user2-msnid |
| 2 | Yahoo | user2-yahooid |
+------------+---------+---------------+
3 rows in set (0.00 sec)
3.14 从查询结果集头或尾取出部分行
mysql> select * from profile limit 1;
+----+------+------------+-------+----------------------+------+
| id | name | birth | color | foods | cats |
+----+------+------------+-------+----------------------+------+
| 1 | Fred | 1970-04-13 | black | lutefisk,fadge,pizza | 1 |
+----+------+------------+-------+----------------------+------+
1 row in set (0.00 sec)
mysql> select * from profile limit 10;
+----+---------+------------+-------+-----------------------+------+
| id | name | birth | color | foods | cats |
+----+---------+------------+-------+-----------------------+------+
| 1 | Fred | 1970-04-13 | black | lutefisk,fadge,pizza | 1 |
| 2 | Mort | 1969-09-30 | white | burrito,curry,eggroll | 3 |
| 3 | Brit | 1957-12-01 | red | burrito,curry,pizza | 1 |
| 4 | Carl | 1973-11-02 | red | eggroll,pizza | 4 |
| 5 | Sean | 1963-07-04 | blue | burrito,curry | 5 |
| 6 | Alan | 1965-02-14 | red | curry,fadge | 1 |
| 7 | Mara | 1968-09-17 | green | lutefisk,fadge | 1 |
| 8 | Shepard | 1975-09-02 | black | curry,pizza | 2 |
| 9 | Dick | 1952-08-20 | green | lutefisk,fadge | 0 |
| 10 | Tony | 1960-05-01 | white | burrito,pizza | 0 |
+----+---------+------------+-------+-----------------------+------+
10 rows in set (0.00 sec)
首先使用order by 对查询结果进行排序,再使用limit从查询结果中取出最大或最小特征的行。
mysql> select * from profile order by birth limit 1;
+----+--------+-------+-------+-------+------+
| id | name | birth | color | foods | cats |
+----+--------+-------+-------+-------+------+
| 17 | Amabel | NULL | NULL | NULL | NULL |
+----+--------+-------+-------+-------+------+
1 row in set (0.00 sec)mysql> select * from profile order by birth desc limit 1;
+----+---------+------------+-------+---------+------+
| id | name | birth | color | foods | cats |
+----+---------+------------+-------+---------+------+
| 18 | De'Mont | 1980-12-12 | NULL | eggroll | 8 |
+----+---------+------------+-------+---------+------+
1 row in set (0.00 sec)mysql> select name, date_format(birth, '%m-%d') as birthday-> from profile order by birthday limit 1;
+------+----------+
| name | birthday |
+------+----------+
| Juan | NULL |
+------+----------+
1 row in set (0.00 sec)
3.15 在结果集中间选取部分行 185/951 Thursday, May 29, 2025
Select from:从哪些表中筛选
on:关联多表查询时,去除笛卡尔积
where:从表中筛选的条件
group by:分组依据
having:在统计结果中再次筛选
order by:排序
limit:分页
以下是对 SQL 查询执行逻辑的专业描述,并附 MySQL 可执行示例:
一、FROM 阶段(表关联处理)
1. 笛卡尔积生成(CROSS JOIN)
原理:生成所有表行的组合。若表 A 有m行,表 B 有n行,则结果包含m×n行。
示例表结构:
sql
CREATE TABLE A (id INT PRIMARY KEY, name VARCHAR(10));
CREATE TABLE B (id INT PRIMARY KEY, value INT);
INSERT INTO A VALUES (1,'Alice'), (2,'Bob');
INSERT INTO B VALUES (1,100), (2,200);执行逻辑:
sql
SELECT * FROM A CROSS JOIN B; -- 生成4行结果(2×2)结果:
plaintext
| id | name | id | value |
|----|-------|----|-------|
| 1 | Alice | 1 | 100 |
| 1 | Alice | 2 | 200 |
| 2 | Bob | 1 | 100 |
| 2 | Bob | 2 | 200 |2. ON 条件筛选
原理:通过ON子句过滤无效行。
示例:
sql
SELECT * FROM A JOIN B ON A.id = B.id; -- 仅保留id匹配的行结果:
plaintext
| id | name | id | value |
|----|-------|----|-------|
| 1 | Alice | 1 | 100 |
| 2 | Bob | 2 | 200 |3. 外部行添加(外连接)
左连接示例:
sql
SELECT * FROM A LEFT JOIN B ON A.id = B.id; -- 保留A的未匹配行结果(若 B 中无匹配行,B 的字段为NULL):
plaintext
| id | name | id | value |
|----|-------|----|-------|
| 1 | Alice | 1 | 100 |
| 2 | Bob | 2 | 200 |
| 3 | Carol | NULL| NULL | -- 假设A新增行(3,'Carol')二、WHERE 阶段(行过滤)
原理:基于FROM结果筛选行。
示例:
sql
SELECT * FROM A JOIN B ON A.id = B.id WHERE B.value > 150;结果:
plaintext
| id | name | id | value |
|----|------|----|-------|
| 2 | Bob | 2 | 200 |三、GROUP BY 阶段(分组操作)
原理:按指定列分组,非分组列需用聚合函数。
示例:
sql
SELECT A.name, SUM(B.value)
FROM A JOIN B ON A.id = B.id
GROUP BY A.name;结果:
plaintext
| name | SUM(B.value) |
|-------|-------------|
| Alice | 100 |
| Bob | 200 |四、HAVING 阶段(分组过滤)
原理:筛选符合条件的分组。
示例:
sql
SELECT A.name, SUM(B.value)
FROM A JOIN B ON A.id = B.id
GROUP BY A.name
HAVING SUM(B.value) > 150;结果:
plaintext
| name | SUM(B.value) |
|------|-------------|
| Bob | 200 |五、SELECT 阶段(字段提取)
原理:提取指定列或表达式。
示例:
sql
SELECT A.name AS user, B.value*1.1 AS adjusted_value
FROM A JOIN B ON A.id = B.id;结果:
plaintext
| user | adjusted_value |
|-------|----------------|
| Alice | 110.0 |
| Bob | 220.0 |六、DISTINCT 阶段(去重)
原理:移除完全重复的行。
示例:
sql
SELECT DISTINCT value FROM B;结果(若 B 中有重复值100,100,200):
plaintext
| value |
|-------|
| 100 |
| 200 |七、ORDER BY 阶段(排序)
原理:按指定列排序。
示例:
sql
SELECT * FROM B ORDER BY value DESC;结果:
plaintext
| id | value |
|----|-------|
| 2 | 200 |
| 1 | 100 |八、LIMIT 阶段(分页截取)
原理:限制返回行数。
示例:
sql
SELECT * FROM B LIMIT 1; -- 返回第1行结果:
plaintext
| id | value |
|----|-------|
| 1 | 100 |完整执行顺序示例
查询语句:
sql
SELECT A.name, SUM(B.value) AS total
FROM A
LEFT JOIN B ON A.id = B.id
WHERE A.id > 0
GROUP BY A.name
HAVING total > 50
ORDER BY total DESC
LIMIT 1;
执行步骤:
FROM+ON:左连接 A 和 B,保留 A 的未匹配行。
WHERE:过滤出 A.id>0 的行(若所有 A.id>0,则无影响)。
GROUP BY:按 A.name 分组。
HAVING:筛选总和 > 50 的分组。
SELECT:提取 A.name 和 SUM (B.value)。
ORDER BY:按总和降序排列。
LIMIT:取第 1 行。
结果(假设数据如前):
plaintext
| name | total |
|------|-------|
| Bob | 200 |
执行顺序总结
plaintext
FROM → ON → 外连接处理 → WHERE → GROUP BY → HAVING → SELECT → DISTINCT → ORDER BY → LIMIT
二、SELECT 语句的底层执行逻辑(以 MySQL 为例)
1. 执行顺序与阶段解析
SQL 语句的书写顺序(如 SELECT ... FROM ... WHERE ... GROUP BY ... HAVING ... ORDER BY ... LIMIT)与实际执行顺序不同。以下是按执行顺序排列的核心阶段(附实例说明):
执行阶段 说明 实例(MySQL 可执行)
1. FROM 阶段 - 解析表名,生成初始结果集(多张表时先计算笛卡尔积)
- 处理 JOIN 逻辑(内连接 / 外连接) sql<br>SELECT *<br>FROM orders o<br>LEFT JOIN users u ON o.user_id = u.id; -- 先执行 FROM + ON 条件<br>
2. ON 过滤 - 对 JOIN 生成的临时表应用 ON 条件(仅在 JOIN 时生效) 同上,ON o.user_id = u.id 在此阶段执行过滤。
3. 添加外部行 - 处理外连接(左 / 右 / 全连接),补充主表中不满足 ON 条件的行(值为 NULL) 左连接时,users 表中无匹配的 orders 行会保留,对应字段为 NULL。
4. WHERE 过滤 - 对 FROM 阶段生成的临时表应用 WHERE 条件(行级过滤) sql<br>SELECT *<br>FROM orders o<br>LEFT JOIN users u ON o.user_id = u.id<br>WHERE o.order_date > '2023-01-01'; -- WHERE 在此阶段执行<br>
5. GROUP BY 分组 - 按指定字段分组,生成分组后的临时表(行合并) sql<br>SELECT user_id, SUM(amount) AS total<br>FROM orders<br>WHERE order_date > '2023-01-01'<br>GROUP BY user_id; -- 分组在此阶段执行<br>
6. HAVING 过滤 - 对分组后的结果应用 HAVING 条件(分组级过滤) sql<br>SELECT user_id, SUM(amount) AS total<br>FROM orders<br>WHERE order_date > '2023-01-01'<br>GROUP BY user_id<br>HAVING total > 1000; -- HAVING 在此阶段执行<br>
7. SELECT 投影 - 计算 SELECT 中的表达式,提取目标字段(列选择与计算) sql<br>SELECT user_id, CONCAT('用户', user_id) AS user_label -- CONCAT 在此阶段计算<br>FROM users;<br>
8. DISTINCT 去重 - 过滤重复行,仅保留唯一记录 sql<br>SELECT DISTINCT user_id<br>FROM orders;<br>
9. ORDER BY 排序 - 对结果集按指定字段排序(内存排序或文件排序) sql<br>SELECT *<br>FROM orders<br>ORDER BY order_date DESC; -- 排序在此阶段执行<br>
10. LIMIT 分页 - 截取指定行数的结果(最后一步执行) sql<br>SELECT *<br>FROM orders<br>ORDER BY order_date DESC<br>LIMIT 10 OFFSET 20; -- LIMIT 在此阶段执行<br>
2. 关键逻辑说明
ON 与 WHERE 的区别:
ON 用于 表关联时的条件过滤,发生在 FROM 阶段,可访问未过滤的原始表数据(如外连接中主表的 NULL 值)。
WHERE 用于 对最终临时表的行级过滤,发生在 FROM 和 JOIN 之后,无法访问外连接中未匹配的 NULL 行(会被提前过滤)。
示例:
sql
-- 左连接中,ON 可保留主表 NULL 行,WHERE 会过滤 NULL 行
SELECT *
FROM orders o
LEFT JOIN users u ON o.user_id = u.id AND u.status = 'active'; -- ON 条件中包含过滤逻辑
-- 等价于:先左连接,再保留 u.status='active' 的匹配行,主表 NULL 行保留 SELECT *
FROM orders o
LEFT JOIN users u ON o.user_id = u.id
WHERE u.status = 'active'; -- WHERE 会过滤掉 u 为 NULL 的行(主表无匹配的记录被删除) 分组与聚合的逻辑:
GROUP BY 会将相同分组字段的行合并为一行,并通过聚合函数(如 SUM、COUNT)计算分组值。
HAVING 必须依赖 GROUP BY,用于过滤分组后的结果(例如 “筛选总金额超过 1000 的用户分组”)。
性能优化关键点:
尽早过滤:将过滤条件(如 WHERE)放在靠前的阶段(避免处理无效数据)。
避免 SELECT *:仅选择需要的字段,减少数据传输和计算量。
合理使用索引:在 JOIN、WHERE、ORDER BY 字段上创建索引,加速过滤和排序。
三、总结
SELECT 语句的底层逻辑是 「按阶段逐步处理数据,从表关联到最终结果集生成」,执行顺序与书写顺序不同。理解这一逻辑有助于优化 SQL 性能,避免逻辑错误(如误用 ON 和 WHERE)。实际开发中,可通过 MySQL 的 EXPLAIN 语句分析执行计划,验证优化效果。
mysql> select count(*) from profile;
+----------+
| count(*) |
+----------+
| 19 |
+----------+
1 row in set (0.02 sec)mysql> select sql_calc_found_rows * from profile order by name limit 5;
+----+--------+------------+-------+---------------------+------+
| id | name | birth | color | foods | cats |
+----+--------+------------+-------+---------------------+------+
| 6 | Alan | 1965-02-14 | red | curry,fadge | 1 |
| 11 | Alison | 1973-01-12 | blue | eggroll | 4 |
| 17 | Amabel | NULL | NULL | NULL | NULL |
| 3 | Brit | 1957-12-01 | red | burrito,curry,pizza | 1 |
| 4 | Carl | 1973-11-02 | red | eggroll,pizza | 4 |
+----+--------+------------+-------+---------------------+------+
5 rows in set, 1 warning (0.00 sec)mysql> select found_rows();
+--------------+
| found_rows() |
+--------------+
| 19 |
+--------------+
1 row in set, 1 warning (0.01 sec)3.16 选择合适的limit参数
mysql> select sql_calc_found_rows * from profile order by name limit 5;
+----+--------+------------+-------+---------------------+------+
| id | name | birth | color | foods | cats |
+----+--------+------------+-------+---------------------+------+
| 6 | Alan | 1965-02-14 | red | curry,fadge | 1 |
| 11 | Alison | 1973-01-12 | blue | eggroll | 4 |
| 17 | Amabel | NULL | NULL | NULL | NULL |
| 3 | Brit | 1957-12-01 | red | burrito,curry,pizza | 1 |
| 4 | Carl | 1973-11-02 | red | eggroll,pizza | 4 |
+----+--------+------------+-------+---------------------+------+
5 rows in set, 1 warning (0.00 sec)mysql> select found_rows();
+--------------+
| found_rows() |
+--------------+
| 19 |
+--------------+
1 row in set, 1 warning (0.01 sec)mysql> select name, wins from al_winner-> order by wins desc, name;
+----------------+------+
| name | wins |
+----------------+------+
| Mulder, Mark | 21 |
| Clemens, Roger | 20 |
| Moyer, Jamie | 20 |
| Garcia, Freddy | 18 |
| Hudson, Tim | 18 |
| Abbott, Paul | 17 |
| Mays, Joe | 17 |
| Mussina, Mike | 17 |
| Sabathia, C.C. | 17 |
| Zito, Barry | 17 |
| Buehrle, Mark | 16 |
| Milton, Eric | 15 |
| Pettitte, Andy | 15 |
| Radke, Brad | 15 |
| Sele, Aaron | 15 |
+----------------+------+
15 rows in set (0.02 sec)mysql> select name, wins from al_winner-> order by wins desc, name-> limit 5;
+----------------+------+
| name | wins |
+----------------+------+
| Mulder, Mark | 21 |
| Clemens, Roger | 20 |
| Moyer, Jamie | 20 |
| Garcia, Freddy | 18 |
| Hudson, Tim | 18 |
+----------------+------+
5 rows in set (0.00 sec)mysql> select name, wins from al_winner-> order by wins desc, name-> limit 3, 1;
+----------------+------+
| name | wins |
+----------------+------+
| Garcia, Freddy | 18 |
+----------------+------+
1 row in set (0.00 sec)mysql> select name, wins from al_winner-> where wins >= 18-> order by wins desc, name;
+----------------+------+
| name | wins |
+----------------+------+
| Mulder, Mark | 21 |
| Clemens, Roger | 20 |
| Moyer, Jamie | 20 |
| Garcia, Freddy | 18 |
| Hudson, Tim | 18 |
+----------------+------+
5 rows in set (0.00 sec)mysql> SELECT name, wins-> FROM al_winner-> WHERE wins >= (-> SELECT wins-> FROM al_winner-> ORDER BY wins DESC, name-> LIMIT 3, 1 -- 英文逗号+英文括号-> )-> ORDER BY wins DESC, name;
+----------------+------+
| name | wins |
+----------------+------+
| Mulder, Mark | 21 |
| Clemens, Roger | 20 |
| Moyer, Jamie | 20 |
| Garcia, Freddy | 18 |
| Hudson, Tim | 18 |
+----------------+------+
5 rows in set (0.00 sec)mysql> SELECT DISTINCT wins-> FROM al_winner-> ORDER BY wins DESC -- 仅保留 wins 列-> LIMIT 3, 1;
+------+
| wins |
+------+
| 17 |
+------+
1 row in set (0.00 sec)mysql> SELECT wins-> FROM (-> SELECT wins,-> DENSE_RANK() OVER (ORDER BY wins DESC) AS rnk-> FROM al_winner-> ) t-> WHERE rnk = 4 -- 直接获取第4名的 wins-> LIMIT 1;
+------+
| wins |
+------+
| 17 |
+------+
1 row in set (0.01 sec)
总结
使用 DISTINCT 时:ORDER BY 必须仅引用 SELECT 列表中的列。
窗口函数:处理排名问题更直观,推荐优先使用(如 RANK()、DENSE_RANK())。
性能提示:若表数据量大,添加 INDEX(wins) 可加速排序和去重。
mysql> select name, wins from al_winner-> where wins >= 17-> order by wins desc, name;
+----------------+------+
| name | wins |
+----------------+------+
| Mulder, Mark | 21 |
| Clemens, Roger | 20 |
| Moyer, Jamie | 20 |
| Garcia, Freddy | 18 |
| Hudson, Tim | 18 |
| Abbott, Paul | 17 |
| Mays, Joe | 17 |
| Mussina, Mike | 17 |
| Sabathia, C.C. | 17 |
| Zito, Barry | 17 |
+----------------+------+
10 rows in set (0.00 sec)
原查询的问题在于子查询的 ORDER BY name 与 DISTINCT wins 不兼容。通过移除 name 解决了这个问题,同时确保主查询逻辑不变。
若你的 MySQL 版本支持窗口函数(8.0+),强烈推荐使用方案 2,它更健壮且易于理解。
mysql> SELECT name, wins-> FROM al_winner-> WHERE wins >= (-> SELECT DISTINCT wins -- 仅选择 wins 列-> FROM al_winner-> ORDER BY wins DESC -- 仅按 wins 排序-> LIMIT 3, 1 -- 获取第4高的 wins 值-> )-> ORDER BY wins DESC, name; -- 主查询排序正常
+----------------+------+
| name | wins |
+----------------+------+
| Mulder, Mark | 21 |
| Clemens, Roger | 20 |
| Moyer, Jamie | 20 |
| Garcia, Freddy | 18 |
| Hudson, Tim | 18 |
| Abbott, Paul | 17 |
| Mays, Joe | 17 |
| Mussina, Mike | 17 |
| Sabathia, C.C. | 17 |
| Zito, Barry | 17 |
+----------------+------+
10 rows in set (0.00 sec)mysql> SELECT name, wins-> FROM al_winner-> WHERE wins >= (-> SELECT wins-> FROM (-> SELECT DISTINCT wins,-> DENSE_RANK() OVER (ORDER BY wins DESC) AS rnk -- 计算排名-> FROM al_winner-> ) t-> WHERE rnk = 4 -- 直接获取第4名的 wins-> );
+----------------+------+
| name | wins |
+----------------+------+
| Abbott, Paul | 17 |
| Clemens, Roger | 20 |
| Garcia, Freddy | 18 |
| Hudson, Tim | 18 |
| Mays, Joe | 17 |
| Moyer, Jamie | 20 |
| Mulder, Mark | 21 |
| Mussina, Mike | 17 |
| Sabathia, C.C. | 17 |
| Zito, Barry | 17 |
+----------------+------+
10 rows in set (0.00 sec)
3.17 当limit需要“错误”的排列顺序时做什么
mysql> select name, birth from profile order by birth desc limit 4;
+---------+------------+
| name | birth |
+---------+------------+
| De'Mont | 1980-12-12 |
| Shepard | 1975-09-02 |
| Carl | 1973-11-02 |
| Alison | 1973-01-12 |
+---------+------------+
4 rows in set (0.00 sec)mysql> select count(*) from profile;
+----------+
| count(*) |
+----------+
| 19 |
+----------+
1 row in set (0.00 sec)mysql> select name, birth from profile order by birth desc limit 6, 4;
+---------+------------+
| name | birth |
+---------+------------+
| De'Mont | 1973-01-12 |
| De'Mont | 1973-01-12 |
| De'Mont | 1973-01-12 |
| Fred | 1970-04-13 |
+---------+------------+
4 rows in set (0.00 sec)mysql> select * from-> (select name, birth from profile order by birth desc limit 5) as t-> order by birth;
+---------+------------+
| name | birth |
+---------+------------+
| De'Mont | 1973-01-12 |
| Alison | 1973-01-12 |
| Carl | 1973-11-02 |
| Shepard | 1975-09-02 |
| De'Mont | 1980-12-12 |
+---------+------------+
5 rows in set (0.00 sec)
3.18 从表达式中计算limit值
步骤解释:
定义变量 @limit_val。
使用 CONCAT 构建 SQL 字符串,将变量值嵌入其中。
通过 PREPARE 和 EXECUTE 执行动态生成的 SQL,确保变量被正确解析为整数。
mysql> SET @limit_val = 5 + 5;
Query OK, 0 rows affected (0.00 sec)mysql> SET @sql = CONCAT('SELECT * FROM profile LIMIT ', @limit_val);
Query OK, 0 rows affected (0.00 sec)mysql>
mysql> PREPARE stmt FROM @sql;
Query OK, 0 rows affected (0.00 sec)
Statement preparedmysql> EXECUTE stmt;
+----+---------+------------+-------+-----------------------+------+
| id | name | birth | color | foods | cats |
+----+---------+------------+-------+-----------------------+------+
| 1 | Fred | 1970-04-13 | black | lutefisk,fadge,pizza | 1 |
| 2 | Mort | 1969-09-30 | white | burrito,curry,eggroll | 3 |
| 3 | Brit | 1957-12-01 | red | burrito,curry,pizza | 1 |
| 4 | Carl | 1973-11-02 | red | eggroll,pizza | 4 |
| 5 | Sean | 1963-07-04 | blue | burrito,curry | 5 |
| 6 | Alan | 1965-02-14 | red | curry,fadge | 1 |
| 7 | Mara | 1968-09-17 | green | lutefisk,fadge | 1 |
| 8 | Shepard | 1975-09-02 | black | curry,pizza | 2 |
| 9 | Dick | 1952-08-20 | green | lutefisk,fadge | 0 |
| 10 | Tony | 1960-05-01 | white | burrito,pizza | 0 |
+----+---------+------------+-------+-----------------------+------+
10 rows in set (0.00 sec)mysql> DEALLOCATE PREPARE stmt;
使用存储过程(简化动态查询)
若需要频繁执行类似查询,可创建存储过程:
mysql> DELIMITER $$
mysql> CREATE PROCEDURE GetProfile(IN limit_val INT)-> BEGIN-> SELECT * FROM profile LIMIT limit_val;-> END$$
Query OK, 0 rows affected (0.04 sec)mysql> DELIMITER ;
mysql>
mysql> -- 调用存储过程
mysql> CALL GetProfile(10); -- 直接传入数值
+----+---------+------------+-------+-----------------------+------+
| id | name | birth | color | foods | cats |
+----+---------+------------+-------+-----------------------+------+
| 1 | Fred | 1970-04-13 | black | lutefisk,fadge,pizza | 1 |
| 2 | Mort | 1969-09-30 | white | burrito,curry,eggroll | 3 |
| 3 | Brit | 1957-12-01 | red | burrito,curry,pizza | 1 |
| 4 | Carl | 1973-11-02 | red | eggroll,pizza | 4 |
| 5 | Sean | 1963-07-04 | blue | burrito,curry | 5 |
| 6 | Alan | 1965-02-14 | red | curry,fadge | 1 |
| 7 | Mara | 1968-09-17 | green | lutefisk,fadge | 1 |
| 8 | Shepard | 1975-09-02 | black | curry,pizza | 2 |
| 9 | Dick | 1952-08-20 | green | lutefisk,fadge | 0 |
| 10 | Tony | 1960-05-01 | white | burrito,pizza | 0 |
+----+---------+------------+-------+-----------------------+------+
10 rows in set (0.01 sec)Query OK, 0 rows affected (0.21 sec)mysql> -- 或使用变量
mysql> SET @limit = 5 + 5;
Query OK, 0 rows affected (0.00 sec)mysql> CALL GetProfile(@limit);
+----+---------+------------+-------+-----------------------+------+
| id | name | birth | color | foods | cats |
+----+---------+------------+-------+-----------------------+------+
| 1 | Fred | 1970-04-13 | black | lutefisk,fadge,pizza | 1 |
| 2 | Mort | 1969-09-30 | white | burrito,curry,eggroll | 3 |
| 3 | Brit | 1957-12-01 | red | burrito,curry,pizza | 1 |
| 4 | Carl | 1973-11-02 | red | eggroll,pizza | 4 |
| 5 | Sean | 1963-07-04 | blue | burrito,curry | 5 |
| 6 | Alan | 1965-02-14 | red | curry,fadge | 1 |
| 7 | Mara | 1968-09-17 | green | lutefisk,fadge | 1 |
| 8 | Shepard | 1975-09-02 | black | curry,pizza | 2 |
| 9 | Dick | 1952-08-20 | green | lutefisk,fadge | 0 |
| 10 | Tony | 1960-05-01 | white | burrito,pizza | 0 |
+----+---------+------------+-------+-----------------------+------+
10 rows in set (0.00 sec)Query OK, 0 rows affected (0.06 sec)
如果你需要频繁执行类似查询,创建存储过程更简洁:
– 创建存储过程
DELIMITER $$
CREATE PROCEDURE GetProfile(IN limit_val INT)
BEGINSELECT * FROM profile LIMIT limit_val;
END$$
DELIMITER ;-- 调用存储过程
CALL GetProfile(10); -- 直接传入数值
-- 或使用变量
SET @limit = 5 + 5;
CALL GetProfile(@limit);
-------------------------------------------------------------
使用 PREPARE 语句
若需要动态生成 LIMIT 参数,可结合 预处理语句(Prepared Statements):
优点:安全处理动态参数,避免 SQL 注入。
mysql> SET @skip = 5;
Query OK, 0 rows affected (0.00 sec)mysql> SET @show = 5;
Query OK, 0 rows affected (0.00 sec)mysql> SET @sql = CONCAT('SELECT * FROM profile LIMIT ', @skip, ', ', @show);
Query OK, 0 rows affected (0.00 sec)mysql>
mysql> PREPARE stmt FROM @sql;
Query OK, 0 rows affected (0.01 sec)
Statement preparedmysql> EXECUTE stmt;
+----+---------+------------+-------+----------------+------+
| id | name | birth | color | foods | cats |
+----+---------+------------+-------+----------------+------+
| 6 | Alan | 1965-02-14 | red | curry,fadge | 1 |
| 7 | Mara | 1968-09-17 | green | lutefisk,fadge | 1 |
| 8 | Shepard | 1975-09-02 | black | curry,pizza | 2 |
| 9 | Dick | 1952-08-20 | green | lutefisk,fadge | 0 |
| 10 | Tony | 1960-05-01 | white | burrito,pizza | 0 |
+----+---------+------------+-------+----------------+------+
5 rows in set (0.00 sec)mysql> DEALLOCATE PREPARE stmt;
Query OK, 0 rows affected (0.00 sec)