子查询是SQL中一种强大的查询构造方式,它允许我们在一个查询中嵌套另一个查询。这种嵌套结构为我们处理复杂的数据关系提供了极大的灵活性。在实际工作中,我经常使用子查询来解决各种数据检索问题,特别是当需要基于中间结果进行进一步筛选或计算时。
单行子查询是指内层查询返回单一值(单行单列)的情况。这种子查询的结果可以像普通值一样在外层查询中使用,通常与单行比较运算符配合使用。
sql复制-- 查找与员工ID为141相同职位且工资高于员工ID为143的所有员工
SELECT last_name, job_id, salary
FROM employees
WHERE job_id = (
SELECT job_id
FROM employees
WHERE employee_id = 141
)
AND salary > (
SELECT salary
FROM employees
WHERE employee_id = 143
);
注意:单行子查询必须确保只返回一行结果,否则会引发错误。在实际应用中,我通常会先单独运行内层查询验证结果是否符合预期。
多行子查询返回多行单列的结果集,需要使用特定的多行比较运算符来处理:
sql复制-- 查找非IT_PROG部门中工资低于IT_PROG部门任一员工的员工
SELECT employee_id, last_name, job_id, salary
FROM employees
WHERE job_id <> 'IT_PROG'
AND salary < ANY (
SELECT salary
FROM employees
WHERE job_id = 'IT_PROG'
);
多行比较运算符包括:
IN:等于结果集中的任意一个值ANY/SOME:与结果集中的每个值比较,至少满足一个比较条件ALL:与结果集中的每个值比较,必须满足所有比较条件不相关子查询是指内层查询可以独立执行,不依赖于外层查询。这类子查询通常先执行一次,然后将结果传递给外层查询。
sql复制-- 查询工资高于公司平均工资的员工
SELECT last_name, salary, department_id
FROM employees
WHERE salary > (
SELECT AVG(salary)
FROM employees
);
相关子查询的内层查询依赖于外层查询的当前行,需要为外层查询的每一行执行一次。
sql复制-- 查询工资高于本部门平均工资的员工
SELECT last_name, salary, department_id
FROM employees e1
WHERE salary > (
SELECT AVG(salary)
FROM employees e2
WHERE department_id = e1.department_id
);
实操心得:相关子查询虽然功能强大,但性能开销较大。对于大数据表,我通常会考虑使用JOIN或临时表来优化这类查询。
WHERE子句中的子查询常用于数据过滤,这是子查询最典型的应用场景。
sql复制-- 查找没有下属的管理者
SELECT employee_id, last_name
FROM employees e
WHERE NOT EXISTS (
SELECT 1
FROM employees
WHERE manager_id = e.employee_id
);
FROM子句中的子查询实际上创建了一个临时表,可以像普通表一样被引用。
sql复制-- 使用FROM子查询计算部门平均工资并筛选
SELECT e.last_name, e.salary, e.department_id
FROM employees e, (
SELECT department_id, AVG(salary) avg_sal
FROM employees
GROUP BY department_id
) t_dept_avg_sal
WHERE e.department_id = t_dept_avg_sal.department_id
AND e.salary > t_dept_avg_sal.avg_sal;
ORDER BY子句中的子查询可以实现基于复杂条件的排序。
sql复制-- 按部门名称排序员工
SELECT employee_id, salary
FROM employees e
ORDER BY (
SELECT department_name
FROM departments d
WHERE e.department_id = d.department_id
) ASC;
SELECT子句中的子查询可以为每行计算一个值。
sql复制-- 显示每个员工及其部门平均工资的差异
SELECT employee_id, salary,
salary - (
SELECT AVG(salary)
FROM employees e2
WHERE e2.department_id = e1.department_id
) AS salary_diff
FROM employees e1;
对于大数据集,EXISTS通常比IN更高效,因为它找到第一个匹配项就会停止搜索。
sql复制-- 使用EXISTS查找有下属的管理者
SELECT employee_id, last_name
FROM employees e
WHERE EXISTS (
SELECT 1
FROM employees
WHERE manager_id = e.employee_id
);
相关子查询往往性能较差,转换为JOIN可以显著提高效率。
sql复制-- 将相关子查询转换为JOIN
SELECT e.last_name, e.salary, e.department_id
FROM employees e
JOIN (
SELECT department_id, AVG(salary) avg_sal
FROM employees
GROUP BY department_id
) dept_avg
ON e.department_id = dept_avg.department_id
WHERE e.salary > dept_avg.avg_sal;
对于多层嵌套的子查询,使用派生表可以提高可读性和性能。
sql复制-- 使用派生表优化复杂查询
WITH dept_stats AS (
SELECT department_id,
AVG(salary) AS avg_sal,
MAX(salary) AS max_sal
FROM employees
GROUP BY department_id
)
SELECT e.employee_id, e.last_name, e.salary,
e.salary - ds.avg_sal AS diff_from_avg
FROM employees e
JOIN dept_stats ds ON e.department_id = ds.department_id
WHERE e.salary > ds.avg_sal;
当单行子查询意外返回多行时,MySQL会报错。解决方案包括:
对于慢速的子查询,可以尝试:
子查询中NULL值的比较需要特别注意:
IN和NOT IN对NULL值的处理可能不符合预期IS NULL或IS NOT NULL明确处理NULLCOALESCE函数提供默认值sql复制-- 正确处理NULL值的子查询
SELECT employee_id, last_name
FROM employees
WHERE department_id IN (
SELECT department_id
FROM departments
WHERE location_id IS NOT NULL
);
MySQL 8.0引入了递归CTE,可以处理层次结构数据。
sql复制-- 查找员工及其所有下属(递归查询)
WITH RECURSIVE emp_hierarchy AS (
-- 基础查询:选择顶级管理者
SELECT employee_id, last_name, manager_id, 1 AS level
FROM employees
WHERE manager_id IS NULL
UNION ALL
-- 递归查询:连接下属员工
SELECT e.employee_id, e.last_name, e.manager_id, eh.level + 1
FROM employees e
JOIN emp_hierarchy eh ON e.manager_id = eh.employee_id
)
SELECT * FROM emp_hierarchy
ORDER BY level, last_name;
横向子查询允许子查询引用前面FROM子句中的表。
sql复制-- 查找每个部门工资最高的员工
SELECT d.department_name, e.last_name, e.salary
FROM departments d,
LATERAL (
SELECT last_name, salary
FROM employees
WHERE department_id = d.department_id
ORDER BY salary DESC
LIMIT 1
) e;
子查询可以用于实现高效的分页查询。
sql复制-- 使用子查询优化分页
SELECT *
FROM employees
WHERE employee_id IN (
SELECT employee_id
FROM employees
ORDER BY salary DESC
LIMIT 10 OFFSET 20
)
ORDER BY salary DESC;
在实际数据库开发中,子查询是解决复杂数据检索问题的利器。掌握各种子查询技术,并根据具体场景选择最优的实现方式,是每个SQL开发者的必备技能。我个人的经验是,对于简单的过滤条件,子查询通常更直观;而对于复杂的多表关联,JOIN往往性能更好。关键是要理解每种方法的适用场景,并通过执行计划分析来验证查询效率。