6、SQL语言进阶(查询):ORDER BY排序、LIMIT分页、GROUP BY分组、HAVING过滤分组、聚合函数
好,咱们继续往下走。上一章我们把 SQL 的基本查询玩熟了,能查数据了。但实际开发中,需求可没这么简单。你想想看,一个列表页,数据总得有个顺序吧?数据多了,得分页吧?想统计一下某个分类的总数,怎么办?
这一章,我就带你把这些「进阶操作」全部拿下。说白了,就是让你的查询语句变得更聪明、更强大。
- ORDER BY:给查询结果排个序
- LIMIT:控制返回多少条数据,实现分页
- 聚合函数:COUNT、SUM、AVG、MAX、MIN
- GROUP BY:按某个字段分组统计
- HAVING:对分组后的结果进行过滤
6.1 ORDER BY:给数据排个队
先说说排序。没有 ORDER BY 的查询,返回的数据顺序是不确定的。你这次查和下次查,顺序可能不一样。这在列表展示时就很尴尬了。
我个人习惯,只要是从数据库取列表数据,一定会加 ORDER BY。哪怕只是按主键排一下,至少结果是稳定的。
-- 基本语法:按一个字段排序
SELECT * FROM users ORDER BY age ASC; -- 升序(默认)
SELECT * FROM users ORDER BY age DESC; -- 降序
-- 多字段排序:先按年龄降序,年龄相同再按姓名升序
SELECT * FROM users ORDER BY age DESC, name ASC;
-- 在 Android 的 Room 中:
@Query("SELECT * FROM users ORDER BY age DESC, name ASC")
fun getUsersSorted(): List<User>
6.2 LIMIT:分页查询的利器
数据多了,总不能一次全查出来吧?内存扛不住,用户也看不过来。这时候就需要 LIMIT 了。
LIMIT 的用法很简单,但不同数据库有点小差异。SQLite 用的是 LIMIT 偏移量, 数量 或者 LIMIT 数量 OFFSET 偏移量。
-- 查前10条
SELECT * FROM users LIMIT 10;
-- 跳过前20条,取接下来的10条(即第21~30条)
SELECT * FROM users LIMIT 10 OFFSET 20;
-- 另一种写法(SQLite 支持)
SELECT * FROM users LIMIT 20, 10; -- 注意:20是偏移量,10是数量
-- 分页公式:pageSize = 20, pageIndex = 3
-- OFFSET = (pageIndex - 1) * pageSize = 40
SELECT * FROM users LIMIT 20 OFFSET 40;
6.3 聚合函数:COUNT、SUM、AVG、MAX、MIN
聚合函数,说白了就是「算总账」的。它们不是返回具体某一行,而是对多行数据进行计算,返回一个汇总结果。
| 函数 | 作用 | 示例 |
|---|---|---|
| COUNT() | 统计行数 | COUNT(*) 统计所有行,COUNT(age) 统计 age 非空的行数 |
| SUM() | 求和 | SUM(price) 计算所有 price 的总和 |
| AVG() | 求平均值 | AVG(score) 计算 score 的平均值 |
| MAX() | 求最大值 | MAX(salary) 找出最高工资 |
| MIN() | 求最小值 | MIN(age) 找出最小年龄 |
-- 统计用户总数
SELECT COUNT(*) FROM users;
-- 统计有年龄的用户数
SELECT COUNT(age) FROM users;
-- 计算所有订单的总金额
SELECT SUM(amount) FROM orders;
-- 计算平均分
SELECT AVG(score) FROM exam_results;
-- 找出最高和最低工资
SELECT MAX(salary), MIN(salary) FROM employees;
COUNT(*)统计所有行,包括某列为 NULL 的行。COUNT(列名)只统计该列非 NULL 的行数。- 我在项目中就踩过这个坑:用 COUNT(email) 统计用户数,结果发现比 COUNT(*) 少了几百,一查才发现有些用户没填邮箱。
6.4 GROUP BY:分组统计
聚合函数单独用,只能算出一个总数。但实际需求往往是「按某个维度分组统计」。比如:每个城市的用户数、每个月的销售额。
这时候就需要 GROUP BY 了。它把数据按某个字段分组,然后对每组应用聚合函数。
-- 按城市统计用户数
SELECT city, COUNT(*) AS user_count
FROM users
GROUP BY city;
-- 按年份统计销售额
SELECT strftime('%Y', order_date) AS year, SUM(amount) AS total
FROM orders
GROUP BY year;
-- 多字段分组:按城市和性别统计
SELECT city, gender, COUNT(*) AS cnt
FROM users
GROUP BY city, gender;
6.5 HAVING:过滤分组结果
WHERE 是在分组前过滤行,那分组后想过滤怎么办?比如:我只想看用户数超过100的城市。
这时候 HAVING 就派上用场了。它和 WHERE 很像,但它是针对分组后的结果进行过滤的。
-- 查询用户数超过100的城市
SELECT city, COUNT(*) AS user_count
FROM users
GROUP BY city
HAVING user_count > 100;
-- 查询平均分大于80的班级
SELECT class_id, AVG(score) AS avg_score
FROM exam_results
GROUP BY class_id
HAVING avg_score > 80;
-- WHERE + GROUP BY + HAVING 组合使用
-- 先过滤掉年龄小于18的用户,再按城市分组,最后只显示用户数大于50的城市
SELECT city, COUNT(*) AS user_count
FROM users
WHERE age >= 18
GROUP BY city
HAVING user_count > 50;
- WHERE:在分组前过滤行,不能使用聚合函数。
- HAVING:在分组后过滤组,可以使用聚合函数。
- 执行顺序:WHERE → GROUP BY → HAVING → ORDER BY → LIMIT
6.6 综合实战:一个完整的查询
咱们来一个综合案例。假设有一个订单表 orders,字段有:id、user_id、amount、order_date、status。
需求:查询2024年每个用户的订单总金额,只显示总金额超过1000的用户,按总金额降序排列,只取前10名。
SELECT
user_id,
SUM(amount) AS total_amount,
COUNT(*) AS order_count
FROM orders
WHERE
order_date >= '2024-01-01'
AND order_date < '2025-01-01'
AND status = 'completed'
GROUP BY user_id
HAVING total_amount > 1000
ORDER BY total_amount DESC
LIMIT 10;
这个查询的执行顺序是这样的:
- WHERE:先过滤出2024年已完成订单
- GROUP BY:按 user_id 分组
- 聚合计算:计算每组的 SUM(amount) 和 COUNT(*)
- HAVING:只保留总金额大于1000的组
- ORDER BY:按总金额降序排列
- LIMIT:只取前10条
WHERE → GROUP BY → HAVING → ORDER BY → LIMIT
这个顺序搞清楚了,复杂的查询你也能写出来。我在面试时经常问这个问题,能答对的人不多。
6.7 避坑指南
最后,分享几个我踩过的坑:
- GROUP BY 与 SELECT 的字段不匹配:SELECT 中的非聚合字段,必须出现在 GROUP BY 中。否则 SQLite 会报错,或者返回不确定的结果。
- HAVING 中误用别名:有些数据库不支持在 HAVING 中使用 SELECT 中定义的别名,但 SQLite 是支持的。不过为了兼容性,我建议直接写表达式。
- LIMIT 分页性能问题:OFFSET 越大,查询越慢。因为数据库需要跳过前面的行。如果数据量很大,可以考虑用「游标分页」代替「OFFSET 分页」。
- COUNT(*) 和 COUNT(1) 的区别:在 SQLite 中,两者性能几乎一样。但 COUNT(列名) 会忽略 NULL,这个区别一定要记住。
好了,这一章的内容就到这里。ORDER BY、LIMIT、聚合函数、GROUP BY、HAVING,这几个东西组合起来,能解决绝大部分的数据统计和展示需求。你可以在自己的项目里多练练,写几个复杂的查询试试看。