核心目标: 学习如何使用 JOIN
、UNION
、自连接和子查询等技术,从一个或多个表中检索和组合复杂的数据。
前提:
SELECT
查询 (SELECT
, FROM
, WHERE
, ORDER BY
, LIMIT
等)。JOIN
)作用:将来自两个或多个表中的行,基于它们之间的相关列组合起来。
INNER JOIN
或 JOIN
)作用:返回两个(或多个)表中 满足连接条件 的行。不匹配的行被排除。最常用。
语法:
select column_list
from table1
inner join table2 on join_condition;
示例:
-- 查询学生及其专业名称 (只显示有匹配专业的学生)
select s.student_name, m.major_name
from students s
inner join majors m on s.major_id = m.major_id;
OUTER JOIN
)LEFT JOIN
或 LEFT OUTER JOIN
)NULL
。select column_list
from table1 -- 左表
left join table2 on join_condition;
示例:
-- 查询所有学生及其专业 (没有专业的学生也会显示)
select s.student_name, m.major_name
from students s
left join majors m on s.major_id = m.major_id;
RIGHT JOIN
或 RIGHT OUTER JOIN
)NULL
。select column_list
from table1
right join table2 on join_condition; -- 右表
示例:
-- 查询所有专业及其学生 (没有学生的专业也会显示)
select s.student_name, m.major_name
from students s
right join majors m on s.major_id = m.major_id;
FULL OUTER JOIN
)NULL
。LEFT JOIN UNION RIGHT JOIN
模拟。select column_list from table1 left join table2 on join_condition
union
select column_list from table1 right join table2 on join_condition;
CROSS JOIN
)作用:返回两表的笛卡尔积(所有可能的行组合)。谨慎使用。
语法:
select column_list from table1 cross join table2;
USING
子句当连接的两个表中用于匹配的列名称完全相同时,可用 USING(common_column_name)
简化 ON
。
语法:
select ... from table1 join table2 using(common_column_name);
作用:将一个表连接到它自身,用于查询表内行与行之间的关系(如员工与经理)。必须使用表别名。
语法:
select alias1.column, alias2.column
from table_name alias1
join table_name alias2 on alias1.related_column = alias2.related_column;
示例 (查询员工及其经理):
select
e.emp_name as employee_name,
m.emp_name as manager_name
from
employees e
left join
employees m on e.manager_id = m.emp_id;
UNION
/ UNION ALL
)作用:合并两个或多个 SELECT
语句的结果集。
语法:
select column_list1 from table1 ...
union | union all
select column_list2 from table2 ...
[order by ...]
[limit ...]
规则:
SELECT
语句列数相同。UNION
去重,UNION ALL
不去重。示例:
-- 合并活跃客户和潜在客户邮箱 (去重)
select email from active_customers
union
select email from potential_customers;
-- 合并不同年份销售记录 (不去重)
select product_id, sale_date, amount from sales_2023
union all
select product_id, sale_date, amount from sales_2024;
作用:嵌套在其他 SQL 语句中的查询。根据返回结果的结构,可分为以下几类:
定义:返回 单个值(即一行一列)的子查询。
用途:可以用在任何需要单个值的地方,如 SELECT
列表、WHERE
子句的比较条件、SET
子句等。
示例:
-- WHERE 子句:查询工资高于平均工资的员工
select emp_name, salary
from employees
where salary > (select avg(salary) from employees); -- 子查询返回平均工资(单个值)
-- SELECT 列表:查询每个学生姓名及其选修的课程总数
select
s.student_name,
(select count(*) from enrollments e where e.student_id = s.student_id) as courses_taken -- 子查询计算每个学生的选课数(单个值)
from students s;
-- SET 子句 (在 UPDATE 中): 将所有员工工资设为平均工资 (危险操作示例)
-- update employees set salary = (select avg(salary) from employees);
定义:返回 单列多行 的子查询。
用途:主要用于 WHERE
子句中与 IN
, NOT IN
, ANY
, SOME
, ALL
等操作符结合使用。
示例:
-- 使用 IN:查询选修了 'CS101' 或 'MA201' 课程的学生姓名
select student_name
from students
where student_id in (select student_id from enrollments where course_id in ('CS101', 'MA201')); -- 子查询返回选了这两门课的学生 ID 列表
-- 使用 NOT IN:查询没有下过订单的客户
select customer_name
from customers
where customer_id not in (select distinct customer_id from orders); -- 子查询返回下过单的客户 ID 列表
-- 使用 ANY/SOME:查询工资高于 IT 部门(dept_id=2)任意一个员工工资的其他部门员工
-- (注意: 不包括 IT 部门自身员工)
select emp_name, salary
from employees
where salary > any (select salary from employees where dept_id = 2)
and dept_id != 2; -- 或 dept_id is null
-- 使用 ALL:查询工资高于 IT 部门所有员工工资的员工
select emp_name, salary
from employees
where salary > all (select salary from employees where dept_id = 2);
定义:返回 单行多列 的子查询。
用途:主要用于 WHERE
子句中进行行级别的比较。需要使用括号将多个列括起来进行比较。
示例:
-- 查询与 ID 为 101 的员工 部门和经理都相同的其他员工
select emp_name
from employees
where (dept_id, manager_id) = (select dept_id, manager_id from employees where emp_id = 101)
and emp_id != 101; -- 排除员工 101 自己
-- 查找选修了与学生 'Alice' 完全相同的一组课程的学生 (这比较复杂,通常用 JOIN 或 EXISTS 更优,仅作行子查询示例)
-- (假设能查询出 Alice 选的所有课作为一个 "行" - 实际场景复杂)
-- select ... where (col1, col2, ...) = (select col1, col2, ... from ... where ...)
注意:行子查询的直接比较在某些场景下可能效率不高或不易理解,常有替代方案。
定义:返回 多行多列(即一个结果集)的子查询。
用途:主要用在 FROM
子句中,将子查询的结果当作一个临时的表(必须为其指定别名)来进一步查询。也可用在 JOIN
子句中。
示例:
-- FROM 子句:查询每个专业的平均 GPA,然后找出平均 GPA 大于 3.5 的专业
select major_avg.major_name, major_avg.avg_gpa
from (
select m.major_name, avg(s.gpa) as avg_gpa -- 子查询计算每个专业的平均 GPA
from students s
join majors m on s.major_id = m.major_id
group by m.major_name
) as major_avg -- 必须为派生表指定别名
where major_avg.avg_gpa > 3.5;
-- JOIN 子句:查询每个部门中工资最高的员工信息
select e.emp_name, e.salary, d.dept_name
from employees e
inner join (
select dept_id, max(salary) as max_salary -- 子查询找出每个部门的最高工资
from employees
where dept_id is not null
group by dept_id
) as dept_max_salary
on e.dept_id = dept_max_salary.dept_id and e.salary = dept_max_salary.max_salary -- 连接条件:部门ID相同且工资等于该部门最高工资
inner join departments d on e.dept_id = d.dept_id;
EXISTS
/ NOT EXISTS
结合子查询EXISTS
操作符用于检查子查询是否返回任何行。如果子查询返回至少一行,EXISTS
返回 TRUE;否则返回 FALSE。NOT EXISTS
相反。这通常用于替代 IN
进行存在性检查,效率可能更高。子查询通常是相关子查询(依赖外部查询的值)。
示例:
-- EXISTS:查询至少选修了一门课程的学生
select s.student_name
from students s
where exists (select 1 from enrollments e where e.student_id = s.student_id limit 1); -- 检查是否存在该学生的选课记录
-- NOT EXISTS:查询没有员工的部门
select d.dept_name
from departments d
where not exists (select 1 from employees e where e.dept_id = d.dept_id limit 1); -- 检查是否不存在该部门的员工记录
继续使用 employees
和 departments
表。
employees
表:
emp_id | emp_name | dept_id | salary | manager_id |
---|---|---|---|---|
101 | Alice | 1 | 60000 | 102 |
102 | Bob | 2 | 75000 | NULL |
103 | Charlie | 1 | 65000 | 101 |
104 | David | NULL | 50000 | 102 |
105 | Eve | 2 | 80000 | 102 |
departments
表:
dept_id | dept_name | location |
---|---|---|
1 | HR | New York |
2 | IT | London |
3 | Finance | New York |
(Self Join) 查询所有员工及其直接经理的姓名。如果员工没有经理,也要显示该员工。
答案:
select e.emp_name as employee_name, m.emp_name as manager_name
from employees e
left join employees m on e.manager_id = m.emp_id;
(Column Subquery with IN) 查询所有在 ‘New York’ 地点工作的员工的姓名和工资。
答案:
select emp_name, salary
from employees
where dept_id in (select dept_id from departments where location = 'New York');
(Scalar Subquery) 查询工资最高的员工的姓名和工资。
答案:
select emp_name, salary
from employees
where salary = (select max(salary) from employees);
(Scalar Subquery in SELECT) 查询每个部门的名称以及该部门的员工人数。
答案:
select
d.dept_name,
(select count(*) from employees e where e.dept_id = d.dept_id) as employee_count
from departments d;
(Table Subquery / Derived Table) 先计算每个部门的平均工资,然后找出平均工资超过 70000 的部门名称及其平均工资。
答案:
select dept_avg.dept_name, dept_avg.avg_salary
from (
select d.dept_name, avg(e.salary) as avg_salary
from employees e
join departments d on e.dept_id = d.dept_id
group by d.dept_name
) as dept_avg
where dept_avg.avg_salary > 70000;
(UNION) 假设有一个 retired_employees
表结构类似 employees
,合并当前员工和退休员工的姓名列表(去除重复)。
答案: (假设 retired_employees
表存在)
select emp_name from employees
union
select emp_name from retired_employees;
(EXISTS) 查询哪些部门至少有一名员工。
答案:
select d.dept_name
from departments d
where exists (select 1 from employees e where e.dept_id = d.dept_id limit 1);
(Column Subquery with ALL) 查询工资比 HR 部门(dept_id=1)所有员工都高的员工姓名和工资。
答案:
select emp_name, salary
from employees
where salary > all (select salary from employees where dept_id = 1);