首页 > 试题广场 >

25年美团-骑手配送履约分层留存与时段热力矩阵

[编程题]25年美团-骑手配送履约分层留存与时段热力矩阵
  • 热度指数:88 时间限制:C/C++ 1秒,其他语言2秒 空间限制:C/C++ 256M,其他语言512M
  • 算法知识视频讲解

1. 题目描述

背景
某即时配送平台的运力运营团队需要对新注册骑手进行首周履约能力分层,并观察不同层级骑手在入职后第 N 周时段(早/午/晚/夜)产能留存情况。团队希望用一张 SQL 同时产出:

  • 每个层级、每个留存周、每个时段的在岗骑手数(该周该时段至少完成 1 单的骑手);
  • 该层级首周人数基准下的时段产能留存率
  • 以及该层级该留存周跨时段平均单均时效(分钟,四舍五入保留 2 位)。

分层规则:以骑手reg_date为锚点,Day0 =reg_date。首周 = Day0 ~ Day0+6 共 7 天。首周完成单量(仅计status='FINISHED'的订单):

  • ≥ 30 单→'T1'(金牌)
  • 15 ~ 29 单→'T2'(银牌)
  • 1 ~ 14 单→'T3'(铜牌)
  • 首周 0 单的骑手不进入分析(整骑手剔除)。

时段规则(按pickup_time的小时判定,闭区间说明:morning= 06:00:00–10:59:59,noon= 11:00:00–13:59:59,evening= 17:00:00–20:59:59,night= 其他所有时间,包括 00:00–05:59、14:00–16:59、21:00–23:59)。

留存周定义(以 Day0 为锚):

  • W1= Day0+7 ~ Day0+13
  • W2= Day0+14 ~ Day0+20
  • W4= Day0+28 ~ Day0+34

首周本身(Day0~Day0+6)只用来定层和算基准人数,不作为留存周输出。

表 1:t_rider(骑手注册表)

  • rider_id:BIGINT,骑手 ID
  • rider_name:VARCHAR(32),骑手姓名
  • city_code:VARCHAR(16),所在城市编码
  • reg_date:DATE,注册日期(2025 年内)

表 2:t_delivery(配送订单流水表)

  • order_id:BIGINT,订单 ID
  • rider_id:BIGINT,配送骑手 ID(关联 t_rider.rider_id)
  • pickup_time:DATETIME,取餐时间(2025 年内)
  • deliver_time:DATETIME,送达时间(> pickup_time,同一订单不跨日问题)
  • status:VARCHAR(16),订单状态,取值'FINISHED'/'CANCELLED'/'TIMEOUT'

2. 问题

输出每个分层 × 留存周 × 时段下的骑手履约情况。

仅统计status = 'FINISHED'的订单;时段仅以pickup_time判定;时效 =TIMESTAMPDIFF(SECOND, pickup_time, deliver_time) / 60.0(分钟)。

输出字段:

  • tier(层级:T1/T2/T3)
  • retention_week(留存周:W1/W2/W4)
  • time_slot(时段:morning/noon/evening/night)
  • active_rider_cnt(该层该周该时段至少完成 1 单的不同骑手数)
  • retention_rate(=active_rider_cnt/ 该层级首周分层后的总骑手数,四舍五入保留 2 位)
  • avg_duration_min(该层该周该时段所有 FINISHED 订单的单均时效分钟,四舍五入保留 2 位;若无订单此字段为0.00)

必须输出所有 3 × 3 × 4 = 36 行组合(即使某组合下active_rider_cnt = 0,也要出行,retention_rate = 0.00、avg_duration_min = 0.00)。

排序规则:

  1. tier按 T1, T2, T3 升序(字典序即可)
  2. retention_week按 W1, W2, W4 升序(字典序即可)
  3. time_slot按固定顺序:morning → noon → evening → night(不是字典序,需显式指定)
  4. 若上述三项完全相同(不会出现,但排序稳定起见)按active_rider_cnt降序。

3. 示例数据表

t_rider

rider_id rider_name city_code reg_date
1 ZhangSan BJ 2025-03-01
2 LiSi BJ 2025-03-01
3 WangWu SH 2025-03-05

t_delivery(只展示 W1/W2/W4 留存期内关键订单,首周订单用于分层不展开)

order_id rider_id pickup_time deliver_time status
10101 1 2025-03-08 09:00:00 2025-03-08 09:40:00 FINISHED
10102 1 2025-03-10 12:30:00 2025-03-10 13:00:00 FINISHED
10201 1 2025-03-16 19:00:00 2025-03-16 19:50:00 FINISHED
10202 1 2025-03-17 19:00:00 2025-03-17 19:30:00 CANCELLED
10401 1 2025-03-30 23:30:00 2025-03-31 00:10:00 FINISHED
20101 2 2025-03-10 11:30:00 2025-03-10 12:00:00 FINISHED
20102 2 2025-03-11 13:00:00 2025-03-11 13:30:00 FINISHED
30101 3 2025-03-13 07:00:00 2025-03-13 07:30:00 FINISHED
30201 3 2025-03-20 12:00:00 2025-03-20 13:10:00 TIMEOUT
30401 3 2025-04-05 20:00:00 2025-04-05 20:25:00 FINISHED

4. 示例数据查询结果表

分层结果:T1=1 人(骑手 1)、T2=1 人(骑手 3)、T3=1 人(骑手 2)。

关键事实推导

  • 骑手 1:W1 morning(10101=40min) 活跃;W1 noon(10102=30min) 活跃;W2 evening(10201=50min) 活跃(CANCELLED 不计);W4 night(10401=40min) 活跃。
  • 骑手 2:W1 noon (20101=30min, 20102=30min) 活跃;W2/W4 无。
  • 骑手 3:W1 morning(30101=30min) 活跃;W2 无(TIMEOUT 剔除);W4 evening(30401=25min) 活跃。

每行基准人数 = 各层总数 = 1。留存率要么 1.00 要么 0.00。完整 36 行按规则排序:

tier retention_week time_slot active_rider_cnt retention_rate avg_duration_min
T1 W1 morning 1 1.00 40.00
T1 W1 noon 1 1.00 30.00
T1 W1 evening 0 0.00 0.00
T1 W1 night 0 0.00 0.00
T1 W2 morning 0 0.00 0.00
T1 W2 noon 0 0.00 0.00
T1 W2 evening 1 1.00 50.00
T1 W2 night 0 0.00 0.00
T1 W4 morning 0 0.00 0.00
T1 W4 noon 0 0.00 0.00
T1 W4 evening 0 0.00 0.00
T1 W4 night 1 1.00 40.00
T2 W1 morning 1 1.00 30.00
T2 W1 noon 0 0.00 0.00
T2 W1 evening 0 0.00 0.00
T2 W1 night 0 0.00 0.00
T2 W2 morning 0 0.00 0.00
T2 W2 noon 0 0.00 0.00
T2 W2 evening 0 0.00 0.00
T2 W2 night 0 0.00 0.00
T2 W4 morning 0 0.00 0.00
T2 W4 noon 0 0.00 0.00
T2 W4 evening 1 1.00 25.00
T2 W4 night 0 0.00 0.00
T3 W1 morning 0 0.00 0.00
T3 W1 noon 1 1.00 30.00
T3 W1 evening 0 0.00 0.00
T3 W1 night 0 0.00 0.00
T3 W2 morning 0 0.00 0.00
T3 W2 noon 0 0.00 0.00
T3 W2 evening 0 0.00 0.00
T3 W2 night 0 0.00 0.00
T3 W4 morning 0 0.00 0.00
T3 W4 noon 0 0.00 0.00
T3 W4 evening 0 0.00 0.00
T3 W4 night 0 0.00 0.00
示例1

输入

DROP TABLE IF EXISTS t_rider;
DROP TABLE IF EXISTS t_delivery;

CREATE TABLE t_rider (
    rider_id    BIGINT,
    rider_name  VARCHAR(32),
    city_code   VARCHAR(16),
    reg_date    DATE
);
CREATE TABLE t_delivery (
    order_id     BIGINT,
    rider_id     BIGINT,
    pickup_time  DATETIME,
    deliver_time DATETIME,
    status       VARCHAR(16)
);

INSERT INTO t_rider VALUES
(1, 'ZhangSan', 'BJ', '2025-03-01'),
(2, 'LiSi',     'BJ', '2025-03-01'),
(3, 'WangWu',   'SH', '2025-03-05');

INSERT INTO t_delivery VALUES
(10001,1,'2025-03-01 08:00:00','2025-03-01 08:30:00','FINISHED'),
(10002,1,'2025-03-01 12:00:00','2025-03-01 12:28:00','FINISHED'),
(10003,1,'2025-03-01 18:00:00','2025-03-01 18:35:00','FINISHED'),
(10004,1,'2025-03-02 08:10:00','2025-03-02 08:40:00','FINISHED'),
(10005,1,'2025-03-02 12:10:00','2025-03-02 12:40:00','FINISHED'),
(10006,1,'2025-03-02 18:10:00','2025-03-02 18:45:00','FINISHED'),
(10007,1,'2025-03-03 08:20:00','2025-03-03 08:50:00','FINISHED'),
(10008,1,'2025-03-03 12:20:00','2025-03-03 12:50:00','FINISHED'),
(10009,1,'2025-03-03 18:20:00','2025-03-03 18:55:00','FINISHED'),
(10010,1,'2025-03-04 08:30:00','2025-03-04 09:00:00','FINISHED'),
(10011,1,'2025-03-04 12:30:00','2025-03-04 13:00:00','FINISHED'),
(10012,1,'2025-03-04 18:30:00','2025-03-04 19:05:00','FINISHED'),
(10013,1,'2025-03-05 08:40:00','2025-03-05 09:10:00','FINISHED'),
(10014,1,'2025-03-05 12:40:00','2025-03-05 13:10:00','FINISHED'),
(10015,1,'2025-03-05 18:40:00','2025-03-05 19:15:00','FINISHED'),
(10016,1,'2025-03-06 08:50:00','2025-03-06 09:20:00','FINISHED'),
(10017,1,'2025-03-06 12:50:00','2025-03-06 13:20:00','FINISHED'),
(10018,1,'2025-03-06 18:50:00','2025-03-06 19:25:00','FINISHED'),
(10019,1,'2025-03-07 09:00:00','2025-03-07 09:30:00','FINISHED'),
(10020,1,'2025-03-07 12:00:00','2025-03-07 12:30:00','FINISHED'),
(10021,1,'2025-03-07 19:00:00','2025-03-07 19:35:00','FINISHED'),
(10022,1,'2025-03-01 09:30:00','2025-03-01 10:00:00','FINISHED'),
(10023,1,'2025-03-02 09:30:00','2025-03-02 10:00:00','FINISHED'),
(10024,1,'2025-03-03 09:30:00','2025-03-03 10:00:00','FINISHED'),
(10025,1,'2025-03-04 09:30:00','2025-03-04 10:00:00','FINISHED'),
(10026,1,'2025-03-05 09:30:00','2025-03-05 10:00:00','FINISHED'),
(10027,1,'2025-03-06 09:30:00','2025-03-06 10:00:00','FINISHED'),
(10028,1,'2025-03-07 09:30:00','2025-03-07 10:00:00','FINISHED'),
(10029,1,'2025-03-01 20:00:00','2025-03-01 20:30:00','FINISHED'),
(10030,1,'2025-03-02 20:00:00','2025-03-02 20:30:00','FINISHED'),
(10101,1,'2025-03-08 09:00:00','2025-03-08 09:40:00','FINISHED'),
(10102,1,'2025-03-10 12:30:00','2025-03-10 13:00:00','FINISHED'),
(10201,1,'2025-03-16 19:00:00','2025-03-16 19:50:00','FINISHED'),
(10202,1,'2025-03-17 19:00:00','2025-03-17 19:30:00','CANCELLED'),
(10401,1,'2025-03-30 23:30:00','2025-03-31 00:10:00','FINISHED'),
(20001,2,'2025-03-02 10:00:00','2025-03-02 10:20:00','FINISHED'),
(20002,2,'2025-03-04 13:00:00','2025-03-04 13:25:00','FINISHED'),
(20003,2,'2025-03-06 20:00:00','2025-03-06 20:40:00','FINISHED'),
(20101,2,'2025-03-10 11:30:00','2025-03-10 12:00:00','FINISHED'),
(20102,2,'2025-03-11 13:00:00','2025-03-11 13:30:00','FINISHED'),
(30001,3,'2025-03-05 09:00:00','2025-03-05 09:30:00','FINISHED'),
(30002,3,'2025-03-05 12:00:00','2025-03-05 12:20:00','FINISHED'),
(30003,3,'2025-03-05 18:00:00','2025-03-05 18:40:00','FINISHED'),
(30004,3,'2025-03-06 09:00:00','2025-03-06 09:30:00','FINISHED'),
(30005,3,'2025-03-06 12:00:00','2025-03-06 12:20:00','FINISHED'),
(30006,3,'2025-03-06 18:00:00','2025-03-06 18:40:00','FINISHED'),
(30007,3,'2025-03-07 09:00:00','2025-03-07 09:30:00','FINISHED'),
(30008,3,'2025-03-07 12:00:00','2025-03-07 12:20:00','FINISHED'),
(30009,3,'2025-03-07 18:00:00','2025-03-07 18:40:00','FINISHED'),
(30010,3,'2025-03-08 09:00:00','2025-03-08 09:30:00','FINISHED'),
(30011,3,'2025-03-09 12:00:00','2025-03-09 12:20:00','FINISHED'),
(30012,3,'2025-03-09 18:00:00','2025-03-09 18:40:00','FINISHED'),
(30013,3,'2025-03-10 09:00:00','2025-03-10 09:30:00','FINISHED'),
(30014,3,'2025-03-11 12:00:00','2025-03-11 12:20:00','FINISHED'),
(30015,3,'2025-03-11 18:00:00','2025-03-11 18:40:00','FINISHED'),
(30101,3,'2025-03-13 07:00:00','2025-03-13 07:30:00','FINISHED'),
(30201,3,'2025-03-20 12:00:00','2025-03-20 13:10:00','TIMEOUT'),
(30401,3,'2025-04-05 20:00:00','2025-04-05 20:25:00','FINISHED');

输出

tier|retention_week|time_slot|active_rider_cnt|retention_rate|avg_duration_min
T1|W1|morning|1|1.00|40.00
T1|W1|noon|1|1.00|30.00
T1|W1|evening|0|0.00|0.00
T1|W1|night|0|0.00|0.00
T1|W2|morning|0|0.00|0.00
T1|W2|noon|0|0.00|0.00
T1|W2|evening|1|1.00|50.00
T1|W2|night|0|0.00|0.00
T1|W4|morning|0|0.00|0.00
T1|W4|noon|0|0.00|0.00
T1|W4|evening|0|0.00|0.00
T1|W4|night|1|1.00|40.00
T2|W1|morning|1|1.00|30.00
T2|W1|noon|0|0.00|0.00
T2|W1|evening|0|0.00|0.00
T2|W1|night|0|0.00|0.00
T2|W2|morning|0|0.00|0.00
T2|W2|noon|0|0.00|0.00
T2|W2|evening|0|0.00|0.00
T2|W2|night|0|0.00|0.00
T2|W4|morning|0|0.00|0.00
T2|W4|noon|0|0.00|0.00
T2|W4|evening|1|1.00|25.00
T2|W4|night|0|0.00|0.00
T3|W1|morning|0|0.00|0.00
T3|W1|noon|1|1.00|30.00
T3|W1|evening|0|0.00|0.00
T3|W1|night|0|0.00|0.00
T3|W2|morning|0|0.00|0.00
T3|W2|noon|0|0.00|0.00
T3|W2|evening|0|0.00|0.00
T3|W2|night|0|0.00|0.00
T3|W4|morning|0|0.00|0.00
T3|W4|noon|0|0.00|0.00
T3|W4|evening|0|0.00|0.00
T3|W4|night|0|0.00|0.00
-- Step1:构造时间窗口(首周 + 各留存周)
with r_date as (
select
rider_id,
reg_date,
date_add(reg_date,interval 6 day) as day_6, -- 首周结束
date_add(reg_date,interval 7 day) as day_7,
date_add(reg_date,interval 13 day) as day_13,
date_add(reg_date,interval 14 day) as day_14,
date_add(reg_date,interval 20 day) as day_20,
date_add(reg_date,interval 28 day) as day_28,
date_add(reg_date,interval 34 day) as day_34
from t_rider
),

-- Step2:筛选留存期订单 + 打标签(周 + 时段)
t1 as (
select
r.rider_id,
r.reg_date,
t.pickup_time,
t.deliver_time,

-- 留存周划分
case
when date(t.pickup_time) between r.day_7 and r.day_13 then 'W1'
when date(t.pickup_time) between r.day_14 and r.day_20 then 'W2'
when date(t.pickup_time) between r.day_28 and r.day_34 then 'W4'
end as retention_week,

-- 时段划分(按 pickup_time)
case
when time(t.pickup_time) between '06:00:00' and '10:59:59' then 'morning'
when time(t.pickup_time) between '11:00:00' and '13:59:59' then 'noon'
when time(t.pickup_time) between '17:00:00' and '20:59:59' then 'evening'
else 'night'
end as time_slot

from t_delivery t
join r_date r on t.rider_id = r.rider_id
where
t.status = 'FINISHED'
-- 只保留留存周订单
and (
date(t.pickup_time) between r.day_7 and r.day_13
or date(t.pickup_time) between r.day_14 and r.day_20
or date(t.pickup_time) between r.day_28 and r.day_34
)
),

-- Step3:计算首周单量 → 分层(T1/T2/T3)
t2 as (
select
r.rider_id,
r.reg_date,
case
when sum(case when date(t.pickup_time) between r.reg_date and r.day_6 then 1 else 0 end) >= 30 then 'T1'
when sum(case when date(t.pickup_time) between r.reg_date and r.day_6 then 1 else 0 end) >= 15 then 'T2'
when sum(case when date(t.pickup_time) between r.reg_date and r.day_6 then 1 else 0 end) >= 1 then 'T3'
end as tier
from t_delivery t
join r_date r on t.rider_id = r.rider_id
where t.status = 'FINISHED'
group by r.rider_id, r.reg_date
-- 剔除首周 0 单骑手
having sum(case when date(t.pickup_time) between r.reg_date and r.day_6 then 1 else 0 end) >= 1
),

-- Step4:计算每个 tier 的基准人数(留存率分母)
tier_base as (
select
tier,
count(distinct rider_id) as base_rider_cnt
from t2
group by tier
),

-- Step5:聚合留存指标(核心统计)
agg as (
select
t2.tier,
t1.retention_week,
t1.time_slot,

-- 在岗骑手数(至少1单)
count(distinct t1.rider_id) as active_rider_cnt,

-- 单均时效(分钟)
round(avg(timestampdiff(second,t1.pickup_time,t1.deliver_time) / 60.0),2) as avg_duration_min

from t1
join t2 on t1.rider_id = t2.rider_id
group by
t2.tier,
t1.retention_week,
t1.time_slot
),

-- Step6:构造维度表(保证输出36行)
t_tier as (
select 'T1' as tier
union all select 'T2'
union all select 'T3'
),

t_retention_week as (
select 'W1' as retention_week
union all select 'W2'
union all select 'W4'
),

t_time_slot as (
select 'morning' as time_slot, 1 as slot_order
union all select 'noon', 2
union all select 'evening', 3
union all select 'night', 4
)

-- Step7:拼接最终结果(补全缺失组合)
select
t_t.tier,
t_r.retention_week,
t_s.time_slot,

-- 在岗骑手数(无数据补0)
ifnull(agg.active_rider_cnt, 0) as active_rider_cnt,

-- 留存率 = 在岗骑手 / 基准骑手
round(ifnull(agg.active_rider_cnt, 0) / tier_base.base_rider_cnt, 2) as retention_rate,

-- 单均时效(无数据补0)
ifnull(agg.avg_duration_min, 0.00) as avg_duration_min

from t_tier t_t
cross join t_retention_week t_r
cross join t_time_slot t_s
join tier_base on t_t.tier = tier_base.tier
left join agg
on t_t.tier = agg.tier
and t_r.retention_week = agg.retention_week
and t_s.time_slot = agg.time_slot

-- 排序规则
order by
t_t.tier,
t_r.retention_week,
t_s.slot_order,
active_rider_cnt desc;
发表于 2026-04-28 23:34:30 回复(0)
with a as(
select rider_id,reg_date
,case
when hour(pickup_time) between 6 and 10 then 'morning'
when hour(pickup_time) between 11 and 13 then 'noon'
when hour(pickup_time) between 17 and 20 then 'evening'
else 'night' end as time_slot
,case
when cnt>=30 then 'T1'
when cnt between 15 and 29 then 'T2'
else 'T3' end as tier
,case
when timestampdiff(day,reg_date,date(pickup_time)) between 7 and 13 then 'W1'
when timestampdiff(day,reg_date,date(pickup_time)) between 14 and 20 then 'W2'
when timestampdiff(day,reg_date,date(pickup_time)) between 28 and 34 then 'W4'
else null end as retention_week
,TIMESTAMPDIFF(SECOND, pickup_time, deliver_time)/60 as duration_min
,date(pickup_time) as dt 
from t_delivery left join t_rider using(rider_id)
left join 
(select rider_id,count(*) as cnt from t_delivery left join t_rider using(rider_id) 
where status='FINISHED' and timestampdiff(day,reg_date,date(pickup_time))<=6 
group by rider_id)t1 using(rider_id)
where status='FINISHED' and cnt>0),
tiers as (select 'T1' as tier union select 'T2' union select 'T3'),
week as (select 'W1' as retention_week UNION SELECT 'W2' UNION  SELECT 'W4'),
slot AS (SELECT 'morning' AS time_slot UNION SELECT 'noon' UNION SELECT 'evening' UNION SELECT 'night'),
full_combo AS (SELECT * FROM tiers CROSS JOIN week CROSS JOIN slot)
select f1.tier,f1.retention_week,f1.time_slot
,ifnull(active_rider_cnt,0) as active_rider_cnt
,ifnull(round(active_rider_cnt/cnt,2),0.00) as retention_rate
,ifnull(avg_duration_min,0.00) as avg_duration_min 
from full_combo f1 left join 
(
select tier,retention_week,time_slot
,count(distinct rider_id) as active_rider_cnt
,round(avg(duration_min),2) as avg_duration_min from a 
where retention_week is not null 
group by tier,retention_week,time_slot)t1
on t1.retention_week=f1.retention_week and t1.tier=f1.tier and t1.time_slot=f1.time_slot
left join (
select tier,count(distinct rider_id) as cnt from a
group by tier)t2 on t2.tier=f1.tier
order by f1.tier,f1.retention_week
,field(f1.time_slot,'morning','noon','evening','night'),active_rider_cnt desc

发表于 2026-04-28 18:54:26 回复(0)