得物大数据面试SQL-用户潮流偏好标签
⚠️ 待修正
一、题目背景
这道题来自得物的数据分析岗面试。得物是潮流电商平台,数据分析师需要从海量业务数据中挖掘洞见。
业务场景:数据分析相关需求在日常工作中频繁出现,这类型的SQL题是面试高频考点。
一、题目
得物需要为每个用户打上潮流偏好标签,用于首页个性化推荐。根据用户的历史浏览、收藏和购买行为,将用户划分为不同的偏好群体。给定 user_behavior 表。
user_behavior 用户行为表:
| behavior_id | user_id | product_category | brand | action_type | action_time |
|---|---|---|---|---|---|
| B001 | U401 | 运动鞋 | Nike | purchase | 2025-01-05 10:00:00 |
| B002 | U401 | 运动鞋 | Air Jordan | favorite | 2025-01-06 14:30:00 |
| B003 | U401 | 运动鞋 | Adidas | view | 2025-01-08 09:15:00 |
| B004 | U402 | 服装 | Supreme | purchase | 2025-01-06 11:00:00 |
| B005 | U402 | 服装 | Off-White | purchase | 2025-01-10 16:20:00 |
| B006 | U402 | 服装 | Fear of God | view | 2025-01-12 10:30:00 |
| B007 | U403 | 配饰 | G-Shock | purchase | 2025-01-07 13:00:00 |
| B008 | U403 | 配饰 | Casio | favorite | 2025-01-09 15:45:00 |
| B009 | U404 | 运动鞋 | Nike | view | 2025-01-08 08:30:00 |
| B010 | U404 | 运动鞋 | Air Jordan | purchase | 2025-01-11 12:00:00 |
| B011 | U404 | 服装 | Supreme | view | 2025-01-12 14:15:00 |
| B012 | U405 | 运动鞋 | Yeezy | purchase | 2025-01-03 10:00:00 |
| B013 | U405 | 运动鞋 | Air Jordan | purchase | 2025-01-07 11:20:00 |
| B014 | U405 | 服装 | BAPE | purchase | 2025-01-09 09:00:00 |
| B015 | U406 | 配饰 | G-Shock | view | 2025-01-04 16:00:00 |
| B016 | U406 | 配饰 | DW | view | 2025-01-06 10:45:00 |
| B017 | U407 | 服装 | Supreme | favorite | 2025-01-10 11:30:00 |
| B018 | U407 | 运动鞋 | Nike | purchase | 2025-01-13 14:00:00 |
要求:
- 统计每个用户在各品类的行为次数(分 action_type),并按用户总行为次数加权
- 为用户打上偏好标签:
- 购买次数最多的品类为"核心偏好"
- 浏览次数最多的品类为"兴趣偏好"
- 如果某个品类既有购买又有收藏,标记为"重度偏好"
- 最终输出每个用户的偏好标签列表,如果多个标签取优先级最高的(重度偏好 > 核心偏好 > 兴趣偏好)
三、思路分析
本题是用户画像打标的综合性题目,核心考察多维度聚合 + 条件判断 + 窗口排名的组合运用。难点在于标签优先级逻辑和"重度偏好"的判断——需要同时检查 purchase 和 favorite 两种行为类型。
| 维度 | 评分 |
|---|---|
| 题目难度 | ⭐⭐⭐ |
| 题目清晰度 | ⭐⭐⭐ |
| 业务常见度 | ⭐⭐⭐⭐⭐ |
解题思路:
- 第一步:按
(user_id, product_category)聚合,分别统计 purchase 次数、favorite 次数、view 次数 - 第二步:使用
ROW_NUMBER()分别对每个用户的购买次数和浏览次数排名,找出排名第一的品类 - 第三步:判断是否存在"重度偏好":某个品类购买次数>0 且收藏次数>0
- 第四步:按优先级输出标签:重度偏好品类 > 核心偏好品类 > 兴趣偏好品类
四、逐步推导
步骤1:按用户和品类统计各行为次数
SELECT
user_id,
product_category,
SUM(CASE WHEN action_type = 'purchase' THEN 1 ELSE 0 END) AS purchase_cnt,
SUM(CASE WHEN action_type = 'favorite' THEN 1 ELSE 0 END) AS favorite_cnt,
SUM(CASE WHEN action_type = 'view' THEN 1 ELSE 0 END) AS view_cnt,
COUNT(*) AS total_actions
FROM user_behavior
GROUP BY user_id, product_category
ORDER BY user_id, total_actions DESC;
执行结果(部分):
| user_id | product_category | purchase_cnt | favorite_cnt | view_cnt | total_actions |
|---|---|---|---|---|---|
| U401 | 运动鞋 | 1 | 1 | 1 | 3 |
| U402 | 服装 | 2 | 0 | 1 | 3 |
| U403 | 配饰 | 1 | 1 | 0 | 2 |
| U404 | 运动鞋 | 1 | 0 | 1 | 2 |
| U404 | 服装 | 0 | 0 | 1 | 1 |
| U405 | 运动鞋 | 2 | 0 | 0 | 2 |
| U405 | 服装 | 1 | 0 | 0 | 1 |
| U406 | 配饰 | 0 | 0 | 2 | 2 |
| U407 | 运动鞋 | 1 | 0 | 0 | 1 |
| U407 | 服装 | 0 | 1 | 0 | 1 |
步骤2:为用户打上偏好标签
WITH category_stats AS (
SELECT
user_id,
product_category,
SUM(CASE WHEN action_type = 'purchase' THEN 1 ELSE 0 END) AS purchase_cnt,
SUM(CASE WHEN action_type = 'favorite' THEN 1 ELSE 0 END) AS favorite_cnt,
SUM(CASE WHEN action_type = 'view' THEN 1 ELSE 0 END) AS view_cnt
FROM user_behavior
GROUP BY user_id, product_category
),
ranked AS (
SELECT
user_id,
product_category,
purchase_cnt,
favorite_cnt,
view_cnt,
ROW_NUMBER() OVER (PARTITION BY user_id ORDER BY purchase_cnt DESC) AS purchase_rank,
ROW_NUMBER() OVER (PARTITION BY user_id ORDER BY view_cnt DESC) AS view_rank,
CASE WHEN purchase_cnt > 0 AND favorite_cnt > 0 THEN 1 ELSE 0 END AS is_heavy
FROM category_stats
)
SELECT
user_id,
-- 标签优先级:重度偏好 > 核心偏好 > 兴趣偏好
CASE
WHEN MAX(CASE WHEN is_heavy = 1 THEN 1 ELSE 0 END) = 1
THEN CONCAT('重度偏好:', MAX(CASE WHEN is_heavy = 1 THEN product_category END))
WHEN SUM(CASE WHEN purchase_rank = 1 THEN purchase_cnt ELSE 0 END) > 0
THEN CONCAT('核心偏好:', MAX(CASE WHEN purchase_rank = 1 THEN product_category END))
ELSE CONCAT('兴趣偏好:', MAX(CASE WHEN view_rank = 1 THEN product_category END))
END AS preference_tag,
SUM(purchase_cnt) AS total_purchases,
SUM(view_cnt) AS total_views
FROM ranked
GROUP BY user_id
ORDER BY user_id;
执行结果:
| user_id | preference_tag | total_purchases | total_views |
|---|---|---|---|
| U401 | 重度偏好:运动鞋 | 1 | 1 |
| U402 | 核心偏好:服装 | 2 | 1 |
| U403 | 重度偏好:配饰 | 1 | 0 |
| U404 | 核心偏好:运动鞋 | 1 | 2 |
| U405 | 核心偏好:运动鞋 | 3 | 0 |
| U406 | 兴趣偏好:配饰 | 0 | 2 |
| U407 | 核心偏好:运动鞋 | 1 | 0 |
五、常见坑点
坑1:NULL值的隐式处理 — 聚合函数跳过NULL,窗口函数不跳过。WHERE需显式过滤或用COALESCE替代。
坑2:数据类型不一致导致JOIN失效 — string vs int隐式转换可能触发全表扫描,性能骤降。
六、举一反三
-
增加时间维度对比:按天/周/月分组,观察指标的趋势和季节性波动
-
增加分组维度:按品类/地区/渠道拆解,发现差异化的业务洞察
-
设置预警阈值:在WHERE中加阈值判断,自动筛选异常数据
七、知识点总结
| 考点 | 说明 |
|---|---|
| ROW_NUMBER + 差值法 | 连续问题经典解法:日期-行号=分组标识,相同差值同一连续段 |
| SUM() OVER 累积求和 | 窗口聚合实现滚动累计,配合ORDER BY实现时间轴展开 |
| CASE WHEN 条件聚合 | 灵活实现分段统计、条件计数、标签化处理 |
| GROUP BY + 聚合函数 | 分组聚合是数据分析的基础,配合HAVING筛选分组结果 |
八、建表语句和数据插入
点击展开 DDL & DML
CREATE TABLE user_behavior (
behavior_id VARCHAR(10) PRIMARY KEY,
user_id VARCHAR(10),
product_category VARCHAR(20),
brand VARCHAR(30),
action_type VARCHAR(20),
action_time DATETIME
);
INSERT INTO user_behavior VALUES
('B001', 'U401', '运动鞋', 'Nike', 'purchase', '2025-01-05 10:00:00'),
('B002', 'U401', '运动鞋', 'Air Jordan', 'favorite', '2025-01-06 14:30:00'),
('B003', 'U401', '运动鞋', 'Adidas', 'view', '2025-01-08 09:15:00'),
('B004', 'U402', '服装', 'Supreme', 'purchase', '2025-01-06 11:00:00'),
('B005', 'U402', '服装', 'Off-White', 'purchase', '2025-01-10 16:20:00'),
('B006', 'U402', '服装', 'Fear of God', 'view', '2025-01-12 10:30:00'),
('B007', 'U403', '配饰', 'G-Shock', 'purchase', '2025-01-07 13:00:00'),
('B008', 'U403', '配饰', 'Casio', 'favorite', '2025-01-09 15:45:00'),
('B009', 'U404', '运动鞋', 'Nike', 'view', '2025-01-08 08:30:00'),
('B010', 'U404', '运动鞋', 'Air Jordan', 'purchase', '2025-01-11 12:00:00'),
('B011', 'U404', '服装', 'Supreme', 'view', '2025-01-12 14:15:00'),
('B012', 'U405', '运动鞋', 'Yeezy', 'purchase', '2025-01-03 10:00:00'),
('B013', 'U405', '运动鞋', 'Air Jordan', 'purchase', '2025-01-07 11:20:00'),
('B014', 'U405', '服装', 'BAPE', 'purchase', '2025-01-09 09:00:00'),
('B015', 'U406', '配饰', 'G-Shock', 'view', '2025-01-04 16:00:00'),
('B016', 'U406', '配饰', 'DW', 'view', '2025-01-06 10:45:00'),
('B017', 'U407', '服装', 'Supreme', 'favorite', '2025-01-10 11:30:00'),
('B018', 'U407', '运动鞋', 'Nike', 'purchase', '2025-01-13 14:00:00');
📱关注公众号
「数据仓库技术」文章同步更新,不错过每一篇干货

💬加群交流
备注「数据仓库技术」加入社群,每日一道大厂SQL真题
