现在是时候聊聊现实了。生活总有避不开的那一面:错误。抓bug、修bug、理解bug——这就是搞数据的日常。来看看在SQL里用 JOIN 时都有哪些坑,以及怎么绕过去。
错误1:漏写连接条件——生成笛卡尔积
最常见的错误就是——忘了写 ON 连接条件。这时候就会出现笛卡尔积,也就是第一张表的每一行都和第二张表的每一行拼在一起。结果就是一堆没意义的行,看着都头大。
举个例子。假设我们有下面两张表:
学生 (students):
| student_id | name |
|---|---|
| 1 | Otto |
| 2 | Anna |
课程 (courses):
| course_id | course_name |
|---|---|
| 101 | 数学 |
| 102 | 历史 |
现在我们写个查询,忘了 ON:
SELECT *
FROM students
JOIN courses;
结果:
| student_id | name | course_id | course_name |
|---|---|---|---|
| 1 | Otto | 101 | 数学 |
| 1 | Otto | 102 | 历史 |
| 2 | Anna | 101 | 数学 |
| 2 | Anna | 102 | 历史 |
这看着就不对劲吧?这个噩梦就叫 笛卡尔积。
怎么修: 用 ON 指定表之间的关联条件。
SELECT *
FROM students
JOIN courses
ON students.student_id = courses.course_id;
然后又有新坑了……
防呆机制
这个问题太常见了,所以PostgreSQL直接禁止你用 JOIN 却不写 ON 和条件。
如果你真想每行都拼每行,可以不用 JOIN,直接这样写:
SELECT *
FROM students, courses;
还有第3种情况——什么时候 JOIN 不用 ON 也能用:
- 用
NATURAL JOIN—— 自动找名字一样的列来连。 - 用
USING—— 你指定哪些列来连。 CROSS JOIN—— 永远不用条件,就是笛卡尔积。
错误2:连接条件写错了
有时候你写了连接条件,但写错了。比如不是用主键连外键,而是用了一些八竿子打不着的字段。
比如我们想查学生和他们选的课程,但写错了,把表连在了不相关的字段上:
SELECT *
FROM students
JOIN courses
ON students.student_id = courses.course_id;
这个查询结果肯定不对,因为 student_id 和 course_id 完全不是一回事。
怎么修:一定要用对的列来连。正确的写法应该是(假如你有个 enrollments 表,专门连学生和课程):
SELECT students.name, courses.course_name
FROM students
JOIN enrollments ON students.student_id = enrollments.student_id
JOIN courses ON enrollments.course_id = courses.course_id;
错误3:结果里有重复行
你在查询里加了好几个 JOIN,有时候就会出现重复行。这通常是因为 JOIN 的表里有重复数据,或者你连的条件写得不对。
比如,学生Otto在 enrollments 表里同一个课程被登记了两次。
enrollments 里的记录:
| student_id | course_id |
|---|---|
| 1 | 101 |
| 1 | 101 |
现在用 JOIN 查出来会这样:
SELECT students.name, courses.course_name
FROM students
JOIN enrollments ON students.student_id = enrollments.student_id
JOIN courses ON enrollments.course_id = courses.course_id;
结果:
| name | course_name |
|---|---|
| Otto | 数学 |
| Otto | 数学 |
怎么修:第一,确保表里没有重复数据。第二,如果这种重复是正常的,那就用 DISTINCT 去重:
SELECT DISTINCT students.name, courses.course_name
FROM students
JOIN enrollments ON students.student_id = enrollments.student_id
JOIN courses ON enrollments.course_id = courses.course_id;
错误4:用 INNER JOIN 时丢了行
INNER JOIN 只会返回两张表都能对上的行。如果有一张表里没有对应的值,这行就直接被扔掉了。选错连接类型就会丢数据。
比如,有个学生还没选任何课程:
学生 (students):
| student_id | name |
|---|---|
| 1 | Otto |
| 2 | Anna |
| 3 | Dhany |
登记 (enrollments):
| student_id | course_id |
|---|---|
| 1 | 101 |
| 2 | 102 |
现在用 INNER JOIN 查:
SELECT students.name, courses.course_name
FROM students
JOIN enrollments ON students.student_id = enrollments.student_id
JOIN courses ON enrollments.course_id = courses.course_id;
结果:
| name | course_name |
|---|---|
| Otto | 数学 |
| Anna | 历史 |
那Dhany去哪了?如果你想把没选课的学生也查出来,就得用 LEFT JOIN:
SELECT students.name, courses.course_name
FROM students
LEFT JOIN enrollments ON students.student_id = enrollments.student_id
LEFT JOIN courses ON enrollments.course_id = courses.course_id;
错误5:NULL 值处理不对
如果有表里有 NULL(空值),它们可能会被过滤掉(比如你加了过滤条件)。
比如:你用了 LEFT JOIN,但又加了 WHERE 过滤。
SELECT students.name, courses.course_name
FROM students
LEFT JOIN enrollments ON students.student_id = enrollments.student_id
LEFT JOIN courses ON enrollments.course_id = courses.course_id
WHERE courses.course_name = '数学';
这样没选课的学生就不会出现在结果里了,哪怕你用了 LEFT JOIN。
怎么修:如果你想把没选课的学生也查出来,要么把 WHERE 换成 ON,要么加个条件:
SELECT students.name, courses.course_name
FROM students
LEFT JOIN enrollments ON students.student_id = enrollments.student_id
LEFT JOIN courses ON enrollments.course_id = courses.course_id
WHERE courses.course_name IS NULL OR courses.course_name = '数学';
错误6:搞混了连接类型
你可能会搞不清该用哪种连接。比如本来可以用 LEFT JOIN,结果用了 RIGHT JOIN,其实只要换下表的顺序就行了。
怎么避免混乱:
- 能用
LEFT JOIN就用,这样更直观。 - 换下表的顺序,就不用
RIGHT JOIN了。
GO TO FULL VERSION