用CASE WHEN横向迭代,6步推算节点时间
我以为一步SQL能搞定。
结果写了6个临时表,几百行CASE WHEN。
运营把需求甩过来时,我看了一眼表结构,心想:关联静态路由表不就完了?
跑完第一条数据,我就愣住了。
错发的运单,静态路由表没数据
全链路节点时效:从寄件网点到派件网点,每个节点的计划到达时间、计划发出时间。首中心、中转1-5、末中心,7个节点。
我一开始写了个简单关联:
sql
SELECT
a.ewb_no,
b.plan_first_center_arr_time,
b.plan_first_center_send_time,
b.plan_trans_center_arr_time1,
b.plan_trans_center_send_time1
FROM dwd.ewb_sub_act_plan_node_time_analysis_h a
LEFT JOIN dw.t_time_rule_ewb_static_route b
ON a.plan_full_code = b.route_code
路由一致的运单没问题,计划时间全出来了。
但错发的运单呢?计划路由是"沈阳→高碑店→广州",实际错发成"沈阳→高碑店→武汉→广州"。静态路由表里没有这条路由,LEFT JOIN全NULL。
运营说:错发也要算后续时间,不然没法评估时效。
我盯着那一排NULL,心里开始打鼓------这玩意一步SQL搞不定。
横向迭代:每一步都依赖上一步的结果
我花了一个下午想推算逻辑,画了满桌子的流程图,终于理清了:
首中心计划发出时间 → 中转节点1计划到达时间 → 中转节点1计划发出时间 → 中转节点2计划到达时间 → ...
每个节点的计划发出时间,依赖上一个节点的结果。
这不是聚合,不是关联,是横向迭代------数据在节点间横向流动,上一步的输出是下一步的输入。
SQL天生不擅长迭代。窗口函数只能在同行做计算,跨行迭代做不了。递归CTE理论上可以做,但这个场景每一步的计算逻辑不一样------第2步算首中心的发出时间,第3步算中转节点1的发出时间,每步关联的班次表条件不同,CASE WHEN嵌套也不一样。递归CTE的UNION逻辑写不了这种每步都变的情况。
只能用临时表,一步步串行推算。
6步临时表,串行推算
第1步:判断路由是否一致
↓
第2步:计算首中心计划发出时间
↓
第3步:计算中转节点1计划时间(FROM tmp.step2)
↓
第4步:计算中转节点2计划时间(FROM tmp.step3)
↓
第5步:计算中转节点3-5计划时间
↓
第6步:计算末中心计划时间
6个临时表,串行推算。每一步都依赖上一步的结果,不能跳步,不能并行。
第1步:判断路由是否一致
关联静态路由表,逐节点比对实际路由和计划路由:
sql
CREATE TABLE tmp.step1 AS
SELECT
a.ewb_no,
-- 首中心:实际 vs 计划
CASE WHEN a.act_first_center_site_code = b.delivery_org_code
THEN 1 ELSE 0 END AS first_center_flag,
-- 中转节点1:实际 vs 计划
CASE WHEN a.trans_center_code1 = b.path_node1_org_code
THEN 1 ELSE 0 END AS trans1_flag
FROM dwd.ewb_sub_act_plan_node_time_analysis_h a
LEFT JOIN dw.t_time_rule_ewb_static_route b
ON a.plan_full_code = b.route_code
flag=1:路由一致,直接取静态路由表的计划时间。
flag=0:路由不一致,要推算。
第2步:计算首中心计划发出时间
这是横向迭代的起点。
路由一致时,直接取静态路由表的值。
路由不一致时,要根据"计划到达时间 + 卸车时长 + 装车时长"在车线班次表里找最近能赶上的班车。
sql
CREATE TABLE tmp.step2 AS
SELECT
a.ewb_no,
CASE
WHEN a.first_center_flag = 1
THEN a.delivery_org_send_time -- 路由一致,直接取
ELSE 找最近班车的逻辑 -- 路由不一致,推算
END AS plan_first_center_send_time
FROM tmp.step1 a
LEFT JOIN dim.qy_vehicle_route b
ON a.act_first_center_site_code = b.departure_org_code
第3步:计算中转节点1的计划时间
这一步用到了第2步的结果------横向迭代的关键:
sql
CREATE TABLE tmp.step3 AS
SELECT
a.ewb_no,
a.plan_first_center_send_time, -- 第2步算出来的
-- 计划到达时间 = 上一步的计划发出时间 + 班次运行时长
a.plan_first_center_send_time + b.arrive_over_time
AS plan_trans_center_arr_time1,
CASE
WHEN a.trans1_flag = 1
THEN b.path_node1_org_send_time
ELSE 找最近班车的逻辑
END AS plan_trans_center_send_time1
FROM tmp.step2 a -- 注意:FROM的是第2步的临时表
LEFT JOIN dim.qy_vehicle_route b ON ...
注意FROM------是第2步的临时表tmp.step2。plan_first_center_send_time是第2步算出来的结果。
这就是横向迭代:每一步的输入是上一步的输出。
第4-6步:同理推算
逻辑和第3步一样,只是每一步FROM的是更前一步的临时表。
找最近班车:5种情况,CASE WHEN逐层判断
路由不一致时,要在车线班次表(dim_ll_qy_vehicle_route)里找最近能赶上的班车。
班次不是每天都发车。run_period字段决定了发车规则:
- 隔日班(run_period='8'):隔天发,要看生效日期差值能不能被2整除
- 周班(run_period='1,2,3...'):逗号分隔的周几数字
原始的run_period是变长的逗号分隔字符串,我用RPAD(run_period,13,',9')填充到13位,变成固定格式方便解析:
| 原始 run_period | RPAD (13 位,右侧补 9) 结构化值 |
|---|---|
| "1,2,3,4,5,6,7" | "1,2,3,4,5,6,7" |
| "1,3,5,7" | "1,3,5,7,9,9,9" |
| "2,4,6" | "2,4,6,9,9,9,9" |
| "8" | "8,9,9,9,9,9,9" |
9是填充值,表示该位置没有班次。8表示隔日班。
5种情况,一层CASE WHEN套一层:
① 隔日班,今天发不发?
run_period='8'表示隔日班。用生效日期和当前日期的差值,看能不能被2整除:
effective_date = 2026-05-01
当前日期 = 2026-05-13,差值 = 12天,12 % 2 = 0 → 今天有班次
当前日期 = 2026-05-14,差值 = 13天,13 % 2 ≠ 0 → 明天才有班次
② 周班,今天有班次,赶得上吗?
run_period='1,3,5'表示周一、三、五发车。
今天周三,run_period含3,今天有班次:
当前发出时间 = 10:00,班次发车 = 14:00 → 赶得上 → 返回当天班次
当前发出时间 = 16:00,班次发车 = 14:00 → 赶不上 → 返回次日班次
③ 周班,今天没班次,找下一个班次
今天周三(week_day=3),run_period='1,3,5,7',RPAD后是'1,3,5,7,9,9,9'。
用SUBSTR逐位解析,找下一个有班次的日子:
SUBSTR(run_period,1,1) = '1' → 周一有班次
SUBSTR(run_period,3,1) = '3' → 周三有班次
SUBSTR(run_period,5,1) = '5' → 周五有班次
SUBSTR(run_period,7,1) = '7' → 周日有班次
今天周三,下一个班次是周五(5),差值 = 5 - 3 = 2天
如果今天是周六(6),没有≥6的班次,取最小值(1),差值 = 1 - 6 + 7 = 2天
核心逻辑:
run_period里有≥当前星期几的值 → 取最小差值
run_period里都<当前星期几 → 取最小值+7再减当前星期几
④ 汇总
每个节点都要跑一遍这5种情况的判断。6个临时表,每个表里都有这段100多行的CASE WHEN。
写到最后,我自己都快绕晕了。
前几步Presto,最后一步Spark
中间步骤用Presto,纯粹是因为Presto快。6个临时表串行跑,每一步都能秒级出结果,调试方便。
最后一步用Spark,是为了动态分区写入 。目标表按天分区,Presto不支持动态分区,得手动指定分区值。Spark的INSERT OVERWRITE自动按分区字段写入,不用每个分区写一条SQL。
横向迭代的核心:数据在层级间流动,每一步依赖上一步的结果。临时表 + CASE WHEN是SQL里最朴素的解法。
写完最后一个临时表,我盯着屏幕上的6行CREATE TABLE,心想:下次再有人问我横向迭代怎么写,直接甩给他------别想一步算完,SQL做不到。