【六】Hive 函数:聚合 / 统计 / 分位数 / 集合 / 高级分组

系统讲解 Hive 聚合函数(count/sum/avg/max/min)、统计学函数(方差/标准差/协方差/相关系数)、分位数(percentile / percentile_approx)、集合聚合(collect_list/collect_set/map_agg)以及 GROUPING SETS / CUBE / ROLLUP 多维分析

次阅读

聚合函数(Aggregate Functions)是 Hive 中用于对一组行进行计算并返回单个结果的函数,通常配合 GROUP BY 使用,是数仓查询、报表统计的核心工具。

聚合函数注意 NULL 值说明
count(*) 包含 NULL 值统计行数,不看列值
count(id) 不包含 NULL 值只统计该列非 NULL 的行
min/max/avg 忽略 NULLNULL 不参与计算
全部是 NULL 时,结果为 NULL不是 0,是 NULL
avg 不包含 NULL = 分母不算那一行分子分母同时排除 NULL

一、聚合函数总览

类别常见函数作用是否支持 DISTINCT
基础聚合count, sum, avg, max, min计数 / 求和 / 平均 / 最值
统计学variance, var_pop, var_samp, stddev_pop, stddev_samp, covar_pop, covar_samp, corr方差、标准差、协方差、相关系数
分位数percentile(col, p), percentile_approx(col, p)精确 / 近似分位数
集合聚合collect_list, collect_sethistogram_numeric(col, b), map_agg,map_from_arrays把多行合并成数组collect_set 自动去重
直方图histogram_numeric(col, b)ngrams近似直方图
高级分组grouping, grouping__id配合GROUPING SETS / CUBE / ROLLUP 标识聚合维度

二、基础聚合函数(最常用)

1. count

1
2
3
4
5
6
7
8
-- 统计所有行(包括 NULL)
SELECT count(*) FROM orders;

-- 统计指定列非 NULL 的行数
SELECT count(user_id) FROM orders;

-- 去重统计(注意性能:会强制走一个 Reducer)
SELECT count(DISTINCT user_id) FROM orders;

count(DISTINCT user_id) 为什么会强制走一个Reducer?

  • COUNT本身是可合并的,每个部分计数,最后加总即可。

  • DISTINCT去重操作是不可合并的。假设数据被分散到10个Mapper处理,每个Mapper都能得到自己局部的去重结果,但这10个局部结果之间很可能有重复。为了得到全局唯一的去重结果,必须将可能重复的键(user_id)全部集中到一起处理。

    假设有3个user_id:A, A, B, B, C, C

    • Map阶段: 读取orders表,以user_idkey输出

      (A), (A), (B), (B), (C), (C)

    • Shuffle阶段: 这是关键!由于需要对user_id进行全局去重,Hive默认会设置 mapred.reduce.tasks = 1,或者通过一个特殊的、强制所有数据流向一个Reducer的分区器。这导致所有数据在网络上传输后,都会堆积到唯一的一个Reducer节点上。

      [(A), (A), (B), (B), (C), (C)]→Reducer 0

    • Reduce阶段: 这个唯一的Reducer节点接收全量数据,在内存中维护一个HashSet结构来去重,然后计数。

      HashSet{A, B, C} → count=3

瓶颈:

  • 单点计算与网络瓶颈: 所有数据都流向一个Reducer,该节点的CPU、内存、网络IO成为瓶颈,而集群其他节点闲置,资源利用率极低。
  • 内存压力巨大: Reducer需要在内存中存储所有不重复的user_id 用于去重。如果去重后的基数(cardinality)很大,比如有上亿个不同的用户,极易导致Java Heap Space内存溢出(OOM)。
  • 数据倾斜的极端情况: 这本质上是100%的数据倾斜,所有数据都“倾斜”到了那一个Reducer上。

⚠️ 性能坑点count(distinct) 在 Hive 中默认只用 1 个 Reducer,数据量大时极慢。 优化方案

count(distinct)优化

使用 GROUP BY改写(最推荐,处理精确去重)

这是标准优化手段,将计算拆分成“局部去重”和“全局汇总”两步。

1
2
3
4
5
6
7
-- 优化后的写法
SELECT count(1)
FROM (
    SELECT user_id
    FROM orders
    GROUP BY user_id
) t;

假设有3个user_id:A, A, B, B, C, C 执行过程解析

  1. 第一个JobGROUP BY):

    • Map阶段: 以user_id为Key输出: (A), (A), (B), (B), (C), (C)

    • Shuffle阶段: 相同的user_id会被发送到同一个Reducer,但不同的user_id可以分散到多个Reducer上。通过设置 set mapred.reduce.tasks = N;来控制并行度。

      1
      2
      3
      
      A的hashReducer 0: (A), (A)
      B的hashReducer 1: (B), (B)
      C的hashReducer 0: (C), (C) // 假设A和C的hash模2相同
      
    • Reduce阶段: 每个Reducer对自己接收到的user_id做局部去重(因为相同user_id已在一起,去重很快),每个user_id输出一行。

      1
      2
      
      Reduce0: 去重后输出 (A), (C)
      Reduce1: 去重后输出 (B)
      
  2. 第二个Jobcount(1))这个Job数据量很小,执行飞快:

    • Map阶段: 以user_id为Key输出:(A), (C), (B)
    • Shuffle阶段:所有→Reducer 0
    • Reduce阶段:Reduce0: count=3

优点: 完美利用集群并行能力,解决了单点瓶颈和内存溢出问题。

使用近似计算(处理超大基数去重)

如果业务可以接受微小误差(误差率通常在1%以内),使用近似聚合函数速度极快,因为它使用了HyperLogLog等概率算法,内存占用恒定且极小。

1
2
3
4
-- 使用近似计算,性能极高
SELECT count(DISTINCT user_id) as exact_cnt, -- 精确值(慢)
       approx_count_distinct(user_id) as approx_cnt -- 近似值(快)
FROM orders;

适用场景: 浏览UV、独立访客等超大基数统计,对绝对精确性要求不高的场景。

2. sum / avg / max / min

1
2
3
4
5
6
7
8
SELECT
dept_id,
sum(salary) AS total_salary,
avg(salary) AS avg_salary,
max(salary) AS max_salary,
min(salary) AS min_salary
FROM employee
GROUP BY dept_id;

📝 NULL 处理规则:除 count(*) 外,所有聚合函数自动忽略 NULL

避坑指南

描述解决方案
count(distinct)只用 1 个 Reducergroup by 子查询替代
聚合后 select 非聚合列Hive 会报错或返回非预期值所有非聚合列必须放进group by
NULL 影响统计count(col) 不统计 NULL改用count(*)nvl(col,0)
collect_list 数据膨胀大 group 下数组超大 OOMLIMIT 或预先过滤
分位数选错函数percentile 慢且只支持 intpercentile_approx
group by 倾斜单 Reducer 跑爆开启hive.groupby.skewindata

三、统计学聚合函数

函数英文全称含义公式(可复制)使用场景说明
variance(col)/ var_pop(col)Variance (Population)总体方差∑(xᵢ - μ)² / N已知全体数据时使用
var_samp(col)Variance (Sample)样本方差∑(xᵢ - x̄)² / (N-1)从样本推断总体时使用(无偏估计)
stddev_pop(col)Standard Deviation (Population)总体标准差√(var_pop)与总体方差配套使用
stddev_samp(col)Standard Deviation (Sample)样本标准差√(var_samp)与样本方差配套使用
covar_pop(c1, c2)Covariance (Population)总体协方差∑(xᵢ - μₓ)(yᵢ - μᵧ) / N两变量总体线性关系度量
covar_samp(c1, c2)Covariance (Sample)样本协方差∑(xᵢ - x̄)(yᵢ - ȳ) / (N-1)样本数据的协方差估计
corr(c1, c2)Correlation Coefficient皮尔逊相关系数cov(x,y) / (σₓ × σᵧ)标准化协方差,范围[-1, 1]

公式符号说明

符号含义代码表示
求和\sumSUM()
xᵢ第 i 个数据点x_i
μ总体均值\muavg_pop
样本均值\bar{x}avg_samp
N总体数据个数N
n样本数据个数n
σ总体标准差\sigma
s样本标准差s
cov协方差\text{cov}

1、总体方差/样本方差

1
2
总体方差: var_pop(col)
样本方差: var_samp(col)

方差是衡量一组数据离散程度(波动大小)的核心统计指标。它计算的是每个数据点与平均值之间距离的平方的平均值。

方差越大,数据越分散、波动越大;方差越小,数据越集中、越稳定。

$$ \begin{align} \text{总体方差(Population Variance)} = \frac{\sum_{i=1}^{N}(x_i - \mu)^2}{N}\text{ N为总体数据个数}\ \text{样本方差(Sample Variance)} = \frac{\sum_{i=1}^{n}(x_i - \bar{x})^2}{n-1}\text{    n为样本数据个数} \end{align} $$

关键区别:样本方差的分母用 n-1(贝塞尔校正),这是为了得到对总体方差更准确的无偏估计。大数据分析中,我们通常处理的是样本数据,所以更常用 var_samp()

场景:分析用户日活跃时长

1
2
3
4
5
6
7
-- 计算用户每日使用时长的波动情况
SELECT 
  AVG(duration_minutes) AS avg_duration,  -- 平均时长
  VAR_SAMP(duration_minutes) AS variance_duration,  -- 时长方差
  STDDEV_SAMP(duration_minutes) AS stddev_duration  -- 时长标准差
FROM user_behavior
WHERE dt BETWEEN '2023-10-01' AND '2023-10-31';

解读结果

  • 如果 variance_duration值很大 → 用户使用习惯不稳定,有的天用很久,有的天几乎不用

  • 如果值很小 → 用户使用习惯很规律,每天时长差不多

计算方差时为什么要平方?

①避免正负偏差相互抵消;②放大较大偏差的影响,使方差对异常值更敏感。

2、总体标准差/样本标准差

1
2
总体标准差: stddev_pop(col)
样本标准差: stddev_samp(col)

标准差是方差的平方根,它衡量数据点相对于平均值的平均离散程度。与方差相比,标准差的单位与原数据一致,因此更直观、更易于业务解释。

标准差是数据波动大小的“标尺”——标准差越大,数据越分散;标准差越小,数据越集中。

$$ \begin{align} \text{总体标准差 ( Population Standard Deviation ) σ} = \sqrt{\frac{\sum_{i=1}^{N}(x_i - \mu)^2}{N}}\text{ N为总体数据个数}\ \text{样本标准差 ( Sample Standard Deviation ) s} = \sqrt{\frac{\sum_{i=1}^{n}(x_i - \bar{x})^2}{n-1}}\text{ n为样本数据个数} \end{align} $$

📊 标准差 vs 方差(关键区别)

指标计算公式单位直观性大数据函数
方差N∑(xi​−μ)2​原单位的平方
(如:元²、分钟²)
较差,难以直接解释VAR_SAMP()
标准差方差​与原数据一致
(如:元、分钟)
极好,可直接用于业务分析STDDEV_SAMP()

简单比喻

  • 方差 = 衡量“波动能量”(平方后放大)

  • 标准差 = 衡量“波动幅度”(恢复原单位,可直接感知)

经验法则(68-95-99.7规则)

对于正态分布数据:

  • 68% 的数据落在均值 ± 1个标准差内

  • 95%​ 的数据落在均值 ± 2个标准差内

  • 99.7%​ 的数据落在均值 ± 3个标准差内

应用

场景1:分析电商用户订单金额波动

1
2
3
4
5
6
7
8
9
-- 计算用户订单金额的标准差,了解消费稳定性
SELECT 
  user_id,
  AVG(order_amount) AS avg_amount,           -- 平均订单金额
  STDDEV_SAMP(order_amount) AS std_amount    -- 订单金额标准差
FROM order_table
WHERE dt >= '2023-10-01'
GROUP BY user_id
LIMIT 10;

结果解读示例

用户A:avg_amount=200元, std_amount=20元  → 消费很稳定(200±20元)
用户B:avg_amount=200元, std_amount=80元  → 消费波动大(有时几十,有时几百)

场景2:监控系统接口响应时间

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
-- 监控API响应时间的稳定性
SELECT 
  api_name,
  AVG(response_time_ms) AS avg_time,
  STDDEV_SAMP(response_time_ms) AS std_time,
  -- 计算波动系数:标准差/平均值,衡量相对波动
  STDDEV_SAMP(response_time_ms) / AVG(response_time_ms) AS cv
FROM api_monitor
WHERE hour = '2023-10-22 10:00:00'
GROUP BY api_name;

场景3:异常检测与监控

1
2
3
4
5
6
-- 识别响应时间异常的服务
SELECT *
FROM api_monitor
WHERE response_time_ms > 
  (SELECT AVG(response_time_ms) + 3 * STDDEV_SAMP(response_time_ms) 
   FROM api_monitor WHERE dt = '2023-10-22')

3σ原则:在正态分布中,99.7%的数据落在均值±3个标准差内。超出此范围可视为异常。

1. 用户分群与画像

  • 低标准差用户:行为稳定,适合标准化服务

  • 高标准差用户:行为多变,需个性化策略

2. 风险评估(金融领域)

  • 股票收益率的标准差 = 波动率 = 风险指标

  • 标准差越大,投资风险越高

3. 质量控制(生产领域)

  • 产品尺寸的标准差衡量生产一致性

  • 六西格玛管理:要求标准差极低


3、总体协方差/样本协方差

1
2
总体协方差: covar_pop(col1, col2)
样本协方差: covar_samp(col1, col2)

协方差衡量两个变量之间的协同变化关系。它告诉你:当一个变量变化时,另一个变量是倾向于同向变化还是反向变化。

协方差的正负表示变化方向,绝对值大小表示变化关系的强弱(但受数据尺度影响)。

$$ \begin{align} \text{总体标准差 ( Population Covariance ) cov(x,y)} = \frac{\sum_{i=1}^{N}(x_i - \mu_x)(y_i - \mu_y)}{N}\text{ N为总体数据个数}\ \text{样本标准差 ( Sample Covariancen ) cov(x,y)} = \frac{\sum_{i=1}^{n}(x_i - \bar{x})(y_i - \bar{y})}{n-1}\text{ n为样本数据个数} \end{align} $$

📈 协方差的三种情况

协方差值变化关系图形表现业务示例
正值正相关
一个↑,另一个也↑
点从左下到右上分布广告曝光量↑,点击量↑
负值负相关
一个↑,另一个↓
点从左上到右下分布商品价格↑,销量↓
接近0无线性关系点随机分布,无趋势用户身高与购物金额

协方差的局限性

2. 量纲敏感问题

1
2
3
4
5
6
-- 同一关系,不同单位导致协方差值完全不同
SELECT 
  COVAR_SAMP(price_yuan, sales) AS covar_yuan,    -- 价格(元)与销量
  COVAR_SAMP(price_cent, sales) AS covar_cent     -- 价格(分)与销量
FROM product_data;
-- covar_cent 会是 covar_yuan 的100倍,但实际关系相同!

2. 无法比较不同变量对

  • 协方差A=1000,协方差B=50 → 不能说A的关系比B强1000/50=20倍

  • 因为量纲不同,绝对值大小无直接可比性

这正是需要“相关系数”的原因:相关系数=标准化协方差,消除了量纲影响。

应用

场景1:分析广告效果

1
2
3
4
5
6
7
8
-- 分析广告曝光量与点击量的关系
SELECT 
  COVAR_SAMP(impressions, clicks) AS covar_imp_clk,
  -- 业务解读:正值表示曝光增加时点击也倾向于增加
  AVG(impressions) AS avg_impressions,
  AVG(clicks) AS avg_clicks
FROM ad_performance
WHERE dt = '2023-10-22';

场景2:用户行为分析

1
2
3
4
5
6
7
8
9
-- 分析用户浏览时长与购买金额的关系
SELECT 
  user_id,
  COVAR_SAMP(session_duration, purchase_amount) AS covar_duration_amount
FROM user_sessions
GROUP BY user_id
HAVING COVAR_SAMP(session_duration, purchase_amount) > 0
-- 只筛选正相关用户:浏览越久,买得越多
LIMIT 10;

场景3:特征选择(机器学习)

1
2
3
4
5
6
7
8
-- 选择与目标变量相关性强的特征
SELECT 
  feature_name,
  COVAR_SAMP(feature_value, target_value) AS covar_with_target
FROM training_data
GROUP BY feature_name
ORDER BY ABS(COVAR_SAMP(feature_value, target_value)) DESC;
-- 协方差绝对值越大,特征与目标变量关系越强

4、皮尔逊相关系数

1
皮尔逊相关系数: corr(c1, c2)

皮尔逊相关系数是协方差的标准化版本,它衡量两个变量之间线性关系的强度和方向,取值范围固定为[-1, 1],消除了量纲影响,可直接比较不同变量对的相关性强弱。

相关系数 = 标准化的协方差。值越接近±1,线性关系越强;值越接近0,线性关系越弱。

$$ \begin{align} \text{皮尔逊相关系数 ( Correlation Coefficient ): } r(X,Y) = \frac{\text{cov}(x,y)}{\sigma_x \sigma_y} \quad \text{ 其中:cov(x,y)为协方差,σ为总体标准}\

\frac{\sum_{i=1}^{n}(x_i - \bar{x})(y_i - \bar{y})} {\sqrt{\sum_{i=1}^{n}(x_i - \bar{x})^2} \cdot \sqrt{\sum_{i=1}^{n}(y_i - \bar{y})^2}} \quad \text{其中: } -1 \leq r \leq 1

\end{align} $$

相关系数 vs 协方差(关键升级)

特性协方差皮尔逊相关系数
取值范围(-∞, +∞)[-1, 1](固定范围)
量纲影响受数据单位影响无量纲,消除单位影响
可比性不同变量对无法直接比较可直接比较不同变量对
直观性数值大小难以解释绝对值越接近1,关系越强
大数据函数COVAR_SAMP()CORR()

📈 相关系数值的直观解释

r 值范围关系强度方向业务意义
0.8 ~ 1.0极强相关一个变量可高度预测另一个
0.6 ~ 0.8强相关明显协同变化关系
0.4 ~ 0.6中等相关有一定关联性
0.2 ~ 0.4弱相关轻微关联,需谨慎解读
-0.2 ~ 0.2极弱/无线性相关-无明显线性关系(可能有非线性关系)
-0.4 ~ -0.2弱相关轻微反向变化
-0.6 ~ -0.4中等相关明显反向变化
-0.8 ~ -0.6强相关强烈反向变化
-1.0 ~ -0.8极强相关高度反向预测关系

重要提示:相关系数只衡量线性关系。r=0不代表没有关系,可能存非线性关系(如U型、抛物线型)。

应用

场景1:特征工程(机器学习)

1
2
3
4
5
6
7
8
-- 筛选与目标变量相关性强的特征
SELECT 
  feature_name,
  ABS(CORR(feature, target)) AS abs_correlation
FROM model_features
GROUP BY feature_name
ORDER BY abs_correlation DESC;
-- 通常保留|r|>0.3的特征,剔除|r|<0.1的特征

场景2:业务指标监控

1
2
3
4
5
6
7
8
-- 监控关键指标对的相关性是否稳定
SELECT 
  dt,
  CORR(active_users, revenue) AS dau_revenue_corr
FROM daily_business_metrics
GROUP BY dt
ORDER BY dt;
-- 如果某天相关性突然下降,需要排查原因

场景3:用户分群与推荐

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
-- 基于行为相关性进行用户分群
SELECT 
  user_id,
  CASE 
    WHEN CORR(browse_time, purchase_amount) > 0.6 THEN '高价值探索型'
    WHEN CORR(browse_time, purchase_amount) < -0.3 THEN '快速决策型'
    ELSE '普通型'
  END AS user_segment
FROM user_behavior
GROUP BY user_id;

四、分位数函数(数仓常用)

  1. 分位数定义

将数据从小到大排序后,分成若干等份的切分点值。常用分位数:

分位数别名含义数据位置
中位数二分位数50%分位点正中间的值
四分位数Q1, Q2, Q325%, 50%, 75%分位点四等分点
百分位数P1, P10, P90, P991%, 10%, 90%, 99%分位点百等分点
  1. 分位数 vs 平均值
1
2
3
4
-- 平均值受异常值影响大,分位数更稳健
数据: [1, 2, 3, 4, 1000]
平均值: 202 (1000拉高)
中位数: 3 (不受极端值影响)

1、PERCENTILE() - 精确分位数

1
2
3
4
5
6
7
-- 语法:PERCENTILE(col, p) OVER()
-- 计算精确分位数,但性能较差,适合小数据集
SELECT 
  PERCENTILE(CAST(salary AS BIGINT), 0.5) AS median_salary,
  PERCENTILE(CAST(salary AS BIGINT), 0.25) AS q1_salary,
  PERCENTILE(CAST(salary AS BIGINT), 0.75) AS q3_salary
FROM employee_table;

注意PERCENTILE()要求列为 BIGINTDOUBLE类型,需要显式转换。

2、PERCENTILE_APPROX() - 近似分位数(推荐)

使用 T-Digest算法,内存效率高,适合大数据集。

1
2
3
4
5
-- 计算单个百分位数
PERCENTILE_APPROX(col, p [, B])

-- 计算多个百分位数(返回数组)
PERCENTILE_APPROX(col, array(p1, p2, ...) [, B])

参数说明表

参数数据类型必选/可选说明示例值
colDOUBLE, DECIMAL, INT 等数值类型必选要计算百分位数的数值列response_time, amount
pDOUBLE 或 ARRAY必选百分位数值,范围 [0, 1]0.95(P95)
array(0.5, 0.95, 0.99)
BINT可选精度控制参数,默认 1000010000, 20000, 9999

B 参数的作用机制

  • 内存控制:B 代表算法保留的数据压缩点数量,B 越大,近似精度越高

  • 精确条件:当列中不同值(distinct values)的数量 ≤ B 时,返回精确百分位数

  • 性能权衡:B 值增加会提升计算成本和内存消耗

快速估算,性能优先(大数据集)B=5000

平衡精度与性能(默认推荐)B=10000

高精度需求(小数据集或关键指标)B=50000

1
2
3
4
5
6
7
-- 语法:PERCENTILE_APPROX(col, p [, B])
-- B: 精度参数,默认10000,越大越精确但越慢
SELECT 
  PERCENTILE_APPROX(salary, 0.5) AS median_salary,      -- 中位数
  PERCENTILE_APPROX(salary, 0.95) AS p95_salary,        -- 95分位
  PERCENTILE_APPROX(salary, 0.99, 100000) AS p99_salary -- 高精度99分位
FROM employee_table;

与 PERCENTILE 函数的区别

函数输入类型计算方式性能适用场景
PERCENTILE仅限 INT​ 类型精确计算较慢,需全排序小数据集,整数列,要求精确
PERCENTILE_APPROX所有数值类型(DOUBLE等)近似计算,精度由B控制,适合大数据海量数据,浮点数列,允许近似
1
2
3
4
5
6
-- 错误示例:PERCENTILE 不能用于浮点数
SELECT PERCENTILE(response_time, 0.95) FROM logs; -- 可能报错

-- 正确做法:使用 PERCENTILE_APPROX 或先转换
SELECT PERCENTILE_APPROX(response_time, 0.95) FROM logs;
SELECT PERCENTILE(CAST(response_time AS INT), 0.95) FROM logs; -- 精度丢失

应用

场景1:多个分位数一次性计算

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
-- 使用数组一次计算多个分位数
SELECT 
  PERCENTILE_APPROX(salary, ARRAY(0.25, 0.5, 0.75, 0.9, 0.95, 0.99)) AS percentiles
FROM employee_table;

-- 结果:数组形式 [q1, median, q3, p90, p95, p99]
-- 提取特定分位值:
SELECT 
  percentiles[0] AS q1,      -- 25%分位(数组索引从0开始)
  percentiles[1] AS median,  -- 中位数
  percentiles[5] AS p99      -- 99%分位
FROM (
  SELECT PERCENTILE_APPROX(salary, ARRAY(0.25, 0.5, 0.75, 0.9, 0.95, 0.99)) AS percentiles
  FROM employee_table
) t;

场景2:系统性能监控(P99延迟)

P99表示在一组数据中,有99%的数据点小于或等于该值,只有1%的数据点大于该值。例如,如果API响应时间的P99是200ms,意味着99%的请求响应时间≤200ms,只有1%的请求超过200ms。

指标特点适用场景
平均值易受极端值影响,掩盖长尾问题整体趋势分析
P50 中位数50%数据低于此值,反映典型情况普通用户体验
P9595%数据低于此值,关注大多数用户一般性能要求
P9999%数据低于此值,关注最差情况关键业务、SLA保障
P99突增通常表示系统存在性能瓶颈​
P99999.9%数据低于此值极端场景、金融交易
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
-- 监控API响应时间的P99,用于SLA评估
SELECT 
  api_name,
  COUNT(*) AS request_count,
  AVG(response_time_ms) AS avg_time,
  MIN(response_time_ms) AS min_time,
  MAX(response_time_ms) AS max_time,
  PERCENTILE_APPROX(response_time_ms, 0.99) AS p99_time,  -- 关键指标
  PERCENTILE_APPROX(response_time_ms, 0.95) AS p95_time
FROM api_monitor
WHERE dt = '2023-10-22' AND hour = '10'
GROUP BY api_name
HAVING p99_time > 1000  -- 筛选P99超过1秒的API
ORDER BY p99_time DESC;

场景3: IQR 方法

  • IQR​ = Q3(第三四分位数,75%分位点) - Q1(第一四分位数,25%分位点)

  • 反映了数据中间50%的离散程度,对极端值不敏感

计算步骤

1
2
3
4
5
6
7
1. 将数据从小到大排序
2. 计算 Q1(第25百分位数)和 Q3(第75百分位数)
3. IQR = Q3 - Q1
4. 异常值边界:
   - 下界 = Q1 - 1.5 × IQR
   - 上界 = Q3 + 1.5 × IQR
5. 任何低于下界或高于上界的数据点视为异常值
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
-- 计算订单金额的IQR并识别异常订单
WITH order_stats AS (
  SELECT 
    PERCENTILE_APPROX(order_amount, 0.25) as q1,
    PERCENTILE_APPROX(order_amount, 0.75) as q3,
    PERCENTILE_APPROX(order_amount, 0.75) - PERCENTILE_APPROX(order_amount, 0.25) as iqr
  FROM orders
  WHERE dt = '2024-01-01'
)
SELECT 
  o.order_id,
  o.order_amount,
  CASE 
    WHEN o.order_amount < (s.q1 - 1.5 * s.iqr) THEN '过低异常'
    WHEN o.order_amount > (s.q3 + 1.5 * s.iqr) THEN '过高异常'
    ELSE '正常'
  END as outlier_type,
  s.q1 - 1.5 * s.iqr as lower_bound,
  s.q3 + 1.5 * s.iqr as upper_bound
FROM orders o
CROSS JOIN order_stats s
WHERE o.dt = '2024-01-01'
  AND (o.order_amount < (s.q1 - 1.5 * s.iqr) 
       OR o.order_amount > (s.q3 + 1.5 * s.iqr));

3、PERCENTILE_CONT() - 连续分位数(Hive 2.2.0+)

1
2
3
4
5
6
-- 语法:PERCENTILE_CONT(p) WITHIN GROUP (ORDER BY col)
-- 返回插值结果(如果分位点不在具体数据点上)
SELECT 
  PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY response_time) AS median_time,
  PERCENTILE_CONT(0.9) WITHIN GROUP (ORDER BY response_time) AS p90_time
FROM api_logs;
1
2
3
4
5
6
7
8
9
-- 精确分位数(只支持 int 类型,数据量大时慢)
SELECT percentile(age, 0.5) AS median_age FROM employee;

-- 近似分位数(支持 double,性能好,推荐)
SELECT percentile_approx(salary, 0.95) AS p95_salary FROM employee;

-- 一次算多个分位数(传数组)
SELECT percentile_approx(salary, array(0.5, 0.9, 0.95, 0.99))
FROM employee;

💡 使用建议:生产环境优先用 percentile_approx,效率高、支持任意数值类型。


五、集合聚合(行转列利器)

集合聚合函数概览

函数返回值类型功能描述是否去重保留顺序
COLLECT_LISTARRAY<T>将多行值收集到数组中❌ 不去重✅ 保留输入顺序
COLLECT_SETARRAY<T>将多行值收集到集合中✅ 去重❌ 不保证顺序
MAP_AGGMAP<K,V>将多行键值对收集为Map✅ Key去重,最新值覆盖
MAP_FROM_ARRAYSMAP<K,V>从两个数组创建Map

1. COLLECT_LIST - 收集为数组(保留重复)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
-- 语法:COLLECT_LIST(expr)
-- 收集所有值,保留重复项,按照输入顺序排列

-- 示例:收集每个部门的员工姓名
SELECT 
  department,
  COLLECT_LIST(employee_name) AS all_employees,
  COUNT(*) AS employee_count
FROM employees
GROUP BY department;

-- 结果示例:
-- IT → ['张三', '李四', '张三', '王五']  -- 张三出现两次
-- HR → ['赵六', '钱七']

应用:

数据透视(行转列)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
-- 将多行数据转为宽表格式
SELECT 
  user_id,
  MAP_AGG(metric_name, metric_value) AS metrics_map
FROM user_metrics
WHERE dt = '2023-10-22'
GROUP BY user_id;

-- 展开为列
SELECT 
  user_id,
  metrics_map['pv'] AS page_views,
  metrics_map['uv'] AS unique_visitors,
  metrics_map['avg_duration'] AS avg_duration
FROM (
  SELECT 
    user_id,
    MAP_AGG(metric_name, metric_value) AS metrics_map
  FROM user_metrics
  WHERE dt = '2023-10-22'
  GROUP BY user_id
) t;
  • 收集用户行为序列

  • 生成特征数组用于机器学习

  • 数据透视(行转列)

2. COLLECT_SET - 收集为集合(去重)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
-- 语法:COLLECT_SET(expr)
-- 收集唯一值,不保证顺序

-- 示例:收集每个部门的唯一员工姓名
SELECT 
  department,
  COLLECT_SET(employee_name) AS unique_employees,
  SIZE(COLLECT_SET(employee_name)) AS unique_count
FROM employees
GROUP BY department;

-- 结果示例:
-- IT → ['张三', '李四', '王五']  -- 张三只出现一次
-- HR → ['赵六', '钱七']

注意COLLECT_SET不保证顺序,如需有序去重,可以这样:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
-- 有序去重:先排序,再去重
SELECT 
  department,
  COLLECT_LIST(employee_name) AS all_ordered,  -- 先收集有序列表
  ARRAY_DISTINCT(COLLECT_LIST(employee_name)) AS unique_ordered  -- 再去重
FROM (
  SELECT department, employee_name
  FROM employees
  DISTRIBUTE BY department
  SORT BY department, employee_name
) t
GROUP BY department;

3. MAP_AGG - 创建键值对Map

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
-- 语法:MAP_AGG(key_expr, value_expr)
-- 将多行键值对收集为Map,重复key会取最后一个value

-- 示例:收集员工ID到姓名的映射
SELECT 
  department,
  MAP_AGG(employee_id, employee_name) AS id_name_map
FROM employees
GROUP BY department;

-- 结果示例:
-- IT → {1001: '张三', 1002: '李四', 1003: '王五'}
-- HR → {2001: '赵六', 2002: '钱七'}

-- 提取Map中的值
SELECT 
  department,
  id_name_map[1001] AS employee_1001,  -- 获取key为1001的值
  MAP_KEYS(id_name_map) AS all_ids,   -- 获取所有key
  MAP_VALUES(id_name_map) AS all_names -- 获取所有value
FROM (
  SELECT department, MAP_AGG(employee_id, employee_name) AS id_name_map
  FROM employees
  GROUP BY department
) t;

4. MAP_FROM_ARRAYS - 从两个数组创建Map

从两个数组创建Map,要求两个数组长度相同

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
-- 语法:MAP_FROM_ARRAYS(keys_array, values_array)
-- 示例:从两列创建Map
SELECT 
  user_id,
  MAP_FROM_ARRAYS(
    COLLECT_LIST(behavior_type),  -- 行为类型数组
    COLLECT_LIST(behavior_count)   -- 行为次数数组
  ) AS behavior_map
FROM user_behavior
GROUP BY user_id;

-- 结果示例:
-- 用户1 → {'view': 10, 'click': 5, 'purchase': 2}
-- 用户2 → {'view': 15, 'click': 3}

2. HISTOGRAM_NUMERIC - 数值直方图

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
-- 语法:HISTOGRAM_NUMERIC(expr, nbuckets)
-- 生成数值分布的直方图,nbuckets指定分桶数量

-- 示例:分析工资分布(分10个桶)
SELECT 
  department,
  HISTOGRAM_NUMERIC(salary, 10) AS salary_histogram
FROM employees
GROUP BY department;

-- 结果示例:Map<桶的上界, 频数>
-- IT → {5000.0: 5, 10000.0: 12, 15000.0: 8, ...}
-- 表示:5000以下5人,5000-10000之间12人,以此类推

3. NGRAMS - N-gram分析

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
-- 语法:NGRAMS(array<T>, n, k)
-- 从数组中提取最常见的n-gram序列,返回前k个

-- 示例:分析搜索词的最常见二元组
SELECT 
  NGRAMS(SPLIT(search_query, ' '), 2, 5) AS top_bigrams
FROM search_logs
WHERE dt = '2023-10-22';

-- 结果示例:
-- [{'ngram': ['大数据', '学习'], 'estfrequency': 100},
--  {'ngram': ['Hive', '教程'], 'estfrequency': 85}]

💻 实战应用场景

场景1:用户行为序列分析

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
-- 收集用户按时间排序的行为序列
SELECT 
  user_id,
  COLLECT_LIST(
    STRUCT(
      event_time,
      event_type,
      page_id
    )
  ) AS behavior_sequence
FROM (
  SELECT 
    user_id,
    event_time,
    event_type,
    page_id
  FROM user_events
  WHERE dt = '2023-10-22'
  DISTRIBUTE BY user_id
  SORT BY user_id, event_time  -- 保证按用户和时间排序
) t
GROUP BY user_id
LIMIT 10;

-- 结果:每个用户的行为轨迹数组
-- 用于后续的序列分析、路径分析、用户画像

场景2:订单商品聚合

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
-- 将订单中的所有商品聚合
SELECT 
  order_id,
  COLLECT_LIST(
    NAMED_STRUCT(
      'product_id', product_id,
      'product_name', product_name,
      'quantity', quantity,
      'price', price
    )
  ) AS order_items,
  SUM(quantity * price) AS order_total,
  COUNT(DISTINCT product_id) AS distinct_products
FROM order_items
GROUP BY order_id
HAVING order_total > 1000
ORDER BY order_total DESC;

场景3:用户标签聚合

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
-- 将用户的多个标签聚合为Map
SELECT 
  user_id,
  MAP_AGG(tag_category, tag_value) AS user_tags
FROM user_tags
WHERE tag_weight > 0.5
GROUP BY user_id;

-- 结果示例:用户标签Map
-- 用户1 → {'兴趣': '科技', '年龄段': '25-30', '消费水平': '高'}
-- 用户2 → {'兴趣': '美食', '年龄段': '30-35', '城市': '成都'}

场景4:数据透视(行转列)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
-- 将多行数据转为宽表格式
SELECT 
  user_id,
  MAP_AGG(metric_name, metric_value) AS metrics_map
FROM user_metrics
WHERE dt = '2023-10-22'
GROUP BY user_id;

-- 展开为列
SELECT 
  user_id,
  metrics_map['pv'] AS page_views,
  metrics_map['uv'] AS unique_visitors,
  metrics_map['avg_duration'] AS avg_duration
FROM (
  SELECT 
    user_id,
    MAP_AGG(metric_name, metric_value) AS metrics_map
  FROM user_metrics
  WHERE dt = '2023-10-22'
  GROUP BY user_id
) t;

场景5:用户相似度计算(基于共同行为)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
-- 计算用户间的共同行为数量
WITH user_behavior_set AS (
  SELECT 
    user_id,
    COLLECT_SET(page_id) AS viewed_pages
  FROM page_views
  WHERE dt = '2023-10-22'
  GROUP BY user_id
)
SELECT 
  a.user_id AS user1,
  b.user_id AS user2,
  SIZE(ARRAY_INTERSECTION(a.viewed_pages, b.viewed_pages)) AS common_pages_count,
  -- 计算Jaccard相似度
  SIZE(ARRAY_INTERSECTION(a.viewed_pages, b.viewed_pages)) * 1.0 /
  SIZE(ARRAY_UNION(a.viewed_pages, b.viewed_pages)) AS jaccard_similarity
FROM user_behavior_set a
JOIN user_behavior_set b
WHERE a.user_id < b.user_id
  AND SIZE(ARRAY_INTERSECTION(a.viewed_pages, b.viewed_pages)) > 5
ORDER BY common_pages_count DESC
LIMIT 100;

collect_list vs collect_set

1
2
3
4
5
6
7
8
9
-- collect_list:保留重复值,保留顺序(不严格保证)
SELECT user_id, collect_list(product_id) AS bought_items
FROM orders
GROUP BY user_id;

-- collect_set:自动去重,无序
SELECT user_id, collect_set(product_id) AS unique_items
FROM orders
GROUP BY user_id;

配合 concat_ws 实现字符串拼接(经典面试题):

1
2
3
4
5
SELECT
user_id,
concat_ws(',', collect_list(cast(product_id AS string))) AS items_str
FROM orders
GROUP BY user_id;

六、高级分组聚合:GROUPING SETS / CUBE / ROLLUP

这是数仓多维分析的核心能力,相当于一次 SQL 跑出多种粒度的聚合结果。

示例数据

假设有订单表:orders(year, month, region, sales)

1. GROUPING SETS:指定哪些维度组合

1
2
3
4
5
6
7
8
9
SELECT year, month, region, sum(sales)
FROM orders
GROUP BY year, month, region
GROUPING SETS (
    (year, month, region), -- 最细粒度
    (year, month), -- 按年月
    (year), -- 按年
    () -- 总计
);

等价于 4 条 SQL 的 union all,但只扫一次表。

2. ROLLUP:层级递进(小计 + 总计)

1
2
3
SELECT year, month, region, sum(sales)
FROM orders
GROUP BY year, month, region WITH ROLLUP;

等价于:

1
2
3
4
5
6
GROUPING SETS (
  (year, month, region),
  (year, month),
  (year),
  ()
)

3. CUBE:所有维度组合

1
2
3
SELECT year, month, region, sum(sales)
FROM orders
GROUP BY year, month, region WITH CUBE;

等价于 2³ = 8 种组合:

1
2
(year,month,region), (year,month), (year,region), (month,region),
(year), (month), (region), ()

4. grouping__id 区分聚合层级

1
2
3
4
5
6
SELECT
year, month, region,
sum(sales) AS total,
grouping__id AS gid
FROM orders
GROUP BY year, month, region WITH CUBE;

grouping__id 可标识当前行是哪一个维度组合的小计,方便下游处理。

使用 Hugo 构建
主题 StackJimmy 设计
无法复制,本站文章内容受保护