MySQL中的公用表表达式CTE实战案例应用
很多同学不会使用MySQL中的公用表表达式,今天这篇文章详细为大家介绍一下。
基础概念:
公用表表达式:MySQL中的公用表表达式(Common Table Expressions,简称CTE)是一种临时的结果集,它可以在一个SELECT、INSERT、UPDATE或DELETE语句中被引用多次。CTE可以使得复杂的查询更加清晰和易于理解,因为它允许将查询分解为多个简单的部分。
实战案例:
先模拟点表和数据,数据大家可以自己造,以下只是方便为大家讲解,随机造的数据:
1. 创建表结构和数据
-- 创建员工表
CREATE TABLE employees (employee_id INT PRIMARY KEY,name VARCHAR(100),department_id INT,salary DECIMAL(10, 2),manager_id INT,hire_date DATE
);-- 创建部门表
CREATE TABLE departments (department_id INT PRIMARY KEY,department_name VARCHAR(100),location VARCHAR(100)
);-- 创建订单表
CREATE TABLE orders (order_id INT PRIMARY KEY,customer_id INT,order_date DATE,total_amount DECIMAL(10, 2)
);-- 创建订单详情表
CREATE TABLE order_details (order_detail_id INT PRIMARY KEY,order_id INT,product_id INT,quantity INT,price DECIMAL(10, 2)
);-- 创建产品表
CREATE TABLE products (product_id INT PRIMARY KEY,product_name VARCHAR(100),category_id INT,unit_price DECIMAL(10, 2)
);-- 创建客户表
CREATE TABLE customers (customer_id INT PRIMARY KEY,customer_name VARCHAR(100),city VARCHAR(100),country VARCHAR(100)
);-- 插入部门数据
INSERT INTO departments VALUES
(1, '研发部', '北京'),
(2, '市场部', '上海'),
(3, '财务部', '广州'),
(4, '人力资源部', '深圳'),
(5, '销售部', '杭州');-- 插入员工数据
INSERT INTO employees VALUES
(1, '张三', 1, 15000.00, NULL, '2018-01-15'),
(2, '李四', 1, 12000.00, 1, '2019-03-20'),
(3, '王五', 2, 13000.00, 1, '2018-05-10'),
(4, '赵六', 2, 10000.00, 3, '2020-02-25'),
(5, '钱七', 3, 11000.00, 1, '2019-07-12'),
(6, '孙八', 3, 9500.00, 5, '2021-01-05'),
(7, '周九', 4, 12500.00, 1, '2018-11-30'),
(8, '吴十', 5, 14000.00, 1, '2019-04-18'),
(9, '郑十一', 5, 10500.00, 8, '2020-08-22'),
(10, '刘十二', 5, 9000.00, 8, '2021-03-15');-- 插入客户数据
INSERT INTO customers VALUES
(1, '阿里巴巴', '杭州', '中国'),
(2, '腾讯', '深圳', '中国'),
(3, '百度', '北京', '中国'),
(4, '京东', '北京', '中国'),
(5, '华为', '深圳', '中国'),
(6, 'Apple', '纽约', '美国'),
(7, 'Google', '旧金山', '美国'),
(8, 'Microsoft', '西雅图', '美国');-- 插入产品数据
INSERT INTO products VALUES
(1, '笔记本电脑', 1, 8999.00),
(2, '智能手机', 1, 4999.00),
(3, '平板电脑', 1, 3999.00),
(4, '办公桌', 2, 1200.00),
(5, '办公椅', 2, 800.00),
(6, '打印机', 3, 2500.00),
(7, '投影仪', 3, 5000.00),
(8, '复印机', 3, 12000.00);-- 插入订单数据
INSERT INTO orders VALUES
(1, 1, '2023-01-15', 26997.00),
(2, 2, '2023-02-20', 9998.00),
(3, 3, '2023-03-10', 7998.00),
(4, 4, '2023-04-05', 12000.00),
(5, 1, '2023-05-12', 8000.00),
(6, 5, '2023-06-18', 15000.00),
(7, 6, '2023-07-22', 7500.00),
(8, 7, '2023-08-30', 24000.00);-- 插入订单详情数据
INSERT INTO order_details VALUES
(1, 1, 1, 3, 8999.00),
(2, 2, 2, 2, 4999.00),
(3, 3, 3, 2, 3999.00),
(4, 4, 4, 10, 1200.00),
(5, 5, 5, 10, 800.00),
(6, 6, 1, 1, 8999.00),
(7, 6, 2, 1, 4999.00),
(8, 7, 6, 3, 2500.00),
(9, 8, 8, 2, 12000.00);
2. WITH查询案例
案例1: 查询每个部门的平均工资并与公司整体平均工资比较
WITH dept_avg_salary AS (SELECT d.department_id,d.department_name,AVG(e.salary) AS avg_salaryFROM departments dJOIN employees e ON d.department_id = e.department_idGROUP BY d.department_id, d.department_name
),
company_avg AS (SELECT AVG(salary) AS company_avg_salaryFROM employees
)
SELECT d.department_id,d.department_name,d.avg_salary,c.company_avg_salary,d.avg_salary - c.company_avg_salary AS salary_difference
FROM dept_avg_salary d
CROSS JOIN company_avg c
ORDER BY d.avg_salary DESC;
案例2: 找出每个部门薪资最高的员工
WITH ranked_employees AS (SELECT e.employee_id,e.name,e.salary,e.department_id,d.department_name,RANK() OVER (PARTITION BY e.department_id ORDER BY e.salary DESC) AS salary_rankFROM employees eJOIN departments d ON e.department_id = d.department_id
)
SELECT employee_id,name,department_name,salary
FROM ranked_employees
WHERE salary_rank = 1
ORDER BY department_id;
案例3: 递归查询员工上下级关系
WITH RECURSIVE employee_hierarchy AS (-- 基础查询:找出顶级管理者SELECT employee_id,name,manager_id,1 AS level,CAST(name AS CHAR(1000)) AS hierarchy_pathFROM employeesWHERE manager_id IS NULLUNION ALL-- 递归查询:找出所有下属SELECT e.employee_id,e.name,e.manager_id,eh.level + 1,CONCAT(eh.hierarchy_path, ' > ', e.name)FROM employees eJOIN employee_hierarchy eh ON e.manager_id = eh.employee_id
)
SELECT employee_id,name,manager_id,level,hierarchy_path
FROM employee_hierarchy
ORDER BY level, employee_id;
案例4: 计算各城市的销售额并排名
WITH city_sales AS (SELECT c.city,SUM(o.total_amount) AS total_salesFROM customers cJOIN orders o ON c.customer_id = o.customer_idGROUP BY c.city
),
ranked_cities AS (SELECT city,total_sales,RANK() OVER (ORDER BY total_sales DESC) AS sales_rankFROM city_sales
)
SELECT city,total_sales,sales_rank
FROM ranked_cities
ORDER BY sales_rank;
案例5: 查找热门产品(销售额前3的产品)
WITH product_sales AS (SELECT p.product_id,p.product_name,SUM(od.quantity * od.price) AS total_salesFROM products pJOIN order_details od ON p.product_id = od.product_idGROUP BY p.product_id, p.product_name
),
ranked_products AS (SELECT product_id,product_name,total_sales,RANK() OVER (ORDER BY total_sales DESC) AS sales_rankFROM product_sales
)
SELECT product_id,product_name,total_sales
FROM ranked_products
WHERE sales_rank <= 3
ORDER BY sales_rank;
案例6: 计算每个月的销售额及环比增长率
WITH monthly_sales AS (SELECT YEAR(order_date) AS year,MONTH(order_date) AS month,SUM(total_amount) AS monthly_salesFROM ordersGROUP BY YEAR(order_date), MONTH(order_date)
),
sales_growth AS (SELECT ms.year,ms.month,ms.monthly_sales,LAG(ms.monthly_sales) OVER (ORDER BY ms.year, ms.month) AS prev_month_salesFROM monthly_sales ms
)
SELECT year,month,monthly_sales,prev_month_sales,CASE WHEN prev_month_sales IS NULL THEN NULLELSE ROUND((monthly_sales - prev_month_sales) / prev_month_sales * 100, 2)END AS growth_rate_percentage
FROM sales_growth
ORDER BY year, month;
案例7: 查找重复购买的客户及其总消费额
WITH customer_order_count AS (SELECT customer_id,COUNT(*) AS order_count,SUM(total_amount) AS total_spentFROM ordersGROUP BY customer_idHAVING COUNT(*) > 1
)
SELECT c.customer_id,c.customer_name,c.city,c.country,coc.order_count,coc.total_spent
FROM customer_order_count coc
JOIN customers c ON coc.customer_id = c.customer_id
ORDER BY coc.total_spent DESC;
案例8: 各部门员工工资区间分布
WITH salary_ranges AS (SELECT department_id,COUNT(CASE WHEN salary < 10000 THEN 1 END) AS low_salary,COUNT(CASE WHEN salary >= 10000 AND salary < 13000 THEN 1 END) AS medium_salary,COUNT(CASE WHEN salary >= 13000 THEN 1 END) AS high_salary,COUNT(*) AS total_employeesFROM employeesGROUP BY department_id
)
SELECT d.department_id,d.department_name,sr.low_salary,ROUND(sr.low_salary / sr.total_employees * 100, 2) AS low_salary_percentage,sr.medium_salary,ROUND(sr.medium_salary / sr.total_employees * 100, 2) AS medium_salary_percentage,sr.high_salary,ROUND(sr.high_salary / sr.total_employees * 100, 2) AS high_salary_percentage,sr.total_employees
FROM salary_ranges sr
JOIN departments d ON sr.department_id = d.department_id
ORDER BY d.department_id;
案例9: 查找客户购买的不同产品种类及总金额
WITH customer_products AS (SELECT o.customer_id,COUNT(DISTINCT od.product_id) AS distinct_products,SUM(od.quantity * od.price) AS total_amountFROM orders oJOIN order_details od ON o.order_id = od.order_idGROUP BY o.customer_id
),
avg_products AS (SELECT AVG(distinct_products) AS avg_distinct_productsFROM customer_products
)
SELECT c.customer_id,c.customer_name,cp.distinct_products,cp.total_amount,CASE WHEN cp.distinct_products > ap.avg_distinct_products THEN '多样化消费'ELSE '专一消费'END AS customer_type
FROM customer_products cp
JOIN customers c ON cp.customer_id = c.customer_id
CROSS JOIN avg_products ap
ORDER BY cp.distinct_products DESC, cp.total_amount DESC;
案例10: 计算员工的入职年限并按部门统计
WITH employee_tenure AS (SELECT employee_id,name,department_id,TIMESTAMPDIFF(YEAR, hire_date, CURDATE()) AS years_of_serviceFROM employees
),
dept_tenure_stats AS (SELECT department_id,AVG(years_of_service) AS avg_tenure,MIN(years_of_service) AS min_tenure,MAX(years_of_service) AS max_tenureFROM employee_tenureGROUP BY department_id
)
SELECT d.department_id,d.department_name,ROUND(dts.avg_tenure, 1) AS avg_years_of_service,dts.min_tenure AS min_years_of_service,dts.max_tenure AS max_years_of_service,COUNT(e.employee_id) AS employee_count
FROM departments d
JOIN dept_tenure_stats dts ON d.department_id = dts.department_id
JOIN employees e ON d.department_id = e.department_id
GROUP BY d.department_id, d.department_name, dts.avg_tenure, dts.min_tenure, dts.max_tenure
ORDER BY avg_years_of_service DESC;
需要注意的是,MySQL 5.7及以下版本不支持WITH RECURSIVE语法,只有MySQL 8.0及以上版本才支持完整的CTE功能,包括递归CTE。