横扫SQL面试——时间序列分组与合并(会话划分)问题

横扫SQL面试题

📌 时间序列分组与合并问题

📚 横扫SQL面试------时间序列分组与合并解析


🌟 核心问题类型

时间序列分组(Sessionization)

处理具有时间维度的连续数据流,根据特定规则(如时间间隔、属性连续性)将相邻记录合并为逻辑组


话不多说------直接上题:🎈🎈🎈🎈🎈🎈🎈

一、用户访问记录会话划分🎈🎈🎈

给定用户的访问记录access_records表,需要按照同一用户相邻两次访问记录时间间隔小于等于60秒则认为两次浏览记录属于同一会话的规则 ,为属于同一会话的访问记录增加一个相同的会话id字段。

表名 字段名 字段含义 数据类型
用户访问记录表 user_id 用户标识 VARCHAR
access_time 访问时间 DATETIME
action 访问时执行的操作(如页面浏览等) VARCHAR
user_id access_time action
User1 2025-03-31 10:00:00 浏览首页
User1 2025-03-31 10:00:30 点击商品详情
User2 2025-03-31 10:10:00 浏览列表页
User1 2025-03-31 10:01:00 返回首页
User2 2025-03-31 10:10:30 加入购物车

结果预期

user_id access_time action session_id
User1 2025-03-31 10:00:00 浏览首页 1
User1 2025-03-31 10:00:30 点击商品详情 1
User1 2025-03-31 10:01:00 返回首页 1
User2 2025-03-31 10:10:00 浏览列表页 2
User2 2025-03-31 10:10:30 加入购物车 2

✅✅✅

sql 复制代码
原始数据 → 计算相邻时间差 → 标记新会话 → 累加分组 → 分配ID
     ↓              ↓               ↓             ↓
prev_access_time → new_session_start → session_groups → 最终结果
步骤1:获取相邻记录时间差(CTE: prev_access_time)✅
sql 复制代码
with prev_access_time as (
    select
        user_id,
        access_time,
        lag(access_time) over (partition by user_id order by access_time
        ) as prev_time
    from access_records
)

输入数据

user_id access_time
UserA 2024-01-01 08:00:00
UserA 2024-01-01 08:00:50
UserA 2024-01-01 08:02:00

中间表 prev_access_time

user_id access_time prev_time
UserA 2024-01-01 08:00:00 NULL
UserA 2024-01-01 08:00:50 2024-01-01 08:00:00
UserA 2024-01-01 08:02:00 2024-01-01 08:00:50
  • LAG(access_time) 获取同一用户前一条记录的访问时间
  • PARTITION BY user_id 确保按用户独立处理
  • ORDER BY access_time 按时间顺序排序

步骤2:标记新会话起点(CTE: new_session_start)✅
sql 复制代码
new_session_start as (
    select
        user_id,
        access_time,
        case
            when timestampdiff(second, prev_time, access_time) >= 60 
                 or prev_time is null
            then 1
            else 0
        end as is_new_session
    from prev_access_time
)

中间表 new_session_start

user_id access_time is_new_session
UserA 2024-01-01 08:00:00 1 (首条记录)
UserA 2024-01-01 08:00:50 0 (间隔50秒)
UserA 2024-01-01 08:02:00 1 (间隔70秒)

判断逻辑

  • 首条记录:prev_time IS NULL → 标记为1
  • 间隔计算:08:00:50 - 08:00:00 = 50秒<60秒 → 标记0
  • 间隔计算:08:02:00 - 08:00:50 = 70秒>=60秒 → 标记1

步骤3:生成会话分组(CTE: session_groups)✅
sql 复制代码
session_groups as (
    select
        user_id,
        access_time,
        sum(is_new_session) over (partition by user_id order by access_time
        ) as session_group
    from new_session_start
)

中间表 session_groups

user_id access_time session_group
UserA 2024-01-01 08:00:00 1
UserA 2024-01-01 08:00:50 1 (累加0)
UserA 2024-01-01 08:02:00 2 (累加1)
  • SUM(is_new_session) 按用户和时间顺序累加标记
  • 同一会话组内的记录具有相同的 session_group

步骤4:分配会话ID(主查询)✅
sql 复制代码
select
    user_id,
    access_time,
    dense_rank() over (
        partition by user_id 
        order by session_group
    ) as session_id
from session_groups;

最终结果

user_id access_time session_id
UserA 2024-01-01 08:00:00 1
UserA 2024-01-01 08:00:50 1
UserA 2024-01-01 08:02:00 2
  • DENSE_RANK() 保证会话ID连续(无间隔)
  • session_group 排序确保同一组ID相同

二、网站页面浏览时长统计🎈🎈🎈

给定网站页面浏览记录,需要按照用户在离开页面后30分钟内再次访问同一页面则认为是连续浏览的规则
计算:

  1. 合并时长 :将连续访问合并为一个时间段,计算从首次开始时间到最后结束时间的总跨度
  2. 实际时长累加每次独立访问的实际时长(不合并连续访问)
  3. 增加会话id :属于同一浏览的访问记录增加一个相同的会话id字段。
表名 字段名 字段含义 数据类型
网站页面浏览记录表 user_id 用户标识 VARCHAR
page_url 页面URL VARCHAR
view_start_time 开始浏览时间 DATETIME
view_end_time 结束浏览时间 DATETIME
user_id page_url view_start_time view_end_time
User1 https://example.com 2025-04-01 09:00:00 2025-04-01 09:10:00
User1 https://example.com 2025-04-01 09:15:00 2025-04-01 09:20:00
User2 https://example.net 2025-04-01 10:00:00 2025-04-01 10:15:00
User1 https://example.com 2025-04-01 09:25:00 2025-04-01 09:30:00
User2 https://example.net 2025-04-01 10:30:00 2025-04-01 10:45:00

结果预期

user_id page_url view_start_time view_end_time merged_duration total_real_duration
User1 https://example.com 2025-04-01 09:00:00 2025-04-01 09:30:00 30 20 (10+5+5)
User2 https://example.net 2025-04-01 10:00:00 2025-04-01 10:15:00 15 15
User2 https://example.net 2025-04-01 10:30:00 2025-04-01 10:45:00 15 15

还是跟上题同样的套路~😝😝😝

步骤 核心操作 关键函数/逻辑
时间排序 获取前次结束时间 LAG() + 窗口排序
标记会话起点 判断30分钟间隔 TIMESTAMPDIFF() + CASE
生成会话组 累加标记值 SUM() OVER()
分配会话ID 生成唯一连续编号 DENSE_RANK()
聚合结果 计算时间跨度和累加时长 MIN()/MAX()/SUM()
步骤1:排序并获取前次结束时间
sql 复制代码
with ordered_views as(
	select
		user_id,
		page_url,
		view_start_time,
		view_end_time,
		lag(view_end_time) over(partition by user_id,page_url 
								order by view_start_time) as prev_end_time
	from table
)

中间表 ordered_views

user_id page_url view_start_time view_end_time prev_end_time
User1 https://example.com 2025-04-01 09:00:00 2025-04-01 09:10:00 NULL
User1 https://example.com 2025-04-01 09:15:00 2025-04-01 09:20:00 2025-04-01 09:10:00
User1 https://example.com 2025-04-01 09:25:00 2025-04-01 09:30:00 2025-04-01 09:20:00
User2 https://example.net 2025-04-01 10:00:00 2025-04-01 10:15:00 NULL
User2 https://example.net 2025-04-01 10:30:00 2025-04-01 10:45:00 2025-04-01 10:15:00

步骤2:标记新会话起点
sql 复制代码
session_flags as(
  select *,
    case
      when prev_end_time IS NULL 
        OR timestampdiff(minute, prev_end_time, view_start_time) > 30 
      then 1 
      else 0 
    end as is_new_session
  from ordered_views
)

中间表 session_flags

user_id page_url is_new_session
User1 https://example.com 1
User1 https://example.com 0
User1 https://example.com 0
User2 https://example.net 1
User2 https://example.net 1

步骤3:生成会话分组ID
sql 复制代码
session_groups as(
  select*,
    sum(is_new_session) over(partition by user_id, page_url 
      						 order by view_start_time) as session_group
  from session_flags
)

中间表 session_groups

user_id page_url session_group
User1 https://example.com 1
User1 https://example.com 1
User1 https://example.com 1
User2 https://example.net 1
User2 https://example.net 2

步骤4:分配唯一会话ID
sql 复制代码
session_ids AS (
  SELECT 
    *,
    dense_rank() over (
      partition by user_id, page_url order by session_group ) as session_id
  from session_groups
)

中间表 session_ids

user_id page_url session_id
User1 https://example.com 1
User1 https://example.com 1
User1 https://example.com 1
User2 https://example.net 1
User2 https://example.net 2

步骤5:聚合结果并计算双指标
sql 复制代码
select
  user_id,
  page_url,
  session_id,
  min(view_start_time) AS view_start_time,
  max(view_end_time) AS view_end_time,
  TIMESTAMPDIFF(MINUTE, min(view_start_time), max(view_end_time)) AS merged_duration,
  sum(TIMESTAMPDIFF(MINUTE, view_start_time, view_end_time)) AS total_real_duration
from session_ids
group by user_id, page_url, session_id;

最终结果

user_id page_url session_id view_start_time view_end_time merged_duration total_real_duration
User1 https://example.com 1 2025-04-01 09:00:00 2025-04-01 09:30:00 30 20
User2 https://example.net 1 2025-04-01 10:00:00 2025-04-01 10:15:00 15 15
User2 https://example.net 2 2025-04-01 10:30:00 2025-04-01 10:45:00 15 15

还是有些难度的哈~

sql 复制代码
-- 使用CTE(公共表表达式),命名为ordered_views
with ordered_views as (
    -- 从"网站页面浏览记录表"中选择数据
    select 
        user_id,
        page_url,
        view_start_time,
        view_end_time,
        -- 使用LAG窗口函数,获取每个用户和页面URL分组下,按浏览开始时间排序后的上一条记录的浏览结束时间
        lag(view_end_time) over (
            partition by user_id, page_url 
            order by view_start_time
        ) as prev_end_time
    from 网站页面浏览记录表
)
-- 第二个CTE,命名为session_flags
, session_flags as (
    -- 选择ordered_views CTE中的所有列,并添加一个新列is_new_session
    select *,
        -- 使用CASE语句判断是否为新会话
        case 
            -- 如果上一条记录的结束时间为空(即当前是该用户和页面的第一条记录)
            -- 或者当前记录的开始时间与上一条记录的结束时间间隔大于30分钟
            when prev_end_time is null 
                or timestampdiff(minute, prev_end_time, view_start_time) > 30 
            then 1 
            else 0 
        end as is_new_session
    from ordered_views
)
-- 第三个CTE,命名为session_groups
, session_groups as (
    -- 选择session_flags CTE中的所有列,并添加一个新列session_group
    select *,
        -- 使用SUM窗口函数,对每个用户和页面URL分组下,按浏览开始时间排序后的is_new_session列进行累加
        sum(is_new_session) over (
            partition by user_id, page_url 
            order by view_start_time
        ) as session_group
    from session_flags
)
-- 第四个CTE,命名为session_ids
, session_ids as (
    -- 选择session_groups CTE中的所有列,并添加一个新列session_id
    select 
        *,
        -- 使用DENSE_RANK窗口函数,对每个用户和页面URL分组下,按session_group进行排名,得到会话ID
        dense_rank() over (
            partition by user_id, page_url 
            order by session_group
        ) as session_id
    from session_groups
)

-- 主查询
select 
    user_id,
    page_url,
    session_id,
    -- 对于每个用户、页面和会话ID分组,获取最小的浏览开始时间
    min(view_start_time) as view_start_time,
    -- 对于每个用户、页面和会话ID分组,获取最大的浏览结束时间
    max(view_end_time) as view_end_time,
    -- 计算合并后的会话时长(以分钟为单位)
    timestampdiff(minute, min(view_start_time), max(view_end_time)) as merged_duration,
    -- 计算每个会话内实际的浏览时长总和(以分钟为单位)
    sum(timestampdiff(minute, view_start_time, view_end_time)) as total_real_duration
from session_ids
-- 按照用户ID、页面URL和会话ID进行分组
group by user_id, page_url, session_id
-- 按照用户ID、页面URL和会话ID进行排序
order by user_id, page_url, session_id;

三、公交乘客乘车时间分析🎈🎈🎈

根据公交乘客乘车记录,按照 "下车后90分钟内再次上车视为同一次出行" 的规则,完成以下任务:

  1. 分配出行ID:为每个乘客的每次连续出行分配唯一ID
  2. 计算双指标
    • 合并时长:从首次上车到最后一次下车的总时间跨度
    • 实际时长:累加各次乘车的独立时长
表名 字段名 字段含义 数据类型
公交乘车记录表 passenger_id 乘客标识 VARCHAR
boarding_time 上车时间 DATETIME
alighting_time 下车时间 DATETIME

passenger_id boarding_time alighting_time
P001 2025-04-01 08:00:00 2025-04-01 08:30:00
P001 2025-04-01 08:40:00 2025-04-01 09:00:00
P002 2025-04-01 09:30:00 2025-04-01 10:00:00
P001 2025-04-01 09:10:00 2025-04-01 09:30:00
P002 2025-04-01 10:40:00 2025-04-01 11:00:00

结果预期

passenger_id trip_id boarding_time alighting_time merged_duration total_actual_duration
P001 1 2025-04-01 08:00:00 2025-04-01 09:30:00 90 70 (30+20+20)
P002 1 2025-04-01 09:30:00 2025-04-01 10:00:00 30 30
P002 2 2025-04-01 10:40:00 2025-04-01 11:00:00 20 20

换汤不换药~😂


步骤1:排序并获取前次下车时间

中间表 ordered_records

passenger_id boarding_time alighting_time prev_alighting_time
P001 2025-04-01 08:00:00 2025-04-01 08:30:00 NULL
P001 2025-04-01 08:40:00 2025-04-01 09:00:00 2025-04-01 08:30:00
P001 2025-04-01 09:10:00 2025-04-01 09:30:00 2025-04-01 09:00:00
P002 2025-04-01 09:30:00 2025-04-01 10:00:00 NULL
P002 2025-04-01 10:40:00 2025-04-01 11:00:00 2025-04-01 10:00:00

步骤2:标记新出行起点

中间表 session_flags

passenger_id is_new_session
P001 1 (首条记录)
P001 0 (间隔10分钟)
P001 0 (间隔10分钟)
P002 1
P002 1 (间隔40分钟)

步骤3:生成出行分组ID

中间表 session_groups

passenger_id session_group
P001 1
P001 1
P001 1
P002 1
P002 2

步骤4:分配出行ID并聚合结果

最终结果

passenger_id boarding_time alighting_time total_riding_duration total_actual_duration
P001 2025-04-01 08:00:00 2025-04-01 09:30:00 90 70 (30+20+20)
P002 2025-04-01 09:30:00 2025-04-01 10:00:00 30 30
P002 2025-04-01 10:40:00 2025-04-01 11:00:00 20 20

sql 复制代码
-- 使用CTE(公共表表达式),命名为ordered_records
with ordered_records as (
    -- 从"公交乘车记录表"中选择数据
    select 
        passenger_id,
        boarding_time,
        alighting_time,
        -- 使用LAG窗口函数,获取每个乘客分组下,按上车时间排序后的上一条记录的下车时间
        lag(alighting_time) over (
            partition by passenger_id 
            order by boarding_time
        ) as prev_alighting_time
    from 公交乘车记录表
)
-- 第二个CTE,命名为session_flags
, session_flags as (
    -- 选择ordered_records CTE中的所有列,并添加一个新列is_new_session
    select 
        *,
        -- 使用CASE语句判断是否为新会话
        case 
            -- 如果上一条记录的下车时间为空(即当前是该乘客的第一条记录)
            -- 或者当前记录的上车时间与上一条记录的下车时间间隔大于90分钟
            when prev_alighting_time is null 
                or timestampdiff(minute, prev_alighting_time, boarding_time) > 90 
            then 1 
            else 0 
        end as is_new_session
    from ordered_records
)
-- 第三个CTE,命名为session_groups
, session_groups as (
    -- 选择session_flags CTE中的所有列,并添加一个新列session_group
    select 
        *,
        -- 使用SUM窗口函数,对每个乘客分组下,按上车时间排序后的is_new_session列进行累加
        sum(is_new_session) over (
            partition by passenger_id 
            order by boarding_time
        ) as session_group
    from session_flags
)

-- 主查询
select 
    passenger_id,
    -- 对于每个乘客和会话组分组,获取最小的上车时间
    min(boarding_time) as boarding_time,
    -- 对于每个乘客和会话组分组,获取最大的下车时间
    max(alighting_time) as alighting_time,
    -- 计算总的乘车时长(以分钟为单位)
    timestampdiff(minute, min(boarding_time), max(alighting_time)) as total_riding_duration,
    -- 计算每个会话组内实际的乘车时长总和(以分钟为单位)
    sum(timestampdiff(minute, boarding_time, alighting_time)) as total_actual_duration
from session_groups
-- 按照乘客ID和会话组进行分组
group by passenger_id, session_group
-- 按照乘客ID和会话组进行排序
order by passenger_id, session_group;

熟悉的套路😂

步骤 核心操作 关键函数/逻辑
数据排序 按乘客和时间排序 LAG() + 窗口排序
标记新出行起点 判断90分钟间隔 TIMESTAMPDIFF() + CASE
生成分组ID 累加标记值 SUM() OVER()
聚合结果 计算时间跨度和实际时长 MIN()/MAX()/SUM()

后面大家可以自己做一做 博主就不啰嗦啦~ 答案贴在最后啦 做完再看哈🤣

📚 时间序列分组与合并扩展题库


题目一:用户登录会话合并

题干

合并用户登录记录,若两次登录间隔 ≤15分钟 视为同一会话,计算每个会话的持续时间和登录次数。

表结构

表名 字段名 字段含义 数据类型
user_login user_id 用户ID VARCHAR
login_time 登录时间 DATETIME

示例数据

user_id login_time
U1001 2024-03-01 08:00:00
U1001 2024-03-01 08:05:00
U1001 2024-03-01 08:25:00

期望输出

user_id session_start session_end duration_min login_count
U1001 2024-03-01 08:00:00 2024-03-01 08:05:00 5 2
U1001 2024-03-01 08:25:00 2024-03-01 08:25:00 0 1

题目二:设备故障时段统计

题干

合并设备的连续故障时段(故障状态代码=500),若两次故障间隔 ≤10分钟 视为同一故障事件。

表结构

表名 字段名 字段含义 数据类型
device_log device_id 设备ID VARCHAR
log_time 日志时间 DATETIME
status_code 状态码 INT

示例数据

device_id log_time status_code
D001 2024-03-01 10:00:00 500
D001 2024-03-01 10:05:00 500
D001 2024-03-01 10:20:00 500

期望输出

device_id start_time end_time error_duration_min
D001 2024-03-01 10:00:00 2024-03-01 10:05:00 5
D001 2024-03-01 10:20:00 2024-03-01 10:20:00 0

题目三:订单支付超时分析

题干

合并订单的创建和支付操作,若支付时间在创建后 30分钟内 视为有效支付,统计每个订单的实际支付处理时长。

表结构

表名 字段名 字段含义 数据类型
orders order_id 订单ID VARCHAR
create_time 创建时间 DATETIME
pay_time 支付时间 DATETIME

示例数据

order_id create_time pay_time
O1001 2024-03-01 12:00:00 2024-03-01 12:05:00
O1001 2024-03-01 12:10:00 2024-03-01 12:30:00
O1002 2024-03-01 13:00:00 NULL

期望输出

order_id valid_pay_duration_min
O1001 25 (12:05-12:30)
O1002 0

题目四:交通卡口连续通行分析

题干

合并车辆通过相邻卡口的记录,若通过时间间隔 ≤5分钟 且行驶方向相同,视为连续通行,计算平均速度。

表结构

表名 字段名 字段含义 数据类型
traffic_log car_plate 车牌号 VARCHAR
direction 行驶方向 VARCHAR
pass_time 通过时间 DATETIME
location 卡口位置 VARCHAR

示例数据

car_plate direction pass_time location
沪A12345 东向 2024-03-01 09:00:00 卡口A
沪A12345 东向 2024-03-01 09:03:00 卡口B
沪A12345 东向 2024-03-01 09:10:00 卡口C

期望输出

car_plate start_time end_time avg_speed_kmh
沪A12345 2024-03-01 09:00:00 2024-03-01 09:10:00 48

答案:
题目一:用户登录会话合并
sql 复制代码
-- 使用公共表表达式(CTE),命名为 ordered_logins
with ordered_logins as (
    -- 从 user_login 表中选择数据
    select 
        user_id,
        login_time,
        -- 使用 LAG 窗口函数,在每个 user_id 分区内,按照 login_time 排序
        -- 获取当前记录的上一条记录的 login_time,命名为 prev_login
        lag(login_time) over (partition by user_id order by login_time) as prev_login
    from user_login
)
-- 第二个 CTE,命名为 session_flags
, session_flags as (
    -- 选择 ordered_logins CTE 中的所有列,并添加一个新列 is_new_session
    select 
        *,
        -- 使用 CASE 语句判断是否为新的会话
        case 
            -- 如果上一条记录的登录时间为空(即当前是该用户的第一条登录记录)
            -- 或者当前记录的登录时间与上一条记录的登录时间间隔大于 15 分钟
            when prev_login is null 
                or timestampdiff(minute, prev_login, login_time) > 15 
            then 1 
            else 0 
        end as is_new_session
    from ordered_logins
)
-- 第三个 CTE,命名为 session_groups
, session_groups as (
    -- 选择 session_flags CTE 中的所有列,并添加一个新列 session_id
    select 
        *,
        -- 使用 SUM 窗口函数,在每个 user_id 分区内,按照 login_time 排序
        -- 对 is_new_session 列进行累加,得到每个会话的唯一标识 session_id
        sum(is_new_session) over (partition by user_id order by login_time) as session_id
    from session_flags
)
-- 主查询
select 
    user_id,
    -- 对于每个用户和会话 ID 分组,获取最小的登录时间作为会话开始时间
    min(login_time) as session_start,
    -- 对于每个用户和会话 ID 分组,获取最大的登录时间作为会话结束时间
    max(login_time) as session_end,
    -- 计算会话的持续时间(以分钟为单位)
    timestampdiff(minute, min(login_time), max(login_time)) as duration_min,
    -- 统计每个会话内的登录次数
    count(*) as login_count
from session_groups
-- 按照用户 ID 和会话 ID 进行分组
group by user_id, session_id;
题目二:设备故障时段统计
sql 复制代码
-- 使用公共表表达式(CTE),命名为 ordered_logs
with ordered_logs as (
    -- 从 device_log 表中选择数据,且只选择状态码为 500 的记录
    select 
        device_id,
        log_time,
        -- 使用 LAG 窗口函数,在每个 device_id 分区内,按照 log_time 排序
        -- 获取当前记录的上一条记录的 log_time,命名为 prev_log
        lag(log_time) over (partition by device_id order by log_time) as prev_log
    from device_log
    where status_code = 500
)
-- 第二个 CTE,命名为 session_flags
, session_flags as (
    -- 选择 ordered_logs CTE 中的所有列,并添加一个新列 is_new_session
    select 
        *,
        -- 使用 CASE 语句判断是否为新的故障时段
        case 
            -- 如果上一条记录的日志时间为空(即当前是该设备的第一条故障记录)
            -- 或者当前记录的日志时间与上一条记录的日志时间间隔大于 10 分钟
            when prev_log is null 
                or timestampdiff(minute, prev_log, log_time) > 10 
            then 1 
            else 0 
        end as is_new_session
    from ordered_logs
)
-- 第三个 CTE,命名为 session_groups
, session_groups as (
    -- 选择 session_flags CTE 中的所有列,并添加一个新列 session_id
    select 
        *,
        -- 使用 SUM 窗口函数,在每个 device_id 分区内,按照 log_time 排序
        -- 对 is_new_session 列进行累加,得到每个故障时段的唯一标识 session_id
        sum(is_new_session) over (partition by device_id order by log_time) as session_id
    from session_flags
)
-- 主查询
select 
    device_id,
    -- 对于每个设备和故障时段 ID 分组,获取最小的日志时间作为故障开始时间
    min(log_time) as start_time,
    -- 对于每个设备和故障时段 ID 分组,获取最大的日志时间作为故障结束时间
    max(log_time) as end_time,
    -- 计算故障时段的持续时间(以分钟为单位)
    timestampdiff(minute, min(log_time), max(log_time)) as error_duration_min
from session_groups
-- 按照设备 ID 和故障时段 ID 进行分组
group by device_id, session_id;
题目三:订单支付超时分析
sql 复制代码
select 
    order_id,
    -- 使用 COALESCE 函数,若第一个参数不为 NULL,则返回第一个参数的值,否则返回第二个参数的值
    -- 这里计算有效支付时长(以分钟为单位)
    coalesce(
        -- 计算在创建时间和支付时间间隔不超过 30 分钟的情况下,最大支付时间和最小支付时间的差值(以分钟为单位)
        timestampdiff(
            minute, 
            min(case when timestampdiff(minute, create_time, pay_time) <= 30 then pay_time end), 
            max(case when timestampdiff(minute, create_time, pay_time) <= 30 then pay_time end)
        ), 
        0
    ) as valid_pay_duration_min
from orders
-- 按照订单 ID 进行分组
group by order_id;
题目四:交通卡口连续通行分析
sql 复制代码
-- 使用公共表表达式(CTE),命名为 ordered_passes
with ordered_passes as (
    -- 从 traffic_log 表中选择数据
    select 
        car_plate,
        direction,
        pass_time,
        -- 使用 LAG 窗口函数,在每个 car_plate 和 direction 分区内,按照 pass_time 排序
        -- 获取当前记录的上一条记录的 pass_time,命名为 prev_pass
        lag(pass_time) over (partition by car_plate, direction order by pass_time) as prev_pass
    from traffic_log
)
-- 第二个 CTE,命名为 session_flags
, session_flags as (
    -- 选择 ordered_passes CTE 中的所有列,并添加一个新列 is_new_group
    select 
        *,
        -- 使用 CASE 语句判断是否为新的连续通行组
        case 
            -- 如果上一条记录的通过时间为空(即当前是该车牌和方向的第一条记录)
            -- 或者当前记录的通过时间与上一条记录的通过时间间隔大于 5 分钟
            when prev_pass is null 
                or timestampdiff(minute, prev_pass, pass_time) > 5 
            then 1 
            else 0 
        end as is_new_group
    from ordered_passes
)
-- 第三个 CTE,命名为 session_groups
, session_groups as (
    -- 选择 session_flags CTE 中的所有列,并添加一个新列 group_id
    select 
        *,
        -- 使用 SUM 窗口函数,在每个 car_plate 和 direction 分区内,按照 pass_time 排序
        -- 对 is_new_group 列进行累加,得到每个连续通行组的唯一标识 group_id
        sum(is_new_group) over (partition by car_plate, direction order by pass_time) as group_id
    from session_flags
)
-- 第四个 CTE,命名为 aggregated
, aggregated as (
    -- 选择 car_plate、计算每个连续通行组的最小通过时间、最大通过时间和通过次数
    select 
        car_plate,
        min(pass_time) as start_time,
        max(pass_time) as end_time,
        count(*) as num_passes
    from session_groups
    -- 按照 car_plate、direction 和 group_id 进行分组
    group by car_plate, direction, group_id
)
-- 主查询
select 
    car_plate,
    start_time,
    end_time,
    -- 计算平均速度(单位:km/h),假设每次通过间隔 4 公里((num_passes - 1) * 4)
    -- 用通过间隔距离除以总时间(以小时为单位),并四舍五入到整数
    round((num_passes - 1) * 4 / (timestampdiff(minute, start_time, end_time) / 60.0), 0) as avg_speed_kmh
from aggregated
-- 只选择通过次数大于 1 的记录
where num_passes > 1;

相关推荐
嘗_5 分钟前
sql特训
数据库·sql
IT小哥哥呀1 小时前
电池制造行业数字化实施
大数据·制造·智能制造·数字化·mom·电池·信息化
Xi xi xi1 小时前
苏州唯理科技近期也正式发布了国内首款神经腕带产品
大数据·人工智能·经验分享·科技
yumgpkpm1 小时前
华为鲲鹏 Aarch64 环境下多 Oracle 、mysql数据库汇聚到Cloudera CDP7.3操作指南
大数据·数据库·mysql·华为·oracle·kafka·cloudera
橙汁味的风2 小时前
3关系型数据库的SQL语言
数据库·sql
UMI赋能企业2 小时前
制造业流程自动化提升生产力的全面分析
大数据·人工智能
TDengine (老段)3 小时前
TDengine 数学函数 FLOOR 用户手册
大数据·数据库·物联网·时序数据库·iot·tdengine·涛思数据
蹦跶的小羊羔4 小时前
sql数据库语法
数据库·sql
PawSQL4 小时前
企业级SQL审核工具PawSQL介绍(1) - 六大核心能力
数据库·sql·oracle
唐古乌梁海4 小时前
【python】在Django中,执行原生SQL查询
python·sql·django