一、SELECT:查询的基础语句
查询是PostgreSQL中最常用的操作,核心是SELECT语句。它的作用是从表中提取指定的数据,就像从书架上挑选你需要的书——你可以选全部,也可以选特定几本,还能按顺序排列。
1.1 SELECT的基本结构
SELECT语句的基础结构如下:
SELECT 列1, 列2, ... -- 要返回的列(或*表示所有列)
FROM 表名 -- 数据来源的表
WHERE 条件 -- 过滤行的条件(可选)
GROUP BY 分组列 -- 按列分组(可选)
HAVING 分组条件 -- 过滤分组结果(可选)
ORDER BY 排序列 -- 排序结果(可选)
LIMIT 数量 -- 限制返回行数(可选)
OFFSET 偏移量 -- 跳过前N行(可选)
如果要选所有列,可以用*代替列名:
-- 选students表的所有数据
SELECT * FROM students;
但注意:*会返回表的所有列,实际开发中尽量指定需要的列(避免多余数据传输,提高性能)。
1.2 选择特定列与别名
如果只需要“姓名”和“专业”,可以明确指定列名:
-- 选学生的姓名和专业
SELECT name, major FROM students;
如果觉得列名不够直观,可以用AS给列起别名(alias):
-- 给列起别名,结果中的列名会变成"学生姓名"和"年龄"
SELECT name AS "学生姓名", age FROM students;
二、WHERE:过滤查询结果
WHERE子句用于筛选符合条件的行,相当于给查询加了一个“过滤条件”。它必须放在FROM之后、ORDER BY之前(记住执行顺序:FROM → WHERE → SELECT → ORDER BY)。
2.1 比较运算符
最常用的比较有:等于(=)、不等于(!=或<>)、大于(>)、小于(<)、大于等于(>=)、小于等于(<=)。
示例:
-- 选"计算机科学"专业的学生
SELECT * FROM students WHERE major = 'Computer Science';
-- 选年龄大于20岁的学生
SELECT * FROM students WHERE age > 20;
2.2 范围与模糊查询
- 范围查询:用
BETWEEN ... AND ...表示“在某个区间内”(包含边界); - 模糊查询:用
LIKE匹配字符串(%代表任意字符,_代表单个字符); - 多条件:用
AND(并且)、OR(或者)组合条件。
示例:
-- 选年龄在19到21岁之间的学生
SELECT * FROM students WHERE age BETWEEN 19 AND 21;
-- 选名字以"A"开头的学生(比如Alice)
SELECT * FROM students WHERE name LIKE 'A%';
-- 选"计算机科学"或"数学"专业、年龄大于20岁的学生
SELECT * FROM students WHERE (major = 'Computer Science' OR major = 'Mathematics') AND age > 20;
2.3 NULL值的处理
NULL代表“未知”或“不存在”,不能用=或!=判断,必须用IS NULL或IS NOT NULL:
-- 选没有填写入学日期的学生(假设enrollment_date有NULL值)
SELECT * FROM students WHERE enrollment_date IS NULL;
-- 选填写了入学日期的学生
SELECT * FROM students WHERE enrollment_date IS NOT NULL;
三、ORDER BY:给结果排序
ORDER BY用于按指定列排序,默认是升序(ASC),可以用DESC指定降序。支持多列排序(先按第一列排,再按第二列排)。
示例:
-- 按年龄降序排列( oldest first )
SELECT name, age FROM students ORDER BY age DESC;
-- 先按专业升序,再按年龄降序(同专业内年龄大的排前面)
SELECT name, major, age FROM students ORDER BY major ASC, age DESC;
四、LIMIT与OFFSET:限制结果数量与分页
LIMIT n:只返回前n行结果;OFFSET m:跳过前m行结果(从第m+1行开始);- 两者结合用于分页(比如网页上的“第2页”)。
示例:
-- 取最新入学的2个学生(假设enrollment_date越晚越新)
SELECT * FROM students ORDER BY enrollment_date DESC LIMIT 2;
-- 分页:取第3、4个学生(跳过前2个,取2个)
SELECT * FROM students ORDER BY id OFFSET 2 LIMIT 2;
五、JOIN:关联多个表
实际场景中,数据通常存在多个表中(比如学生表、课程表、选课表)。JOIN用于将多个表按共同字段连接,提取关联数据。
5.1 INNER JOIN:取两个表的交集
INNER JOIN(内连接)只返回两个表中都有匹配的行。比如要查“选了课的学生及其课程成绩”:
-- 关联3张表:学生表(students) → 选课表(student_courses) → 课程表(courses)
SELECT
s.name AS "学生姓名",
c.course_name AS "课程名称",
sc.grade AS "成绩"
FROM students s -- s是students的别名
INNER JOIN student_courses sc ON s.id = sc.student_id -- 关联学生和选课表(共同字段是id/student_id)
INNER JOIN courses c ON sc.course_id = c.course_id; -- 关联选课表和课程表(共同字段是course_id)
结果会显示所有选了课的学生的课程和成绩(没选课的学生不会出现)。
5.2 LEFT JOIN:保留左表的所有记录
LEFT JOIN(左连接)会返回左表的所有行,即使右表没有匹配(右表字段用NULL填充)。比如要查“所有学生的选课情况(包括没选课的)”:
SELECT
s.name AS "学生姓名",
COALESCE(c.course_name, '未选课') AS "课程名称" -- 用COALESCE把NULL替换成"未选课"
FROM students s
LEFT JOIN student_courses sc ON s.id = sc.student_id
LEFT JOIN courses c ON sc.course_id = c.course_id;
结果中,没选课的学生会显示“未选课”(比如如果有个学生没选任何课,course_name会是“未选课”)。
5.3 其他连接类型
- RIGHT JOIN:保留右表的所有记录(类似LEFT JOIN,但方向相反);
- FULL JOIN:保留两个表的所有记录(匹配的行合并,不匹配的用NULL填充)。
六、聚合函数与分组查询
聚合函数用于对一组数据进行计算(比如统计数量、求和、平均值),常见的有:
COUNT():统计行数;SUM():求和;AVG():求平均值;MAX():求最大值;MIN():求最小值。
6.1 常用聚合函数示例
-- 统计学生总数
SELECT COUNT(*) AS "总学生数" FROM students;
-- 统计计算机专业的学生数
SELECT COUNT(*) AS "计科学生数" FROM students WHERE major = 'Computer Science';
-- 求所有学生的平均年龄
SELECT AVG(age) AS "平均年龄" FROM students;
-- 求课程的最高学分
SELECT MAX(credits) AS "最高学分" FROM courses;
6.2 GROUP BY:按列分组统计
GROUP BY用于将数据按指定列分组,然后对每组计算聚合值。比如“统计每个专业的学生数”:
-- 按major分组,统计每组的学生数
SELECT major AS "专业", COUNT(*) AS "学生数" FROM students GROUP BY major;
注意:SELECT中的列要么在GROUP BY中,要么用聚合函数(否则会报错,见后面的“常见报错”)。
6.3 HAVING:过滤分组后的结果
HAVING用于过滤分组后的结果(类似WHERE,但WHERE过滤行,HAVING过滤分组)。比如“统计平均成绩≥85的课程”:
-- 先按课程分组求平均成绩,再过滤掉平均成绩<85的课程
SELECT
c.course_name AS "课程名称",
AVG(sc.grade) AS "平均成绩"
FROM courses c
INNER JOIN student_courses sc ON c.course_id = sc.course_id
GROUP BY c.course_name -- 按课程分组
HAVING AVG(sc.grade) ≥ 85; -- 过滤平均成绩≥85的分组
七、子查询:嵌套的查询逻辑
子查询(Subquery)是嵌套在另一个查询中的查询,用于解决复杂逻辑。常见位置:
WHERE子句中(标量子查询);FROM子句中(派生表);SELECT子句中(标量子查询)。
7.1 WHERE中的子查询
比如“查成绩高于全班平均成绩的学生”:
-- 子查询:先求所有学生的平均成绩(标量值)
SELECT
s.name AS "学生姓名",
sc.grade AS "成绩"
FROM students s
INNER JOIN student_courses sc ON s.id = sc.student_id
WHERE sc.grade > (SELECT AVG(grade) FROM student_courses); -- 子查询在这里
7.2 FROM中的子查询(派生表)
比如“查平均成绩≥85的课程,并显示课程名和平均成绩”:
-- 子查询作为"派生表"(临时表),先统计每个课程的平均成绩
SELECT
avg_grade.course_name,
avg_grade.avg_grade
FROM (
-- 子查询:统计每个课程的平均成绩
SELECT
c.course_name,
AVG(sc.grade) AS avg_grade
FROM courses c
INNER JOIN student_courses sc ON c.course_id = sc.course_id
GROUP BY c.course_name
) AS avg_grade -- 给子查询起别名avg_grade
WHERE avg_grade.avg_grade ≥ 85; -- 过滤派生表的结果
八、常见函数:让查询更强大
PostgreSQL提供了丰富的内置函数,覆盖字符串、日期、数值等场景。以下是常用的几个:
8.1 字符串函数
CONCAT(s1, s2, ...):拼接字符串;LEFT(s, n):取字符串左边n个字符;LOWER(s):转小写;UPPER(s):转大写。
示例:
-- 拼接"姓名-专业"(比如"Alice-Computer Science")
SELECT CONCAT(name, '-', major) AS "学生信息" FROM students;
8.2 日期函数
CURRENT_DATE:当前日期;AGE(end_date, start_date):计算两个日期的间隔;TO_CHAR(date, format):将日期转成字符串(比如TO_CHAR(enrollment_date, 'YYYY-MM-DD'))。
示例:
-- 计算学生的入学时长(比如"2 years 3 months")
SELECT
name AS "学生姓名",
AGE(CURRENT_DATE, enrollment_date) AS "入学时长"
FROM students;
8.3 数值函数
ROUND(n, d):四舍五入到d位小数;ABS(n):取绝对值;SUM(n):求和(聚合函数)。
示例:
-- 统计每个课程的平均成绩,保留1位小数
SELECT
course_name,
ROUND(AVG(grade), 1) AS "平均成绩"
FROM courses c
INNER JOIN student_courses sc ON c.course_id = sc.course_id
GROUP BY course_name;
九、课后Quiz:巩固你的查询能力
Quiz 1:基础组合查询
问题:如何查询所有“Computer Science”专业的学生,按年龄从大到小排序,只显示前3个?
答案:
SELECT * FROM students WHERE major = 'Computer Science' ORDER BY age DESC LIMIT 3;
Quiz 2:LEFT JOIN与NULL处理
问题:用LEFT JOIN查询所有学生及其选的课程,要求“未选课”的学生显示“未选课”而不是NULL,如何实现?
答案(用COALESCE函数替换NULL):
SELECT
s.name AS "学生姓名",
COALESCE(c.course_name, '未选课') AS "课程名称"
FROM students s
LEFT JOIN student_courses sc ON s.id = sc.student_id
LEFT JOIN courses c ON sc.course_id = c.course_id;
Quiz 3:GROUP BY与HAVING
问题:统计每个教师的课程数量,只显示课程数≥2的教师,如何写?
答案:
SELECT instructor AS "教师姓名", COUNT(*) AS "课程数"
FROM courses
GROUP BY instructor
HAVING COUNT(*) ≥ 2;
十、常见报错与解决办法
报错1:ERROR: syntax error at or near "WHERE"
原因:WHERE子句位置错误(比如放在ORDER BY之后)。
错误示例:
SELECT name FROM students ORDER BY age WHERE major = 'Math'; -- 错!WHERE应在ORDER BY之前
解决:调整顺序:
SELECT name FROM students WHERE major = 'Math' ORDER BY age;
预防:记住SQL执行顺序:FROM → WHERE → GROUP BY → HAVING → SELECT → ORDER BY → LIMIT。
报错2:ERROR: column "s.name" must appear in the GROUP BY clause or be used in an aggregate function
原因:GROUP BY分组时,SELECT中的列既不在GROUP BY里,也没用聚合函数。
错误示例:
SELECT major, name FROM students GROUP BY major; -- 错!name不在GROUP BY,也没用聚合函数
解决:要么把name加入GROUP BY(但逻辑不对,一个专业有多个name),要么用聚合函数(比如COUNT(name)):
SELECT major, COUNT(name) AS "学生数" FROM students GROUP BY major;
预防:GROUP BY的列要覆盖SELECT中所有非聚合列。
报错3:ERROR: operator does not exist: integer = text
原因:数据类型不匹配(比如用整数列和字符串比较)。
错误示例:
SELECT * FROM students WHERE id = '1'; -- 错!id是INT,'1'是TEXT
解决:统一类型(直接用整数,或用CAST转类型):
SELECT * FROM students WHERE id = 1; -- 正确
-- 或
SELECT * FROM students WHERE id = CAST('1' AS INT); -- 转成INT
预防:写条件时注意列的数据类型,避免字符串和数值混用。
报错4:ERROR: relation "students" does not exist
原因:表名拼写错误,或表不在当前schema(默认是public)。
解决:
- 检查表名拼写(比如
studentsvsstudent); - 指定
schema(比如public.students):SELECT * FROM public.students;
预防:用\d命令(psql中)查看当前数据库的表列表,确认表名和schema。
参考链接
- SELECT语句语法:www.postgresql.org/docs/17/sql…
- WHERE子句:www.postgresql.org/docs/17/sql…
- JOIN连接:www.postgresql.org/docs/17/que…
- 聚合函数:www.postgresql.org/docs/17/fun…
- LIMIT/OFFSET:www.postgresql.org/docs/17/sql…
余下文章内容请点击跳转至 个人博客页面 或者 扫码关注或者微信搜一搜:编程智域 前端至全栈交流与成长,阅读完整的文章:想解锁PostgreSQL查询从基础到进阶的核心知识点?你都get了吗?