想解锁PostgreSQL查询从基础到进阶的核心知识点?你都get了吗?

167 阅读10分钟

一、SELECT:查询的基础语句

查询是PostgreSQL中最常用的操作,核心是SELECT语句。它的作用是从表中提取指定的数据,就像从书架上挑选你需要的书——你可以选全部,也可以选特定几本,还能按顺序排列。

1.1 SELECT的基本结构

SELECT语句的基础结构如下:

SELECT1, 列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 NULLIS 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)。
解决

  1. 检查表名拼写(比如students vs student);
  2. 指定schema(比如public.students):
    SELECT * FROM public.students;
    

预防:用\d命令(psql中)查看当前数据库的表列表,确认表名和schema。

参考链接


余下文章内容请点击跳转至 个人博客页面 或者 扫码关注或者微信搜一搜:编程智域 前端至全栈交流与成长,阅读完整的文章:想解锁PostgreSQL查询从基础到进阶的核心知识点?你都get了吗?