SQL228. 使用join查询方式找出没有分类的电影id以及名称
题目描述
使用 JOIN 查询方式,找出没有分类的电影 id 以及电影名称。
表结构
表1:film(电影信息表)
CREATE TABLE film (
film_id SMALLINT(5) PRIMARY KEY COMMENT '电影id',
title VARCHAR(255) COMMENT '电影名称',
description TEXT COMMENT '电影描述信息'
);
| 字段名 | 类型 | 说明 |
|---|---|---|
| film_id | SMALLINT(5) | 电影id,主键 |
| title | VARCHAR(255) | 电影名称 |
| description | TEXT | 电影描述信息 |
表2:category(类别表)
CREATE TABLE category (
category_id TINYINT(3) PRIMARY KEY COMMENT '电影分类id',
name VARCHAR(25) COMMENT '电影分类名称',
last_update TIMESTAMP COMMENT '最后更新时间'
);
| 字段名 | 类型 | 说明 |
|---|---|---|
| category_id | TINYINT(3) | 电影分类id,主键 |
| name | VARCHAR(25) | 电影分类名称 |
| last_update | TIMESTAMP | 最后更新时间 |
表3:film_category(电影分类关联表)
CREATE TABLE film_category (
film_id SMALLINT(5) COMMENT '电影id',
category_id TINYINT(3) COMMENT '电影分类id',
last_update TIMESTAMP COMMENT '最后更新时间',
PRIMARY KEY (film_id, category_id)
);
| 字段名 | 类型 | 说明 |
|---|---|---|
| film_id | SMALLINT(5) | 电影id |
| category_id | TINYINT(3) | 电影分类id |
| last_update | TIMESTAMP | 最后更新时间 |
示例数据
film
| film_id | title | description |
|---|---|---|
| 1 | ACADEMY DINOSAUR | ... |
| 2 | ACE GOLDFINGER | ... |
| 3 | ADAPTATION HOLES | ... |
film_category
| film_id | category_id | last_update |
|---|---|---|
| 1 | 6 | 2006-02-14 21:07:09 |
| 2 | 11 | 2006-02-14 21:07:09 |
预期输出
| film_id | title |
|---|---|
| 3 | ADAPTATION HOLES |
电影 3(ADAPTATION HOLES)在 film_category 表中没有对应记录,因此是没有分类的电影。
解题思路
第一步:理解需求
题目要求找出"没有分类"的电影。这意味着这些电影在 film 表中存在,但在 film_category 关联表中没有对应的记录。
第二步:分析表结构
三张表的关系:
film:存储所有电影的基本信息category:存储所有分类的名称film_category:存储电影和分类的关联关系
要判断一部电影是否有分类,只需检查它是否存在于 film_category 表中。
第三步:确定 SQL 方案
核心思路是左外连接 + NULL 判断:
- 将
film表 LEFT JOINfilm_category表 - 没有分类的电影在
film_category一侧会匹配为 NULL - 筛选
film_category.film_id IS NULL的记录
第四步:编写并验证 SQL
使用 LEFT JOIN 保证 film 表中的所有电影都被保留,即使它们在 film_category 中没有匹配项。
完整 SQL 实现
-- 方法一:LEFT JOIN + IS NULL —— 题目要求的 JOIN 方式
SELECT
f.film_id,
f.title
FROM film AS f
LEFT JOIN film_category AS fc
ON f.film_id = fc.film_id
WHERE fc.film_id IS NULL;
-- 方法二:NOT IN —— 另一种思路(非 JOIN 方式,供参考)
SELECT film_id, title
FROM film
WHERE film_id NOT IN (
SELECT film_id FROM film_category WHERE film_id IS NOT NULL
);
-- 方法三:NOT EXISTS —— 效率通常优于 NOT IN,尤其在数据量大时
SELECT film_id, title
FROM film AS f
WHERE NOT EXISTS (
SELECT 1 FROM film_category AS fc WHERE fc.film_id = f.film_id
);
解法详解
方法一:LEFT JOIN + IS NULL(推荐,符合题意)
SELECT f.film_id, f.title
FROM film AS f
LEFT JOIN film_category AS fc ON f.film_id = fc.film_id
WHERE fc.film_id IS NULL;
执行过程:
LEFT JOIN将film表的每一行与film_category表匹配- 电影 1 和 2 在
film_category中有匹配记录,fc.film_id有值 - 电影 3 在
film_category中没有匹配,fc.film_id为 NULL WHERE fc.film_id IS NULL筛选出电影 3
LEFT JOIN 图解:
film film_category (LEFT JOIN 结果)
+----+---------------+ +----+----+ +----+---------------+----+------+
| id | title | | id |cat | | id | title |cat_id|
+----+---------------+ +----+----+ +----+---------------+------+
| 1 | ACADEMY DINO |→ | 1 | 6 | | 1 | ACADEMY DINO | 6 |
| 2 | ACE GOLDFINGER|→ | 2 | 11 | | 2 | ACE GOLDFINGER| 11 |
| 3 | ADAPTATION... |→ 无匹配 | 3 | ADAPTATION... | NULL |
+----+---------------+ +----+---------------+------+
↑ 筛选 IS NULL
方法二:NOT IN
SELECT film_id, title FROM film
WHERE film_id NOT IN (
SELECT film_id FROM film_category WHERE film_id IS NOT NULL
);
注意:子查询中的 WHERE film_id IS NOT NULL 非常重要!如果 film_category.film_id 存在 NULL 值,NOT IN 会返回空结果(因为 x NOT IN (NULL, ...) 在 SQL 三值逻辑中结果为 UNKNOWN)。
方法三:NOT EXISTS
SELECT film_id, title FROM film AS f
WHERE NOT EXISTS (
SELECT 1 FROM film_category AS fc WHERE fc.film_id = f.film_id
);
NOT EXISTS 不会受 NULL 值影响,且在大数据量时通常比 NOT IN 性能更好,因为数据库可以更早停止扫描。
知识点总结
1. LEFT JOIN(左外连接)
LEFT JOIN 返回左表的所有记录,右表没有匹配时补 NULL。
SELECT * FROM A
LEFT JOIN B ON A.id = B.id;
| A.id | A.name | B.id | B.value |
|---|---|---|---|
| 1 | Alice | 1 | 100 |
| 2 | Bob | NULL | NULL |
2. JOIN 类型对比
| JOIN 类型 | 说明 |
|---|---|
| INNER JOIN | 只返回两表匹配的记录 |
| LEFT JOIN | 返回左表全部,右表不匹配补 NULL |
| RIGHT JOIN | 返回右表全部,左表不匹配补 NULL |
| FULL JOIN | 返回两表全部,不匹配侧补 NULL |
3. IS NULL 与 = NULL
在 SQL 中,必须使用 IS NULL 判断空值,不能用 = NULL。因为 NULL = NULL 的结果是 UNKNOWN,不是 TRUE。
易错点总结
1. 误用 INNER JOIN
-- 错误!INNER JOIN 只会返回有分类的电影
SELECT f.film_id, f.title
FROM film AS f
INNER JOIN film_category AS fc ON f.film_id = fc.film_id; -- 没有分类的电影被过滤掉了
2. NOT IN 遇到 NULL 值
-- 危险写法!如果 film_category.film_id 有 NULL,结果为空
SELECT * FROM film
WHERE film_id NOT IN (SELECT film_id FROM film_category); -- 不要省略 WHERE 子查询的 IS NOT NULL
3. 混淆 LEFT JOIN 的方向
-- 错误!方向反了,结果会不同
SELECT * FROM film_category AS fc -- 这是左表!
LEFT JOIN film AS f ON f.film_id = fc.film_id
WHERE f.film_id IS NULL;
-- 这会返回 film_category 中有但 film 中没有的记录(通常是空结果)
扩展思考
- 如何找出有分类的电影? 使用
INNER JOIN或LEFT JOIN + WHERE fc.film_id IS NOT NULL。 - 如何统计每类电影的数量?
JOIN后GROUP BY category.name, COUNT(*)。 - 性能考虑:确保
film_category.film_id有索引,LEFT JOIN和NOT EXISTS都能高效执行。