Hive专题:数据开发面试高频题(TopN、留存、连续登录等)

Hive 专题:数据开发面试高频题(TopN、留存、连续登录等)

本文精选 Hive SQL 面试中最高频的 8 类题型,每道题提供 业务场景核心思路完整 SQL 示例关键点解析。所有代码均基于 Hive 窗口函数、日期函数、条件聚合等特性编写,可直接运行。


📑 目录

  1. [分组 TopN 问题](#分组 TopN 问题)
    • 1.1 [每个部门薪资最高的 3 名员工](#每个部门薪资最高的 3 名员工)
    • 1.2 [每个品类销量前 2 的商品(并列处理)](#每个品类销量前 2 的商品(并列处理))
  2. 用户留存率计算
  3. [连续登录 / 活跃天数问题](#连续登录 / 活跃天数问题)
    • 3.1 用户最大连续登录天数
    • 3.2 [连续 3 天登录的用户](#连续 3 天登录的用户)
    • 3.3 [连续登录超过 N 天的用户及起止日期](#连续登录超过 N 天的用户及起止日期)
  4. 用户行为序列与漏斗分析
  5. 行列转换
  6. 拉链表设计与回滚查询
  7. UV、PV、新增等常见指标

1. 分组 TopN 问题

1.1 每个部门薪资最高的 3 名员工

业务场景:查询每个部门中薪资排名前三的员工,若薪资相同则按工号排序。

核心思路 :使用窗口函数 ROW_NUMBER()(无并列)、RANK()(并列跳跃)或 DENSE_RANK()(并列不跳跃)。

SQL 示例

sql 复制代码
-- 示例表结构
CREATE TABLE emp (
    emp_id INT,
    name STRING,
    dept STRING,
    salary DECIMAL(10,2)
);

-- 查询每个部门薪资最高的3人(无并列,若薪资相同按emp_id升序)
SELECT dept, name, salary, rn
FROM (
    SELECT dept, name, salary,
           ROW_NUMBER() OVER (PARTITION BY dept ORDER BY salary DESC, emp_id) AS rn
    FROM emp
) t
WHERE rn <= 3;

关键点解析

  • PARTITION BY dept:按部门分组。
  • ORDER BY salary DESC, emp_id:薪资降序,相同薪资则按工号升序,保证唯一顺序。
  • 若要求并列处理(如两个第一,则跳过第二名),使用 RANK();若并列不跳过,使用 DENSE_RANK()

1.2 每个品类销量前 2 的商品(并列处理)

业务场景:电商平台,每个商品品类下按销量排序,销量相同时均入选(可能出现超过 2 条)。

SQL 示例

sql 复制代码
SELECT category, product_name, sales
FROM (
    SELECT category, product_name, sales,
           DENSE_RANK() OVER (PARTITION BY category ORDER BY sales DESC) AS dr
    FROM product_sales
) t
WHERE dr <= 2;

关键点解析DENSE_RANK() 在并列时不产生名次跳跃,保证销量相同的所有商品都能进入前 2。


2. 用户留存率计算

2.1 次日 / 7 日 / 30 日留存率

业务场景:计算某日新增用户在次日、第 7 日、第 30 日仍活跃的比例。

核心思路

  • 定义"新增用户":首次登录日期(或指定注册日期)。
  • 定义"活跃":当天有登录行为(记录在 user_active 表中,每用户每天一条)。
  • 通过左连接计算留存。

表结构假设user_active(user_id, dt),已去重。

SQL 示例(计算 2026-04-01 新增用户的后续留存):

sql 复制代码
WITH new_users AS (
    SELECT user_id, MIN(dt) AS first_dt
    FROM user_active
    GROUP BY user_id
    HAVING MIN(dt) = '2026-04-01'
),
retention AS (
    SELECT 
        n.first_dt,
        COUNT(DISTINCT n.user_id) AS new_cnt,
        COUNT(DISTINCT CASE WHEN a1.dt = DATE_ADD(n.first_dt, 1) THEN n.user_id END) AS day1_ret,
        COUNT(DISTINCT CASE WHEN a7.dt = DATE_ADD(n.first_dt, 7) THEN n.user_id END) AS day7_ret,
        COUNT(DISTINCT CASE WHEN a30.dt = DATE_ADD(n.first_dt, 30) THEN n.user_id END) AS day30_ret
    FROM new_users n
    LEFT JOIN user_active a1 ON n.user_id = a1.user_id AND a1.dt = DATE_ADD(n.first_dt, 1)
    LEFT JOIN user_active a7 ON n.user_id = a7.user_id AND a7.dt = DATE_ADD(n.first_dt, 7)
    LEFT JOIN user_active a30 ON n.user_id = a30.user_id AND a30.dt = DATE_ADD(n.first_dt, 30)
    GROUP BY n.first_dt
)
SELECT first_dt, new_cnt,
       day1_ret / new_cnt AS day1_retention_rate,
       day7_ret / new_cnt AS day7_retention_rate,
       day30_ret / new_cnt AS day30_retention_rate
FROM retention;

关键点解析

  • DATE_ADD(n.first_dt, 1) 计算次日日期。
  • 使用 LEFT JOIN 避免因次日无活跃而丢失新增用户基数。
  • COUNT(DISTINCT CASE WHEN ... THEN user_id END) 是 Hive 中常用的条件计数写法。

2.2 多日连续留存(留存矩阵)

业务场景:批量计算某段时间内每天新增用户的次日、3 日、7 日留存,生成留存矩阵报表。

SQL 示例

sql 复制代码
WITH new_users AS (
    SELECT user_id, MIN(dt) AS first_dt
    FROM user_active
    GROUP BY user_id
    HAVING first_dt BETWEEN '2026-04-01' AND '2026-04-07'
),
active_ret AS (
    SELECT 
        n.first_dt,
        DATEDIFF(a.dt, n.first_dt) AS day_gap,
        COUNT(DISTINCT n.user_id) AS ret_cnt
    FROM new_users n
    JOIN user_active a ON n.user_id = a.user_id
    WHERE a.dt BETWEEN n.first_dt AND DATE_ADD(n.first_dt, 30)
    GROUP BY n.first_dt, DATEDIFF(a.dt, n.first_dt)
)
SELECT first_dt,
       MAX(CASE WHEN day_gap=1 THEN ret_cnt ELSE 0 END) / MAX(CASE WHEN day_gap=0 THEN ret_cnt ELSE 0 END) AS day1_rate,
       MAX(CASE WHEN day_gap=3 THEN ret_cnt ELSE 0 END) / MAX(CASE WHEN day_gap=0 THEN ret_cnt ELSE 0 END) AS day3_rate,
       MAX(CASE WHEN day_gap=7 THEN ret_cnt ELSE 0 END) / MAX(CASE WHEN day_gap=0 THEN ret_cnt ELSE 0 END) AS day7_rate
FROM active_ret
GROUP BY first_dt
ORDER BY first_dt;

关键点解析DATEDIFF(a.dt, n.first_dt) 得到距离首次登录的天数,通过 CASE WHEN 将不同间隔的留存人数转为列。


3. 连续登录 / 活跃天数问题

3.1 用户最大连续登录天数

业务场景:计算每个用户历史上最长的连续登录天数。

核心思路(日期减排名技巧):

  • 对每个用户按日期排序并编号 rn
  • 若日期连续,则 dt - rn 为常量。
  • 按用户和 dt - rn 分组,统计个数即为连续天数。

SQL 示例

sql 复制代码
WITH user_dates AS (
    SELECT DISTINCT user_id, dt FROM user_active
),
ranked AS (
    SELECT user_id, dt,
           ROW_NUMBER() OVER (PARTITION BY user_id ORDER BY dt) AS rn
    FROM user_dates
)
SELECT user_id, MAX(continuous_days) AS max_continuous_days
FROM (
    SELECT user_id, COUNT(*) AS continuous_days
    FROM ranked
    GROUP BY user_id, DATE_SUB(dt, rn)
) t
GROUP BY user_id;

关键点解析

  • DATE_SUB(dt, rn):将日期减去行号,连续登录的这组值相同。
  • 先按 (user_id, dt) 去重,避免同一天多条记录。

3.2 连续 3 天登录的用户

业务场景:找出至少连续 3 天登录的用户。

SQL 示例(基于同一技巧):

sql 复制代码
WITH user_dates AS (
    SELECT DISTINCT user_id, dt FROM user_active
),
ranked AS (
    SELECT user_id, dt,
           ROW_NUMBER() OVER (PARTITION BY user_id ORDER BY dt) AS rn
    FROM user_dates
)
SELECT DISTINCT user_id
FROM (
    SELECT user_id, DATE_SUB(dt, rn) AS group_flag
    FROM ranked
) t
GROUP BY user_id, group_flag
HAVING COUNT(*) >= 3;

3.3 连续登录超过 N 天的用户及起止日期

业务场景:不仅找出用户,还要显示每次连续登录的起始和结束日期。

SQL 示例

sql 复制代码
WITH user_dates AS (
    SELECT DISTINCT user_id, dt FROM user_active
),
ranked AS (
    SELECT user_id, dt,
           ROW_NUMBER() OVER (PARTITION BY user_id ORDER BY dt) AS rn
    FROM user_dates
),
grouped AS (
    SELECT user_id, DATE_SUB(dt, rn) AS group_flag,
           MIN(dt) AS start_date, MAX(dt) AS end_date, COUNT(*) AS days
    FROM ranked
    GROUP BY user_id, DATE_SUB(dt, rn)
)
SELECT user_id, start_date, end_date, days
FROM grouped
WHERE days >= 3
ORDER BY user_id, start_date;

4. 用户行为序列与漏斗分析

4.1 页面访问路径(Clickstream)

业务场景:统计用户从首页到商品详情页的路径,计算各步骤转化。

SQL 示例(获取每个用户的页面访问序列):

sql 复制代码
WITH user_actions AS (
    SELECT user_id, page, action_time,
           ROW_NUMBER() OVER (PARTITION BY user_id ORDER BY action_time) AS seq
    FROM clickstream
)
SELECT user_id,
       COLLECT_LIST(page) AS page_path
FROM user_actions
GROUP BY user_id;

4.2 转化漏斗(浏览→加购→支付)

业务场景:统计某日所有用户中,完成浏览商品、加入购物车、支付三个步骤的人数及转化率。

SQL 示例

sql 复制代码
WITH user_funnel AS (
    SELECT user_id,
           MAX(CASE WHEN action = 'view' THEN 1 ELSE 0 END) AS has_view,
           MAX(CASE WHEN action = 'cart' THEN 1 ELSE 0 END) AS has_cart,
           MAX(CASE WHEN action = 'pay' THEN 1 ELSE 0 END) AS has_pay
    FROM user_behavior
    WHERE dt = '2026-04-01'
    GROUP BY user_id
)
SELECT 
    COUNT(user_id) AS total_users,
    SUM(has_view) AS view_cnt,
    SUM(has_cart) AS cart_cnt,
    SUM(has_pay) AS pay_cnt,
    SUM(has_cart) / SUM(has_view) AS view_to_cart_rate,
    SUM(has_pay) / SUM(has_cart) AS cart_to_pay_rate
FROM user_funnel;

关键点解析MAX(CASE WHEN ... THEN 1 ELSE 0 END) 可判断用户是否发生过该行为。


5. 行列转换

5.1 行转列(多行合并为一行)

业务场景:将每个用户的多个标签合并为一个逗号分隔的字符串。

SQL 示例

sql 复制代码
SELECT user_id,
       CONCAT_WS(',', COLLECT_SET(tag)) AS tags_str
FROM user_tags
GROUP BY user_id;

关键点解析COLLECT_SET 去重,COLLECT_LIST 保留重复;CONCAT_WS 指定分隔符拼接。

5.2 列转行(一行拆成多行)

业务场景:将逗号分隔的标签字符串拆分为多行。

SQL 示例

sql 复制代码
SELECT user_id, tag
FROM (
    SELECT user_id, SPLIT(tags_str, ',') AS tags_array
    FROM user_tags_table
) t
LATERAL VIEW EXPLODE(tags_array) tmp AS tag;

6. 拉链表设计与回滚查询

6.1 拉链表的每日增量更新

业务场景:用户维度表(如会员等级)缓慢变化,需要保留历史状态。

拉链表结构

sql 复制代码
CREATE TABLE dim_user (
    user_id BIGINT,
    level STRING,
    start_date DATE,
    end_date DATE,
    is_current STRING   -- 'Y' 或 'N'
) STORED AS ORC;

每日增量更新 SQL(合并变化数据):

sql 复制代码
-- 假设 ods_user 为今日全量用户数据
INSERT OVERWRITE TABLE dim_user
SELECT * FROM dim_user WHERE is_current = 'N'   -- 保留已关闭的历史
UNION ALL
-- 新增/变化的用户(取今日最新数据作为当前有效)
SELECT 
    user_id, level, 
    CURRENT_DATE AS start_date, 
    '9999-12-31' AS end_date, 
    'Y' AS is_current
FROM ods_user
UNION ALL
-- 关闭那些等级发生变化的旧记录
SELECT 
    old.user_id, old.level, old.start_date, 
    DATE_SUB(CURRENT_DATE, 1) AS end_date, 
    'N' AS is_current
FROM dim_user old
JOIN ods_user new ON old.user_id = new.user_id
WHERE old.is_current = 'Y' AND old.level != new.level;

6.2 查询指定时间点的维度值

业务场景:查询 2026-03-15 当天的用户等级。

SQL 示例

sql 复制代码
SELECT user_id, level
FROM dim_user
WHERE start_date <= '2026-03-15' AND end_date >= '2026-03-15';

7. UV、PV、新增等常见指标

7.1 每日新增用户数

业务场景:统计每天首次登录的用户数。

SQL 示例

sql 复制代码
WITH first_login AS (
    SELECT user_id, MIN(dt) AS first_dt
    FROM user_active
    GROUP BY user_id
)
SELECT first_dt, COUNT(user_id) AS new_users
FROM first_login
GROUP BY first_dt
ORDER BY first_dt;

7.2 每日活跃用户数(DAU)及周同比

SQL 示例

sql 复制代码
SELECT dt, COUNT(DISTINCT user_id) AS dau
FROM user_active
WHERE dt >= DATE_SUB(CURRENT_DATE, 30)
GROUP BY dt
ORDER BY dt;

以上题目覆盖了 Hive SQL 面试中 80% 的高频考点。建议读者在理解思路后,结合实际数据表进行练习,并熟练使用 EXPLAIN 优化执行计划。

相关推荐
studyForMokey1 小时前
【Android面试】设计模式专题
android·设计模式·面试
Bigger2 小时前
面试官问我:“AI 写代码比你快 100 倍,你的价值在哪?”
前端·面试·ai编程
.NET修仙日记2 小时前
2026 .NET 面试八股文:高频题 + 答案 + 原理(进阶核心篇)
面试·职场和发展·c#·.net·.net core·微软技术·webapi
木心术14 小时前
大数据处理技术:Hadoop与Spark核心原理解析
大数据·hadoop·分布式·spark
ShineWinsu13 小时前
对于Linux:Ext系列文件系统的解析—下
linux·面试·笔试·文件系统··ext2·挂载分区
张元清17 小时前
Pareto 动态路由实战:[slug]、catch-all、嵌套布局
前端·javascript·面试
programhelp_18 小时前
Snowflake OA 2026 面经|3道高频真题拆解 + 速通攻略
经验分享·算法·面试·职场和发展
海寻山19 小时前
Java枚举(Enum):基础语法+高级用法+实战场景+面试避坑
java·开发语言·面试