拾穗数据

Back

数据与图表分析数据与图表分析

一道看似简单的SQL题,背后考察的是你对数据仓库分层设计的理解,特别是中间表的设计能力。

引子:那道让人”脑雾”的面试题#

最近,一位朋友分享了他在字节跳动面试大数据开发工程师的经历。面试官给了一道SQL题:

场景:抖音流量日志表visit_log,包含用户ID(uid)和访问时间戳(timestamp),按日期(date)分区。数据规模:日增百亿级记录。

需求

  1. 计算T日的用户7日留存率
  2. 统计每个用户近1/7/30/365天的访问天数
  3. 找出近7天内访问间隔在24小时内的用户

朋友说:“我当时就写了个JOIN,面试官问:‘你们生产环境真的会这么查询吗?没有中间表吗?’ 我就懵了…”

关键洞察:面试官真正想了解的是,你是否知道如何设计中间表来支撑业务查询,而不是每次都从原始日志计算。

一、为什么直接查询原始表是错误的?#

生产环境的残酷现实#

-- ❌ 没有人会在生产环境这么写
SELECT COUNT(DISTINCT a.uid)
FROM visit_log a
JOIN visit_log b ON a.uid = b.uid
WHERE a.date = 'T' AND b.date BETWEEN 'T+1' AND 'T+7'
sql

问题不仅是性能:

  • 原始日志表数据量巨大,查询成本极高
  • 每次查询都要重新计算,浪费资源
  • 无法支撑复杂的业务需求
  • 数据口径不统一,容易出错

数据仓库的核心理念:空间换时间#

层级表类型数据特点查询场景
ODS原始日志最细粒度,数据量大几乎不直接查询
DWD明细事实表清洗后的明细少量明细查询
DWS轻度汇总表用户粒度汇总常规分析查询
ADS应用层表高度聚合报表直接查询

二、核心解决方案:设计合理的中间表#

方案一:用户状态快照表(最常用)#

-- 每日创建用户状态快照表(T+1凌晨运行)
CREATE TABLE dws_user_daily_snapshot (
    uid BIGINT COMMENT '用户ID',
    date STRING COMMENT '快照日期',

    -- 核心状态字段
    first_visit_date STRING COMMENT '首次访问日期',
    last_visit_date STRING COMMENT '最后访问日期',
    total_visit_days INT COMMENT '累计访问天数',

    -- 近期活跃度指标(这些字段直接支撑业务查询)
    visits_last_1d INT COMMENT '近1天访问天数',
    visits_last_7d INT COMMENT '近7天访问天数',
    visits_last_30d INT COMMENT '近30天访问天数',
    visits_last_365d INT COMMENT '近365天访问天数',

    -- 访问间隔特征
    last_visit_gap_hours INT COMMENT '最近两次访问间隔小时数',
    is_frequent_user INT COMMENT '是否24小时内多次访问用户',

    -- 留存标记(预计算)
    is_new_user INT COMMENT '是否新用户',
    is_retained_1d INT COMMENT '是否次日留存',
    is_retained_7d INT COMMENT '是否7日留存',
    is_retained_30d INT COMMENT '是否30日留存'
)
PARTITIONED BY (date STRING)
STORED AS PARQUET;
sql

基于快照表回答面试问题变得极其简单:

-- 问题1:7日留存率(直接查询,秒级返回)
SELECT
    COUNT(CASE WHEN is_new_user = 1 THEN uid END) as new_users,
    COUNT(CASE WHEN is_retained_7d = 1 THEN uid END) as retained_users,
    COUNT(CASE WHEN is_retained_7d = 1 THEN uid END) * 100.0 /
    COUNT(CASE WHEN is_new_user = 1 THEN uid END) as retention_rate_7d
FROM dws_user_daily_snapshot
WHERE date = DATE_SUB(CURRENT_DATE, 7);  -- T+7查询T日留存

-- 问题2:用户访问天数(直接读取)
SELECT
    uid,
    visits_last_1d,
    visits_last_7d,
    visits_last_30d,
    visits_last_365d
FROM dws_user_daily_snapshot
WHERE date = CURRENT_DATE;

-- 问题3:24小时内访问用户(已预计算)
SELECT uid
FROM dws_user_daily_snapshot
WHERE date = CURRENT_DATE
  AND is_frequent_user = 1;
sql

三、中间表设计的最佳实践#

1. 分层设计原则#

-- ODS层:原始数据,保持原貌
CREATE TABLE ods_visit_log LIKE visit_log;

-- DWD层:清洗后的明细数据
CREATE TABLE dwd_visit_detail (
    uid BIGINT,
    visit_time TIMESTAMP,
    session_id STRING,
    -- 清洗:去重、过滤异常
) PARTITIONED BY (date STRING);

-- DWS层:轻度汇总表(用户粒度)
CREATE TABLE dws_user_daily_agg (
    uid BIGINT,
    date STRING,
    visit_count INT,
    total_duration INT
) PARTITIONED BY (date STRING);

-- ADS层:应用层指标表
CREATE TABLE ads_retention_metrics (
    date STRING,
    new_users INT,
    retained_1d INT,
    retained_7d INT,
    retention_rate_1d FLOAT,
    retention_rate_7d FLOAT
) PARTITIONED BY (date STRING);
sql

2. 更新策略选择#

更新策略适用场景优缺点
全量快照数据量小,状态变化频繁简单但存储成本高
增量快照只记录变化数据存储省但查询复杂
拉链表缓慢变化维度存储最优但维护复杂
累积快照需要历史所有状态查询方便但数据量大

四、面试官真正想看到什么?#

标准答案模板#

“对于这个问题,在生产环境中,我不会直接查询原始日志表,而是设计合理的中间表:

第一步:设计用户状态快照表

  • 每日生成用户快照,包含累计和近期指标
  • 预计算留存标记,查询时直接读取
  • 存储成本可控,查询性能好

第二步:考虑存储优化

  • 如果存储压力大,采用拉链表设计
  • 只记录状态变化,大幅减少存储
  • 适合用户状态这种缓慢变化维度

第三步:构建应用层宽表

  • 面向具体业务需求设计宽表
  • 一张表满足多个查询需求
  • 用空间换时间,提升查询效率

实际查询时,基于这些中间表,原本复杂的计算变成简单的SELECT,性能提升百倍以上。“

五、总结:面试脑雾自救指南#

三句话搞定面试#

当你脑子一片空白时,记住这三句话:

  1. “数据太大,不能直接查”(展示你懂规模)
  2. “生产环境用中间表”(展示你有经验)
  3. “我先写个简化版”(展示你会SQL)

最小可行代码模板(背下来!)#

-- 万能模板:能应付80%的场景
WITH temp AS (
    SELECT uid, date, [其他字段]
    FROM visit_log
    WHERE date = '条件'  -- 记得分区裁剪
    GROUP BY uid, date   -- 记得去重
)
SELECT
    COUNT(DISTINCT uid),
    其他聚合函数
FROM temp;
sql

最后的真心话#

面试不是考试,面试官不期待你写出完美代码。他们想看到的是:

  • 你遇到问题时的思考方式
  • 你是否了解生产环境的做法
  • 你能否清晰地沟通

记住:

  • 简单正确 > 复杂错误
  • 说出思路 > 闷头写代码
  • 承认不会 > 胡编乱造
  • 展示经验 > 炫技

当面试官看到你说:“这个数据量太大,生产环境一定要用中间表”,他就知道你是有实战经验的。这比写100行复杂SQL更有说服力。

面试是一场交流,不是考试。保持冷静,展示思维,你就已经成功了一半。

从字节跳动SQL面试题看数据思维:如何用中间表设计优雅解决亿级数据问题
https://blog.ss-data.cc/blog/bytedance-sql-interview-middleware-table
Author 石头
Published at 2025年8月12日
Comment seems to stuck. Try to refresh?✨