📚 前言:什么是子查询?
想象一下,你想在图书馆找书:
先找到"计算机"分类的书架(主查询)
在这个书架上找"MySQL"相关的书(子查询)
子查询就是:在一个SQL语句里面,嵌套另一个SQL查询语句。就像"俄罗斯套娃"一样,一层套一层!
第一部分:子查询的三种基础用法
1.1 最简单的子查询:作为查询条件
场景:找出买了"iPhone 15"的顾客信息
bash
sql
-- 分两步理解:
-- 第一步:先找出哪些订单买了iPhone 15
SELECT customer_id
FROM orders
WHERE product_name = 'iPhone 15';
-- 第二步:用第一步的结果找顾客信息
SELECT *
FROM customers
WHERE customer_id IN (上一步的结果);
-- 合并成一句就是:
SELECT *
FROM customers
WHERE customer_id IN (
SELECT customer_id
FROM orders
WHERE product_name = 'iPhone 15'
);
📝 语法要点:
子查询要用小括号 () 包起来
子查询写在 WHERE 条件里
IN 表示"在...列表中"
1.2 稍微复杂点:子查询在SELECT中
场景:显示每个顾客的订单数量
bash
sql
-- 传统方法(需要两张表连接):
SELECT
c.customer_name,
COUNT(o.order_id) as order_count
FROM customers c
LEFT JOIN orders o ON c.customer_id = o.customer_id
GROUP BY c.customer_name;
-- 使用子查询的方法:
SELECT
customer_name,
(SELECT COUNT(*)
FROM orders
WHERE customer_id = c.customer_id) as order_count
FROM customers c;
✨ 对比:
子查询版本更直观:一眼就能看出是"为每个顾客统计订单数"
适合初学者理解
1.3 实战练习:生活中的例子
bash
sql
-- 找出比平均工资高的员工
SELECT name, salary
FROM employees
WHERE salary > (
SELECT AVG(salary) -- 先算平均工资
FROM employees
);
-- 找出最畅销的产品
SELECT product_name
FROM products
WHERE sales = (
SELECT MAX(sales) -- 先找最高销量
FROM products
);
第二部分:用子查询操作数据(增删改)
2.1 用子查询插入数据 📥
场景:创建"VIP客户表",把消费超过5000的客户放进去
bash
sql
-- 第一步:创建VIP表
CREATE TABLE vip_customers (
id INT PRIMARY KEY,
name VARCHAR(50),
total_spent DECIMAL(10,2)
);
-- 第二步:插入数据(使用子查询)
INSERT INTO vip_customers (id, name, total_spent)
SELECT
customer_id,
customer_name,
SUM(amount) -- 计算每个客户的总消费
FROM orders
GROUP BY customer_id, customer_name
HAVING SUM(amount) > 5000; -- 只选消费>5000的
💡 小贴士:
INSERT INTO ... SELECT ... 是最常用的组合
子查询的结果直接插入到目标表
字段要一一对应(数量、类型都要匹配)
2.2 用子查询更新数据 ✏️
场景:给消费大户打标签
bash
sql
-- 给消费超过10000的客户标记为"钻石会员"
UPDATE customers
SET member_level = '钻石会员'
WHERE customer_id IN (
SELECT customer_id
FROM orders
GROUP BY customer_id
HAVING SUM(amount) > 10000
);
-- 分步骤理解:
-- 1. 子查询:找出消费>10000的客户ID
-- 2. 主查询:把这些客户的会员级别更新
⚠️ 注意:MySQL中,更新时如果用同一张表做子查询,需要特殊处理:
bash
sql
-- 错误写法(MySQL不允许):
UPDATE customers
SET score = score + 10
WHERE customer_id IN (
SELECT customer_id
FROM customers
WHERE age > 30
);
-- 正确写法:
UPDATE customers c1
SET score = score + 10
WHERE EXISTS (
SELECT 1
FROM customers c2
WHERE c2.customer_id = c1.customer_id
AND c2.age > 30
);
2.3 用子查询删除数据 🗑️
场景:删除30天未登录的用户
bash
sql
-- 删除30天没登录的用户
DELETE FROM users
WHERE user_id IN (
SELECT user_id
FROM login_log
GROUP BY user_id
HAVING MAX(login_time) < DATE_SUB(NOW(), INTERVAL 30 DAY)
);
-- 更安全的写法(先查后删):
bash
-- 第一步:先看看要删哪些人
SELECT * FROM users
WHERE user_id IN (...上面的查询...);
-- 第二步:确认无误后再删除
🔒 安全提示:
删除前一定要先 SELECT 确认数据
重要数据用软删除(标记为删除,不真删)
做好备份!
第三部分:通用表达式(CTE)------让SQL更清晰
3.1 CTE是什么?为什么需要它?
问题:当子查询嵌套太多时...
bash
sql
-- 这种代码很难看懂:
SELECT * FROM (
SELECT * FROM (
SELECT * FROM table1 WHERE ...
) AS t1 JOIN table2 ON ...
) AS t2 WHERE ...
解决方案:CTE(Common Table Expressions)!
给子查询起个名字
像搭积木一样组合查询
提高可读性
3.2 CTE基础语法
bash
sql
WITH 临时表名 AS (
SELECT ... -- 你的查询
)
SELECT * FROM 临时表名;
3.3 实战:用CTE重构复杂查询
场景:分析学生成绩
bash
sql
-- 不使用CTE(比较乱):
SELECT
student_name,
course_name,
score,
(SELECT AVG(score) FROM scores WHERE course_id = s.course_id) as avg_score
FROM scores s
WHERE score > (
SELECT AVG(score)
FROM scores
WHERE student_id = s.student_id
);
-- 使用CTE(清晰多了):
WITH
-- 第一步:计算每门课的平均分
course_avg AS (
SELECT
course_id,
AVG(score) as avg_score
FROM scores
GROUP BY course_id
),
-- 第二步:计算每个学生的平均分
student_avg AS (
SELECT
student_id,
AVG(score) as avg_score
FROM scores
GROUP BY student_id
)
-- 第三步:组合查询
SELECT
s.student_name,
c.course_name,
sc.score,
ca.avg_score as course_avg,
sa.avg_score as student_avg
FROM scores sc
JOIN students s ON sc.student_id = s.student_id
JOIN courses c ON sc.course_id = c.course_id
JOIN course_avg ca ON sc.course_id = ca.course_id
JOIN student_avg sa ON sc.student_id = sa.student_id
WHERE sc.score > sa.avg_score;
3.4 CTE的特别功能:递归查询
神奇的场景:查询公司组织架构
bash
sql
-- 创建员工表(有上下级关系)
CREATE TABLE employees (
id INT PRIMARY KEY,
name VARCHAR(50),
manager_id INT -- 上级的ID
);
-- 使用递归CTE查询某个经理的所有下属
WITH RECURSIVE subordinate_tree AS (
-- 基础情况:先找到经理本人
SELECT id, name, manager_id, 1 as level
FROM employees
WHERE name = '张经理'
UNION ALL
-- 递归情况:找下属的下属...
SELECT
e.id,
e.name,
e.manager_id,
st.level + 1
FROM employees e
JOIN subordinate_tree st ON e.manager_id = st.id
)
SELECT * FROM subordinate_tree;
递归CTE的组成:
基础查询:起点(比如:经理本人)
递归查询:不断找下一级
停止条件:没有更多下级时自动停止
第四部分:常见错误和解决方法
❌ 错误1:子查询返回多行
bash
sql
-- 错误:等号只能比较一个值
SELECT * FROM products
WHERE price = (
SELECT price FROM products WHERE category = '手机'
);
-- 正确:用IN代替=
SELECT * FROM products
WHERE price IN (
SELECT price FROM products WHERE category = '手机'
);
❌ 错误2:忽略NULL值
bash
sql
-- 可能漏掉数据
SELECT * FROM customers
WHERE customer_id NOT IN (
SELECT customer_id FROM orders
);
-- 正确处理NULL
SELECT * FROM customers
WHERE customer_id NOT IN (
SELECT customer_id
FROM orders
WHERE customer_id IS NOT NULL -- 排除NULL
);
❌ 错误3:性能问题
bash
sql
-- 慢:子查询执行很多次
SELECT * FROM customers c
WHERE EXISTS (
SELECT 1 FROM orders o
WHERE o.customer_id = c.customer_id
AND o.amount > 1000
);
-- 优化:使用JOIN
SELECT DISTINCT c.*
FROM customers c
JOIN orders o ON c.customer_id = o.customer_id
WHERE o.amount > 1000;
💪 实战练习题
bash
练习1:电商数据分析
sql
-- 1. 找出从未下单的用户
-- 2. 找出复购率最高的商品
-- 3. 计算每个用户的平均订单金额
练习2:员工管理系统
sql
-- 1. 找出每个部门工资最高的员工
-- 2. 找出有下属的经理
-- 3. 计算部门平均工资
练习3:学校成绩管理
sql
-- 1. 找出每科成绩前3名的学生
-- 2. 计算每个学生的总分和平均分
-- 3. 找出成绩进步最大的学生
📖 学习资源推荐
在线练习平台:
SQLZoo(免费,互动式学习)
LeetCode SQL题库(从易到难)
可视化工具:
MySQL Workbench(官方工具)
DataGrip(功能强大)
记忆口诀:
text
子查询,括号里
先执行,后外批
增删改,要小心
CTE让代码清
🎉 总结:关键要点速记
💡 最后的小建议
从简单开始:先写分步骤查询,再合并成子查询
多实践:在自己的项目中找机会使用
善用注释:复杂查询一定要写注释
测试数据:用小数据测试,确认正确后再用到大表
记住:学习SQL就像学做饭,开始可能手忙脚乱,多做几次就熟练了!遇到问题不要怕,多查资料多实践,你一定能掌握子查询这个强大工具! 🚀
祝你学习愉快,早日成为SQL高手! ✨