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

SQL135 每个6/7级用户活跃情况

SQL135 每个6/7级用户活跃情况

版本1

withexam as (select distinctuid,level,start_time as submit_time,exam_id as qq_idfromuser_infoleft join exam_record using (uid)),question as (select distinctuid,level,submit_time,question_id as qq_idfromuser_infoleft join practice_record using (uid)),temp1 as (selectuid,count(distinct date_format(submit_time, "%Y-%m")) as act_month_total,count(distinct if(year(submit_time) = 2021,date_format(submit_time, "%Y-%m-%d"),null)) as act_days_2021from(select*fromexamunionselect*fromquestion) as t1wherelevel in (6, 7)group byuid),temp2 as (selectuid,count(distinct if(year(submit_time) = 2021,date_format(submit_time, "%Y-%m-%d"),null)) as act_days_2021_examfromexamwherelevel in (6, 7)group byuid),temp3 as (selectuid,count(distinct if(year(submit_time) = 2021,date_format(submit_time, "%Y-%m-%d"),null)) as act_days_2021_questionfromquestionwherelevel in (6, 7)group byuid)
select*
fromtemp1join temp2 using (uid)join temp3 using (uid)
order byact_month_total desc,act_days_2021 desc;
  1. 数据准备阶段​:

    • 创建exam临时表:从用户信息和考试记录中提取用户ID、等级、提交时间和考试ID
    • 创建question临时表:从用户信息和练习记录中提取用户ID、等级、提交时间和问题ID
  2. 汇总统计阶段​:

    • temp1:计算每个用户的总活跃月数和2021年的活跃天数(合并考试和练习记录)
    • temp2:单独计算每个用户在2021年通过考试的活跃天数
    • temp3:单独计算每个用户在2021年通过练习的活跃天数
  3. 最终输出​:

    • 将三个临时表通过用户ID连接起来
    • 按总活跃月数(降序)和2021年总活跃天数(降序)排序

好啰嗦,受不了了....


版本2

WITH user_activities AS (SELECTu.uid,u.level,a.submit_time,CASE WHEN e.exam_id IS NOT NULL THEN 'exam' ELSE 'question' END AS activity_typeFROM user_info uLEFT JOIN (SELECT uid, start_time AS submit_time, exam_id FROM exam_recordUNION ALLSELECT uid, submit_time, question_id FROM practice_record) a ON u.uid = a.uidLEFT JOIN exam_record e ON a.uid = e.uid AND a.submit_time = e.start_timeWHERE u.level IN (6, 7)
)SELECTuid,COUNT(DISTINCT DATE_FORMAT(submit_time, '%Y-%m')) AS act_month_total,COUNT(DISTINCT IF(YEAR(submit_time) = 2021, DATE(submit_time), NULL)) AS act_days_2021,COUNT(DISTINCT IF(YEAR(submit_time) = 2021 AND activity_type = 'exam', DATE(submit_time), NULL)) AS act_days_2021_exam,COUNT(DISTINCT IF(YEAR(submit_time) = 2021 AND activity_type = 'question', DATE(submit_time), NULL)) AS act_days_2021_question
FROM user_activities
GROUP BY uid
ORDER BY act_month_total DESC, act_days_2021 DESC;

    这个优化后的SQL查询在以下几个方面进行了简化:

    1. ​合并数据源

    原查询使用了三个临时表(examquestiontemp1temp2temp3),而新查询通过一个user_activities CTE合并了所有数据源,减少了中间表的数量。

    2. ​简化JOIN逻辑

    原查询:

    • 先分别创建examquestion两个临时表
    • 然后在temp1中通过UNION合并
    • 最后再JOIN两个单独统计的表

    新查询:

    • 一次性通过UNION ALL合并考试和练习记录
    • 使用CASE WHEN直接标记活动类型
    • 只需要一次GROUP BY就能完成所有统计

    3. ​减少重复计算

    原查询:

    • 对2021年的活跃天数计算了三次(总天数、考试天数、练习天数)

    新查询:

    • 在一次GROUP BY中同时计算所有指标
    • 使用条件聚合(COUNT DISTINCT + IF)一次性得出三个指标

    4. ​更清晰的逻辑

    • 使用activity_type字段明确区分活动类型
    • 所有统计逻辑集中在一个SELECT中,更容易理解
    • 避免了多次JOIN操作

    5. ​性能优势

    • 减少了对基表的扫描次数(原查询扫描了三次:exam、question、合并后)
    • 减少了中间结果的存储和传递
    • 只需要一次分组操作就能得到所有结果

     LEFT JOIN exam_record e ON a.uid = e.uid AND a.submit_time = e.start_time 是查询中最关键的部分之一,​这条JOIN语句的目的是将合并后的活动记录(a)与原始的考试记录(e)重新关联起来。​虽然子查询a已经包含了考试记录,但我们额外做这个JOIN是为了:

    • 区分考试和练习活动​:通过检查e.exam_id IS NOT NULL来判断是否是考试
    • 确保数据一致性​:二次验证考试记录的真实性​
    • a.uid = e.uid:确保是同一个用户
    • a.submit_time = e.start_time:确保是同一次考试(时间戳匹配)

    使用LEFT JOIN是为了:

    • 保留所有活动记录(包括练习记录)
    • 对于练习记录,e表的字段都会是NULL(因为时间戳不匹配)
    • 对于考试记录,能关联到原始的考试详情

    版本3

    SELECTu.uid,COUNT(DISTINCT DATE_FORMAT(a.submit_time, '%Y-%m')) AS act_month_total,COUNT(DISTINCT IF(YEAR(a.submit_time) = 2021, DATE(a.submit_time), NULL)) AS act_days_2021,COUNT(DISTINCT IF(YEAR(a.submit_time) = 2021 AND e.exam_id IS NOT NULL, DATE(a.submit_time), NULL)) AS act_days_2021_exam,COUNT(DISTINCT IF(YEAR(a.submit_time) = 2021 AND e.exam_id IS NULL, DATE(a.submit_time), NULL)) AS act_days_2021_question
    FROM user_info u
    LEFT JOIN (SELECT uid, start_time AS submit_time, exam_id FROM exam_recordUNION ALLSELECT uid, submit_time, NULL FROM practice_record
    ) a ON u.uid = a.uid
    LEFT JOIN exam_record e ON a.submit_time = e.start_time AND a.uid = e.uid
    WHERE u.level IN (6, 7)
    GROUP BY u.uid
    ORDER BY act_month_total DESC, act_days_2021 DESC;

    这个最新版本的SQL查询相比上一个版本又做了几项重要的优化:

    1. ​消除了CASE WHEN判断

    上一个版本使用了CASE WHEN e.exam_id IS NOT NULL THEN 'exam' ELSE 'question'来判断活动类型,而新版本直接在JOIN阶段通过exam_id IS NOT NULLexam_id IS NULL来区分考试和练习活动,减少了条件判断的开销。

    2. ​优化了UNION ALL子查询

    上一个版本在UNION ALL后保留了question_id,但实际上不需要这个字段。新版本在练习记录中直接使NULL代替question_id,减少了不必要的数据传输。

    3. ​简化了JOIN逻辑

    上一个版本需要LEFT JOIN exam_record来确定活动类型,而新版本通过UNION ALL子查询中保留的exam_id信息就能区分活动类型,减少了JOIN操作的复杂度。

    4. ​更精确的JOIN条件

    新版本在LEFT JOIN exam_record时同时使用了时间(a.submit_time = e.start_time)和用户ID(a.uid = e.uid)作为连接条件,比上一个版本更精确,避免了可能的错误匹配。

    5. ​减少了一次子查询嵌套

    上一个版本使用了CTE (WITH子句),而新版本直接在FROM子句中使用内联视图,对于简单查询可以减少一层查询嵌套。

    性能影响

    1. 减少了内存使用(UNION ALL中传输的数据更少)
    2. 降低了CPU计算量(去除了CASE WHEN判断)
    3. 提高了JOIN效率(更精确的JOIN条件)
    4. 查询计划可能更简单直接

    补充知识

    字段名继承​:UNION ALL会使用第一个SELECT语句中的字段名作为结果集的列名

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

    相关文章:

  1. ${project.basedir}延申出来的Maven内置的一些常用属性
  2. Python入门Day5
  3. 嵌入式面试八股文100题(二)
  4. 分库分表之实战-sharding-JDBC水平分库+水平分表配置实战
  5. 【深度学习入门 鱼书学习笔记(1)感知机】
  6. 7月8日学习笔记——统计决策方法
  7. 基于springboot的物流配货系统
  8. Nuxt.js 静态生成中的跨域问题解决方案
  9. C++学习笔记之数组、指针和字符串
  10. 【PyTorch】PyTorch中torch.nn模块的激活函数
  11. 项目Win系统下可正常获取Header字段,但是到了linux、docker部署后无法获取
  12. python基础day08
  13. linux wsl2 docker 镜像复用快速方法
  14. 【读代码】GLM-4.1V-Thinking:开源多模态推理模型的创新实践
  15. 基于模板设计模式开发优惠券推送功能以及对过期优惠卷进行定时清理
  16. C++ 遍历可变参数的几种方法
  17. 数据库表设计:图片存储与自定义数据类型的实战指南
  18. C语言宏替换比较练习
  19. 暑假算法日记第四天
  20. 5.6.2、ZeroMQ源码分析
  21. 利用AI Agent实现精准的数据分析
  22. ARM环境openEuler2203sp4上部署19c单机问题-持续更新
  23. VM上创建虚拟机以及安装RHEL9操作系统并ssh远程连接
  24. 大模型系列——RAG-Anything:开启多模态 RAG 的新纪元,让文档“活”起来!
  25. Proface触摸屏编程软件(GP-Pro EX)介绍及下载
  26. 金融行业信息
  27. 力扣-75.颜色分类
  28. Sentinel入门篇【流量治理】
  29. 行业实践案例:医疗行业数据治理的挑战与突破
  30. 【RAG知识库实践】数据源Data Source