3、聚合函数与分组统计:COUNT、SUM、AVG、MIN、MAX函数详解,GROUP BY与HAVING子句在质量探查中的应用

数据质量探查的核心任务之一,是对数据集进行宏观与微观的量化评估。聚合函数与分组统计正是完成这一任务的基础工具。通过 COUNT、SUM、AVG、MIN、MAX 这五大聚合函数,可以快速掌握字段的完整性、数值范围与分布特征;结合 GROUP BY 与 HAVING 子句,则能深入发现不同维度下的数据异常与质量缺陷。

3.1 五大聚合函数在质量探查中的角色

聚合函数对一组值执行计算并返回单个汇总值。在数据质量场景中,每个函数都对应特定的探查目标:

函数 质量探查目标 典型应用场景
COUNT(*) 记录总数、行级完整性 探查表是否为空、对比源端与目标端行数
COUNT(column) 非空值数量、字段填充率 计算某字段的缺失比例
COUNT(DISTINCT column) 唯一值数量、基数 探查主键是否重复、枚举值是否合理
SUM(column) 数值总量、汇总一致性 验证金额字段合计是否与业务台账一致
AVG(column) 数值中心趋势、异常值敏感度 探查平均单价是否在合理区间
MIN(column) 最小值、边界异常 发现日期字段是否出现 1900-01-01 或负数
MAX(column) 最大值、边界异常 发现金额字段是否出现极端大值
探查技巧:在质量探查初期,建议对每个关键字段同时执行 COUNT(column) 与 COUNT(*),两者之差即为缺失值数量。这是计算字段完整率最直接的方法。

3.2 聚合函数实战:字段级质量探查

以下示例基于一张电商订单表 orders,包含字段:order_id、customer_id、order_amount、order_date、region。

3.2.1 探查字段完整性与基数

-- 探查订单表的基本质量指标
SELECT
    COUNT(*)                                          AS 总行数,
    COUNT(order_id)                                   AS 订单ID非空数,
    COUNT(DISTINCT order_id)                          AS 唯一订单ID数,
    COUNT(customer_id)                                AS 客户ID非空数,
    COUNT(DISTINCT customer_id)                       AS 唯一客户数,
    COUNT(region)                                     AS 区域非空数,
    COUNT(DISTINCT region)                            AS 区域枚举数
FROM orders;

质量解读:若 COUNT(order_id) 与 COUNT(DISTINCT order_id) 不相等,说明存在重复的订单ID,这是严重的数据一致性问题。若 COUNT(region) 远小于 COUNT(*),则区域字段缺失严重。

3.2.2 探查数值字段的边界与分布

-- 探查金额字段的质量边界
SELECT
    COUNT(order_amount)                               AS 金额非空数,
    MIN(order_amount)                                 AS 最小金额,
    MAX(order_amount)                                 AS 最大金额,
    AVG(order_amount)                                 AS 平均金额,
    SUM(order_amount)                                 AS 总金额
FROM orders;
边界异常警示:当 MIN(order_amount) 出现负数或 0 时,需排查是否存在退款单未标记或测试数据混入。当 MAX(order_amount) 远高于业务上限(如单笔超过 100 万),应标记为疑似异常值,纳入后续探查范围。

3.3 GROUP BY:按维度分组探查质量

全局聚合只能看到整体情况,而数据质量问题往往隐藏在特定维度中。GROUP BY 允许按一个或多个字段分组,逐组计算聚合指标,从而发现局部异常。

GROUP BY 分组探查数据质量流程 原始订单表 按 region 分组 按 order_date 分组 按 customer_id 分组 COUNT / SUM / AVG MIN / MAX COUNT(DISTINCT) 输出:各维度质量指标(行数/缺失率/极值/基数)

3.3.1 按区域探查订单质量

-- 按区域分组探查:发现哪些区域数据质量最差
SELECT
    region,
    COUNT(*)                                          AS 订单数,
    COUNT(customer_id)                                AS 客户ID非空数,
    COUNT(order_amount)                               AS 金额非空数,
    ROUND(100.0 * (COUNT(*) - COUNT(customer_id)) / COUNT(*), 2) AS 客户缺失率_pct,
    MIN(order_amount)                                 AS 最小金额,
    MAX(order_amount)                                 AS 最大金额,
    AVG(order_amount)                                 AS 平均金额
FROM orders
GROUP BY region
ORDER BY 客户缺失率_pct DESC;

质量发现:通过 ORDER BY 客户缺失率_pct DESC,可以快速定位哪些区域的数据录入质量最差,为后续数据清洗提供优先级依据。

3.4 HAVING:筛选分组后的质量缺陷

WHERE 子句在分组前过滤行,而 HAVING 子句在分组后过滤组。在质量探查中,HAVING 用于筛选出那些满足特定质量阈值(如缺失率过高、重复行数过多)的分组。

3.4.1 筛选缺失率超过阈值的分组

-- 只保留客户ID缺失率超过 5% 的区域
SELECT
    region,
    COUNT(*)                                          AS 订单数,
    COUNT(customer_id)                                AS 客户ID非空数,
    ROUND(100.0 * (COUNT(*) - COUNT(customer_id)) / COUNT(*), 2) AS 客户缺失率_pct
FROM orders
GROUP BY region
HAVING (COUNT(*) - COUNT(customer_id)) * 1.0 / COUNT(*) > 0.05
ORDER BY 客户缺失率_pct DESC;

3.4.2 筛选存在重复主键的分组

-- 按客户分组,找出存在重复订单的客户
SELECT
    customer_id,
    COUNT(*)                                          AS 订单数,
    COUNT(DISTINCT order_id)                          AS 唯一订单数,
    COUNT(*) - COUNT(DISTINCT order_id)               AS 重复订单数
FROM orders
GROUP BY customer_id
HAVING COUNT(*) > COUNT(DISTINCT order_id)
ORDER BY 重复订单数 DESC;
HAVING vs WHERE 速记:
WHERE 过滤原始行(分组前),HAVING 过滤分组结果(分组后)。
HAVING 中可以使用聚合函数(如 COUNT(*)、SUM(amount)),而 WHERE 中不允许直接使用聚合函数。

3.5 综合实战:多维度质量探查报告

将上述技术整合,生成一份按日期和区域交叉分组的质量探查报告:

-- 按日期和区域交叉分组,输出完整质量指标
SELECT
    order_date,
    region,
    COUNT(*)                                          AS 总订单数,
    COUNT(customer_id)                                AS 有效客户数,
    COUNT(order_amount)                               AS 有效金额数,
    ROUND(AVG(order_amount), 2)                       AS 平均金额,
    MIN(order_amount)                                 AS 最小金额,
    MAX(order_amount)                                 AS 最大金额,
    SUM(order_amount)                                 AS 总金额,
    COUNT(DISTINCT customer_id)                       AS 独立客户数
FROM orders
WHERE order_date >= '2024-01-01'                      -- 先过滤近期数据
GROUP BY order_date, region
HAVING COUNT(*) > 10                                  -- 只保留订单数超过10的分组
   AND (COUNT(*) - COUNT(customer_id)) * 1.0 / COUNT(*) < 0.1  -- 客户缺失率低于10%
ORDER BY order_date, region;
质量探查最佳实践:建议将上述查询结果存入一张质量探查日志表,每日或每周定时运行,形成质量趋势基线。当某个分组的 MIN(order_amount) 突然变为负数,或 COUNT(DISTINCT customer_id) 骤降时,系统应自动触发告警。

3.6 常见陷阱与避坑指南

  • 陷阱1:COUNT(*) 与 COUNT(1) 等价,都统计所有行;COUNT(column) 只统计该列非空的行。混淆两者会导致缺失率计算错误。
  • 陷阱2:AVG(column) 会自动忽略 NULL 值,若想将 NULL 视为 0 参与平均,需先使用 COALESCE(column, 0)。
  • 陷阱3:GROUP BY 的字段必须出现在 SELECT 列表中(除非该字段在聚合函数中),否则 SQL 语法报错。
  • 陷阱4:HAVING 中不能使用列的别名(如 HAVING 客户缺失率_pct > 5),必须重复写完整的聚合表达式。部分数据库(如 MySQL)允许别名,但为兼容性建议写完整表达式。
  • 陷阱5:当 GROUP BY 的字段基数很高(如订单ID),分组后的组数可能极大,导致查询性能下降。此时应先用 WHERE 缩小数据范围。
性能警示:在大表上执行 COUNT(DISTINCT column) 是非常消耗资源的操作。如果只是探查唯一值的大致数量,可以使用 APPROX_COUNT_DISTINCT(适用于 BigQuery、Redshift、Spark SQL)或 HyperLogLog 算法,在精度与性能之间取得平衡。