引言
各位数据库爱好者们好!今天我们要挑战MySQL查询技能的高阶关卡——复杂查询 🚀。在真实业务场景中,数据往往分散在多个表中,就像拼图的各个碎片,只有掌握了多表查询的"拼图技巧",才能将它们组合成完整的信息图谱。本教程将带你深入理解多表连接、子查询、联合查询等高级技术,让你从"单表查询新手"成长为"多表操作高手"!💪
一、多表连接查询:数据关系的桥梁
1.1 INNER JOIN:内连接(交集查询)
内连接就像数学中的交集,只返回两表匹配的记录 🔍:
-- 基础语法
SELECT 列列表
FROM 表1
INNER JOIN 表2 ON 表1.列 = 表2.列;-- 查询订单及对应的客户信息
SELECT o.order_id,o.order_date,c.customer_name,c.phone
FROM orders o
INNER JOIN customers c ON o.customer_id = c.customer_id;
特点:
- 结果只包含两表都匹配的行
- 如果某行在一边没有匹配,则该行不会出现在结果中
- 性能通常较好(MySQL优化器会优先考虑)
1.2 LEFT JOIN:左外连接(左表全保留)
左连接就像"保底条款",左表数据全部保留,右表没有匹配则补NULL 🛡️:
-- 查询所有员工及其部门信息(包括未分配部门的员工)
SELECT e.employee_id,e.first_name,d.department_name
FROM employees e
LEFT JOIN departments d ON e.department_id = d.department_id;
典型应用场景:
- 统计时保留主表全部记录
- 查找没有关联记录的项(WHERE 右表.列 IS NULL)
- 确保主表数据完整性
1.3 RIGHT JOIN:右外连接(右表全保留)
右连接与左连接方向相反,但实际较少使用 🔄:
-- 查询所有部门及员工信息(包括没有员工的部门)
SELECT d.department_name,e.first_name
FROM employees e
RIGHT JOIN departments d ON e.department_id = d.department_id;
注意:RIGHT JOIN通常可以用LEFT JOIN重写,建议保持代码风格一致
1.4 多表连接实战技巧
自连接(同一表的不同实例):
-- 查询员工及其经理信息
SELECT e.employee_id,e.first_name AS employee_name,m.first_name AS manager_name
FROM employees e
LEFT JOIN employees m ON e.manager_id = m.employee_id;
多表JOIN:
-- 查询订单详情(客户+产品+订单项)
SELECT o.order_id,c.customer_name,p.product_name,oi.quantity,oi.unit_price
FROM orders o
JOIN customers c ON o.customer_id = c.customer_id
JOIN order_items oi ON o.order_id = oi.order_id
JOIN products p ON oi.product_id = p.product_id;
JOIN优化建议:
- 为连接条件列建立索引
- 大表JOIN小表效率更高
- 避免连接超过5个表,考虑拆解查询
- 使用EXPLAIN分析执行计划
二、子查询:查询中的查询
2.1 标量子查询(返回单个值)
标量子查询就像公式中的变量,返回单一值 🔢:
-- 查询高于平均薪资的员工
SELECT employee_id, first_name, salary
FROM employees
WHERE salary > (SELECT AVG(salary) FROM employees);-- 在SELECT中使用
SELECT product_id,product_name,price,(SELECT AVG(price) FROM products) AS avg_price,price - (SELECT AVG(price) FROM products) AS diff
FROM products;
特点:
- 必须且只能返回一行一列
- 可以出现在SELECT、WHERE、HAVING等子句中
- 通常性能较好
2.2 列子查询(返回一列多行)
列子查询生成一个值列表,常与IN、ANY/SOME、ALL配合使用 📜:
-- 查询没有订单的客户
SELECT customer_id, customer_name
FROM customers
WHERE customer_id NOT IN (SELECT DISTINCT customer_id FROM orders
);-- 使用EXISTS替代IN(大数据量时更高效)
SELECT c.customer_id, c.customer_name
FROM customers c
WHERE NOT EXISTS (SELECT 1 FROM orders o WHERE o.customer_id = c.customer_id
);
操作符对比:
操作符 | 描述 |
---|---|
IN / NOT IN | 等于/不等于列表中的任何值 |
ANY / SOME | 满足子查询结果的任一条件 |
ALL | 满足子查询结果的所有条件 |
2.3 行子查询(返回一行多列)
行子查询返回单行多列,可以一次比较多个字段 🧵:
-- 查询与特定员工薪资和职位都相同的其他员工
SELECT employee_id, first_name, salary, job_id
FROM employees
WHERE (salary, job_id) = (SELECT salary, job_idFROM employeesWHERE employee_id = 123
)
AND employee_id <> 123;
2.4 表子查询(返回多行多列)
表子查询生成临时表,通常用于FROM子句或JOIN 🏗️:
-- 查询各部门薪资最高的员工
SELECT e.department_id,e.employee_id,e.first_name,e.salary
FROM employees e
JOIN (SELECT department_id, MAX(salary) AS max_salaryFROM employeesGROUP BY department_id
) dept_max ON e.department_id = dept_max.department_id AND e.salary = dept_max.max_salary;
优化技巧:
- 给子查询结果起别名
- 考虑使用JOIN替代子查询
- 复杂子查询可以改为临时表
三、联合查询:结果集的合并
3.1 UNION:去重合并
UNION像数学中的并集,合并并去除重复行 ✨:
-- 合并两个查询结果(去重)
SELECT product_id FROM current_products
UNION
SELECT product_id FROM discontinued_products;
特点:
- 两个SELECT的列数必须相同
- 对应列的数据类型必须兼容
- 结果列名取自第一个SELECT
- 默认会去除重复行(性能开销)
3.2 UNION ALL:简单合并
UNION ALL直接拼接结果,保留所有行(包括重复)📦:
-- 合并两个查询结果(保留重复)
SELECT customer_id FROM domestic_customers
UNION ALL
SELECT customer_id FROM international_customers;
性能优势:
- 比UNION快(不需要去重)
- 适合确定没有重复或需要保留重复的场景
3.3 联合查询应用场景
分表数据合并:
-- 合并2022和2023年的订单数据
SELECT * FROM orders_2022
UNION ALL
SELECT * FROM orders_2023;
复杂条件组合:
-- 查询高价值客户或最近活跃客户
SELECT customer_id FROM customers WHERE total_spend > 10000
UNION
SELECT customer_id FROM orders
WHERE order_date > DATE_SUB(NOW(), INTERVAL 30 DAY);
注意事项:
- 只能有一个ORDER BY子句,放在最后
- 可以在各个SELECT中使用WHERE过滤
- 大数据量时考虑分页处理
四、临时表与派生表
4.1 临时表:会话级存储
临时表就像便签纸,会话结束自动销毁 🗒️:
-- 创建临时表
CREATE TEMPORARY TABLE temp_sales AS
SELECT product_id,SUM(quantity) AS total_quantity
FROM order_items
GROUP BY product_id;-- 使用临时表
SELECT p.product_name,t.total_quantity
FROM products p
JOIN temp_sales t ON p.product_id = t.product_id
ORDER BY t.total_quantity DESC;-- 会话结束自动删除
特点:
- 仅对当前会话可见
- 可以与普通表同名(优先使用临时表)
- 适合存储中间结果
4.2 派生表:查询中的临时表
派生表是"一次性"临时表,只在查询期间存在 ⏳:
-- FROM子句中的派生表
SELECT d.department_name,emp_stats.avg_salary
FROM departments d
JOIN (SELECT department_id,AVG(salary) AS avg_salary,COUNT(*) AS emp_countFROM employeesGROUP BY department_id
) emp_stats ON d.department_id = emp_stats.department_id
WHERE emp_stats.emp_count > 5;
优化建议:
- 给派生表起有意义的别名
- 复杂派生表考虑改为临时表
- MySQL 8.0+支持WITH子句(CTE)更清晰
4.3 WITH子句(CTE):更优雅的临时表
公共表表达式(CTE)让复杂查询更清晰 🌟:
-- 使用WITH定义CTE
WITH dept_stats AS (SELECT department_id,AVG(salary) AS avg_salary,COUNT(*) AS emp_countFROM employeesGROUP BY department_id
),
high_salary_depts AS (SELECT department_idFROM dept_statsWHERE avg_salary > 8000
)
SELECT d.department_name,e.first_name,e.salary
FROM departments d
JOIN employees e ON d.department_id = e.department_id
JOIN high_salary_depts h ON d.department_id = h.department_id
ORDER BY d.department_name, e.salary DESC;
CTE优势:
- 提高复杂查询的可读性
- 可以引用前面定义的CTE
- 支持递归查询(处理树形结构)
五、综合案例:电商数据分析实战
5.1 多表关联分析
-- 查询客户购买行为分析
SELECT c.customer_id,c.customer_name,COUNT(DISTINCT o.order_id) AS order_count,SUM(oi.quantity * oi.unit_price) AS total_spend,MAX(o.order_date) AS last_order_date
FROM customers c
LEFT JOIN orders o ON c.customer_id = o.customer_id
LEFT JOIN order_items oi ON o.order_id = oi.order_id
GROUP BY c.customer_id, c.customer_name
HAVING order_count > 0
ORDER BY total_spend DESC
LIMIT 10;
5.2 复杂子查询应用
-- 查询每个品类最畅销的3个产品
WITH product_sales AS (SELECT p.category_id,p.product_id,p.product_name,SUM(oi.quantity) AS total_quantity,RANK() OVER (PARTITION BY p.category_id ORDER BY SUM(oi.quantity) DESC) AS sales_rankFROM products pJOIN order_items oi ON p.product_id = oi.product_idGROUP BY p.category_id, p.product_id, p.product_name
)
SELECT c.category_name,ps.product_name,ps.total_quantity
FROM product_sales ps
JOIN categories c ON ps.category_id = c.category_id
WHERE ps.sales_rank <= 3
ORDER BY c.category_name, ps.sales_rank;
5.3 递归查询处理层级数据
-- 查询组织架构层级(MySQL 8.0+)
WITH RECURSIVE org_hierarchy AS (-- 基础查询:找出所有顶级管理者SELECT employee_id,first_name,manager_id,1 AS levelFROM employeesWHERE manager_id IS NULLUNION ALL-- 递归查询:找出下级员工SELECT e.employee_id,e.first_name,e.manager_id,oh.level + 1FROM employees eJOIN org_hierarchy oh ON e.manager_id = oh.employee_id
)
SELECT LPAD('', (level-1)*4, ' ') || first_name AS employee_name,level
FROM org_hierarchy
ORDER BY level, first_name;
总结 🎯
通过本教程,我们系统掌握了MySQL复杂查询的核心技能 🎓:
- 多表连接:学会了INNER JOIN、LEFT JOIN等连接方式及应用场景
- 子查询:掌握了标量、列、行、表子查询的使用方法
- 联合查询:理解了UNION和UNION ALL的区别与适用场景
- 临时表:认识了临时表、派生表和CTE的用法
关键收获:
- 多表连接是关系型数据库的核心特性
- 子查询可以解决复杂的数据筛选问题
- 联合查询适合合并多个结果集
- 临时表和CTE能提高复杂查询的可读性和性能
下一步学习建议:
- 在实际业务场景中练习复杂查询
- 学习使用EXPLAIN分析查询性能
- 探索窗口函数等高级分析功能
- 研究查询优化技巧和索引策略
PS:如果你在学习过程中遇到问题,别慌!欢迎在评论区留言,我会尽力帮你解决!😄