当前位置: 首页 > news >正文

第五章:MySQL DQL 进阶 —— 动态计算与分类(IF 与 CASE WHEN)多表查询

条件判断 —— 动态计算与分类(IF 与 CASE WHEN)

💡 核心价值:根据字段值动态返回不同结果,实现“如果…就…”的逻辑。


一、IF() 函数(MySQL 简洁写法)

语法:

IF(condition, value_if_true, value_if_false)
示例 1:用户状态标记
SELECT username,created_at,IF(created_at > '2025-01-01', '新用户', '老用户') AS user_type
FROM users;

✅ 输出:

usernamecreated_atuser_type
alice2025-11-01 10:00:00新用户
bob2024-05-10 09:00:00老用户
示例 2:订单是否大额
SELECT product_name,amount,IF(amount >= 1000, '高价值', '普通') AS value_level
FROM orders;

⚠️ 注意:IF() 只支持 二选一(类似三元运算符)。多条件请用 CASE


二、CASE WHEN 语句(SQL 标准,推荐用于复杂逻辑)

有两种写法:

1. 简单 CASE(等值判断)
CASE columnWHEN value1 THEN result1WHEN value2 THEN result2ELSE default_result
END
SELECT product_name,status,CASE statusWHEN 'pending'   THEN '待支付'WHEN 'paid'      THEN '已支付'WHEN 'shipped'   THEN '已发货'WHEN 'cancelled' THEN '已取消'ELSE '未知状态'END AS status_zh
FROM orders;
2. 搜索 CASE(条件判断,更常用)
CASEWHEN condition1 THEN result1WHEN condition2 THEN result2...ELSE default_result
END
SELECT username,created_at,CASEWHEN created_at > '2025-10-01' THEN '活跃用户'WHEN created_at > '2025-01-01' THEN '普通用户'WHEN created_at IS NOT NULL THEN '沉默用户'ELSE '异常用户'END AS user_category
FROM users;

三、在聚合中使用条件判断(强大组合!)

统计不同类型订单数量
SELECT COUNT(*) AS total_orders,SUM(IF(status = 'paid', 1, 0)) AS paid_count,SUM(CASE WHEN amount >= 1000 THEN 1 ELSE 0 END) AS high_value_count
FROM orders;

💡 技巧:SUM(IF(...))SUM(CASE WHEN ... THEN 1 ELSE 0 END) 是实现“条件计数”的经典模式。

按用户分组 + 分类统计
SELECT user_id,COUNT(*) AS total,SUM(CASE WHEN status = 'paid' THEN 1 ELSE 0 END) AS paid_orders,AVG(CASE WHEN status = 'paid' THEN amount END) AS avg_paid_amount
FROM orders
GROUP BY user_id;

🔍 注意:AVG(CASE WHEN ... THEN amount END) 会自动忽略 NULL,只对符合条件的行求平均。


四、IF vs CASE WHEN 对比

特性IF()CASE WHEN
标准兼容性❌ MySQL 特有✅ ANSI SQL 标准
条件数量仅支持 1 个条件(二选一)支持任意多个条件
可读性简单场景更简洁复杂逻辑更清晰
嵌套能力可嵌套,但难维护可嵌套,结构清晰
适用场景快速二值判断多分支、等值映射、复杂逻辑

最佳实践

  • 简单二选一 → 用 IF()
  • 多条件、跨数据库兼容 → 用 CASE WHEN

五、常见错误与避坑

❌ 错误 1:忘记 END
-- 缺少 END,语法错误!
SELECT CASE WHEN amount > 1000 THEN 'high' FROM orders;

✅ 正确:

SELECT CASE WHEN amount > 1000 THEN 'high' END FROM orders;
❌ 错误 2:在 WHERE 中直接用别名(即使来自 CASE)
SELECT username,CASE WHEN created_at > '2025-01-01' THEN 'new' ELSE 'old' END AS type
FROM users
WHERE type = 'new';  -- ❌ Unknown column 'type'

✅ 正确做法:

-- 方法1:重复条件
WHERE created_at > '2025-01-01'-- 方法2:用子查询
SELECT * FROM (SELECT username, CASE WHEN created_at > '2025-01-01' THEN 'new' ELSE 'old' END AS typeFROM users
) t WHERE type = 'new';

六、小结:条件判断能力清单

场景推荐写法
二值判断(是/否)IF(condition, A, B)
多分支逻辑CASE WHEN ... THEN ... END
状态码转中文CASE status WHEN 'code' THEN '文本' END
条件计数/求和SUM(CASE WHEN ... THEN 1 ELSE 0 END)
跨数据库兼容优先用 CASE

🌟 一句话总结
“简单用 IF,复杂用 CASE;聚合加条件,统计更灵活。”


二、环境准备(基于第三章的数据库)

我们沿用 demo_dml 数据库,并新增两张表:

-- 订单表
CREATE TABLE orders (id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,user_id BIGINT UNSIGNED NOT NULL,product_name VARCHAR(100) NOT NULL,amount DECIMAL(10, 2) NOT NULL,status ENUM('pending', 'paid', 'shipped', 'cancelled') DEFAULT 'pending',created_at DATETIME DEFAULT CURRENT_TIMESTAMP,FOREIGN KEY (user_id) REFERENCES users(id)
) ENGINE=InnoDB CHARSET=utf8mb4;-- 插入测试订单
INSERT INTO orders (user_id, product_name, amount, status) VALUES
(1, '笔记本电脑', 5999.00, 'paid'),
(1, '无线鼠标', 89.00, 'paid'),
(2, '机械键盘', 399.00, 'shipped'),
(3, '显示器', 1299.00, 'paid');

当前数据关系:

users (id=1: Alice) → orders (2笔)
users (id=2: Bob)   → orders (1笔)
users (id=3: Charlie)→ orders (1笔)

三、多表连接(JOIN)—— 关联数据的基石

1. INNER JOIN:只返回匹配的记录

-- 查询已支付订单的用户信息
SELECT u.username,o.product_name,o.amount
FROM users u
INNER JOIN orders o ON u.id = o.user_id
WHERE o.status = 'paid';

✅ 结果:Alice 的两笔 + Charlie 的一笔(Bob 的订单状态是 shipped,不满足条件)。


2. LEFT JOIN:保留左表所有记录(即使无匹配)

-- 查询所有用户及其订单(包括无订单用户)
SELECT u.username,COALESCE(o.product_name, '无订单') AS product,COALESCE(o.amount, 0) AS amount
FROM users u
LEFT JOIN orders o ON u.id = o.user_id;

✅ 结果:Charlie、Bob、Alice 都会出现,即使某人没订单。

💡 关键技巧:用 COALESCE() 处理 NULL,避免前端显示异常。


3. RIGHT JOIN 与 FULL OUTER JOIN

  • MySQL 不支持 FULL OUTER JOIN(可用 LEFT JOIN + RIGHT JOIN + UNION 模拟);
  • RIGHT JOIN 很少用,通常改写为 LEFT JOIN 更清晰。

四、子查询(Subquery)—— 查询中的查询

场景:找出消费总额超过平均值的用户

方法 1:WHERE 中的标量子查询
SELECT user_id,SUM(amount) AS total_spent
FROM orders
WHERE status = 'paid'
GROUP BY user_id
HAVING total_spent > (SELECT AVG(total_per_user) FROM (SELECT SUM(amount) AS total_per_userFROM ordersWHERE status = 'paid'GROUP BY user_id) AS avg_table
);
方法 2:FROM 中的派生表(更高效)
SELECT t.user_id,u.username,t.total_spent
FROM (SELECT user_id,SUM(amount) AS total_spentFROM ordersWHERE status = 'paid'GROUP BY user_id
) AS t
JOIN users u ON t.user_id = u.id
WHERE t.total_spent > (SELECT AVG(total_spent) FROM (SELECT SUM(amount) AS total_spentFROM ordersWHERE status = 'paid'GROUP BY user_id) AS avg_t
);

⚠️ 注意:子查询性能可能较差,大数据量建议用 JOIN 优化。


五、聚合分析(GROUP BY + 聚合函数)

常用聚合函数:

函数说明
COUNT()计数
SUM()求和
AVG()平均值
MAX() / MIN()最大/最小值
GROUP_CONCAT()合并字符串

实战:用户消费画像

SELECT u.username,COUNT(o.id) AS order_count,SUM(o.amount) AS total_spent,AVG(o.amount) AS avg_order_value,GROUP_CONCAT(o.product_name ORDER BY o.created_at) AS products
FROM users u
LEFT JOIN orders o ON u.id = o.user_id AND o.status = 'paid'
GROUP BY u.id, u.username
ORDER BY total_spent DESC;

✅ 输出:

usernameorder_counttotal_spentavg_order_valueproducts
alice26088.003044.00笔记本电脑,无线鼠标
charlie11299.001299.00显示器
bob00NULL

💡 技巧:LEFT JOIN 中把过滤条件 status='paid' 放在 ON 子句,而非 WHERE,否则会过滤掉无订单用户!


六、窗口函数(MySQL 8.0+)—— 分析利器

⚠️ 要求 MySQL ≥ 8.0。若使用 5.7,请跳过此节或升级。

场景:给用户按消费排名

SELECT u.username,SUM(o.amount) AS total_spent,RANK() OVER (ORDER BY SUM(o.amount) DESC) AS spending_rank
FROM users u
LEFT JOIN orders o ON u.id = o.user_id AND o.status = 'paid'
GROUP BY u.id, u.username;

✅ 结果:

usernametotal_spentspending_rank
alice6088.001
charlie1299.002
bob03

其他常用窗口函数:

  • ROW_NUMBER():唯一行号
  • DENSE_RANK():密集排名(无间隔)
  • LAG()/LEAD():前后行数据引用
  • NTILE(n):分桶(如 quartile)

四大类窗口函数详解

第一类:排名函数(Ranking Functions)

函数说明示例
ROW_NUMBER()连续唯一排名(1,2,3…)每组第1名
RANK()跳跃排名(相同值并列,下一名跳过)1,1,3…
DENSE_RANK()密集排名(相同值并列,下一名连续)1,1,2…
NTILE(n)将分区分为 n 个桶四分位、十分位
示例:用户订单排名
SELECT user_id,amount,ROW_NUMBER() OVER (PARTITION BY user_id ORDER BY amount DESC) AS rn,RANK()       OVER (PARTITION BY user_id ORDER BY amount DESC) AS rnk,DENSE_RANK() OVER (PARTITION BY user_id ORDER BY amount DESC) AS drnk
FROM orders;

假设用户1有两笔订单:5999 和 89
✅ 结果:

user_idamountrnrnkdrnk
15999111
189222

若两笔都是 1000:

amountrnrnkdrnk
1000111
1000211

💡 应用:Top N、去重(取 rn = 1)、分位分析。


第二类:聚合函数(作为窗口函数)

几乎所有聚合函数都可作窗口函数:

  • SUM()AVG()COUNT()MAX()MIN()STDDEV()VAR()
示例:累计销售额(Running Total)
SELECT order_id,created_at,amount,SUM(amount) OVER (ORDER BY created_at ROWS UNBOUNDED PRECEDING) AS running_total
FROM orders
ORDER BY created_at;

✅ 输出:

order_idcreated_atamountrunning_total
12025-11-01 10:0059995999
22025-11-02 11:00896088
32025-11-03 09:003996487

🔍 关键:ROWS UNBOUNDED PRECEDING 表示从第一行到当前行。


第三类:偏移函数(Offset / Navigation)

用于访问“前后行”的数据:

函数说明
LAG(col, n, default)向上取第 n 行的值(默认 n=1)
LEAD(col, n, default)向下取第 n 行的值
FIRST_VALUE(col)窗口第一行的值
LAST_VALUE(col)窗口最后一行的值
示例:订单金额环比变化
SELECT order_id,created_at,amount,LAG(amount, 1) OVER (ORDER BY created_at) AS prev_amount,amount - LAG(amount, 1) OVER (ORDER BY created_at) AS diff
FROM orders
ORDER BY created_at;

✅ 输出:

order_idamountprev_amountdiff
15999NULLNULL
2895999-5910
339989310

💡 应用:同比/环比、趋势分析、差值计算。


第四类:分布函数(Distribution)

函数说明
PERCENT_RANK()百分等级(0 到 1)
CUME_DIST()累积分布
NTILE(n)分桶(已在排名类介绍)
示例:用户消费百分位
SELECT user_id,total_spent,PERCENT_RANK() OVER (ORDER BY total_spent) AS pct_rank
FROM (SELECT user_id, SUM(amount) AS total_spentFROM ordersGROUP BY user_id
) t;

OVER() 子句深度解析

1. PARTITION BY:分组(窗口边界)

  • 类似 GROUP BY,但不合并行
  • 每个分区内独立计算
-- 每个用户的订单排名
ROW_NUMBER() OVER (PARTITION BY user_id ORDER BY amount DESC)

2. ORDER BY:窗口内排序

  • 决定窗口函数的计算顺序
  • 对 LAG/LEAD、累计和等至关重要

3. 帧范围(Frame Clause):精确控制窗口行

语法:

ROWS BETWEEN start AND end
-- 或
RANGE BETWEEN start AND end

常用选项:

选项含义
UNBOUNDED PRECEDING从分区第一行开始
CURRENT ROW当前行
n PRECEDING当前行前 n 行
n FOLLOWING当前行后 n 行
UNBOUNDED FOLLOWING到分区最后一行
示例:3日移动平均
SELECT created_at,amount,AVG(amount) OVER (ORDER BY created_atROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING) AS moving_avg_3d
FROM orders;

⚠️ 注意:ROWS 基于物理行,RANGE 基于值范围(需谨慎使用)。


实战案例

案例1:每个用户的最新订单

SELECT *
FROM (SELECT *,ROW_NUMBER() OVER (PARTITION BY user_id ORDER BY created_at DESC) AS rnFROM orders
) t
WHERE rn = 1;

案例2:销售额 Top 3 用户(允许并列)

SELECT user_id, total_spent
FROM (SELECT user_id,SUM(amount) AS total_spent,DENSE_RANK() OVER (ORDER BY SUM(amount) DESC) AS drnkFROM ordersGROUP BY user_id
) t
WHERE drnk <= 3;

案例3:订单金额高于用户平均值的记录

SELECT order_id,user_id,amount,avg_user_amount
FROM (SELECT *,AVG(amount) OVER (PARTITION BY user_id) AS avg_user_amountFROM orders
) t
WHERE amount > avg_user_amount;

常见误区与性能提示

❌ 误区1:认为窗口函数会改变行数

✅ 正解:行数不变,只是每行新增计算列。

❌ 误区2:在 WHERE 中使用窗口函数

-- ❌ 错误!窗口函数在 SELECT 阶段计算,WHERE 无法访问
SELECT * FROM orders WHERE ROW_NUMBER() OVER (...) = 1;

✅ 正确:用子查询或 CTE。

⚡ 性能提示:

  • 窗口函数通常比自连接或子查询更快、更易读
  • 确保 PARTITION BY 和 ORDER BY 字段有合适索引
  • 避免在大表上无分区的全局窗口(如 OVER ()

MySQL 版本要求

  • ✅ 必须 MySQL ≥ 8.0
  • ❌ MySQL 5.7 及以下 不支持窗口函数

可通过以下命令检查版本:

SELECT VERSION(); -- 需 >= 8.0.0

总结:窗口函数能力矩阵

需求推荐函数
排名、Top NROW_NUMBER()RANK()DENSE_RANK()
累计和、移动平均SUM() OVER (ORDER BY ... ROWS ...)
前后行比较LAG()LEAD()
分组统计(保留明细)AVG()COUNT() + PARTITION BY
分位、分布PERCENT_RANK()NTILE()

🌟 终极口诀
“分组用 PARTITION,排序靠 ORDER BY,帧控精细范围,函数各显神通。”

http://www.dtcms.com/a/602060.html

相关文章:

  • 【SQL server】不同平台相同数据库之间某个平台经常性死锁
  • Ubuntu系统安装.NET SDK 7.0
  • 基于深度学习与MATLAB的脑电信号情绪识别系统
  • 十大旅游电子商务网站wordpress 国内 慢
  • 大连网站制作姚喜运成都网页设计培训班
  • Apache POI
  • 某景区网站建设策划书利用codeing做网站
  • rhce作业
  • 网页网站原型图占位符怎么做定制一个微信小程序要多少钱
  • Python-PLAXIS自动化建模技术与典型岩土工程案例
  • 4-ARM-PEG-Fmoc protected Amine(2),合成设计思路与路线选择
  • 自主可控背景下MCU芯片的替代之路:从ARM到RISC-V的机遇与挑战
  • 想建设个网站全国最大的网站建设公司
  • 做网站的编程语言组合江西建设厅特殊工种的网站
  • HDPlanner 代码阅读
  • AOSP Android13 Launcher3 最近任务详解
  • 青岛市网站制作seo搜索引擎优化薪资
  • MediaPipe LLM Inference:在WEB浏览器中“裸跑”大语言模型
  • 网站平台建设公司经营范围域名注册成功怎么做网站
  • 南昌哪里有网站建设网页制作平台flash
  • 2025 创客匠人全球创始人 IP + AI 万人高峰论坛:家庭教育与企业管理的变革指南
  • Canvas指纹模拟避坑指南Canvas指纹防护实测案例
  • 2.11 实践二:基于 LoRA 微调一个垂直领域客服问答模型并部署为 API
  • 建设房屋出租网站饮食网站首页页面
  • 网站怎样自动文字排版网站建设58
  • 从工作流搭建看智能体与RPA流程自动化有何不同?
  • C语言编译器IDE | 提升程序开发效率的最佳选择
  • 当遇到 502 错误(Bad Gateway)怎么办
  • 告别停机焦虑:耐达讯自动化Profibus光纤模块——您的控制链路‘救星’在此”
  • 天津做网站优化的公司酒店网站收入如何做帐务处理