Mysql企业级sql编写实战
- 1 一对多,列表展示最新记录字段
- 1.1 场景
- 1.2 需求
- 1.3 实现
- 1.3.1 表及数据准备
- 1.3.2 Sql编写
- 2 区间统计(if/case when)
- 2.1 场景
- 2.2 需求
- 2.3 实现
- 2.2.1 表及数据准备
- 2.3.2 sql编写
- 3 多类别分组统计(竖表转横表)
- 3.1 场景
- 3.2 需求
- 3.3 实现
- 3.3.1 表及数据准备
- 3.3.2 Sql编写
- 4 占比统计(多层子查询)
- 4.1 场景
- 4.2 需求
- 4.3 实现
- 4.3.1 表及数据准备
- 4.3.2 sql编写
1 一对多,列表展示最新记录字段
1.1 场景
在一对多的数据关联下,列表页面展示获取字表中的最新记录的指定字段,比如:
- 人员最近登录记录
- 订单的最新支付状态
- 产品的最新库存更新记录
这些场景都需要在一对多关系的主表中,展示子表中满足特定条件(通常是时间戳最新)的记录字段。
1.2 需求
假设我们有两张表:sys_user(用户表)和sys_login_log(用户登录记录表)。sys_user表中的每个用户可能在sys_login_log表中有多条登录记录。我们的需求是在用户列表页面展示每个用户的最近一次登录时间、登录IP等信息。
1.3 实现
1.3.1 表及数据准备
-- 创建 sys_user表
CREATE TABLE `sys_user` (`id` bigint(10) NOT NULL COMMENT '主键',`user_name` varchar(200) DEFAULT NULL COMMENT '用户名',`dept_id` bigint(10) DEFAULT NULL COMMENT '所属部门',PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;-- 创建 sys_login_log表
CREATE TABLE `sys_login_log` (`id` bigint(20) NOT NULL COMMENT '主键',`user_id` bigint(20) DEFAULT NULL COMMENT '所属用户',`dept_id` bigint(20) DEFAULT NULL COMMENT '所属部门',`login_time` datetime DEFAULT NULL COMMENT '登录时间',`login_ip` varchar(20) DEFAULT NULL COMMENT '登录ip',PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;-- 插入一些示例数据到 sys_user表
INSERT INTO `sys_user` (`id`, `user_name`, `dept_id`) VALUES (1832959318869716994, '用户2', 1833094812337737730);
INSERT INTO `sys_user` (`id`, `user_name`, `dept_id`) VALUES (1832959390013501441, '用户3', 1839196308802420737);
INSERT INTO `sys_user` (`id`, `user_name`, `dept_id`) VALUES (1832959495936454657, '用户4', 1839196494123548673);-- 插入一些示例数据到 sys_login_log表
INSERT INTO `sys_login_log` (`id`, `user_id`, `dept_id`, `login_time`, `login_ip`) VALUES (1, 1832959318869716994, 1833094812337737730, '2025-02-07 22:39:58', '192.168.1.1');
INSERT INTO `sys_login_log` (`id`, `user_id`, `dept_id`, `login_time`, `login_ip`) VALUES (2, 1832959390013501441, 1839196308802420737, '2025-02-07 22:38:20', '192.168.1.2');
INSERT INTO `sys_login_log` (`id`, `user_id`, `dept_id`, `login_time`, `login_ip`) VALUES (3, 1832959495936454657, 1839196494123548673, '2025-02-07 22:40:02', '192.168.1.3');
INSERT INTO `sys_login_log` (`id`, `user_id`, `dept_id`, `login_time`, `login_ip`) VALUES (4, 1832959318869716994, 1833094812337737730, '2025-02-07 23:01:04', '192.168.1.2');
-- 从数据分析来看,用户【1832959318869716994】对应的最近登录时间与ip为 2025-02-07 23:01:04 192.168.1.2
原始数据:
1.3.2 Sql编写
- 如果只需要查询最近的登录时间(子表的其中一个字段的值),使用max函数或者子查询查询即可
-- max 函数实现
SELECT usr.*,max(log.login_time) '最近登录时间'
from sys_user usr
LEFT JOIN sys_login_log log on log.user_id = usr.id
GROUP BY usr.id-- 子查询方式实现
SELECT usr.*,(select login_time from sys_login_log WHERE user_id = usr.id ORDER BY login_time desc LIMIT 1) '最近登录时间'
from sys_user usr
GROUP BY usr.id
执行效果如下
- 如果需要字表中的多个字段,则只有通过子查询进行查询
-- 方式一 通过获取id进行关联查询(推荐使用)
SELECT usr.*,log.login_time,log.login_ip
from sys_user usr
LEFT join sys_login_log log on log.id = (SELECT id from sys_login_log WHERE user_id = usr.id ORDER BY login_time desc LIMIT 1)-- 方式二,先通过子查询分组,获取匹配字段,然后根据该字段关联进行查询
SELECT usr.*,log.login_time,log.login_ip
FROM sys_user usr
left JOIN (SELECT user_id, MAX(login_time) AS latest_login_timeFROM sys_login_logGROUP BY user_id
)latest_logins ON usr.id = latest_logins.user_id
left JOIN sys_login_log log ON latest_logins.user_id = log.user_id AND latest_logins.latest_login_time = log.login_time
查询效果如下,由查询结果可看出,login_time 和 login_ip 是我们想要获取的数据
2 区间统计(if/case when)
2.1 场景
在进行数据统计过程中,需要按区间范围进行统计,比如:
- 年龄区间的人员数(10-20、20-30,30以上),表头:年龄区间、人员数量
- 金额区间的商品数(0-1万、1-2万、2万以上),表头:金额区间、商品数量
2.2 需求
针对系统用户 ,需要按年龄段分别统计各年龄段的人数占比情况
2.3 实现
2.2.1 表及数据准备
-- 创建表结构
CREATE TABLE `tb_person` (`id` bigint(10) NOT NULL COMMENT '主键',`user_name` varchar(200) DEFAULT NULL COMMENT '用户名',`age` int(10) DEFAULT NULL COMMENT '年龄',PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;-- 初始化测试数据
INSERT INTO `tb_person` (`id`, `user_name`, `age`) VALUES (1, '人员1', 10);
INSERT INTO `tb_person` (`id`, `user_name`, `age`) VALUES (2, '人员2', 15);
INSERT INTO `tb_person` (`id`, `user_name`, `age`) VALUES (3, '人员3', 33);
INSERT INTO `tb_person` (`id`, `user_name`, `age`) VALUES (4, '人员4', 23);
INSERT INTO `tb_person` (`id`, `user_name`, `age`) VALUES (5, '人员5', 18);
INSERT INTO `tb_person` (`id`, `user_name`, `age`) VALUES (6, '人员6', 50);
INSERT INTO `tb_person` (`id`, `user_name`, `age`) VALUES (7, '人员7', 29);
INSERT INTO `tb_person` (`id`, `user_name`, `age`) VALUES (8, '人员8', 36);
INSERT INTO `tb_person` (`id`, `user_name`, `age`) VALUES (9, '人员9', 46);
原始数据:
2.3.2 sql编写
-- 1、 使用if 实现
SELECT if(age <= 20, '20岁及以下',if(age BETWEEN 21 and 30,'21-30岁','30岁以上')) '年龄段',count(*) '人数'
from tb_person
GROUP BY if(age <= 20, '20岁及以下',if(age BETWEEN 21 and 30,'21-30岁','30岁以上'))-- 2、使用case when 实现
SELECT CASE WHEN age <= 20 THEN '20岁及以下'WHEN age BETWEEN 21 AND 30 THEN '21-30岁'ELSE '30岁以上'END AS '年龄段',COUNT(*) AS '人数'
FROM tb_person
GROUP BY CASE WHEN age <= 20 THEN '20岁及以下'WHEN age BETWEEN 21 AND 30 THEN '21-30岁'ELSE '30岁以上'END;
实现效果如下:
3 多类别分组统计(竖表转横表)
3.1 场景
数据报表统计过程中,需要按多类别进行组合分组进行统计,比如:
- 商城需要按商品类型统计每月销售额情况,表头:类别、1月、2月…
这种报表通常需要将原本按行存储的数据(竖表)转换为按列展示的数据(横表),以便更直观地查看和分析数据。
3.2 需求
创建一个表来存储订单数据,包括商品类型、订单时间和订单月份。
编写SQL查询,将订单数据按商品类型分组,根据年份筛选统计每月的订单金额
3.3 实现
3.3.1 表及数据准备
-- 订单表创建
CREATE TABLE `tb_order` (`id` bigint(10) NOT NULL COMMENT '主键',`cate` varchar(20) DEFAULT NULL COMMENT '分类',`order_date` datetime DEFAULT NULL COMMENT '订单日期',`order_amount` decimal(10,2) DEFAULT NULL COMMENT '订单金额', -- 金额建议用decimal进行存储PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;-- 初始化数据
INSERT INTO `tb_order` (`id`, `cate`, `order_date`, `order_amount`) VALUES (1, '服装', '2024-01-08 14:23:10', 1000.00);
INSERT INTO `tb_order` (`id`, `cate`, `order_date`, `order_amount`) VALUES (2, '手机', '2024-02-08 14:23:10', 1000.00);
INSERT INTO `tb_order` (`id`, `cate`, `order_date`, `order_amount`) VALUES (3, '平板', '2024-02-08 14:23:10', 2000.00);
INSERT INTO `tb_order` (`id`, `cate`, `order_date`, `order_amount`) VALUES (4, '电脑', '2024-03-08 14:23:10', 4000.00);
原始数据:
3.3.2 Sql编写
SELECT cate,
sum(if(MONTH(order_date) =1,order_amount,0)) '1月',
sum(if(MONTH(order_date) =2,order_amount,0)) '2月',
sum(if(MONTH(order_date) =3,order_amount,0)) '3月'
from tb_order ord
WHERE year(order_date) =2024 -- 查询指定年份
GROUP BY cate;
实现效果如下,根据结果可看出,统计出每个分类下每月的销售额汇总数据
4 占比统计(多层子查询)
4.1 场景
数据报表统计过程中,需要统计某一分类的占比情况,比如:
- 成绩管理系统中,按学科分组,统计该学科总人数、及格人数、及格率
4.2 需求
创建一个学生考试成绩表,包含所属学生、学科、成绩字段,以60分为及格线,统计及格人数和及格率,按学科分组,统计该学科总人数、及格人数、及格率
4.3 实现
4.3.1 表及数据准备
-- 成绩表创建
CREATE TABLE `tb_score` (`id` bigint(10) NOT NULL COMMENT '主键',`stu_id` bigint(10) DEFAULT NULL COMMENT '学生id',`subject` varchar(50) DEFAULT NULL COMMENT '学科',`score` int(2) DEFAULT NULL COMMENT '成绩',PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- 初始化数据
INSERT INTO `tb_score` (`id`, `stu_id`, `subject`, `score`) VALUES (1, 1, '语文', 50);
INSERT INTO `tb_score` (`id`, `stu_id`, `subject`, `score`) VALUES (2, 2, '语文', 70);
INSERT INTO `tb_score` (`id`, `stu_id`, `subject`, `score`) VALUES (3, 3, '语文', 80);
INSERT INTO `tb_score` (`id`, `stu_id`, `subject`, `score`) VALUES (4, 1, '数学', 48);
INSERT INTO `tb_score` (`id`, `stu_id`, `subject`, `score`) VALUES (5, 2, '数学', 20);
INSERT INTO `tb_score` (`id`, `stu_id`, `subject`, `score`) VALUES (6, 3, '数学', 80);
INSERT INTO `tb_score` (`id`, `stu_id`, `subject`, `score`) VALUES (7, 1, '英语', 70);
INSERT INTO `tb_score` (`id`, `stu_id`, `subject`, `score`) VALUES (8, 2, '英语', 20);
原始数据:
4.3.2 sql编写
SELECT t.*,CONCAT(round(t.pass_stu / t.total_stu * 100,2),'%') '及格率'
from (-- 通过子查询,先进行初步统计SELECT subject,count(stu_id) total_stu,sum(if(score>= 60,1,0)) pass_stu from tb_scoreGROUP BY subject
)t
查询效果