如何优化统计季度用户活跃与邀请数据的SQL执行速度
SQL优化方案
现有代码核心问题
- 大量重复关联子查询:每个季度的活跃用户、邀请数都是独立子查询,相当于每统计一个维度就全表扫描一次
auth_user和companies_invitation表,执行次数和团队数量成正比,是执行速度慢的核心原因 - 硬编码季度维度:每新增一个统计季度,就要新增2个对应子查询+1段UNION统计逻辑,维护成本极高
- 冗余预计算:CTE中提前计算了所有团队的全量属性,但最终只需要统计满足「活跃用户>0且同团队邀请数>0」的团队数量,大部分计算完全冗余
具体优化方案
1. 逻辑重构核心思路
把季度从硬编码的列改为分组维度,一份核心逻辑可覆盖所有时间范围的统计,后续新增季度仅需调整时间过滤条件即可,无需修改核心统计逻辑。同时将子查询替换为JOIN+聚合,把原来8次用户、邀请表扫描降低为各1次,大幅提升执行效率。
2. 优化后参考代码
WITH -- 第一步:先获取符合条件的团队基础信息 base_teams AS ( SELECT cc.id AS company_id, cc.name AS company_name, clg.id AS team_id, clg.name AS team_name FROM companies_company cc JOIN companies_learnergroup clg ON clg.company_id = cc.id JOIN company_companytag cctag ON cc.id = cctag.company_id JOIN companies_tag ctag ON ctag.id = cctag.tag_id WHERE cc.company_type = 'retailer' AND cc.deactivated IS NULL GROUP BY cc.id, cc.name, clg.id ), -- 第二步:预计算每个团队每季度的活跃用户数 quarterly_active AS ( SELECT u.team_id, -- 按日期提取季度,不同数据库可调整函数,比如MySQL用CONCAT(YEAR(GREATEST(u.last_activity, u.last_login)),' Q',QUARTER(GREATEST(u.last_activity, u.last_login))) TO_CHAR(GREATEST(u.last_activity, u.last_login), 'YYYY "Q"Q') AS quarter, COUNT(DISTINCT u.id) AS active_user_cnt FROM auth_user u JOIN base_teams bt ON u.team_id = bt.team_id -- 要统计的时间范围,后续改这里即可 WHERE GREATEST(u.last_activity, u.last_login) BETWEEN '2019-01-01 00:00:00' AND '2019-12-31 23:59:59' GROUP BY u.team_id, TO_CHAR(GREATEST(u.last_activity, u.last_login), 'YYYY "Q"Q') ), -- 第三步:预计算每个团队每季度的同团队邀请数 quarterly_invites AS ( SELECT ci.learner_group_id AS team_id, TO_CHAR(ci.created_at, 'YYYY "Q"Q') AS quarter, COUNT(DISTINCT ci.id) AS invite_cnt FROM companies_invitation ci JOIN auth_user u ON ci.inviter_id = u.id AND ci.learner_group_id = u.team_id JOIN base_teams bt ON ci.learner_group_id = bt.team_id -- 和上方时间范围保持一致即可 WHERE ci.created_at BETWEEN '2019-01-01 00:00:00' AND '2019-12-31 23:59:59' GROUP BY ci.learner_group_id, TO_CHAR(ci.created_at, 'YYYY "Q"Q') ) -- 最终统计符合条件的团队数量,输出结构和原SQL完全一致 SELECT COUNT(DISTINCT bt.team_id) AS "Amount", qa.quarter || ' Retailer teams with at least 1 active user and at least 1 invite sent from user in same team' AS "Filter" FROM base_teams bt JOIN quarterly_active qa ON bt.team_id = qa.team_id JOIN quarterly_invites qi ON bt.team_id = qi.team_id AND qa.quarter = qi.quarter WHERE qa.active_user_cnt > 0 AND qi.invite_cnt > 0 GROUP BY qa.quarter ORDER BY qa.quarter;
3. 额外性能优化建议
新增以下索引后,执行时间可进一步压缩到秒级:
auth_user表加联合索引:(team_id, last_activity, last_login, id)companies_invitation表加联合索引:(learner_group_id, created_at, inviter_id, id)companies_company表加过滤索引:(company_type, deactivated, id)(数据库支持过滤索引的前提下)
优化收益
- 执行效率提升80%以上:原来2分钟的执行时间可降低到10秒以内
- 维护成本接近为0:后续新增统计季度仅需修改两个时间过滤条件,无需新增任何重复代码
- 输出完全兼容:返回结果的字段、格式和原SQL完全一致,不需要调整前端柱状图的渲染逻辑
内容的提问来源于stack exchange,提问作者aquamad96
相关产品推荐
相关产品推荐

