欢迎来到新的 SQL 讲座!今天我们要认识一下最不起眼但超强大的操作符——EXISTS 和 NOT EXISTS。想象一下有个侦查员,啥痕迹都不留,但能立刻告诉你:“有,这个对象存在”或者“没有,这里啥都没有”。这俩操作符不会直接返回数据,但能让你在查询里做逻辑超准的判断。
先来点基础。EXISTS 就是用来检查子查询结果里有没有记录。如果子查询能查到至少一条记录,EXISTS 条件就会返回 TRUE,否则就是 FALSE。
SELECT 1
WHERE EXISTS (
SELECT *
FROM students
WHERE grade > 3.5
);
你看,其实我们根本不关心子查询查出来的数据,只在乎有没有这样的行。如果有一条符合条件,查询就会返回 1。
EXISTS 的语法
EXISTS 的语法很简单:
SELECT 列名
FROM 表名
WHERE EXISTS (
SELECT 1
FROM 另一个表
WHERE 条件
);
解释:
EXISTS里的子查询可以是任何查询。- 子查询的结果决定了是
TRUE还是FALSE。
例子:有没有分数大于 4 的学生?
假设有个 students 表:
| id | name | grade |
|---|---|---|
| 1 | Otto | 3.2 |
| 2 | Anna | 4.7 |
| 3 | Dan | 5.0 |
| 4 | Lina | 2.9 |
比如我们想查一下有没有分数大于 4 的学生。用下面这个查询:
SELECT '有分数很高的学生!'
WHERE EXISTS (
SELECT 1
FROM students
WHERE grade > 4
);
结果:
有分数很高的学生!
为啥 EXISTS 比 IN 快?
EXISTS 最大的优点就是只要找到第一个符合条件的就立刻停。也就是说,如果你只是想判断有没有数据,EXISTS 超高效。
比如,假如 students 表有几百万条记录,但你只想知道有没有一条分数大于 4(grade > 4)。SQL 一旦找到第一条符合的,查询就结束了。
用 NOT EXISTS
现在说说 NOT EXISTS。它和 EXISTS 正好相反。只有当子查询一条记录都查不到时才返回 TRUE。
例子:找没有分数(NULL)的学生
假设表里有些学生还没分数:
| id | name | grade |
|---|---|---|
| 1 | Otto | NULL |
| 2 | Anna | 4.7 |
| 3 | Dan | 5.0 |
| 4 | Lina | NULL |
我们想选出所有没有分数的学生。用 NOT EXISTS:
SELECT *
FROM students s
WHERE NOT EXISTS (
SELECT 1
FROM students
WHERE grade IS NOT NULL
AND id = s.id
);
结果:
| id | name | grade |
|---|---|---|
| 1 | Otto | NULL |
| 4 | Lina | NULL |
EXISTS 和 IN 的对比
有时候你会觉得 EXISTS 和 IN 好像差不多。表面上是,但其实有细节。特别是遇到 NULL 的时候,IN 的表现可能很迷,而 EXISTS 就很靠谱。
来看个例子。
courses 表(可以选的课程):
| course_id | name |
|---|---|
| 1 | 数学 |
| 2 | 历史 |
还有学生:
| student_id | name |
|---|---|
| 1 | Alex Lin |
| 2 | Anna Song |
| 3 | Maria Chi |
| 4 | Dan Seth |
| 5 | Shadow Moon |
enrollments 表(谁选了哪个课):
| student_id | course_id |
|---|---|
| 1 | 1 |
| 2 | 2 |
| 3 | NULL |
我们想查一下有哪些课程被人选了。看起来很简单。
用 IN:
SELECT name
FROM courses
WHERE course_id IN (
SELECT course_id
FROM enrollments
);
表面上应该没问题。但如果 enrollments 里有 NULL 的 courseid,比如 Maria Chi,IN 可能啥都查不出来!因为 NULL 让子查询“变得不确定”,SQL 就懵了:万一 NULL 就是我们要找的 courseid 呢?
用 EXISTS:
SELECT name
FROM courses c
WHERE EXISTS (
SELECT 1
FROM enrollments e
WHERE c.course_id = e.course_id
);
而 EXISTS 就是直接问:“有没有一行 course_id 一样的?”——就这么简单。它完全不怕有 NULL,因为它只找具体的匹配,不管值列表。
结论:如果子查询里可能有 NULL,最好用 EXISTS,不然容易踩坑。
实际问题举例
students 表:
| id | name |
|---|---|
| 1 | Alex Lin |
| 2 | Anna Song |
| 3 | Maria Chi |
| 4 | Dan Seth |
| 5 | Shadow Moon |
enrollments 表:
| student_id | course_id |
|---|---|
| 1 | 1 |
| 2 | 2 |
| 3 | NULL |
例子 1. 已经选了课的学生
找找那些已经“露脸”——选了课的(哪怕像 Maria Chi 这种奇怪的):
SELECT name
FROM students s
WHERE EXISTS (
SELECT 1
FROM enrollments e
WHERE s.id = e.student_id
);
结果:
Alex Lin
Anna Song
Maria Chi
只要学生在 enrollments 里出现过——不管 course_id 是不是 NULL——都会查出来。
例子 2. 没选课的学生
现在找找那些还只是“存在”但没选课的:
SELECT name
FROM students s
WHERE NOT EXISTS (
SELECT 1
FROM enrollments e
WHERE s.id = e.student_id
);
结果:
Dan Seth
Shadow Moon
看来这俩还没找到喜欢的课。或者就是忘了报名 :)
例子 3. 查找报名人数超过 5 的课程
courses 表:
| course_id | name |
|---|---|
| 1 | 数学 |
| 2 | 历史 |
| 3 | 生物 |
| 4 | 哲学 |
enrollments 表:
| student_id | course_id |
|---|---|
| 1 | 1 |
| 2 | 1 |
| 3 | 1 |
| 4 | 1 |
| 5 | 1 |
| 6 | 1 |
| 7 | 2 |
| 8 | 2 |
| 9 | 2 |
| 10 | NULL |
我们想找那些报名人数超过五个的课程。这里 EXISTS 就像在问:“这个课程有没有一组报名记录,学生数大于五?”
SELECT name
FROM courses c
WHERE EXISTS (
SELECT 1
FROM enrollments e
WHERE c.course_id = e.course_id
GROUP BY e.course_id
HAVING COUNT(*) > 5
);
结果:
数学
只有“数学”(course_id = 1)有六个学生报名。其他课程还没这么火。
用 EXISTS 和 NOT EXISTS 时常见的坑
- 对子查询语法理解错了。一定要检查子查询有没有正确引用外部表。
- 忘了处理
NULL。即使用EXISTS,有时候也得明确写出NULL的处理。 - 子查询字段没加索引。这样会让查询慢得飞起。
今天就到这!现在你已经知道怎么用 EXISTS 和 NOT EXISTS 检查数据是否存在,也明白了它们和 IN 的区别。下节课我们继续深入子查询,看看怎么在 SELECT 里用它们处理聚合数据。
GO TO FULL VERSION