想象一下,你数据库里的每张表其实就是一个大拼图的一块。学生表知道谁在上学,课程表知道学啥内容,报名表知道谁报了啥。但这些碎片单独看都没啥用。想看全图?那就得把它们拼起来——这时候就轮到多个 JOIN出场了。
现实里,数据经常被拆成相关的表,这样结构更清晰,也能避免重复。比如我们这个大学数据库,有这些表:
students— 学生信息。enrollments— 学生选课信息。courses— 课程信息。
如果你想查出所有学生、他们的课程和老师,就得用 JOIN 把三张表连起来。
JOIN 的执行顺序
用多个 JOIN 时,PostgreSQL 是从左到右处理的。也就是说,先连前两张表,然后再把结果和第三张表连起来,依次类推。
举个例子:
SELECT *
FROM students
INNER JOIN enrollments ON students.id = enrollments.student_id
INNER JOIN courses ON enrollments.course_id = courses.id;
- 先把
students和enrollments按students.id = enrollments.student_id连起来。 - 第一步的结果再和
courses按enrollments.course_id = courses.id连起来。
顺序很重要,特别是表很大的时候。写错 JOIN 的结构,性能可能会掉得很惨。
例子:学生、课程和老师的列表
假设我们有这些表的数据:
students 表:
| id | name |
|---|---|
| 1 | Otto Song |
| 2 | Maria Chi |
| 3 | Alex Lin |
courses 表:
| id | name | teacher |
|---|---|---|
| 101 | Mathematics | Ellen Moore |
| 102 | Physics | James Okoro |
| 103 | Computer Science | Nina Delgado |
enrollments 表:
| student_id | course_id |
|---|---|
| 1 | 101 |
| 1 | 103 |
| 2 | 102 |
| 3 | 101 |
查询:
SELECT
students.name AS student_name,
courses.name AS course_name,
courses.teacher AS teacher_name
FROM students
INNER JOIN enrollments ON students.id = enrollments.student_id
INNER JOIN courses ON enrollments.course_id = courses.id;
结果:
| student_name | course_name | teacher_name |
|---|---|---|
| Otto Song | Mathematics | Ellen Moore |
| Otto Song | Computer Science | Nina Delgado |
| Maria Chi | Physics | James Okoro |
| Alex Lin | Mathematics | Ellen Moore |
多 JOIN 查询里的过滤
你可以在 JOIN 查询里加过滤条件,限制返回的数据量,还能让查询更快。比如,只查学“Mathematics”课程的学生:
SELECT
students.name AS student_name,
courses.name AS course_name
FROM students
INNER JOIN enrollments ON students.id = enrollments.student_id
INNER JOIN courses ON enrollments.course_id = courses.id
WHERE courses.name = 'Mathematics';
结果:
| student_name | course_name |
|---|---|
| Otto Song | Mathematics |
| Alex Lin | Mathematics |
多 JOIN 查询的优化
表很大的时候,优化查询就特别重要了。给你几个建议:
- 用索引
索引能让 PostgreSQL 查得更快,尤其是按主键连表的时候。记得在 enrollments 表的 student_id 和 course_id 字段上建索引。
建索引的例子:
CREATE INDEX idx_enrollments_student_id ON enrollments(student_id);
CREATE INDEX idx_enrollments_course_id ON enrollments(course_id);
索引的细节你后面会学到,但这里先提一下,因为 JOIN 经常和索引一起用。
- 尽早过滤数据
用 WHERE 条件,能在 JOIN 前就把不需要的行过滤掉。比如:
SELECT
students.name AS student_name,
courses.name AS course_name
FROM students
INNER JOIN enrollments ON students.id = enrollments.student_id
INNER JOIN courses ON enrollments.course_id = courses.id
WHERE
courses.teacher = '伊万 彼得罗夫';
- 尽量减少要连的行数
别一上来就全表 JOIN,可以先用子查询把数据筛一遍:
SELECT
students.name AS student_name,
courses.name AS course_name
FROM
(SELECT * FROM students WHERE id IN (1, 2)) sub_students
INNER JOIN enrollments ON sub_students.id = enrollments.student_id
INNER JOIN courses ON enrollments.course_id = courses.id;
子查询(嵌套 SELECT)你马上就会学到,敬请期待 :P
复杂 JOIN 例子:学生、课程和学院
假设我们又加了一张 faculties 表:
faculties 表:
| id | name |
|---|---|
| 10 | 工程学院 |
| 20 | 自然科学学院 |
courses 表更新:
| id | name | teacher | faculty_id |
|---|---|---|---|
| 101 | Mathematics | Ellen Moore | 10 |
| 102 | Physics | James Okoro | 20 |
| 103 | Computer Science | Nina Delgado | 10 |
想查学生、课程和学院,就再加一个 JOIN:
SELECT
students.name AS student_name,
courses.name AS course_name,
faculties.name AS faculty_name
FROM students
INNER JOIN enrollments ON students.id = enrollments.student_id
INNER JOIN courses ON enrollments.course_id = courses.id
INNER JOIN faculties ON courses.faculty_id = faculties.id;
结果:
| student_name | course_name | faculty_name |
|---|---|---|
| Otto Song | Mathematics | 工程学院 |
| Otto Song | Computer Science | 工程学院 |
| Maria Chi | Physics | 自然科学学院 |
| Alex Lin | Mathematics | 工程学院 |
多 JOIN 的 SQL 查询虽然有点复杂,但能让你写出很强大的报表,挖出有用的信息。只要结构和优化搞对了,它们就是你玩转大数据库的利器!
GO TO FULL VERSION