直接用GROUP BY算不出多级返利,因为返利需按代理层级逐层递推计算(如一级10%、二级在一级基础上再抽5%),而GROUP BY仅做静态分组聚合,无法实现依赖父节点值的跨行递推;窗口函数如LAG()或SUM()OVER也不支持跨层级递归引用,必须先用递归CTE或路径字符串构造代理树,再按path排序进行逐层累加计算。

为什么直接用 GROUP BY 算不出多级返利?
因为返利要按代理层级逐层累加:一级代理拿 10%,二级再从一级的返利里抽 5%,三级再从二级的返利里抽 2%……这不是简单分组聚合,而是依赖父节点值的递推计算。窗口函数本身不支持跨行递归引用(比如 LAG() 只能取上一行,不能自动向上追溯到上级代理),硬套 SUM() OVER (PARTITION BY ...) 会把所有同级代理混在一起算,结果全错。
必须先构造代理树路径,再用字符串拆解模拟层级
核心思路是把每个代理的完整上级链存成有序路径(如 '/root/A/B/C'),这样就能用字符串长度或层级分割数判断深度,并按路径前缀关联上下级。数据库里没有原生树遍历函数时,这是最稳的落地方式。
- 建表时确保有
agent_id、parent_id、commission_rate字段 - 用递归 CTE(PostgreSQL/SQL Server)或自连接(MySQL 8.0+)生成
path字段,例如:WITH RECURSIVE agent_tree AS ( SELECT agent_id, parent_id, commission_rate, CAST('/' || agent_id AS VARCHAR) AS path, 1 AS level FROM agents WHERE parent_id IS NULL UNION ALL SELECT a.agent_id, a.parent_id, a.commission_rate, t.path || '/' || a.agent_id, t.level + 1 FROM agents a JOIN agent_tree t ON a.parent_id = t.agent_id ) - 路径越长,层级越深;
SUBSTRING_INDEX(path, '/', 3)(MySQL)或SPLIT_PART(path, '/', 3)(PostgreSQL)可提取第 2 级代理 ID
用窗口函数算逐层返利时,ORDER BY 必须按路径排序
层级计算本质是“先算顶层,再算下一层”,所以窗口必须按 path 的字典序升序排列,保证父节点一定出现在子节点之前。否则 LAG() 或累计逻辑会引用到尚未计算的行。
- 错误写法:
SUM(commission) OVER (PARTITION BY level ORDER BY agent_id)——agent_id无层级顺序 - 正确写法:
SUM(commission_base * rate) OVER (ORDER BY path ROWS UNBOUNDED PRECEDING),其中commission_base是上级返利结果(需先算出) - 实际要两步:第一步用 CTE 算出每层原始返利(销售额 × 本级费率),第二步用窗口按
path累加并乘以下级费率
MySQL 5.7 没递归 CTE?用临时表+循环模拟也行,但别真在 SQL 里写 WHILE
低版本 MySQL 无法递归,硬写存储过程容易超时或锁表。更可行的是在应用层(Python/Java)查出全量代理关系,内存中构树、DFS 遍历生成 path,再批量 INSERT 到临时表。SQL 层只负责最后的窗口计算。
- 临时表字段至少含:
agent_id、path、level、sales_amount、rate - 避免在 SQL 中用
CONCAT('/', @path)变量拼接——并发时变量状态不可控,结果随机错 - 如果数据量 t1.parent_id = t2.agent_id …),但超过 4 层就爆炸
真正麻烦的不是语法,是返利规则常变动:某级突然加个“满 100 万才触发返点”,这种条件一加,整个路径预计算就得重来。留好 path 字段和层级缓存,比每次重跑递归强得多。

















