哈喽,各位码友!在数据库开发中,SELECT 查询 是使用率最高的核心语法,无论是日常业务开发、数据统计,还是接口数据查询,都离不开它。很多初学者能写简单查询,但对 where、group by、having、order by、limit offset 的执行顺序、使用场景、区别以及分页性能问题一知半解。

本篇博客将从零梳理 SELECT 基础查询核心知识点,包含完整语法、关键字用法、distinct 去重、offset 分页性能缺陷及优化思路,全程搭配可运行 SQL 案例,适合零基础入门、查漏补缺。
适用数据库:PostgreSQL 12+
说明:PostgreSQL 的 limit/offset 语法和 MySQL 写法很接近,但分组、聚合、严格模式、分页底层实现有区别,下文会标注差异点。
测试表说明 :本文所有案例基于学生成绩表 stu_score
sql
-- 测试数据表 PostgreSQL
CREATE TABLE stu_score (
id INT PRIMARY KEY GENERATED BY DEFAULT AS IDENTITY COMMENT '主键ID',
stu_name VARCHAR(20) NOT NULL COMMENT '学生姓名',
subject VARCHAR(20) NOT NULL COMMENT '考试科目',
score INT NOT NULL COMMENT '分数',
class_id INT COMMENT '班级ID'
) COMMENT '学生成绩表';
-- 测试数据
INSERT INTO stu_score (stu_name, subject, score, class_id) VALUES
('张三','语文',88,1),
('张三','数学',95,1),
('李四','语文',76,2),
('李四','数学',82,2),
('王五','语文',92,1),
('王五','数学',89,1),
('赵六','语文',85,2),
('赵六','数学',90,2);
一、SELECT 完整基础语法结构
首先记住 SELECT 标准执行顺序(重中之重),这是区分各类查询关键字的核心:
- 语法书写顺序:select → from → where → group by → having → order by → limit
- 数据库真实执行顺序:from → where → group by → having → select → order by → limit
很多语法报错、查询结果异常,都是因为混淆了执行顺序!下面我们逐个拆解每个关键字的用法。
完整通用语法:
sql
SELECT [DISTINCT] 字段列表
FROM 表名
[WHERE 行过滤条件]
[GROUP BY 分组字段]
[HAVING 分组后过滤条件]
[ORDER BY 排序字段 [ASC/DESC]]
[LIMIT 条数 OFFSET 偏移量];
PG 小提示:PostgreSQL 推荐
LIMIT size OFFSET offset写法;不支持 MySQLLIMIT offset,size逗号写法!这点和 MySQL 不一样,写 SQL 时容易踩坑。
二、核心关键字详解
1、WHERE:分组前行过滤
where 的作用是筛选原始数据表中的单行数据 ,在 group by 分组之前 执行,过滤掉不符合条件的原始数据。
✅ 支持条件:比较运算(>、<、=)、逻辑运算(and/or/not)、模糊查询(like)、范围查询(between and、in) ❌ 不支持:聚合函数(sum、count、avg 等),因为执行 where 时还未分组,没有聚合结果
案例:查询语文分数大于 80 分的学生成绩
sql
SELECT * FROM stu_score
WHERE subject = '语文' AND score > 80;
执行逻辑:先从数据表中筛选出科目为语文、分数大于 80 的原始数据,再返回结果。
2、GROUP BY:数据分组统计
group by 用于将查询结果按照指定字段分组,通常搭配聚合函数使用(count、sum、avg、max、min),实现分组统计。
使用规则(PostgreSQL 强制严格校验):
- SELECT 后面出现的非聚合字段,必须全部出现在 GROUP BY 子句中,PG 默认不允许 MySQL 那种宽松分组模式,否则直接报错。
- 可以单个字段分组,也可以多个字段联合分组。
案例:统计每个学生的总成绩、平均成绩
sql
SELECT
stu_name,
SUM(score) AS total_score,
AVG(score) AS avg_score
FROM stu_score
GROUP BY stu_name;
3、HAVING:分组后结果过滤
having 是分组后的过滤条件 ,在 group by 分组、聚合计算完成后执行,专门用于筛选聚合后的统计结果。
✅ 支持:聚合函数、普通字段条件 ❌ 和 where 的核心区别:where 过滤原始数据(分组前),having 过滤统计数据(分组后)
案例:统计总成绩大于 170 分的学生
sql
SELECT
stu_name,
SUM(score) AS total_score
FROM stu_score
GROUP BY stu_name
HAVING SUM(score) > 170;
PG 小提示:having 里不能直接使用 select 中定义的别名
total_score,需要写原始聚合表达式,这点和 MySQL 不同!
4、ORDER BY:结果排序
order by 用于对最终查询结果进行排序,在所有筛选、分组、聚合完成后执行。
排序规则:
ASC:升序(默认,可省略)DESC:降序
支持单字段排序、多字段优先级排序。
案例:按学生总成绩降序排序
sql
SELECT
stu_name,
SUM(score) AS total_score
FROM stu_score
GROUP BY stu_name
ORDER BY total_score DESC;
PG 这里支持使用 select 别名排序,和 MySQL 一致。
5、LIMIT & OFFSET:分页查询
PostgreSQL 使用 LIMIT 限制返回行数,OFFSET 设置跳过多少行,没有 MySQL 逗号简写语法。
标准写法:
sql
-- PG 标准写法:LIMIT 返回条数 OFFSET 跳过行数
LIMIT size OFFSET offset
- offset:偏移量,跳过前 N 条数据
- size:每页展示的数据条数
案例:分页查询,第 2 页,每页 2 条数据(页码从第 0 页开始)
sql
SELECT * FROM stu_score
ORDER BY id ASC
LIMIT 2 OFFSET 2;
三、DISTINCT 去重用法详解
DISTINCT 用于去除查询结果中的重复数据,保留唯一值,写在 SELECT 之后。
1、单字段去重
sql
-- 查询所有不重复的班级ID
SELECT DISTINCT class_id FROM stu_score;
2、多字段联合去重
重点 :多字段去重时,只有所有字段值完全一致才会判定为重复,单一字段重复不会去重。
sql
-- 姓名+科目联合去重
SELECT DISTINCT stu_name, subject FROM stu_score;
、DISTINCT 注意事项
- DISTINCT 作用于后续所有字段,而非单个字段
- 底层会执行排序(HashAggregate / Sort),大数据量下会消耗 CPU 和内存,慎用
- PostgreSQL 还提供
DISTINCT ON(),可以实现按指定字段只取第一条,是 PG 特有语法,后续进阶篇讲解
四、OFFSET 分页核心性能问题(面试高频)
很多人以为只有 MySQL 存在 offset 深分页问题,PostgreSQL 的大 offset 分页同样有严重性能问题,后端面试高频考点。
1、性能问题根源
PostgreSQL 在执行 OFFSET M LIMIT N 时:数据库需要扫描并丢弃前面 M 行数据,再返回 N 条。
LIMIT 10 OFFSET 10000PG 需要读取 10010 行数据,丢弃前 10000 行,只返回最后 10 条。offset 越大,扫描行数越多,IO 和 CPU 开销越高,分页越深查询越慢。
2、案例对比
sql
-- 浅分页:性能几乎无影响
SELECT * FROM stu_score LIMIT 10 OFFSET 10;
-- 深分页:百万级数据下极度卡顿
SELECT * FROM stu_score LIMIT 10 OFFSET 1000000;
3、优化方案(PostgreSQL 落地)
方案 1:主键书签分页(推荐,也叫 keyset 分页)
利用有序主键索引,用 where 条件代替 offset,不需要扫描丢弃前面数据。
sql
-- 已知上一页最后一条ID为10,查询下一页10条数据
SELECT * FROM stu_score WHERE id > 10 ORDER BY id LIMIT 10;
优点:走索引查询,速度稳定,不受分页深度影响 缺点:不适合 ID 存在删除、不连续场景;不支持直接跳页(跳转到第 1000 页)
方案 2:延迟关联优化
先从索引取出主键,再关联回表拿完整字段,减少回表开销。
sql
SELECT s.* FROM stu_score s
INNER JOIN (SELECT id FROM stu_score ORDER BY id LIMIT 10 OFFSET 10000) temp
ON s.id = temp.id
ORDER BY s.id;
方案 3:业务层限制深分页
产品层面限制用户最大分页页数(例如最多允许查前 1000 页),从业务上规避大 offset,企业项目最常用方案。
PG 额外补充:PostgreSQL 13+ 支持
FETCH FIRST ... ROWS ONLY,是 SQL 标准语法,等价 LIMIT
sql
SELECT * FROM stu_score ORDER BY id FETCH FIRST 2 ROWS ONLY OFFSET 2;
五、高频易错知识点总结
- 执行顺序:where(原始过滤)→ group by(分组)→ having(聚合过滤)→ order by(排序)→ limit(分页)
- Where 和 Having 区别 :where 不能用聚合函数,过滤原始行;having 过滤分组聚合结果。PostgreSQL 的 having 不能直接引用 select 别名
- Distinct :多字段联合去重,全部字段一致才去重;底层排序开销大。PG 特有
DISTINCT ON - Offset 分页坑:PG 同样存在大偏移量性能衰减,优先使用主键书签分页优化
六、文末总结
SELECT 查询是 SQL 的基石,where、group by、having、order by、limit offset 的组合使用,覆盖了 90% 的数据查询、统计、分页场景。
掌握执行顺序 和性能痛点,不仅能写出正确的 SQL,更能规避线上慢查询问题,也是面试、开发的核心加分点。后续会持续更新 PostgreSQL 进阶语法、索引优化、联表查询等干货!