游戏行业实战案例 4 :在线时长分析

【面试题】某游戏数据后台设有「登录日志」和「登出日志」两张表。
「登录日志」记录各玩家的登录时间和登录时的角色等级。

「登出日志」记录各玩家的登出时间和登出时的角色等级。

其中,「角色id」字段唯一识别玩家。
游戏开服前两天( 2022-08-13 至 2022-08-14 )的角色登录和登出日志如下


一天中,玩家可以多次登录登出游戏,请使用 SQL 分析出以下业务问题:
请根据玩家登录登出的时间,统计各玩家每天总在线时长情况。
(如玩家登录后没有对应的登出日志,可以使用当天 23:59:59 作为登出时间,时间之间的计算可以考虑使用时间戳函数 unix_timestamp 。)
问题 4 :
统计各玩家每天总在线时长分为两步:
第一步,计算各玩家每天每次登录游戏后的在线时长;
第二步,对各玩家每天每次的在线时长进行求和,得到各玩家每天的总在线时长。
1. 计算各玩家每天每次登录游戏后的在线时长
玩家每次登录后的在线时长=每次的登出时间-每次对应的登录时间,因此,我们需要对玩家的登录时间、登出时间进行一一对应。
登录时间从「登录日志」表获取,登出时间从「登出日志」表获取。那么,如何对玩家的登录时间、登出时间进行一一对应呢?
玩家每次登录后必然伴随着登出,因此玩家的登录时间顺序与登出时间顺序是一致的。对每个玩家的登录时间进行排序得到排名,再对每个玩家的登出时间进行排序得到排名,那么登录时间对应的排名必然与登出时间对应的排名一致。即:排名为1的登录时间与排名为 1 的登出时间相对应,排名为 2 的登录时间与排名为 2 的登出时间相对应……
使用排序窗口函数对每个玩家的登录登出时间进行排序(三个排序窗口函数选择其一即可,在此选择 rank() 窗口函数),由于要获取每个玩家每天的登录登出时间排名,因此以角色 id ,日期进行分组,以登录或登出时间升序排序,即 partition by 角色 id ,日期 order by 登录时间/登出时间 asc 对登录登出时间进行排序的 SQL 的书写方法:
#对每个玩家每天的登录时间进行排序
select 角色id,日期,登录时间,rank() over(partition by 角色id,日期 order by 登录时间 asc) as 登录排名
from 登录日志;
#对每个玩家每天的登出时间进行排序
select 角色id,日期,登出时间,rank() over(partition by 角色id,日期 order by 登出时间 asc) as 登出排名
from 登出日志; 查询结果如下:


对每个玩家每天的登录登出时间进行排序后,就可以将登录登出时间进行一一对应了。
如何一一对应呢?通过横向联结就可以实现,即使用 join 联结方法。
根据题意,「登录日志」表中的登录时间不存在缺失,而「登出日志」表中某个玩家的登出时间可能存在缺失,为了在联结的时候完整的保留登录登出时间,将上述查询结果1设为临时表a,查询结果 2 设为临时表 b ,并让临时表 a 左联结( left join )临时表 b 。
左联结时,还需要设置条件使两个临时表的角色 id 、日期和排名相等,这样才能使登录登出时间一一对应。
进行左联结的 SQL 的书写方法:
select a.角色id,a.日期,a.登录时间,b.登出时间
from
(select 角色id,日期,登录时间,rank() over(partition by 角色id,日期 order by 登录时间 asc) as 登录排名
from 登录日志) as a
left join
(select 角色id,日期,登出时间,rank() over(partition by 角色id,日期 order by 登出时间 asc) as 登出排名
from 登出日志) as b
on a.角色id = b.角色id and a.日期 = b.日期 and a.登录排名 = b.登出排名; 查询结果如下:

需要注意的是,根据题意:如玩家登录后没有对应的登出日志,可以使用当天 23:59:59 作为登出时间。也就是说,若玩家登录后没有对应的登出日志,则进行左联结后「登出时间」这一列会存在空值,而空值可以使用当 23:59:59 进行填充。
如何实现这一操作呢?
可以使用 case when 子句进行条件判断,当「登出时间」这一列的某个值为空值时,则使用当天 23:59:59 作为值,否则就不改变值,即:
case when 登出时间 is null then 当天23:59:59 else 登出时间 end 除了使用 case when 填充空值,还可以使用 ifnull() 函数填充空值。ifnull() 函数的语法为:
ifnull(值1,值2) 其中,若值 1 为 null ,则返回值 2 ,若值 1 不为 null ,则返回值 1 。
比如:
ifnull(null,1) ,返回值为 1 ;ifnull(0,1) ,返回值为 0 。
将其应用于本问题,则是:
ifnull(登出时间,'当天23:59:59') 即:若登出时间为 null ,则返回当天 23:59:59 ,若登出时间不为 null ,则返回登出时间。
case when 子句和 ifnull() 函数能达到同样的效果,两者选择其一即可。在此选择 case when 子句进行条件判断。
那么,如何得到当天 23:59:59 呢?
当天即为「日期」列中的值,因此我们可以将「日期」列中的值与 23:59:59 进行合并得到当天 23:59:59 。合并字符串使用 concat() 函数,合并时日期与 23:59:59 之间存在一个空格,使时间格式一致,即:
concat(日期,' 23:59:59') 这样,在左联结时,同时填充「登出时间」字段空值的 SQL 的书写方法为:
select a.角色id,a.日期,a.登录时间,(case when b.登出时间 is null then concat(a.日期,'23:59:59') else b.登出时间 end) as 登出时间 #使用ifnull()函数,则为ifnull(b.登出时间,concat(a.日期,' 23:59:59')) as 登出时间
from
(select 角色id,日期,登录时间,rank() over(partition by 角色id,日期 order by 登录时间 asc) as 登录排名
from 登录日志) as a
left join
(select 角色id,日期,登出时间,rank() over(partition by 角色id,日期 order by 登出时间 asc) as 登出排名
from 登出日志) as b
on a.角色id = b.角色id and a.日期 = b.日期 and a.登录排名 = b.登出排名; 查询结果如下:

可以看到,登录时间和登出时间已经一一对应,将登出时间减去登录时间就可以得到玩家每次登录后的在线时长。
将上述查询结果设为临时表 c ,则计算每个玩家每天每次登录后的在线时长的 SQL 的书写方法为:
select 角色id,日期,
unix_timestamp(登出时间) - unix_timestamp(登录时间) as 每次在线时长
from c; unix_timestamp() 函数可以将日期时间格式转化成 10 位数的时间戳格式,单位为秒,因此,为了得到单位为分钟的在线时长,我们需要在登出登录时间相减后再除以 60 秒,即:
select 角色id,日期,(unix_timestamp(登出时间) - unix_timestamp(登录时间))/60 as 每次在线时长_min
from c; 利用 with…as 语句来封装临时表 c 的查询语句,则 SQL 的书写方法:
with c as
(select a.角色id,a.日期,a.登录时间,(case when b.登出时间 is null then concat(a.日期,'23:59:59') else b.登出时间 end) as 登出时间
from
(select 角色id,日期,登录时间,rank() over(partition by 角色id,日期 order by 登录时间 asc) as 登录排名
from 登录日志) as a
left join
(select 角色id,日期,登出时间,rank() over(partition by 角色id,日期 order by 登出时间 asc) as 登出排名
from 登出日志) as b
on a.角色id = b.角色id and a.日期 = b.日期 and a.登录排名 = b.登出排名
)
select 角色id,日期,
round((unix_timestamp(登出时间)- unix_timestamp(登录时间))/60,2) as 每次在线时长_min #使用round()函数保留2位小数
from c; 查询结果如下:

2. 计算各玩家每天的总在线时长
使用 group by 子句对角色 id 、日期进行分组,再使用 sum() 函数对每个玩家每天的每次在线时长进行求和,就可以得到各玩家每天的总在线时长。
SQL 的书写方法:
with c as
(select a.角色id,a.日期,a.登录时间,(case when b.登出时间 is null then concat(a.日期,'23:59:59') else b.登出时间 end) as 登出时间
from
(select 角色id,日期,登录时间,rank() over(partition by 角色id,日期 order by 登录时间 asc) as 登录排名
from 登录日志) as a
left join
(select 角色id,日期,登出时间,rank() over(partition by 角色id,日期 order by 登出时间 asc) as 登出排名
from 登出日志) as b
on a.角色id = b.角色id and a.日期 = b.日期 and a.登录排名 = b.登出排名
)
select 角色id,日期,
sum(round((unix_timestamp(登出时间)- unix_timestamp(登录时间))/60,2)) as 总在线时长_min #使用round()函数保留2位小数
from c
group by 角色id,日期; 查询结果如下:


⬇️点击「阅读原文」
免费报名 数据分析训练营
相关文章:
游戏行业实战案例 4 :在线时长分析
【面试题】某游戏数据后台设有「登录日志」和「登出日志」两张表。 「登录日志」记录各玩家的登录时间和登录时的角色等级。 「登出日志」记录各玩家的登出时间和登出时的角色等级。 其中,「角色id」字段唯一识别玩家。 游戏开服前两天( 2022-08-13 至 …...
记一次图片压缩引发的生产问题
省流: 死循环导致没有commit(提交事务),transaction一直没有结束。 正文: 调用接口报错: jdbc报错: MySQLTransactionRollbackException: Lock wait timeout exceeded; try restarting tran…...
mybatis-flex探索
mybatis古今未来 最近无意之中发现了一个非常棒的持久层框架mybatis-flex,迫不及待研究了一下 发现简直就是我的梦中情框,之前写ibatis,后来写mybatis,接着写mybatis-plus,接着研究mybatis-flex ibatis ibatis是apa…...
用ClickHouse 文件表引擎快速查询分析文件数据
有时我们需要快速查询分析文件数据,正常流程需要在数据库中创建表,然后利用工具或编码导入数据,这时才能在数据库中查询分析。利用ClickHouse文件引擎可以快速查询文件数据。本文首先介绍ClickHouse文件引擎,然后介绍如何快速实现…...
esp8266httpclient_get_post使用
esp8266httpclient_get_post使用 #include<ESP8266WiFi.h> #include <ESP8266HTTPClient.h>//const char *ssid "AxxxIFI"; const char *password "xxxs879xxx68";const char* ssid "IT-nxxxang";const char* URL "http://…...
【Spring】创建一个Spring项目与Bean对象的存储
目录 一、创建Spring项目 1、创建Maven项目 2、配置maven国内源 3、引入spring依赖 4、添加启动类 二、将Bean对象存储到Spring(IoC容器) 1、创建Bean对象 2、将Bean存储到spring(容器)中 3、获取Bean对象 3.1、Applicatio…...
Docker的入门与使用
什么是Docker? docker官网 简介与概述 Docker 是一个开源的应用容器引擎,基于 Go 语言 并遵从 Apache2.0 协议开源。 Docker 可以让开发者打包他们的应用以及依赖包到一个轻量级、可移植的容器中,然后发布到任何流行的 Linux 机器上&#x…...
Smart HTML Elements 16.1 Crack
Smart HTML Elements 是一个现代 Vanilla JS 和 ES6 库以及下一代前端框架。企业级 Web 组件包括辅助功能(WAI-ARIA、第 508 节/WCAG 合规性)、本地化、从右到左键盘导航和主题。与 Angular、ReactJS、Vue.js、Bootstrap、Meteor 和任何其他框架集成。 智…...
[分享]STM32G070 串口 乱码 解决方法
硬件 NUCLEO-G070RB 工具 cubemx 解决方法 7bit 改为 8bit printf 配置方法 添加头文件 #include <stdio.h> 添加重定向代码 #ifdef __GNUC__#define PUTCHAR_PROTOTYPE int __io_putchar(int ch)#else#define PUTCHAR_PROTOTYPE int fputc(int ch, FILE *f)#endi…...
[代码案例]学会python读写各类文件的操作(excel,txt,mat)
简介 python读写三类文件 excel文件 txt文件 mat文件 代码 """Description: python 读写各类文件 操作 """ import scipy as scipy from scipy.io import loadmat import xlwt import xlrd 读写excel文件workbook xlrd.open_workbook(test1.…...
【LeetCode】练习习题集【4月 - 7 月】
LEETCODE习题集【4月-7月总结】 简单 数组部分 1.重复数 题目: 在一个长度u为 n 的数组 nums 里的所有数字都在 0~n-1 的范围内。数组中某些数字是重复的,但不知道有几个数字重复了,也不知道每个数字重复了几次。请找出数组中…...
C# 子类强制转换为父类异常,引出的C#Dll加载机制,以及同类名同命名空间同dll程序集在C#中是否为同一个类的研究。
已知,子类B继承自父类A,但是在代码运行时,B类强制转换为A类,却报代码转换异常。 很奇怪的问题吧,不过这个也是难得机会,去研究C#运行的底层原理。 下面是报错的代码片段。 string className _shapeRefle…...
Go语言进阶
个人笔记,大量摘自Go语言高级编程、Go|Dave Cheney等 更新 go get -u all 在非go目录运行go install golang.org/x/tools/goplslatest更新go tools:在go目录运行go get -u golang.org/x/tools/...,会更新bin目录下的应用; 运行…...
Java的枚举
枚举 对应英文(enumeration, 简写enum) 枚举是一组常量的集合,属于一种特殊的类,里面只包含一组有限的特定的对象。 自定义类实现枚举 1.不需要提供setXxx方法,因为枚举对象值通常为只读. 2.对枚举对象/属性使用 final static共同修饰…...
Pytest测试框架3
目录: pytest结合数据驱动-yamlpytest结合数据驱动-excelpytest结合数据驱动-csvpytest结合数据驱动-jsonpytest测试用例生命周期管理(一)pytest测试用例生命周期管理(二)pytest测试用例生命周期管理(三&a…...
【数学建模】-- Matlab中图的最短路径
前言: 图的基本概念: 若想简单绘制图可以利用此网站: 左上角Undirected/Directed是无向图/有向图 左边 0-index ,1-index为0下标,1下标。 Node Count为节点个数 Graph Data:最初尾节点的名称ÿ…...
中国月入过万的人多不多
Q:中国月入过万的人多不多 单从这个问题来看,这是个费米问题啊: 估算中国月入过万的有多少人? 要解决费米问题,其实也很好办,就是逻辑拆解,这篇文章也分为3个部分,先从公开数据中估…...
苹果电脑图像元数据编辑器:MetaImage for Mac
MetaImage for Mac是一款功能强大的照片元数据编辑器,它可以帮助用户编辑并管理照片的元数据信息,包括基本信息和扩展信息。用户可以根据需要进行批量处理,方便快捷地管理大量照片。 MetaImage for Mac还提供了多种导入和导出格式࿰…...
BeanUtils.copyProperties() 详解
BeanUtils.copyProperties会进行类型转换; BeanUtils.copyProperties方法简单来说就是将两个字段相同的对象进行属性值的复制。如果 两个对象之间存在名称不相同的属性,则 BeanUtils 不对这些属性进行处理,需要程序手动处理。 这两个类在不同…...
基于CentOS 7构建LVS-DR集群
DIPVIPRIPClient192.169.41.139 LVS 192.168.41.134192.169.41.10RS1192.168.41.135RS2192.168.41.138 要求: node4为客户端,node2为LVS,node3和node4为RS。 1.配置DNS解析(我这里使用本地解析) 192.168.41.134 www.y…...
Ubuntu系统下交叉编译openssl
一、参考资料 OpenSSL&&libcurl库的交叉编译 - hesetone - 博客园 二、准备工作 1. 编译环境 宿主机:Ubuntu 20.04.6 LTSHost:ARM32位交叉编译器:arm-linux-gnueabihf-gcc-11.1.0 2. 设置交叉编译工具链 在交叉编译之前&#x…...
反向工程与模型迁移:打造未来商品详情API的可持续创新体系
在电商行业蓬勃发展的当下,商品详情API作为连接电商平台与开发者、商家及用户的关键纽带,其重要性日益凸显。传统商品详情API主要聚焦于商品基本信息(如名称、价格、库存等)的获取与展示,已难以满足市场对个性化、智能…...
生成 Git SSH 证书
🔑 1. 生成 SSH 密钥对 在终端(Windows 使用 Git Bash,Mac/Linux 使用 Terminal)执行命令: ssh-keygen -t rsa -b 4096 -C "your_emailexample.com" 参数说明: -t rsa&#x…...
GitHub 趋势日报 (2025年06月08日)
📊 由 TrendForge 系统生成 | 🌐 https://trendforge.devlive.org/ 🌐 本日报中的项目描述已自动翻译为中文 📈 今日获星趋势图 今日获星趋势图 884 cognee 566 dify 414 HumanSystemOptimization 414 omni-tools 321 note-gen …...
[Java恶补day16] 238.除自身以外数组的乘积
给你一个整数数组 nums,返回 数组 answer ,其中 answer[i] 等于 nums 中除 nums[i] 之外其余各元素的乘积 。 题目数据 保证 数组 nums之中任意元素的全部前缀元素和后缀的乘积都在 32 位 整数范围内。 请 不要使用除法,且在 O(n) 时间复杂度…...
华为云Flexus+DeepSeek征文|DeepSeek-V3/R1 商用服务开通全流程与本地部署搭建
华为云FlexusDeepSeek征文|DeepSeek-V3/R1 商用服务开通全流程与本地部署搭建 前言 如今大模型其性能出色,华为云 ModelArts Studio_MaaS大模型即服务平台华为云内置了大模型,能助力我们轻松驾驭 DeepSeek-V3/R1,本文中将分享如何…...
Web 架构之 CDN 加速原理与落地实践
文章目录 一、思维导图二、正文内容(一)CDN 基础概念1. 定义2. 组成部分 (二)CDN 加速原理1. 请求路由2. 内容缓存3. 内容更新 (三)CDN 落地实践1. 选择 CDN 服务商2. 配置 CDN3. 集成到 Web 架构 …...
20个超级好用的 CSS 动画库
分享 20 个最佳 CSS 动画库。 它们中的大多数将生成纯 CSS 代码,而不需要任何外部库。 1.Animate.css 一个开箱即用型的跨浏览器动画库,可供你在项目中使用。 2.Magic Animations CSS3 一组简单的动画,可以包含在你的网页或应用项目中。 3.An…...
C++ 设计模式 《小明的奶茶加料风波》
👨🎓 模式名称:装饰器模式(Decorator Pattern) 👦 小明最近上线了校园奶茶配送功能,业务火爆,大家都在加料: 有的同学要加波霸 🟤,有的要加椰果…...
【FTP】ftp文件传输会丢包吗?批量几百个文件传输,有一些文件没有传输完整,如何解决?
FTP(File Transfer Protocol)本身是一个基于 TCP 的协议,理论上不会丢包。但 FTP 文件传输过程中仍可能出现文件不完整、丢失或损坏的情况,主要原因包括: ✅ 一、FTP传输可能“丢包”或文件不完整的原因 原因描述网络…...
