这是一道大数据 Hive 面试题,核心考察:自定义 UDTF 函数开发、日期处理、行列转换、会员有效周期统计。我把题目完整拆解、代码逐行注释、执行流程讲透,面试 / 工作直接复用。

一、业务需求

给定会员表:会员ID、会员生效日期、会员失效日期要求:统计每个月的有效会员数量(只要会员在当月任意一天有效,就计入当月统计)。

示例数据:

text

1,2021-01-01,2022-01-01
2,2021-02-02,2022-02-02
3,2021-03-03,2022-03-03

二、第一步:建表 + 导入数据

1. 创建会员表

hive

create table t_consumer(
  consumerid  string,  -- 会员ID
  startdate   string,  -- 生效日期
  enddate     string   -- 失效日期
) 
row format delimited 
fields terminated by ',';  -- 逗号分隔

2. 加载本地数据到表

hive

load data local inpath '/home/hivedata/consumer.txt' into table t_consumer;

3. 验证数据

hive

select * from t_consumer;

三、核心思路

  1. 一行变多行:把一个会员的生效-失效时间段,拆成每一个有效月份(用UDTF实现)
  2. 分组统计:按月份分组,count 会员 ID,得到每月有效会员数

四、方案 1:自定义 GenericUDTF(面试重点)

1. Maven 依赖

xml

<dependency>
    <groupId>org.apache.hive</groupId>
    <artifactId>hive-exec</artifactId>
    <version>3.1.2</version>
    <scope>provided</scope>
</dependency>

2. 日期工具类:生成有效月份列表

java

运行

package com.qyh;

import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.List;
import java.util.Locale;

public class DateUtils {

    // 格式化日期:Jan-2021 → Jan-21
    public static String getNewStr(String date){
        String[] arr = date.split("-");
        String yearSuffix = arr[1].substring(2); // 截取年份后两位
        return arr[0] + yearSuffix;
    }

    // 核心:根据开始、结束日期,生成所有有效月份
    public static List<String> getAllDate(String beginDate, String endDate) throws Exception {
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
        SimpleDateFormat sdfMonth = new SimpleDateFormat("MMM-yyyy", Locale.ENGLISH);
        
        Calendar startCal = Calendar.getInstance();
        startCal.setTime(sdf.parse(beginDate));
        
        Calendar endCal = Calendar.getInstance();
        endCal.setTime(sdf.parse(endDate));

        List<String> monthList = new ArrayList<>();

        // 循环:逐月增加,直到结束日期
        while (endCal.after(startCal) || endCal.equals(startCal)) {
            String monthStr = sdfMonth.format(startCal.getTime());
            monthList.add(getNewStr(monthStr));
            // 月份+1
            startCal.add(Calendar.MONTH, 1);
        }
        return monthList;
    }
}

3. 自定义 UDTF 函数:炸裂日期

java

运行

package com.qyh;

import org.apache.hadoop.hive.ql.exec.UDFArgumentException;
import org.apache.hadoop.hive.ql.metadata.HiveException;
import org.apache.hadoop.hive.ql.udf.generic.GenericUDTF;
import org.apache.hadoop.hive.serde2.objectinspector.ObjectInspector;
import org.apache.hadoop.hive.serde2.objectinspector.ObjectInspectorFactory;
import org.apache.hadoop.hive.serde2.objectinspector.StructObjectInspector;
import org.apache.hadoop.hive.serde2.objectinspector.primitive.PrimitiveObjectInspectorFactory;
import java.util.ArrayList;
import java.util.List;

public class DateExplodeUDTF extends GenericUDTF {

    // 初始化:定义输出列名、类型
    @Override
    public StructObjectInspector initialize(StructObjectInspector argOIs) throws UDFArgumentException {
        List<String> fieldNames = new ArrayList<>();
        List<ObjectInspector> fieldOIs = new ArrayList<>();
        
        // 输出列:月份
        fieldNames.add("month");
        fieldOIs.add(PrimitiveObjectInspectorFactory.javaStringObjectInspector);
        
        return ObjectInspectorFactory.getStandardStructObjectInspector(fieldNames, fieldOIs);
    }

    // 核心处理逻辑
    @Override
    public void process(Object[] objects) throws HiveException {
        // 获取入参:开始日期、结束日期
        String startDate = objects[0].toString();
        String endDate = objects[1].toString();

        try {
            // 获取所有有效月份
            List<String> monthList = DateUtils.getAllDate(startDate, endDate);
            // 逐行输出
            for (String month : monthList) {
                forward(new String[]{month});
            }
        } catch (Exception e) {
            throw new HiveException("日期处理失败", e);
        }
    }

    @Override
    public void close() throws HiveException {}
}

五、Hive 中使用自定义函数

1. 打包上传

IDEA 打包 jar → 上传到 Linux:/opt/installs/hive/lib/xxx.jar

2. Hive 客户端添加 jar + 创建临时函数

hive

-- 添加jar包
add jar /opt/installs/hive/lib/MyFunction-1.0-SNAPSHOT.jar;

-- 创建临时函数(类全名)
create temporary function date_explode as 'com.qyh.DateExplodeUDTF';

3. 统计 SQL(带排序)

hive

-- 统计每月有效会员
select 
  month, 
  count(consumerid) as consumer_num
from t_consumer
lateral view date_explode(startdate, enddate) tmp as month
group by month
-- 按时间正确排序(面试加分项)
order by 
  substr(month,4,2),
  case substr(month,0,3)
    when 'Jan' then 1
    when 'Feb' then 2
    when 'Mar' then 3
    when 'Apr' then 4
    when 'May' then 5
    when 'Jun' then 6
    when 'Jul' then 7
    when 'Aug' then 8
    when 'Sep' then 9
    when 'Oct' then 10
    when 'Nov' then 11
    when 'Dec' then 12
  end;

六、方案 2:纯 SQL 实现(无需写 Java,更简洁)

面试中如果要求不写 UDTF,用纯 Hive SQL 也能实现:

hive

WITH
-- 1. 生成连续月份维度表(2021-01 ~ 2022-03)
month_dim AS (
    SELECT
        add_months('2021-01-01', idx) AS month_first_day,
        date_format(add_months('2021-01-01', idx), 'MMM-yy') AS stat_month
    FROM (SELECT posexplode(split(repeat(',',14),',')) AS (idx,val)) t
),
-- 2. 会员日期标准化
consumer_info AS (
    SELECT
        consumerid,
        trunc(to_date(startdate),'MM') start_month,
        trunc(to_date(enddate),'MM') end_month
    FROM t_consumer
)
-- 3. 关联统计
SELECT
    stat_month,
    COUNT(consumerid) AS consumer_num
FROM month_dim m
LEFT JOIN consumer_info c
ON m.month_first_day BETWEEN c.start_month AND c.end_month
GROUP BY stat_month, month_first_day
ORDER BY month_first_day;

七、面试考点总结(必背)

  1. UDTF 是什么:一行输入,多行输出(炸裂函数)
  2. GenericUDTF 必须实现initialize() + process() + close()
  3. 业务判断逻辑:会员当月有效条件:会员开始日期 <= 月末 AND 会员结束日期 >= 月初
  4. 日期处理trunc截断月份、add_months月份加减
  5. 优化点:生成连续月份维度表,避免遗漏无会员的月份

八、预期输出结果

表格

stat_month consumer_num
Jan-21 1
Feb-21 2
Mar-21 3
... ...
Jan-22 1
Feb-22 1
Mar-22 1

总结

  1. 这道题两种解法:Java 自定义 UDTF(面试常考)、纯 SQL(简洁高效)
  2. 核心逻辑:把会员周期拆成月份 → 分组统计
  3. 面试重点:GenericUDTF 开发流程、日期处理、lateral view 使用
Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐