4、基础SQL语法回顾:SELECT、WHERE、GROUP BY、HAVING、ORDER BY、窗口函数(LAG/LEAD)在批次对比中的应用
在构建每日PSI监控报表时,核心任务是比较当前批次与上一批次的数据分布差异。这要求我们熟练掌握SQL的基础查询与聚合语法,并灵活运用窗口函数进行跨行计算。本节将结合批次对比场景,系统回顾这些核心语法。
4.1 核心语法速览与批次对比映射
下表列出了各SQL子句在批次偏移诊断中的典型用途:
| SQL子句 | 批次对比场景中的角色 | 典型示例 |
|---|---|---|
| SELECT | 选择需要监控的特征字段与计算指标 | SELECT feature_name, bucket, record_count |
| WHERE | 过滤特定批次ID或时间范围,排除异常数据 | WHERE batch_id = '20250301' |
| GROUP BY | 按特征分桶聚合,计算每个桶的占比 | GROUP BY feature_name, bucket |
| HAVING | 过滤聚合后的结果,如筛选波动超过阈值的特征 | HAVING ABS(psi) > 0.1 |
| ORDER BY | 按PSI值降序排列,优先展示波动最大的特征 | ORDER BY psi DESC |
| LAG/LEAD | 获取上一批次或下一批次的占比数据,实现跨行对比 | LAG(proportion) OVER (PARTITION BY feature, bucket ORDER BY batch_id) |
4.2 SELECT 与 WHERE:数据准备与过滤
批次对比的第一步是从原始明细表中提取所需字段。通常,一张特征分布明细表包含以下关键列:batch_id(批次标识)、feature_name(特征名称)、bucket(分桶标签)、record_count(该桶内的记录数)。
基础查询模板: 提取两个指定批次的原始分布数据。
SELECT
batch_id,
feature_name,
bucket,
record_count
FROM feature_distribution_daily
WHERE batch_id IN ('20250301', '20250302')
AND feature_name = 'age_group';
避坑提示: 在 WHERE 子句中过滤批次时,务必确保批次字段上有索引,避免全表扫描。对于历史数据回溯,建议使用分区裁剪。
4.3 GROUP BY 与 HAVING:计算占比与筛选异常特征
原始计数需要转换为占比才能计算PSI。这通常通过窗口函数或子查询实现,但 GROUP BY 负责最基础的聚合逻辑。
计算逻辑: 每个桶的占比 = 该桶计数 / 该批次总计数。
SELECT
batch_id,
feature_name,
bucket,
record_count,
SUM(record_count) OVER (PARTITION BY batch_id, feature_name) AS batch_total,
record_count * 1.0 / SUM(record_count) OVER (PARTITION BY batch_id, feature_name) AS proportion
FROM feature_distribution_daily
WHERE batch_id IN ('20250301', '20250302');
在计算出每个特征的PSI值后,HAVING 子句用于筛选出需要告警的特征:
SELECT
feature_name,
ROUND(SUM((curr_prop - prev_prop) * LOG(curr_prop / prev_prop)), 6) AS psi_value
FROM (
-- 假设已通过LAG获取了curr_prop和prev_prop
...
) t
GROUP BY feature_name
HAVING psi_value > 0.1 -- 筛选PSI超过0.1的异常特征
ORDER BY psi_value DESC;
4.4 ORDER BY:排序与优先级展示
在监控报表中,ORDER BY 用于将最严重的偏移排在首位,帮助分析师快速定位问题。
- 按PSI降序:
ORDER BY psi_value DESC—— 波动最大的特征排第一。 - 按特征+桶排序:
ORDER BY feature_name, bucket—— 便于查看同一特征下各桶的占比变化。 - 按变化幅度排序:
ORDER BY ABS(curr_prop - prev_prop) DESC—— 关注占比绝对值变化最大的桶。
4.5 窗口函数 LAG/LEAD:批次对比的核心利器
窗口函数 LAG 和 LEAD 允许我们在不改变行数的情况下,访问同一分区内前一行或后一行的数据。在批次对比中,LAG 用于获取上一批次的占比,这是计算PSI的前提。
关键前提: 数据必须按
batch_id 排序,且每个特征-桶组合在每个批次都有且仅有一行记录。
SELECT
batch_id,
feature_name,
bucket,
proportion AS curr_proportion,
LAG(proportion) OVER (
PARTITION BY feature_name, bucket
ORDER BY batch_id
) AS prev_proportion
FROM (
-- 占比计算子查询
SELECT
batch_id,
feature_name,
bucket,
record_count * 1.0 / SUM(record_count) OVER (PARTITION BY batch_id, feature_name) AS proportion
FROM feature_distribution_daily
WHERE batch_id IN ('20250301', '20250302', '20250303')
) t;
LAG 与 LEAD 对比:
| 函数 | 方向 | 批次对比用途 |
|---|---|---|
LAG(column, offset) |
向上取前N行 | 获取上一批次(昨日/上周)的占比 |
LEAD(column, offset) |
向下取后N行 | 获取下一批次(明日/下周)的占比,用于趋势预测 |
4.6 综合案例:单特征PSI计算SQL
以下SQL将上述所有语法整合,计算特征 age_group 在20250302批次相对于20250301批次的PSI值:
WITH dist AS (
SELECT
batch_id,
feature_name,
bucket,
record_count,
record_count * 1.0 / SUM(record_count) OVER (PARTITION BY batch_id, feature_name) AS proportion
FROM feature_distribution_daily
WHERE batch_id IN ('20250301', '20250302')
AND feature_name = 'age_group'
),
lagged AS (
SELECT
batch_id,
feature_name,
bucket,
proportion AS curr_prop,
LAG(proportion) OVER (PARTITION BY feature_name, bucket ORDER BY batch_id) AS prev_prop
FROM dist
)
SELECT
feature_name,
bucket,
curr_prop,
prev_prop,
ROUND((curr_prop - prev_prop) * LOG(curr_prop / prev_prop), 6) AS psi_contribution
FROM lagged
WHERE batch_id = '20250302' -- 只取当前批次
AND prev_prop IS NOT NULL -- 排除没有上一批次对比的行
ORDER BY psi_contribution DESC;
4.7 语法执行顺序与优化要点
理解SQL子句的逻辑执行顺序对编写高效的批次对比查询至关重要:
- FROM / JOIN —— 确定数据源
- WHERE —— 过滤行(批次、特征)
- GROUP BY —— 分组聚合
- HAVING —— 过滤聚合结果
- SELECT —— 选择列与计算表达式
- 窗口函数 —— 在SELECT阶段计算(但逻辑上在ORDER BY之前)
- ORDER BY —— 排序输出
性能提醒: 窗口函数
LAG/LEAD 通常不会减少行数,在数据量极大时可能导致排序溢出。建议在应用窗口函数前,先通过 WHERE 将批次范围缩小到最近N天,并使用 PARTITION BY 合理分区。