SQL 面试系列(一)【留存率问题】
前言
在学 HQL 之前是不太了解 SQL 的,以为 SQL 只可以实现 CRUD ,直到面试的公司让我下去多了解一些 SQL ,我才最近开始再次深入学习 MySQL 和 Oracle。而且越学越发现 SQL 真的是一门很有深度的语言,我以前的使用只是皮毛而已,自惭形秽之下决定必须深入学习 SQL ,毕竟大数据工作之后主要就是 SQL 嘛。
关于留存率问题是一个面试的重点,面一个暑期实习都几乎每次都被问,每次问到留存率我都试图通过障耳法搞晕面试官,结果事实上面试官都能准确发现我回答中的问题。所以不能再拖了,留存率的问题应该好好拿出来深入理解掌握了。
1、留存率问题
1.1、留存率的定义
留存率:用户在注册之后的第 N 天仍然登录的比例,称为第 N 日留存率。
1.2、数据准备
下面我们通过 SQL 先创建一些数据(用户数据 5000条、登录数据 50w条):
-- 留存率问题
-- 示例表
DROP TABLE IF EXISTS t_user;
CREATE TABLE t_user(id INT AUTO_INCREMENT PRIMARY KEY,user_name VARCHAR(50) NOT NULL,register_time DATETIME NOT NULL
);SET SESSION cte_max_recursion_depth=9999999;INSERT INTO t_user(user_name, register_time)
WITH RECURSIVE t AS (SELECT 1 n, '2022-01-01 00:00:00' dUNION ALLSELECT n+1, d + INTERVAL '1' MINUTEFROM tWHERE n<10000
)
SELECT concat('user', n), d FROM t;SELECT date(register_time), count(*) FROM t_user GROUP BY date(register_time);DROP TABLE IF EXISTS t_user_login;
CREATE TABLE t_user_login(id INT AUTO_INCREMENT PRIMARY KEY,uid INT NOT NULL,login_time DATETIME NOT NULL
);SET SESSION cte_max_recursion_depth=9999999;SELECT @@cte_max_recursion_depth;INSERT INTO t_user_login(uid, login_time)
WITH RECURSIVE t AS (SELECT 1 n, rand()*10000 id, '2022-01-01 00:00:00' dUNION ALLSELECT n+1, rand()*10000, d + INTERVAL CEIL(n/3000) secondFROM tWHERE n<500000
)
SELECT CEIL(id), d FROM t;
1.3、计算留存率
1.3.1、思路1:多表连接
我们先计算 1月1日的近一日留存率:
SELECT COUNT(DISTINCT tul.uid)/COUNT(DISTINCT tu.id) rr1
FROM t_user tu
LEFT JOIN t_user_login tul ON (tul.uid = tu.id AND DATE(tul.login_time) = DATE(tu.register_time) + INTERVAL '1' DAY)
WHERE tu.register_time BETWEEN '2022-01-01 00:00:00' AND '2022-01-01 23:59:59';
根据这个思路,我们可以同样实现多日的留存率:
SELECT date(u.register_time),100*count(DISTINCT ul1.uid)/count(DISTINCT u.id) rr1,100*count(DISTINCT ul2.uid)/count(DISTINCT u.id) rr3,100*count(DISTINCT ul3.uid)/count(DISTINCT u.id) rr7,100*count(DISTINCT ul4.uid)/count(DISTINCT u.id) rr30
FROM t_user u
LEFT JOIN t_user_login ul1 ON (ul1.uid = u.id AND date(ul1.login_time) = date(u.register_time) + INTERVAL '1' DAY)
LEFT JOIN t_user_login ul2 ON (ul2.uid = u.id AND date(ul2.login_time) = date(u.register_time) + INTERVAL '3' DAY)
LEFT JOIN t_user_login ul3 ON (ul3.uid = u.id AND date(ul3.login_time) = date(u.register_time) + INTERVAL '7' DAY)
LEFT JOIN t_user_login ul4 ON (ul4.uid = u.id AND date(ul4.login_time) = date(u.register_time) + INTERVAL '30' DAY)
GROUP BY date(u.register_time);
运行结果:
date(u.register_time) | rr1 | rr3 | rr7 | rr30 |
---|---|---|---|---|
2022-01-01 | 61.1806 | 45.4167 | 34.5833 | 18.9583 |
2022-01-02 | 50.0000 | 43.6806 | 32.2917 | 18.4028 |
2022-01-03 | 44.5139 | 38.7500 | 31.4583 | 18.5417 |
2022-01-04 | 43.0556 | 36.1111 | 29.5139 | 16.7361 |
2022-01-05 | 38.1944 | 34.6528 | 29.3056 | 16.3194 |
2022-01-06 | 35.4167 | 33.3333 | 28.1944 | 18.1944 |
2022-01-07 | 34.4118 | 29.9265 | 27.5735 | 17.6471 |
1.3.2、思路2:窗口函数
需要细心的地方就是每个聚合函数的粒度(group by 哪些字段),不同的粒度代表的含义是完全不一样的(比如 group by register_date,login_date 和 group by register_date 是不同的粒度,前者的最细粒度是 login_date,后者的最细粒度是 register_date)
WITH t1 AS(
SELECT tu.id,tul.uid,DATE(tu.register_time) reg_date,DATE(tul.login_time) login_date,DENSE_RANK() OVER (PARTITION BY DATE(tu.register_time) ORDER BY tu.id) reg_rk, -- 按照用户注册日期进行分区 并按照用户id进行排名 得到的max(排名)就是当天注册的总人数DENSE_RANK() OVER (PARTITION BY DATE(tu.register_time),DATE(tul.login_time) ORDER BY tul.uid) log_rk -- max(排名)就是当天登录的人数
FROM t_user tu
LEFT JOIN t_user_login tul
ON (tu.id = tul.uidAND(DATE(tul.login_time) = DATE(tu.register_time) + INTERVAL 1 DAYOR DATE(tul.login_time) = DATE(tu.register_time) + INTERVAL 3 DAYOR DATE(tul.login_time) = DATE(tu.register_time) + INTERVAL 7 DAYOR DATE(tul.login_time) = DATE(tu.register_time) + INTERVAL 30 DAY ))
),
t2 AS (-- 这里的 MAX(reg_rk) 并不代表 reg_date 这一天注册的人数,因为它是按照 reg_date 和 login_date 共同分组的-- 所以最细粒度是 log_date 而不是 reg_date,所以当只按照 reg_date 分组时得到的才是-- 所以这里得到的结果中 log_sum 可以代表reg_date注册后的在log_date登录的用户数,而 reg_sum 并不能代表reg_date注册的总人数SELECT reg_date, login_date, MAX(reg_rk) reg_sum, MAX(log_rk) log_sumFROM t1GROUP BY reg_date,login_date -- 因为是 left join 所以会保留所有注册信息,这里同时按照 login_date 分组就会把 login_date为null的数据排除在统计范围之外
)
SELECT reg_date, MAX(reg_sum), -- 这里的max的粒度才是reg_date,得到才是正确的注册人数MAX(CASE WHEN login_date = reg_date + INTERVAL 1 DAY THEN log_sum END)/MAX(reg_sum) rr1,MAX(CASE WHEN login_date = reg_date + INTERVAL 3 DAY THEN log_sum END)/MAX(reg_sum) rr3,MAX(CASE WHEN login_date = reg_date + INTERVAL 7 DAY THEN log_sum END)/MAX(reg_sum) rr7,MAX(CASE WHEN login_date = reg_date + INTERVAL 30 DAY THEN log_sum END)/MAX(reg_sum) rr30
from t2
GROUP BY reg_date;
相关文章:
SQL 面试系列(一)【留存率问题】
前言 在学 HQL 之前是不太了解 SQL 的,以为 SQL 只可以实现 CRUD ,直到面试的公司让我下去多了解一些 SQL ,我才最近开始再次深入学习 MySQL 和 Oracle。而且越学越发现 SQL 真的是一门很有深度的语言,我以前的使用只是皮毛而已&a…...
2024OD机试卷-游戏分组 (java\python\c++)
题目:游戏分组 题目描述 部们准备举办一场 王者荣耀 表演赛,有 10 名游戏爱好者参与,分为两队,每队 5 人。 每位参与者都有一个评分,代表着他的游戏水平。为了表演赛尽可能精彩,我们需要把 10 名参赛者分为实力尽量相近的两队。 一队的实力可以表示为这一队 5 名队员的…...

重装前端整体流程
用户管理 --汇总 -- 明细-CSDN博客 一、node 这个看环境变量 2023最新版Node.js下载安装及环境配置教程(非常详细)从零基础入门到精通,看完这一篇就够了_nodejs安装及环境配置-CSDN博客 配置到国内镜像的时候,去看,淘…...
Oracle Database 23ai Free版本体验
Oracle Database 23ai 体验链接: Oracle Database 23ai Free (https://www.oracle.com/database/free/get-started/) Autonomous Database 23ai Container Image (https://www.oracle.com/autonomous-database/free-trial/) Oracle GoldenGate 23ai (https://www…...

84.网络游戏逆向分析与漏洞攻防-游戏技能系统分析-筛选与技能有关的数据包
免责声明:内容仅供学习参考,请合法利用知识,禁止进行违法犯罪活动! 如果看不懂、不知道现在做的什么,那就跟着做完看效果,代码看不懂是正常的,只要会抄就行,抄着抄着就能懂了 内容…...

维护表空间中的数据文件
目录 向表空间中添加数据文件 从表空间中删除数据文件 删除users表空间中的users02.dbf数据文件 对数据文件的自动扩展设置 Oracle从入门到总裁:https://blog.csdn.net/weixin_67859959/article/details/135209645 维护表空间中的数据文件主要包括向表空间中添…...

2024五月母亲节嘉年华活动方案
2024以花之名 致谢母亲簪花趣味亲子五月母亲节嘉年华活动方案-43P 活动策划信息: 方案页码:43页 文件格式:PPT 方案简介: 岁月温柔治愈 母爱闪闪发光 一年一度的PY圈孝子孝女大赛即将到来 本次母亲节,“以花之名…...

Linux 第三十三章
🐶博主主页:ᰔᩚ. 一怀明月ꦿ ❤️🔥专栏系列:线性代数,C初学者入门训练,题解C,C的使用文章,「初学」C,linux 🔥座右铭:“不要等到什么都没有了…...

低空经济:无人机竞赛详解
无人机竞赛市场近年来呈现出蓬勃发展的态势,其市场价值不仅体现在竞赛本身,还体现在推动无人机技术创新、拓展应用场景以及促进产业链发展等多个方面。 一、比赛项目介绍 无人机竞赛通常分为多个项目,包括竞速赛、技巧赛、航拍赛等。每个项目…...

CAD插入文字到另一图形样式变相同
CAD从一张图形复制到另外一张图形后,文字样式变成一样是因为两张图所用的文字样式名称一样,但是样式里面的使用字体样式不一样。如下图所示,找到工具栏中的注释 ,点击文字样式。里面就会显示当前图形中使用的样式名称及其对应的字…...
算法随想录第八天打卡|344.反转字符串,541. 反转字符串II, 卡码网:54.替换数字, 151.翻转字符串里的单词,卡码网:55.右旋转字符串
344.反转字符串 建议: 本题是字符串基础题目,就是考察 reverse 函数的实现,同时也明确一下 平时刷题什么时候用 库函数,什么时候 不用库函数 题目链接/文章讲解/视频讲解:代码随想录 Python class Solution:def reve…...
外卖系统的JWT实现登录
1、什么是JWT jwt可以生成一个加密的token,作为用户登录的令牌,当用户登陆成功之后,发放给客户端。请求需要登录的资源或者接口的时候,将token携带,后端验证token是否合法。jwt有三部分组成: A:…...

基于springboot实现的家具销售电商平台
开发语言:Java 框架:springboot JDK版本:JDK1.8 服务器:tomcat7 数据库:mysql 5.7(一定要5.7版本) 数据库工具:Navicat11 开发软件:eclipse/myeclipse/idea Maven包&…...
STM32中的Systick的使用
SysTick,全称System Tick Timer,是Cortex-M microcontrollers内核中提供的一个简单而有效的系统定时器,设计用来给操作系统提供时间基准,或用于生成周期性的中断。STM32系列微控制器,作为基于ARM Cortex-M内核的设备&a…...
做一个桌面悬浮翻页时钟
毛玻璃效果翻页桌面悬浮时钟,TopMost(Topmost“True”),不在任务栏显示(ShowInTaskbar“False”),在托盘区显示图标,双击托盘区图标实现最小化和还原,右键托盘图标可选“…...

Transformers中加载预训练模型的过程剖析(一)
使用HuggingFace的Transformers库加载预训练模型来处理下游深度学习任务很是方便,然而加载预训练模型的方法多种多样且过程比较隐蔽,这在一定程度上会给人带来困惑。因此,本篇文章主要讲一下使用不同方法加载本地预训练模型的区别、加载预训练模型及其配置的过程,藉此做个记…...

数据可视化的艺术:使用Matplotlib和Seaborn揭示数据故事
引言 数据可视化是数据分析中的关键一环,它帮助我们理解数据模式、趋势和异常。在Python中,Matplotlib和Seaborn是两个流行的数据可视化库,它们提供了丰富的图表和图形选项,使数据的可视化变得简单而强大。 Matplotlibÿ…...

2024全新小狐狸AI免授权源码
源码安装说明: 下 载 地 址 : runruncode.com/php/19757.html 1. 在宝塔新建一个站点,选择 PHP 版本为 7.2、7.3 或 7.4。将压缩包上传到站点的根目录,并设置运行目录为 /public。 2. 导入数据库文件,该文件位于 …...

Python基础详解四
目录 一,Json解析 二,pyecharts模块 三,类和对象 四,类型注解 一,Json解析 字典转换为JSON: import jsondata [{"name":"袁震","age":20},{"name":"张三&…...
es6新语法和ajax和json
es6新语法 1.定义变量:let 2.定义常量:const <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"…...

Chapter03-Authentication vulnerabilities
文章目录 1. 身份验证简介1.1 What is authentication1.2 difference between authentication and authorization1.3 身份验证机制失效的原因1.4 身份验证机制失效的影响 2. 基于登录功能的漏洞2.1 密码爆破2.2 用户名枚举2.3 有缺陷的暴力破解防护2.3.1 如果用户登录尝试失败次…...

UE5 学习系列(三)创建和移动物体
这篇博客是该系列的第三篇,是在之前两篇博客的基础上展开,主要介绍如何在操作界面中创建和拖动物体,这篇博客跟随的视频链接如下: B 站视频:s03-创建和移动物体 如果你不打算开之前的博客并且对UE5 比较熟的话按照以…...

LeetCode - 394. 字符串解码
题目 394. 字符串解码 - 力扣(LeetCode) 思路 使用两个栈:一个存储重复次数,一个存储字符串 遍历输入字符串: 数字处理:遇到数字时,累积计算重复次数左括号处理:保存当前状态&a…...
鸿蒙中用HarmonyOS SDK应用服务 HarmonyOS5开发一个医院挂号小程序
一、开发准备 环境搭建: 安装DevEco Studio 3.0或更高版本配置HarmonyOS SDK申请开发者账号 项目创建: File > New > Create Project > Application (选择"Empty Ability") 二、核心功能实现 1. 医院科室展示 /…...

基于当前项目通过npm包形式暴露公共组件
1.package.sjon文件配置 其中xh-flowable就是暴露出去的npm包名 2.创建tpyes文件夹,并新增内容 3.创建package文件夹...

五子棋测试用例
一.项目背景 1.1 项目简介 传统棋类文化的推广 五子棋是一种古老的棋类游戏,有着深厚的文化底蕴。通过将五子棋制作成网页游戏,可以让更多的人了解和接触到这一传统棋类文化。无论是国内还是国外的玩家,都可以通过网页五子棋感受到东方棋类…...

ZYNQ学习记录FPGA(一)ZYNQ简介
一、知识准备 1.一些术语,缩写和概念: 1)ZYNQ全称:ZYNQ7000 All Pgrammable SoC 2)SoC:system on chips(片上系统),对比集成电路的SoB(system on board) 3)ARM:处理器…...

链式法则中 复合函数的推导路径 多变量“信息传递路径”
非常好,我们将之前关于偏导数链式法则中不能“约掉”偏导符号的问题,统一使用 二重复合函数: z f ( u ( x , y ) , v ( x , y ) ) \boxed{z f(u(x,y),\ v(x,y))} zf(u(x,y), v(x,y)) 来全面说明。我们会展示其全微分形式(偏导…...
用 Rust 重写 Linux 内核模块实战:迈向安全内核的新篇章
用 Rust 重写 Linux 内核模块实战:迈向安全内核的新篇章 摘要: 操作系统内核的安全性、稳定性至关重要。传统 Linux 内核模块开发长期依赖于 C 语言,受限于 C 语言本身的内存安全和并发安全问题,开发复杂模块极易引入难以…...

麒麟系统使用-进行.NET开发
文章目录 前言一、搭建dotnet环境1.获取相关资源2.配置dotnet 二、使用dotnet三、其他说明总结 前言 麒麟系统的内核是基于linux的,如果需要进行.NET开发,则需要安装特定的应用。由于NET Framework 是仅适用于 Windows 版本的 .NET,所以要进…...