SQL子查询本质上是一个嵌套在其他SQL语句中的完整查询语句。它就像是一个独立的小程序,先执行自己的运算,然后把结果传递给外层查询使用。这种嵌套结构让SQL具备了更强大的数据处理能力。
从执行方式来看,子查询主要分为两种类型:
从返回结果来看,子查询又可以分为:
这是最常见的子查询用法,通过子查询动态生成过滤条件。例如查找销售额高于平均值的商品:
sql复制SELECT product_name, price
FROM products
WHERE price > (SELECT AVG(price) FROM products);
这里子查询先计算出商品平均价格,然后外层查询用这个值作为过滤条件。
注意:在WHERE中使用子查询时,要特别注意子查询返回的结果类型必须与比较运算符匹配。比如使用IN运算符时,子查询应该返回单列多行结果。
子查询可以作为临时表出现在FROM子句中,这种用法在处理复杂逻辑时特别有用:
sql复制SELECT d.dept_name, e.avg_salary
FROM departments d
JOIN (
SELECT dept_id, AVG(salary) as avg_salary
FROM employees
GROUP BY dept_id
) e ON d.dept_id = e.dept_id;
这个例子中,子查询先按部门计算平均薪资生成临时表,然后与部门表关联查询。
在SELECT列表中使用返回单值的子查询,可以为每行结果动态计算附加信息:
sql复制SELECT
product_id,
product_name,
price,
(SELECT AVG(price) FROM products) as avg_price,
price - (SELECT AVG(price) FROM products) as price_diff
FROM products;
这种用法需要注意子查询的效率问题,因为它会为结果集的每一行都执行一次。
相关子查询在SELECT列表中会导致性能问题,因为它会为每行数据执行一次。例如:
sql复制-- 低效写法
SELECT
o.order_id,
(SELECT COUNT(*) FROM order_items oi WHERE oi.order_id = o.order_id) as item_count
FROM orders o;
-- 优化写法
SELECT
o.order_id,
COUNT(oi.item_id) as item_count
FROM orders o
LEFT JOIN order_items oi ON o.order_id = oi.order_id
GROUP BY o.order_id;
当检查记录是否存在时,EXISTS通常比IN更高效:
sql复制-- 低效写法
SELECT *
FROM customers
WHERE customer_id IN (SELECT customer_id FROM orders);
-- 优化写法
SELECT *
FROM customers c
WHERE EXISTS (SELECT 1 FROM orders o WHERE o.customer_id = c.customer_id);
EXISTS在找到第一个匹配项后就会停止搜索,而IN需要收集所有结果。
确保子查询中使用的列都有适当的索引:
sql复制-- 确保orders表的customer_id有索引
SELECT *
FROM customers
WHERE customer_id IN (SELECT customer_id FROM orders WHERE order_date > '2023-01-01');
公共表表达式(CTE)可以让复杂子查询更易读:
sql复制WITH dept_stats AS (
SELECT
dept_id,
AVG(salary) as avg_salary,
COUNT(*) as emp_count
FROM employees
GROUP BY dept_id
)
SELECT
d.dept_name,
ds.avg_salary,
ds.emp_count
FROM departments d
JOIN dept_stats ds ON d.dept_id = ds.dept_id;
MySQL 8.0+支持LATERAL JOIN,允许子查询引用前面表的列:
sql复制SELECT
d.dept_name,
top_emp.emp_name,
top_emp.salary
FROM departments d
CROSS JOIN LATERAL (
SELECT emp_name, salary
FROM employees e
WHERE e.dept_id = d.dept_id
ORDER BY salary DESC
LIMIT 3
) AS top_emp;
窗口函数可以替代一些需要子查询的场景,性能更好:
sql复制-- 使用子查询
SELECT
emp_id,
emp_name,
salary,
(SELECT AVG(salary) FROM employees) as avg_salary
FROM employees;
-- 使用窗口函数
SELECT
emp_id,
emp_name,
salary,
AVG(salary) OVER() as avg_salary
FROM employees;
当子查询可能返回多行但外层查询期望单值时,会出现错误:
sql复制-- 错误示例
SELECT *
FROM products
WHERE price = (SELECT price FROM special_offers);
解决方案:
子查询可能导致性能下降的几种情况:
解决方案:
子查询中NULL值可能导致意外结果:
sql复制SELECT *
FROM table1
WHERE col1 NOT IN (SELECT col2 FROM table2);
如果table2.col2包含NULL值,整个查询可能返回空结果。解决方案:
sql复制SELECT *
FROM table1
WHERE col1 NOT IN (SELECT col2 FROM table2 WHERE col2 IS NOT NULL);
找出销售额高于品类平均的商品:
sql复制SELECT
p.product_id,
p.product_name,
c.category_name,
SUM(oi.quantity * oi.unit_price) as total_sales
FROM products p
JOIN categories c ON p.category_id = c.category_id
JOIN order_items oi ON p.product_id = oi.product_id
GROUP BY p.product_id, p.product_name, c.category_name
HAVING SUM(oi.quantity * oi.unit_price) > (
SELECT AVG(cat_sales.avg_sales)
FROM (
SELECT
p.category_id,
AVG(SUM(oi.quantity * oi.unit_price)) as avg_sales
FROM products p
JOIN order_items oi ON p.product_id = oi.product_id
GROUP BY p.product_id, p.category_id
) cat_sales
WHERE cat_sales.category_id = p.category_id
);
找出薪资高于部门平均且绩效为A的员工:
sql复制SELECT
e.emp_id,
e.emp_name,
e.salary,
d.dept_name
FROM employees e
JOIN departments d ON e.dept_id = d.dept_id
WHERE e.performance = 'A'
AND e.salary > (
SELECT AVG(salary)
FROM employees
WHERE dept_id = e.dept_id
);
找出每门课程成绩高于该课程平均分的学生:
sql复制SELECT
s.student_id,
s.student_name,
c.course_name,
sc.score
FROM students s
JOIN student_courses sc ON s.student_id = sc.student_id
JOIN courses c ON sc.course_id = c.course_id
WHERE sc.score > (
SELECT AVG(score)
FROM student_courses
WHERE course_id = sc.course_id
);
明确子查询目的:在编写子查询前,先明确要实现什么逻辑,是否真的需要子查询。
优先考虑JOIN:很多子查询可以重写为JOIN,性能通常更好。
注意子查询位置:
控制子查询复杂度:过度嵌套的子查询难以维护,考虑使用CTE拆分。
测试不同写法:对于关键查询,尝试多种写法并比较执行计划。
索引是关键:确保子查询中使用的连接条件和过滤条件都有适当索引。
监控性能:上线后监控子查询性能,特别是数据量增长时。
适时考虑物化视图:对于频繁使用的复杂子查询,考虑使用物化视图。
子查询是SQL强大功能的体现,合理使用可以让查询更灵活、更强大。但也要注意避免滥用,特别是在处理大数据量时。掌握各种子查询技巧,根据实际场景选择最合适的实现方式,是成为SQL高手的必经之路。