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;
窗口函数 LAG/LEAD 在批次对比中的行级操作示意 batch_id bucket proportion prev_prop (LAG) next_prop (LEAD) 20250301 0-18 0.25 NULL 0.28 20250302 0-18 0.28 0.25 0.31 20250303 0-18 0.31 0.28 NULL 图例说明 • LAG(proportion) OVER (PARTITION BY feature, bucket ORDER BY batch_id) • 紫色列 (prev_prop):从上一行(上一批次)取值,第一行为 NULL • 青色列 (next_prop):从下一行(下一批次)取值,最后一行为 NULL

4.7 语法执行顺序与优化要点

理解SQL子句的逻辑执行顺序对编写高效的批次对比查询至关重要:

  1. FROM / JOIN —— 确定数据源
  2. WHERE —— 过滤行(批次、特征)
  3. GROUP BY —— 分组聚合
  4. HAVING —— 过滤聚合结果
  5. SELECT —— 选择列与计算表达式
  6. 窗口函数 —— 在SELECT阶段计算(但逻辑上在ORDER BY之前)
  7. ORDER BY —— 排序输出
性能提醒: 窗口函数 LAG/LEAD 通常不会减少行数,在数据量极大时可能导致排序溢出。建议在应用窗口函数前,先通过 WHERE 将批次范围缩小到最近N天,并使用 PARTITION BY 合理分区。