ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

MySQL聚合函数与窗口函数实战:从分组统计到排名计算

MySQL聚合函数与窗口函数实战:从分组统计到排名计算 1. 聚合函数先打个底1.1 五个最常用的聚合函数你真的用对了吗MySQL 里的聚合函数说白了就是“把多行数据揉成一行结果”的函数。平时工作中最常见的无非是 COUNT、SUM、AVG、MIN、MAX 这五个但它们各自的细节坑并不少。先说 COUNT。很多人写统计总数时随手就是COUNT(*)这没问题它统计的是符合条件的所有行数包括整行都是 NULL 的行。但如果你写成COUNT(salary)那就只统计 salary 字段非 NULL 的行数了。这个区别在字段允许为 NULL 的表中特别容易翻车后面我在坑点部分会专门展开。SUM 和 AVG 比较好理解但也有一个共性陷阱如果参与计算的列里有 NULL 值MySQL 的 SUM、AVG 会直接忽略 NULL而不是把 NULL 当成 0。举个例子一个订单明细表里退款金额字段很多行是 NULL你直接SUM(refund_amount)结果其实是“非 NULL 退款金额的合计”不是“默认 0 的合计”。如果业务上 NULL 表示“没发生退款”那预期应该是 0直接 SUM 也没毛病但如果你的数据入库时没做默认值处理NULL 和 0 混在一堆统计结果就可能比预期小排查还特别费劲。MIN 和 MAX 多数时候很直观但需要注意它们在不同数据类型上的表现对字符串取 MIN/MAX是按字符串排序规则来的不是按“看起来像数字”的顺序对日期时间类型直接取 MAX 就是算“最近一次”这在取用户最后登录时间时很实用。来个最简单的例子还是用下面这个员工表CREATE TABLE emp ( id INT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(20), dept VARCHAR(20), salary DECIMAL(10, 2) ); INSERT INTO emp (name, dept, salary) VALUES (张三, 技术部, 12000), (李四, 技术部, 13500), (王五, 技术部, 11000), (赵六, 运营部, 9500), (孙七, 运营部, 10200), (周八, 运营部, 8900), (吴九, 产品部, 14500), (郑十, 产品部, 15000);基础聚合随便一写就是这样SELECT COUNT(*) AS 员工数, SUM(salary) AS 总工资, AVG(salary) AS 平均工资, MIN(salary) AS 最低工资, MAX(salary) AS 最高工资 FROM emp;这个结果集只有一行但已经把这些函数的脾性全暴露出来了。AVG 在 MySQL 里默认就是按照浮点或 DECIMAL 精度算出的小数不会自动四舍五入成整数。你如果业务上需要“人均工资保留两位小数”要么在查询结果里套 ROUND要么建表时把字段类型设计成 DECIMAL(10, 2) 并在统计时注意返回精度。1.2 GROUP BY 分组聚合的发动机聚合函数不配 GROUP BY那只是“全局统计”一旦配上 GROUP BY聚合函数才会真正体现出“分部门、分城市、分渠道”的价值。它的逻辑很像 Excel 里的透视表先按指定列把数据切片然后在每个切片里执行聚合。常见写法是这样SELECT dept, COUNT(*) AS cnt, AVG(salary) AS avg_sal FROM emp GROUP BY dept;这里有个非常关键的执行顺序问题WHERE 是先过滤行再分组HAVING 是先分组再过滤组。所以如果你想查“技术部里工资大于 10000 的员工有多少”应该把salary 10000放进 WHERE而不是 HAVING。但是如果你想查“平均工资大于 10000 的部门有哪些”那必须在 HAVING 里写AVG(salary) 10000因为 WHERE 不能使用聚合函数的计算结果。SELECT dept, AVG(salary) AS avg_sal FROM emp GROUP BY dept HAVING AVG(salary) 10000;另一个容易踩的坑是 ONLY_FULL_GROUP_BY 模式。MySQL 5.7 之后默认开启了这个 SQL 模式它要求出现在 SELECT 列表里的普通列必须要么被 GROUP BY 包含要么被聚合函数包住。不然会直接报错。这其实是好事能逼你在写 SQL 时想清楚“非聚合列”到底取哪一行。如果业务上确实想取“组内第一个值”或“组内某个特殊行”更稳的做法是配合窗口函数或子查询而不是去关闭这个模式。GROUP BY 还支持多列比如按部门和城市统计GROUP BY dept, city。它的分组粒度会更细等于先按 dept 分成大组再在大组里按 city 分成小组所有聚合都是基于最小分组的。1.3 GROUP_CONCAT 及其他冷门聚合函数除了常规五件套GROUP_CONCAT 在我实际工作中出镜率也很高。它能把组内多个字段值拼成一个字符串比如查“每个部门都有哪些员工”一条 SQL 就出来了SELECT dept, GROUP_CONCAT(name SEPARATOR 、) AS emp_names FROM emp GROUP BY dept;默认分隔符是逗号想改可以用SEPARATOR关键字。更实用的是它支持在拼接前排序比如按工资从高到低拼接SELECT dept, GROUP_CONCAT(name ORDER BY salary DESC SEPARATOR 、) AS emp_names FROM emp GROUP BY dept;这个函数在生成报表、导出数据、处理一对多关系时能省掉大量代码但要注意它有长度限制默认值是 1024 字节超过了会被截断。如果需要拼很长文本可以调group_concat_max_len系统变量。MySQL 里还有几个不那么常用但值得知道的聚合函数BIT_AND、BIT_OR、BIT_XOR做位运算聚合适合处理权限位、开关位这种场景STDDEV和VAR_POP/VAR_SAMP做标准差和方差统计数据分析场景能用上。这些不常用但面试问到“MySQL 聚合函数有哪些”时点名提到它们能体现你的储备不是背出来的。2. 窗口函数不折叠行的“分组计算”2.1 窗口函数到底解决了什么问题传统聚合函数最大的“毛病”是折叠行。比如你按部门统计平均工资部门里每个员工的原始行数据就没了只能看到一个部门的汇总结果。可实际业务里我经常遇到这样的需求既要看到员工自己的工资又要知道所在部门的平均工资甚至还想算“员工工资占部门总工资的比例”。如果用普通聚合函数你得先写个子查询算部门统计再 JOIN 回去麻烦且性能一般。窗口函数就是为这种场景设计的它既能像 GROUP BY 一样按某个维度分组又不会把多行压缩成一行每一行原样保留旁边多出一列计算结果。窗口函数的核心语法是函数名 OVER (PARTITION BY 列 ORDER BY 列)PARTITION BY相当于分组叫“分区”它定义了窗口范围ORDER BY在窗口函数里不单单是排序它还决定了窗口计算的顺序和累计逻辑这一点特别容易和普通 ORDER BY 混淆。比如SUM(salary) OVER (PARTITION BY dept ORDER BY salary)这个 SUM 是“截至当前行的累计和”不是“整个部门的总和”。如果你只想要部门总和一定不要加 ORDER BYSELECT name, dept, salary, SUM(salary) OVER (PARTITION BY dept) AS dept_total FROM emp;执行结果里同部门每一行显示的 dept_total 都一样这就是“部门总工资”。如果你脑抽加了ORDER BY salary结果就会变成累计求和同部门每个员工看到的数都不相同这个区别极其关键。2.2 排名三兄弟ROW_NUMBER、RANK、DENSE_RANK窗口函数里有三个函数几乎每天都会用上就是排名三兄弟。ROW_NUMBER()不管有没有并列依次给每一行分配唯一的序号1、2、3、4……RANK()有并列时会出现跳号比如两个并列第 1下一个就是第 3。DENSE_RANK()有并列时不会跳号两个第 1 之后下一个还是第 2。员工表里按部门内部工资排名SELECT name, dept, salary, ROW_NUMBER() OVER (PARTITION BY dept ORDER BY salary DESC) AS row_num, RANK() OVER (PARTITION BY dept ORDER BY salary DESC) AS rk, DENSE_RANK() OVER (PARTITION BY dept ORDER BY salary DESC) AS dense_rk FROM emp;如果同一个部门里有两个人的工资正好都是 13500那么 ROW_NUMBER 会硬分出 1 和 2RANK 会给出 1 和 1下一个名次是 3DENSE_RANK 也会给出 1 和 1但下一个名次是 2。这个细微差别在做竞赛排行榜、销售榜单时直接决定业务口径。比如“并列第二算不算第二”如果是“取前两名并列只算两个名额”用 ROW_NUMBER如果是“并列都应该入选”就得用 RANK 或 DENSE_RANK具体再看要不要跳号。NTILE(n)也是灵活度很高的一个窗口函数它会把分区里的行尽量均匀地分成 n 组然后告诉你每行落在第几组。这个在“按排名分桶”比如把用户按消费金额分成高、中、低三档时非常方便。2.3 LAG、LEAD 和滑动窗口函数除了排名窗口函数另一个高频用法是访问相邻行。LAG(列, n)取当前行往前数第 n 行的值LEAD(列, n)取往后数第 n 行的值。这个能力用来算环比、同比、对比上一笔订单都特别舒服。比如有一张每日销售额表CREATE TABLE daily_sales ( sale_date DATE PRIMARY KEY, amount DECIMAL(10, 2) ); INSERT INTO daily_sales (sale_date, amount) VALUES (2025-01-01, 1200.00), (2025-01-02, 1500.00), (2025-01-03, 1300.00), (2025-01-04, 1700.00);计算每日销售额和它前一天的差值SELECT sale_date, amount, LAG(amount, 1) OVER (ORDER BY sale_date) AS prev_amount, amount - LAG(amount, 1) OVER (ORDER BY sale_date) AS diff FROM daily_sales ORDER BY sale_date;这里窗口里的 ORDER BY 决定了 LAG 的方向。如果不写 ORDER BY那 LAG 就不知道“往前数”到底按什么顺序所以这种场景下 ORDER BY 不是可选项而是必须项。滑动窗口则更复杂一点它通过ROWS或RANGE子句来控制窗口的行范围常见写法是SELECT sale_date, amount, AVG(amount) OVER (ORDER BY sale_date ROWS BETWEEN 2 PRECEDING AND CURRENT ROW) AS moving_avg FROM daily_sales ORDER BY sale_date;这算的是“当前行以及前两行”的移动平均常用于平滑曲线图、过滤数据噪声。注意ROWS BETWEEN ... AND ...的语法可以定义当前行之前多少行和之后多少行。如果不写帧定义MySQL 会根据是否存在 ORDER BY 给出默认窗口有 ORDER BY 时默认是RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW这是累计语义没有 ORDER BY 时才是整个分区这个默认行为很多人记不住索性每次写窗口时都把自己想要的边界写清楚才是好习惯。3. 数学函数SQL 里的科学计算器3.1 常用数学函数快速过一遍MySQL 的数学函数不像聚合和窗口那样“高深”但胜在数量多很多人在用到的时候才搜其实提前掌握能省不少事。绝对值函数ABS(-10)返回 10不用多想。取整函数有两个容易混CEIL/CEILING向上取整FLOOR向下取整。CEIL(3.2)结果是 4FLOOR(3.8)结果是 3。注意它们都不做四舍五入也不关心正负号的方向CEIL(-3.2)结果是 -3别搞反了。ROUND是最常用的四舍五入函数支持指定小数位ROUND(3.14159, 2)返回 3.14ROUND(3.145, 2)返回 3.15。这里有个隐蔽的坑MySQL 的 ROUND 对小数位的处理依赖浮点数的二进制表示某些值会出现诡异的舍入结果比如ROUND(2.675, 2)在某些版本下可能返回 2.67 而不是 2.68。这涉及浮点精度问题后面单开一节讲。TRUNCATE和 ROUND 长得挺像但它是直接截断TRUNCATE(3.14159, 2)返回 3.14不管第三位是 4 还是 5直接砍掉不做舍入。如果你处理的是金额明细要求“分以下直接舍去”用 TRUNCATE 而不是 ROUND。求余数用MOD(10, 3)返回 1也可以直接写10 % 3效果一样。幂运算和开方分别是POWER(2, 10)返回 1024SQRT(9)返回 3。SIGN(-5)返回 -1SIGN(0)返回 0SIGN(5)返回 1判断正负数很顺手。RAND()返回 0 到 1 之间的随机数可以带种子参数RAND(100)每次执行结果相同适合调试可复现的随机场景。3.2 ROUND 精度里藏着的浮点坑浮点数精度问题在 MySQL 里特别容易翻车。原因很本质计算机里很多十进制小数没法用二进制精确表示比如 0.675 在二进制里是个无限小数存储时被近似了ROUND 再按照这个近似值做舍入结果就会偶尔“离谱”。我实测过的一个经典案例是ROUND(2.675, 2)在很多 MySQL 版本里返回 2.67因为这个数实际存储的浮点值比 2.675 略小一点点。解决方案有两个一是把运算的字段类型定义为 DECIMALDECIMAL 是定点数不存在浮点误差ROUND(CAST(2.675 AS DECIMAL(10, 3)), 2)就正常返回 2.68二是允许接受极小误差在很苛刻的金额计算里用浮点本身就是错误源头。还有一点要注意MySQL 的 ROUND 和很多程序设计语言的“四舍五入”规则并不完全一样。传统意义上的四舍五入是 0.5 就进位但某些语言用的是“银行家舍入”round half to evenMySQL 默认行为更接近于“离零更远的方向舍入”。所以跨语言核对金额时别假设规则相同最好以数据库的最终计算结果为准或者统一在应用层计算。3.3 数学函数在业务里的常见组合数学函数的价值不会单独体现一定要和业务场景结合。比如订单金额里有折扣和税费计算实付金额SELECT order_id, ROUND(original_amount * 0.8, 2) AS discounted_amount, TRUNCATE(original_amount * 0.8, 2) AS actual_paid_amount FROM orders;折扣通常要四舍五入到分但如果业务方要求“抹零”则用 TRUNCATE。随机抽样是另一个高频场景。从用户表里随机抽 5 个用户做活动测试大家大概率会写SELECT * FROM users ORDER BY RAND() LIMIT 5;这个写法数据量小完全没问题但如果表有几百万行ORDER BY RAND()会产生临时表和全表排序性能很差。可以改成先取主键范围再用 RAND 在主键区间里随机选几个 ID性能能提升几个数量级。还有地理距离计算。如果只做粗略预估可以用:SELECT name, SQRT(POWER((lng1 - lng2) * 111000, 2) POWER((lat1 - lat2) * 111000, 2)) AS distance_m FROM places;纬度方向上每度大约 111 公里经度方向上会随纬度变化如果对精度要求很高就别偷懒直接用 Haversine 公式和三角函数MySQL 也提供了ACOS、COS、SIN、RADIANS这些函数。数学函数单个都很简单组合起来解决实际问题才是价值所在。4. 三军联合作战一个完整案例带你把函数揉进真实业务4.1 场景一按部门计算员工工资占比假设现在公司要让每个员工看到自己在部门内的工资排名和占比这是聚合函数、窗口函数、数学函数三合一的经典场景。员工表还是前面那张 emp 表。需求拆开来是这样的每个员工的工资在部门内的排名每个员工的工资占部门总工资的百分比百分比保留两位小数普通聚合思路先按部门分组算总工资再 JOIN 回员工表最后用 员工工资 / 部门总工资 算占比。这样的 SQL 要写两层子查询看起来不优雅。窗口函数一条 SQL 就能解决SELECT name, dept, salary, RANK() OVER (PARTITION BY dept ORDER BY salary DESC) AS dept_rank, ROUND( salary / SUM(salary) OVER (PARTITION BY dept) * 100, 2 ) AS salary_percent FROM emp ORDER BY dept, salary DESC;这里的SUM(salary) OVER (PARTITION BY dept)返回的是部门总工资同一部门内每一行都一样所以每个员工都能拿着这个值计算自己的占比。RANK 用来做部门内排名处理并列时更符合“薪酬排名”业务习惯。最后用 ROUND 把占比保留两位小数。如果这时候需求再细化一点只想看“部门内工资占比超过 20% 的员工”你会发现 WHERE 和 HAVING 都没法直接用因为窗口函数计算出的结果在 SELECT 阶段才产生。正确做法是套一层子查询SELECT * FROM ( SELECT name, dept, salary, RANK() OVER (PARTITION BY dept ORDER BY salary DESC) AS dept_rank, ROUND(salary / SUM(salary) OVER (PARTITION BY dept) * 100, 2) AS salary_percent FROM emp ) t WHERE salary_percent 20;这种“先算窗口再过滤结果”的模式在复杂报表里极其常见请务必记住窗口函数不能直接出现在 WHERE 子句里。4.2 场景二销售订单的环比增长和移动平均再来看一段销售分析需求。有一张订单表记录每天的销售额现在需要按天算出销售额环比增长率和三日移动平均。表结构我们前面建过 daily_sales现在往里面插多一点数据再演示INSERT INTO daily_sales (sale_date, amount) VALUES (2025-01-05, 2000.00), (2025-01-06, 1800.00), (2025-01-07, 2400.00), (2025-01-08, 2600.00);环比增长率的定义是(当天销售额 - 前一天销售额) / 前一天销售额 × 100%。这里用 LAG 很顺手SELECT sale_date, amount, LAG(amount, 1) OVER (ORDER BY sale_date) AS prev_amount, ROUND( (amount - LAG(amount, 1) OVER (ORDER BY sale_date)) / LAG(amount, 1) OVER (ORDER BY sale_date) * 100, 2 ) AS growth_rate, ROUND( AVG(amount) OVER ( ORDER BY sale_date ROWS BETWEEN 2 PRECEDING AND CURRENT ROW ), 2 ) AS moving_avg_3 FROM daily_sales ORDER BY sale_date;第一天的 prev_amount 是 NULLgrowth_rate 也自然是 NULL在报表里显示为空这符合业务预期不用特意处理。这个查询同时用到了 LAG、窗口函数里的 AVG、ROWS BETWEEN 滑动窗口、ROUND 和小数精确控制。窗口函数加数学函数配合聚合函数做时间序列分析是数据分析岗最常用的技能组合之一。4.3 场景三各种分组 TopN 查询TopN 查询是窗口函数最经典的应用场景。需求找出每个部门工资最高的前 2 名员工。如果不用窗口函数你得写各种带有“相关子查询”的复杂 SQL还容易算错。使用 ROW_NUMBER 很简单SELECT name, dept, salary FROM ( SELECT name, dept, salary, ROW_NUMBER() OVER (PARTITION BY dept ORDER BY salary DESC) AS rn FROM emp ) t WHERE rn 2 ORDER BY dept, salary DESC;注意这里子查询里排序是按工资降序然后外部再按部门排序目的是保持输出顺序好读。实际业务中如果遇到“取的 TopN 中工资并列的都要”就把 ROW_NUMBER 换成 RANK 或 DENSE_RANK并根据业务口径决定是否允许跳号。TopN 和前面员工占比、环比增长一样本质思路都是“先通过窗口函数生成辅助列再用子查询做过滤或二次加工”。把这个模式吃透你大概也就掌握了 70% 的窗口函数实战场景。5. 这三个函数最容易踩的坑我挨个帮你趟一遍5.1 COUNT(*) 与 COUNT(列) 的结果不同这是聚合函数里翻车率最高的一个问题。COUNT(*) 统计的是行数不管这一行是不是全 NULLCOUNT(列名) 统计的是该列非 NULL 的行数。之前有个朋友跑数据业务上要统计“这个月下单用户数”他直接写了COUNT(user_id)结果发现比COUNT(*)少了很多。排查半天发现是用户表里历史数据有一批 user_id 是 NULL 的记录这些记录根本不是有效用户但又占着行。实际上在这个需求场景里COUNT(user_id)反而是对的因为它会自动忽略空用户。关键是写 SQL 前你得想清楚你要的是“所有行数”还是“某个字段有值的行数”。统计口径一旦定错后面所有报表都会跟着错。5.2 SUM、AVG 遇到 NULL 不等于 0SUM 和 AVG 对 NULL 的处理是直接跳过既不报错也不把它当 0 计算。这个机制天然合理但它会带来一个反直觉的结果如果参与聚合的全部都是 NULLAVG 返回的是 NULL而不是 0。比如订单表里有 5 单退款金额都是 NULL你执行AVG(refund_amount)得到的是 NULL前端展示出来就是个空值用户看着像 bug。稳妥的做法是对结果做一次空值兜底SELECT IFNULL(AVG(refund_amount), 0) AS avg_refund FROM orders;或者用COALESCE(AVG(refund_amount), 0)语义一样但 COALESCE 是标准 SQL 写法可读性和兼容性更好。5.3 GROUP BY 导致 ONLY_FULL_GROUP_BY 报错MySQL 5.7 以上默认开启 ONLY_FULL_GROUP_BY 模式最典型的报错信息是Expression #2 of SELECT list is not in GROUP BY clause and contains nonaggregated column ...意思是 SELECT 后面的普通列没有出现在 GROUP BY 里也没有被聚合函数包裹。解决方式只有两个一是把你真正要展示的列加进 GROUP BY二是用合适的方式聚合。千万不要遇到报错就关掉这个模式除非你清楚地知道自己在干什么。关掉之后 MySQL 会从分组结果中随机选一行不同版本、不同执行计划可能选出不同的值这种隐藏的不确定性比报错可怕得多。5.4 窗口函数不能在 WHERE 和 HAVING 中直接使用窗口函数是在查询执行到 SELECT 阶段才计算的所以 WHERE、HAVING 虽然在语义上“更早”执行但在 SQL 语法层面都没法直接引用窗口函数的结果。我见过不少初级同事写这种 SQL-- 错误的写法 SELECT name, dept, salary, RANK() OVER (PARTITION BY dept ORDER BY salary DESC) AS rk FROM emp WHERE rk 1;这会在解析阶段直接报错。正确做法是把窗口函数查询放到子查询里然后在外层过滤。前面 TopN 的例子里就是用这个模式。这个习惯一定要养成不然等你写复杂嵌套查询时会反复被这种语法错误卡住。5.5 ROUND、TRUNCATE 和 DECIMAL 别混用数学函数的坑集中在精度和类型。ROUND 返回的结果类型跟着入参走如果入参是 FLOAT结果可能带浮点尾数如果入参是 DECIMAL结果也是 DECIMAL。所以在金额计算时建议把列类型设计成 DECIMAL计算函数也统一用 ROUND。还有一个小细节MOD函数对 DECIMAL 也有效但如果你的需求是“判断是否为偶数”用MOD(id, 2) 0没问题但 id 一旦超过某些精度范围浮点误差也可能钻进来。别嫌啰嗦数据库里的每一个看起来“差不多”的结果最后都可能成为报表里对不上的那几毛钱。5.6 窗口函数的 ORDER BY 和普通 ORDER BY 语义不同这个坑我放在最后说因为它最难发现。普通查询的 ORDER BY 只影响最终结果的展示顺序。但窗口函数里的 ORDER BY 会直接影响计算结果。同样是滑动求和SUM(salary) OVER (PARTITION BY dept) -- 部门总工资每行一样 SUM(salary) OVER (PARTITION BY dept ORDER BY salary) -- 截至当前行的累计工资 SUM(salary) OVER (PARTITION BY dept ORDER BY salary DESC) -- 从当前行到末尾的逆序累计三行 SQL 只差一个 ORDER BY结果天差地别。如果你不是故意要“累计”“移动”语义就千万别在窗口函数里随手加 ORDER BY。反过来如果你要算同比、环比、移动平均又必须确保 ORDER BY 字段和业务上的时间顺序完全一致否则算出来的 LAG 会指向完全不相干的行。6. 我对这三个函数使用的几点体感最后说点我自己在实际项目里的体感。聚合函数看着简单却是很多 SQL 性能问题的起点。比如COUNT(DISTINCT 列)在数据量大时会触发去重排序性能成本很高如果业务允许近似值可以考虑用专门的统计方案又比如 GROUP BY 字段如果没建索引临时表可能直接落在磁盘上查询会突然慢一个量级。这些都属于“知道写法只是入门知道为什么慢才是进阶”的问题。窗口函数在 MySQL 8.0 之前是不支持的如果你还在维护 5.7 的老库很多排名和环比逻辑只能靠变量模拟又慢又绕。升级到 8.0 之后窗口函数提供了很大便利。不过窗口函数也别滥用遇到“每个分组取前几条”这种需求用窗口函数没问题但如果只是简单分组统计用普通聚合就够了窗口函数通常不会减少扫描成本还容易让执行计划变重。数学函数单个都很轻但组合起来时要注意每个函数的返回类型。比如 ROUND 以后的结果继续参与除法如果不注意用 CAST 或直接依赖 DECIMAL 列最终结果就可能出现精度漂移。数据领域常说“垃圾进垃圾出”在 MySQL 的数值计算里应该改成“精度进精度出”。本篇从聚合函数的分组逻辑到窗口函数不折叠行的计算方式再到数学函数在业务里的组合用法基本把 MySQL 里和“计算”相关的常用函数拉通了一遍。下一篇有思路的话我想接着聊时间日期函数和流程控制函数这两个在实际报表里和聚合、窗口配合得也相当紧密。
返回列表