6、SQL语言进阶(查询):ORDER BY排序、LIMIT分页、GROUP BY分组、HAVING过滤分组、聚合函数

好,咱们继续往下走。上一章我们把 SQL 的基本查询玩熟了,能查数据了。但实际开发中,需求可没这么简单。你想想看,一个列表页,数据总得有个顺序吧?数据多了,得分页吧?想统计一下某个分类的总数,怎么办?

这一章,我就带你把这些「进阶操作」全部拿下。说白了,就是让你的查询语句变得更聪明、更强大。

本章核心知识点一览:
  • ORDER BY:给查询结果排个序
  • LIMIT:控制返回多少条数据,实现分页
  • 聚合函数:COUNT、SUM、AVG、MAX、MIN
  • GROUP BY:按某个字段分组统计
  • HAVING:对分组后的结果进行过滤
SQL 查询进阶知识体系 SELECT 进阶查询 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>
💡 我的经验:ASC 可以省略,因为默认就是升序。但 DESC 必须写。另外,排序字段尽量用索引列,否则数据量大时性能会下降。我曾经在一个百万级的表上做 ORDER BY 非索引字段,查询直接卡了十几秒……

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;
⚠️ 注意:LIMIT 一定要配合 ORDER BY 使用!否则你分页出来的数据,翻页时可能会出现重复或遗漏。为什么?因为没有 ORDER BY 时,数据库返回的顺序是不确定的。你想想看,第一页查了1~10条,第二页查11~20条,但数据库底层数据顺序变了,那结果就乱套了。

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 的细节:
  • 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;
💡 我的习惯:GROUP BY 后面的字段,一定要出现在 SELECT 中(除非是聚合函数)。不然查出来的数据,你都不知道是哪一组的。另外,GROUP BY 之前可以用 WHERE 过滤数据,但 WHERE 是在分组前过滤的。

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;

这个查询的执行顺序是这样的:

  1. WHERE:先过滤出2024年已完成订单
  2. GROUP BY:按 user_id 分组
  3. 聚合计算:计算每组的 SUM(amount) 和 COUNT(*)
  4. HAVING:只保留总金额大于1000的组
  5. ORDER BY:按总金额降序排列
  6. 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,这几个东西组合起来,能解决绝大部分的数据统计和展示需求。你可以在自己的项目里多练练,写几个复杂的查询试试看。

公众号:蓝海资料掘金营,微信deep3321