1. CS50 Week 7 SQL 问题集深度解析
作为一名经历过CS50课程的学习者,我深知SQL问题集对初学者的挑战。Week 7的SQL问题集从基础查询到复杂推理,循序渐进地培养我们的数据库思维。本文将详细解析Songs、Movies和Fiftyville三个问题集的解决方案,并分享我在解决过程中的实战经验。
1.1 学习SQL的重要性
SQL是数据领域的通用语言,掌握它能让你:
- 高效地从海量数据中提取有价值的信息
- 理解数据之间的关系和连接方式
- 为后续学习Web开发、数据分析打下坚实基础
CS50的SQL问题集设计精妙,从单表查询逐步过渡到多表连接,最后以一个侦探游戏综合运用所有技能,这种渐进式的学习方式非常有效。
2. Songs问题集解析
2.1 数据库结构分析
首先我们查看songs.db的结构:
sql复制sqlite3 songs.db
sqlite> .schema
CREATE TABLE songs (
id INTEGER,
name TEXT,
artist_id INTEGER,
danceability REAL,
energy REAL,
key INTEGER,
loudness REAL,
speechiness REAL,
valence REAL,
tempo REAL,
duration_ms INTEGER
);
CREATE TABLE artists (
id INTEGER,
name TEXT
);
这个数据库包含两张表:
- songs表:存储歌曲的详细信息,包括名称、艺术家ID和各种音频特征
- artists表:存储艺术家的ID和名称
2.2 基础查询模板
对于初学者,我建议使用以下SQL查询模板:
sql复制SELECT [要显示的列]
FROM [数据来源的表]
WHERE [筛选条件]
ORDER BY [排序依据]
LIMIT [返回结果数量];
这个模板覆盖了80%的基础查询场景,下面我们用它来解决所有题目。
2.3 题目详解
2.3.1 列出所有歌曲名称
sql复制SELECT name
FROM songs;
这是最简单的查询,从songs表中选择name列。
2.3.2 按节奏升序列出歌曲
sql复制SELECT name
FROM songs
ORDER BY tempo;
使用ORDER BY对结果按tempo升序排列(默认ASC可省略)。
2.3.3 列出前5首最长的歌曲
sql复制SELECT name
FROM songs
ORDER BY duration_ms DESC
LIMIT 5;
这里使用了:
- ORDER BY duration_ms DESC:按时长降序排列
- LIMIT 5:只返回前5条记录
2.3.4 筛选特定特征的歌曲
sql复制SELECT name
FROM songs
WHERE danceability > 0.75 AND energy > 0.75;
WHERE子句中使用AND连接多个条件,筛选出同时满足两个条件的歌曲。
2.3.5 计算平均能量值
sql复制SELECT AVG(energy)
FROM songs;
使用AVG聚合函数计算energy列的平均值。
2.3.6 查询特定艺术家的歌曲(子查询)
sql复制SELECT name
FROM songs
WHERE artist_id = (
SELECT id
FROM artists
WHERE name = 'Post Malone'
);
这里使用了子查询:
- 先在artists表中找到Post Malone的ID
- 然后在songs表中查找对应artist_id的歌曲
2.3.7 计算特定艺术家的平均能量值
sql复制SELECT AVG(energy)
FROM songs
WHERE artist_id = (
SELECT id
FROM artists
WHERE name = 'Drake'
);
与上题类似,只是将SELECT name改为AVG(energy)。
2.3.8 模糊查询包含"feat."的歌曲
sql复制SELECT name
FROM songs
WHERE name LIKE '%feat.%';
LIKE操作符配合通配符%实现模糊查询,'%feat.%'表示包含"feat."的任意位置。
2.4 经验总结
洋葱剥皮法
- 先用SELECT * FROM table查看所有数据
- 添加WHERE条件过滤数据
- 使用ORDER BY和LIMIT整理结果
- 最后调整SELECT选择需要的列
常用SQL关键字速查
| 关键字 | 用途 | 示例 |
|---|---|---|
| SELECT | 选择列 | SELECT name |
| FROM | 指定表 | FROM songs |
| WHERE | 筛选条件 | WHERE energy > 0.75 |
| ORDER BY | 排序 | ORDER BY tempo DESC |
| LIMIT | 限制数量 | LIMIT 5 |
| AVG() | 平均值 | SELECT AVG(energy) |
| LIKE | 模糊匹配 | WHERE name LIKE '%feat.%' |
3. Movies问题集解析
3.1 数据库结构分析
movies.db的结构更复杂:
sql复制CREATE TABLE directors (
movie_id INTEGER NOT NULL,
person_id INTEGER NOT NULL
);
CREATE TABLE movies (
id INTEGER,
title TEXT NOT NULL,
year NUMERIC
);
CREATE TABLE people (
id INTEGER,
name TEXT NOT NULL,
birth NUMERIC
);
CREATE TABLE ratings (
movie_id INTEGER NOT NULL UNIQUE,
rating REAL NOT NULL,
votes INTEGER NOT NULL
);
CREATE TABLE stars (
movie_id INTEGER NOT NULL,
person_id INTEGER NOT NULL
);
这是一个典型的多对多关系数据库:
- movies:电影基本信息
- people:人员信息(演员和导演)
- ratings:电影评分
- stars:演员与电影的关联表
- directors:导演与电影的关联表
3.2 精选难题解析
3.2.1 多表连接查询
查询2010年电影及其评分:
sql复制SELECT movies.title, ratings.rating
FROM movies
JOIN ratings ON movies.id = ratings.movie_id
WHERE year = 2010
ORDER BY rating DESC, title;
这里使用了JOIN连接movies和ratings表,ON指定连接条件。
3.2.2 三表连接查询
查询出演过《Toy Story》的演员:
sql复制SELECT people.name
FROM people
JOIN stars ON people.id = stars.person_id
JOIN movies ON stars.movie_id = movies.id
WHERE movies.title = 'Toy Story';
需要连接people、stars和movies三张表。
3.2.3 使用DISTINCT去重
查询出演2004年电影的演员并按出生年份排序:
sql复制SELECT DISTINCT(name)
FROM people
WHERE id IN (
SELECT person_id
FROM stars
WHERE movie_id IN (
SELECT id
FROM movies
WHERE year = 2004
)
)
ORDER BY birth;
DISTINCT确保结果中不出现重复的演员姓名。
3.2.4 复杂JOIN查询
查询Chadwick Boseman主演的评分最高的5部电影:
sql复制SELECT title
FROM movies
JOIN stars ON movies.id = stars.movie_id
JOIN people ON stars.person_id = people.id
JOIN ratings ON movies.id = ratings.movie_id
WHERE people.name = 'Chadwick Boseman'
ORDER BY rating DESC
LIMIT 5;
这个查询连接了4张表,展示了JOIN的强大功能。
3.2.5 交集查询
查询Bradley Cooper和Jennifer Lawrence共同主演的电影:
sql复制SELECT title FROM movies
WHERE id IN (
SELECT movie_id FROM stars
WHERE person_id IN (
SELECT id FROM people
WHERE name = 'Bradley Cooper'
)
)
AND id IN (
SELECT movie_id FROM stars
WHERE person_id IN (
SELECT id FROM people
WHERE name = 'Jennifer Lawrence'
)
);
使用两个子查询和AND操作符找出交集。
3.3 子查询 vs JOIN对比
| 特性 | 子查询 | JOIN |
|---|---|---|
| 思维方式 | 垂直钻取 | 水平拼接 |
| 适用场景 | 单结果列 | 多表多列 |
| 可读性 | 嵌套较深 | 表多时直观 |
对于初学者,我建议先掌握子查询,再学习JOIN,最后根据具体情况选择合适的方法。
4. Fiftyville侦探游戏
4.1 数据库结构
fiftyville.db包含多张表:
- crime_scene_reports:犯罪现场报告
- interviews:证人访谈记录
- atm_transactions:ATM交易记录
- bank_accounts:银行账户
- airports:机场信息
- flights:航班信息
- passengers:乘客信息
- phone_calls:电话记录
- people:人员信息
- bakery_security_logs:面包店安全日志
4.2 破案步骤
4.2.1 查阅犯罪报告
sql复制SELECT description
FROM crime_scene_reports
WHERE year = 2025 AND month = 7 AND day = 28
AND street = 'Humphrey Street';
确定案件发生在7月28日上午10:15的Humphrey Street面包店。
4.2.2 分析证人证词
sql复制SELECT name, transcript FROM interviews
WHERE year = 2025 AND month = 7 AND day = 28;
获得三条关键线索:
- 小偷在10:15-10:25间驾车离开
- 当天在Leggett Street ATM取款
- 计划乘坐次日最早的航班离开
4.2.3 综合查询锁定嫌疑人
sql复制SELECT name
FROM people
WHERE license_plate IN (
SELECT license_plate FROM bakery_security_logs
WHERE year = 2025 AND month = 7 AND day = 28
AND hour = 10 AND minute BETWEEN 15 AND 25
AND activity = 'exit'
)
AND id IN (
SELECT person_id FROM bank_accounts
WHERE account_number IN (
SELECT account_number FROM atm_transactions
WHERE year = 2025 AND month = 7 AND day = 28
AND atm_location = 'Leggett Street'
AND transaction_type = 'withdraw'
)
)
AND phone_number IN (
SELECT caller FROM phone_calls
WHERE year = 2025 AND month = 7 AND day = 28
AND duration < 60
)
AND passport_number IN (
SELECT passport_number FROM passengers
WHERE flight_id = (
SELECT id FROM flights
WHERE year = 2025 AND month = 7 AND day = 29
ORDER BY hour, minute LIMIT 1
)
);
这个复杂查询综合所有线索,最终锁定嫌疑人Bruce。
4.2.4 确定逃跑目的地
sql复制SELECT city FROM airports
WHERE id = (
SELECT destination_airport_id FROM flights
WHERE year = 2025 AND month = 7 AND day = 29
ORDER BY hour, minute LIMIT 1
);
查询结果为New York City。
4.2.5 找出同伙
sql复制SELECT name FROM people
WHERE phone_number = (
SELECT receiver FROM phone_calls
WHERE year = 2025 AND month = 7 AND day = 28
AND duration < 60
AND caller = (
SELECT phone_number FROM people WHERE name = 'Bruce'
)
);
通过通话记录找到同伙Robin。
4.3 案件结论
| 问题 | 答案 |
|---|---|
| 小偷是谁? | Bruce |
| 逃往哪个城市? | New York City |
| 同伙是谁? | Robin |
5. SQL学习进阶路线
| 阶段 | 技能点 | 对应题目 |
|---|---|---|
| 入门 | 基础查询 | Songs 1-5 |
| 进阶 | 聚合函数、模糊查询 | Songs 5-8 |
| 中级 | 子查询 | Movies 6 |
| 高级 | 多表连接 | Movies 7-11 |
| 专家 | 复杂逻辑推理 | Fiftyville |
在实际工作中,我经常使用这些SQL技能分析数据。掌握好CS50的这些练习,能为实际工作打下坚实基础。特别是多表连接和复杂查询,在数据分析工作中非常实用。
对于初学者,我的建议是:
- 先理解数据库结构
- 从简单查询开始,逐步增加复杂度
- 多用EXPLAIN分析查询执行计划
- 尝试用不同方法解决同一问题
记住,SQL是一种实践性很强的技能,多练习才能真正掌握。CS50的这些问题是很好的起点,但真正的学习是在实际项目中不断应用和优化。