SQL 面试题(数据分析师)

共 50 题,按业务领域分类:电商(14)、游戏(12)、社交(12)、供应链(12)。

知识点覆盖:多表关联、窗口函数、条件筛选、常用指标、常见业务场景。

语法以 MySQL 8.0 为准(含窗口函数),个别题目附方言说明。

每题含:题目、参考答案、考点解析。题干中埋有边界/陷阱问题,不会明确点出,请在答题时自行识别。


第一章 电商(14 题)

电商-01 支付转化率

题目 :表 orders 字段 order_iduser_idcreate_timepay_timeamount。请统计最近 7 天每天的支付转化率。

参考答案

复制代码
SELECT
    DATE(create_time) AS dt,
    SUM(pay_time IS NOT NULL) AS paid_orders,
    COUNT(*) AS total_orders,
    ROUND(SUM(pay_time IS NOT NULL) / COUNT(*), 4) AS pay_rate
FROM orders
WHERE create_time >= CURDATE() - INTERVAL 7 DAY
GROUP BY DATE(create_time)
ORDER BY dt;

考点解析 :支付转化率 = 支付订单数 / 下单订单数,create_time 为下单时间、pay_time 为支付时间(未支付为 NULL)。条件聚合 SUM(条件) 等价于 COUNT(CASE WHEN ... THEN 1 END)。陷阱:① 分母是"下单数"而非"支付数";② "最近 7 天"若包含今天,当天未结束会拉低转化率,追问可答"改用 T-1 或滚动 24 小时口径"。


电商-02 每个用户的首单渠道

题目 :表 orders 字段 order_iduser_idchannelcreate_time。请统计各渠道的首单用户数。

参考答案

复制代码
WITH first_order AS (
    SELECT
        user_id,
        channel,
        ROW_NUMBER() OVER (PARTITION BY user_id ORDER BY create_time ASC, order_id ASC) AS rn
    FROM orders
)
SELECT channel, COUNT(*) AS first_order_users
FROM first_order
WHERE rn = 1
GROUP BY channel
ORDER BY first_order_users DESC;

考点解析channel 为下单渠道,"首单渠道" = 用户第一笔订单所在渠道。窗口函数取每组第一行。陷阱:① 同一用户同一秒有多笔订单时 create_time 相同,必须加第二排序键 order_id 保证结果稳定;② "首单渠道"是用户维度去重后归因,不能直接按订单数统计。


电商-03 有效 GMV

题目 :表 orders 字段 order_idamountstatuscreate_time,其中 status 取值 paid/refunded/cancelled。请按天统计有效 GMV。

参考答案

复制代码
SELECT
    DATE(create_time) AS dt,
    SUM(CASE WHEN status = 'paid' THEN amount ELSE 0 END) AS valid_gmv
FROM orders
GROUP BY DATE(create_time)
ORDER BY dt;

考点解析 :有效 GMV = 已支付且未退款。陷阱:① refundedcancelled 都不能计入 GMV,很多人只排除 cancelled;② 若存在"部分退款",真实场景应引入退款流水表 refund,GMV = 订单金额 - 退款金额,纯 status 字段无法精确表达,面试可主动点出这个局限。


电商-04 次月复购率

题目 :表 orders 字段 user_idcreate_time。请统计每个月新用户的次月复购率。

参考答案

复制代码
WITH first_month AS (
    SELECT user_id, MIN(DATE_FORMAT(create_time, '%Y-%m-01')) AS first_month
    FROM orders
    GROUP BY user_id
),
repurchase AS (
    SELECT DISTINCT f.first_month,
        (SELECT COUNT(DISTINCT o.user_id)
         FROM orders o
         WHERE o.user_id = f.user_id
           AND o.create_time >= DATE_ADD(f.first_month, INTERVAL 1 MONTH)
           AND o.create_time < DATE_ADD(f.first_month, INTERVAL 2 MONTH)) AS is_repurchase
    FROM first_month f
)
SELECT first_month,
       COUNT(*) AS new_users,
       SUM(is_repurchase > 0) AS repurchase_users,
       ROUND(SUM(is_repurchase > 0) / COUNT(*), 4) AS repurchase_rate
FROM repurchase
GROUP BY first_month
ORDER BY first_month;

考点解析 :次月复购率 = 当月首次下单的用户中、次月再次下单的占比。留存/复购类经典题。陷阱:① 分子是"次月有复购的用户数",要 DISTINCT 去重,同一用户次月多笔订单只能算一次;② 首购月用月初对齐,判断次月需同时限定上下界,避免把更晚月份也算进来。


电商-05 各品类客单价最高的商品

题目 :表 order_items 字段 order_idproduct_idquantityprice;表 products 字段 product_idproduct_namecategory。请找出每个品类中销售额最高的商品。

参考答案

复制代码
WITH sales AS (
    SELECT
        p.product_id,
        p.product_name,
        p.category,
        SUM(oi.quantity * oi.price) AS total_sales
    FROM order_items oi
    JOIN products p ON oi.product_id = p.product_id
    GROUP BY p.product_id, p.product_name, p.category
),
ranked AS (
    SELECT
        product_id, product_name, category, total_sales,
        ROW_NUMBER() OVER (PARTITION BY category ORDER BY total_sales DESC) AS rn
    FROM sales
)
SELECT product_id, product_name, category, total_sales
FROM ranked
WHERE rn = 1;

考点解析 :分组 TopN 窗口函数。陷阱:① 销售额 = 单价 × 数量,不能只 SUM(price);② 并列销售额时 ROW_NUMBER 只取一条,若要求"并列都取"应改用 RANKWHERE rank = 1,面试可追问这个差异。


电商-06 连续下单超过 3 天的用户

题目 :表 orders 字段 user_idcreate_time。请找出连续下单超过 3 天的用户。

参考答案

复制代码
WITH daily AS (
    SELECT DISTINCT user_id, DATE(create_time) AS dt
    FROM orders
),
grp AS (
    SELECT user_id, dt,
        ROW_NUMBER() OVER (PARTITION BY user_id ORDER BY dt) AS rn,
        DATE_SUB(dt, INTERVAL ROW_NUMBER() OVER (PARTITION BY user_id ORDER BY dt) DAY) AS grp_key
    FROM daily
),
consec AS (
    SELECT user_id, grp_key, COUNT(*) AS days
    FROM grp
    GROUP BY user_id, grp_key
)
SELECT DISTINCT user_id
FROM consec
WHERE days > 3;

考点解析 :连续下单按自然日计。经典"日期减序号"技巧。陷阱:① 必须先把同一用户同一天的多笔订单去重成"一天一行",否则连续天数被重复计算;② 需求是"超过 3 天"即 > 3 而非 >= 3


电商-07 加购 24 小时内未支付比例

题目 :表 cart_events 字段 user_idproduct_idevent_time;表 orders 字段 order_iduser_idcreate_timepay_time。请统计"加购后 24 小时内完成支付"的加购行为占比。

参考答案

复制代码
SELECT
    ROUND(
        SUM(CASE WHEN EXISTS (
            SELECT 1 FROM orders o
            WHERE o.user_id = c.user_id
              AND o.pay_time BETWEEN c.event_time AND DATE_ADD(c.event_time, INTERVAL 24 HOUR)
        ) THEN 1 ELSE 0 END) / COUNT(*), 4
    ) AS pay_within_24h_rate
FROM cart_events c;

考点解析cart_events 每加购一条。行为转化口径。陷阱:① 一次加购可能对应多笔订单、多笔加购可能归到同一订单,用 EXISTS 判定"该加购行为之后 24h 内有支付"是最稳的口径;② 是否要求"同一商品"要提前约定,本答案按"用户维度"判定。


电商-08 RFM 用户分层

题目 :表 orders 字段 user_idamountcreate_time,当前时间为 2026-08-20。请按 RFM 模型为每个用户计算 R、F、M 三个指标值。

参考答案

复制代码
SELECT
    user_id,
    DATEDIFF('2026-08-20', MAX(create_time)) AS recency_days,
    COUNT(*) AS frequency,
    SUM(amount) AS monetary
FROM orders
GROUP BY user_id;

考点解析 :R = 最近一次下单距今天数、F = 下单次数、M = 累计金额。陷阱:① "最近一次"用 MAX(create_time);② 真实业务需先剔除退款/取消订单再算 F/M;③ 打分通常按分位数切 5 档,面试可追问分箱逻辑与边界处理。


电商-09 活动 ROI

题目 :表 orders 字段 order_idactivity_idamountcreate_time(活动订单带 activity_id,自然订单为 NULL);表 activities 字段 activity_idcoststart_timeend_time。请计算每个活动的 ROI。

参考答案

复制代码
SELECT
    a.activity_id,
    a.cost,
    COALESCE(SUM(o.amount), 0) AS gmv,
    ROUND(COALESCE(SUM(o.amount), 0) / a.cost, 4) AS roi
FROM activities a
LEFT JOIN orders o
    ON o.activity_id = a.activity_id
   AND o.create_time BETWEEN a.start_time AND a.end_time
GROUP BY a.activity_id, a.cost;

考点解析 :ROI = 活动 GMV / 活动成本,cost 为活动成本。多表关联 + 口径。陷阱:① 用 LEFT JOIN 保留无订单的活动,否则 ROI 为 0 的活动会消失;② 订单必须落在活动时间窗口内,否则跨期订单污染 ROI;③ cost 为 0 时 ROI 除零报错,需 CASE 兜底。


电商-10 订单金额对账

题目 :表 orders 字段 order_iduser_idamount;表 order_items 字段 order_idproduct_idquantityprice。请找出明细金额之和与订单总金额不一致的异常订单。

参考答案

复制代码
SELECT o.order_id, o.amount, SUM(oi.quantity * oi.price) AS detail_amount
FROM orders o
JOIN order_items oi ON o.order_id = oi.order_id
GROUP BY o.order_id, o.amount
HAVING o.amount <> SUM(oi.quantity * oi.price);

考点解析 :HAVING 对聚合结果过滤。陷阱:① 过滤聚合结果必须用 HAVING 而非 WHERE;② 金额用浮点存储时 <> 可能因精度误判,生产建议 ABS(o.amount - detail) > 0.01 容差比较。


电商-11 流失用户画像

题目 :表 orders 字段 user_idcreate_time。定义"流失用户"为近 30 天(截至 2026-08-20)无下单、但历史上曾下过单的用户。请统计流失用户"最后一次下单距今天数"的分布(按月分组)。

参考答案

复制代码
WITH last_order AS (
    SELECT user_id, MAX(create_time) AS last_time
    FROM orders
    GROUP BY user_id
)
SELECT
    TIMESTAMPDIFF(MONTH, last_time, '2026-08-20') AS months_since_last,
    COUNT(*) AS lost_users
FROM last_order
WHERE last_time < DATE_SUB('2026-08-20', INTERVAL 30 DAY)
GROUP BY months_since_last
ORDER BY months_since_last;

考点解析 :流失定义与分布统计。陷阱:① 流失条件是"30 天前有最后下单",用 last_time 判断,而非某笔订单;② 时间跨度用 TIMESTAMPDIFF(MONTH, ...) 做月粒度分桶,注意其"满整月"语义与自然月的差异。


电商-12 GMV 环比与同比

题目 :表 orders 字段 amountstatuscreate_time。请按天输出有效 GMV,并附带环比前一天和同比上周同一天的增长率。

参考答案

复制代码
WITH daily AS (
    SELECT DATE(create_time) AS dt, SUM(amount) AS gmv
    FROM orders
    WHERE status = 'paid'
    GROUP BY DATE(create_time)
)
SELECT
    dt,
    gmv,
    ROUND((gmv - LAG(gmv) OVER (ORDER BY dt)) / LAG(gmv) OVER (ORDER BY dt), 4) AS dod,
    ROUND((gmv - LAG(gmv, 7) OVER (ORDER BY dt)) / LAG(gmv, 7) OVER (ORDER BY dt), 4) AS wow
FROM daily
ORDER BY dt;

考点解析 :有效 GMV 仅统计 status = 'paid' 的订单,同比上周 = 前第 7 天。LAG 窗口函数做环比同比。陷阱:① 分母为 0 或上一行 NULL 时返回 NULL,需处理;② "同比上周"是 LAG(gmv, 7),前提是每天都有数据,缺天会错位------面试可追问如何补零保证对齐。


电商-13 新老客 GMV 占比

题目 :表 orders 字段 user_idamountcreate_time。请按月统计新客 GMV 与老客 GMV 及其占比。

参考答案

复制代码
WITH first_month AS (
    SELECT user_id, MIN(DATE_FORMAT(create_time, '%Y-%m-01')) AS first_m
    FROM orders
    GROUP BY user_id
),
monthly AS (
    SELECT
        DATE_FORMAT(o.create_time, '%Y-%m-01') AS m,
        SUM(o.amount) AS total_gmv,
        SUM(CASE WHEN f.first_m = DATE_FORMAT(o.create_time, '%Y-%m-01') THEN o.amount ELSE 0 END) AS new_gmv
    FROM orders o
    JOIN first_month f ON o.user_id = f.user_id
    GROUP BY DATE_FORMAT(o.create_time, '%Y-%m-01')
)
SELECT m,
       new_gmv,
       total_gmv - new_gmv AS old_gmv,
       ROUND(new_gmv / total_gmv, 4) AS new_ratio
FROM monthly
ORDER BY m;

考点解析 :新客 = 该月首次下单的用户。陷阱:① "新客"是相对该用户首单月而言,须先用 MIN 求首单月再关联;② 关联后按月聚合,避免把首单月之外的订单误判为新客。


电商-14 购物车漏斗转化

题目 :埋点表 events 字段 user_idevent_nameevent_timeevent_name 取值 view/add_cart/order/pay。请输出"浏览→加购→下单→支付"的整体漏斗转化率。

参考答案

复制代码
SELECT
    COUNT(DISTINCT CASE WHEN event_name = 'view' THEN user_id END) AS view_uv,
    COUNT(DISTINCT CASE WHEN event_name = 'add_cart' THEN user_id END) AS cart_uv,
    COUNT(DISTINCT CASE WHEN event_name = 'order' THEN user_id END) AS order_uv,
    COUNT(DISTINCT CASE WHEN event_name = 'pay' THEN user_id END) AS pay_uv,
    ROUND(COUNT(DISTINCT CASE WHEN event_name = 'add_cart' THEN user_id END)
        / COUNT(DISTINCT CASE WHEN event_name = 'view' THEN user_id END), 4) AS view_to_cart
FROM events;

考点解析:转化率相对上一步计算。漏斗统计与去重。陷阱:① 漏斗按"用户 UV"还是"事件次数"口径差异很大,本答案按 UV 且不保证先后顺序,仅是简化版;② 严格漏斗应保证"同一用户按事件时间顺序递进",需窗口函数判断,面试可追问这一局限。


第二章 游戏(12 题)

游戏-01 次日留存率

题目 :表 login_log 字段 user_idlogin_date。请计算每日新增用户的次日留存率。

参考答案

复制代码
WITH first_login AS (
    SELECT user_id, MIN(login_date) AS first_date
    FROM login_log
    GROUP BY user_id
),
retention AS (
    SELECT
        f.first_date,
        COUNT(DISTINCT l.user_id) AS retained
    FROM first_login f
    LEFT JOIN login_log l
        ON l.user_id = f.user_id
       AND l.login_date = DATE_ADD(f.first_date, INTERVAL 1 DAY)
    GROUP BY f.first_date
)
SELECT
    first_date,
    COUNT(*) AS new_users,
    SUM(retained) AS retained_users,
    ROUND(SUM(retained) / COUNT(*), 4) AS day1_retention
FROM first_login f
JOIN retention r ON f.first_date = r.first_date
GROUP BY first_date
ORDER BY first_date;

考点解析 :次日留存率 = 次日仍登录的用户占比,"新增用户" = 首次登录日。留存率经典口径。陷阱:① "新增用户"需 MIN(login_date) 而非注册表;② 留存判断是"次日当天有登录",用 LEFT JOIN + 日期精确匹配,不能用"下次登录间隔=1天"(跨多天的登录会漏)。


游戏-02 连续登录 N 天的用户

题目 :表 login_log 字段 user_idlogin_date。请找出连续登录达到 3 天及以上的用户。

参考答案

复制代码
WITH daily AS (
    SELECT DISTINCT user_id, login_date
    FROM login_log
),
grp AS (
    SELECT user_id, login_date,
        DATE_SUB(login_date, INTERVAL ROW_NUMBER() OVER (PARTITION BY user_id ORDER BY login_date) DAY) AS grp_key
    FROM daily
),
consec AS (
    SELECT user_id, grp_key, COUNT(*) AS days
    FROM grp
    GROUP BY user_id, grp_key
)
SELECT DISTINCT user_id
FROM consec
WHERE days >= 3;

考点解析 :同电商连续下单,"日期减序号"技巧。陷阱:① 登录表可能有同一用户同一天多条记录,需先去重;② 阈值 >= 3(含 3 天)。


游戏-03 ARPU 与 ARPPU

题目 :表 login_log 字段 user_idlogin_date;表 pay_log 字段 user_idamountpay_time。请按天计算 ARPU 与 ARPPU。

参考答案

复制代码
WITH dau AS (
    SELECT login_date AS dt, COUNT(DISTINCT user_id) AS dau
    FROM login_log
    GROUP BY login_date
),
pay AS (
    SELECT DATE(pay_time) AS dt,
           COUNT(DISTINCT user_id) AS pay_users,
           SUM(amount) AS revenue
    FROM pay_log
    GROUP BY DATE(pay_time)
)
SELECT
    d.dt,
    COALESCE(p.revenue, 0) AS revenue,
    d.dau,
    COALESCE(p.pay_users, 0) AS pay_users,
    ROUND(COALESCE(p.revenue, 0) / d.dau, 4) AS arpu,
    ROUND(COALESCE(p.revenue, 0) / NULLIF(p.pay_users, 0), 4) AS arppu
FROM dau d
LEFT JOIN pay p ON d.dt = p.dt
ORDER BY d.dt;

考点解析 :ARPU = 人均收入(分母为当日活跃用户),ARPPU = 付费用户人均付费(分母为当日付费用户)。陷阱:① 两者分母不同,别混;② ARPPU 分母为 0 会除零,用 NULLIF 兜底;③ 付费用户可能当天不登录,纯按登录表会漏,需结合业务定义活跃口径。


游戏-04 7 日 LTV

题目 :表 login_log 字段 user_idlogin_date;表 pay_log 字段 user_idamountpay_time。请按首次登录日分组,统计每批用户注册后 7 日内的累计人均付费。

参考答案

复制代码
WITH first_login AS (
    SELECT user_id, MIN(login_date) AS first_date
    FROM login_log
    GROUP BY user_id
),
pay7 AS (
    SELECT
        f.first_date,
        SUM(p.amount) AS total_pay
    FROM first_login f
    LEFT JOIN pay_log p
        ON p.user_id = f.user_id
       AND p.pay_time >= f.first_date
       AND p.pay_time < DATE_ADD(f.first_date, INTERVAL 7 DAY)
    GROUP BY f.first_date
)
SELECT
    f.first_date,
    COUNT(*) AS users,
    COALESCE(p.total_pay, 0) / COUNT(*) AS ltv7
FROM first_login f
LEFT JOIN pay7 p ON f.first_date = p.first_date
GROUP BY f.first_date
ORDER BY f.first_date;

考点解析 :本题即 7 日 LTV。陷阱:① 分母是该批"新增用户数"(含未付费的),这是 LTV 与 ARPPU 的本质区别;② 付费时间窗口要落在注册后 7 天内([first_date, first_date+7))。


游戏-05 累计充值档位分布

题目 :表 pay_log 字段 user_idamountpay_time。请按累计充值金额把用户分成 0~100 / 100~500 / 500~2000 / 2000+ 四档,输出各档人数。

参考答案

复制代码
WITH total AS (
    SELECT user_id, SUM(amount) AS total_amount
    FROM pay_log
    GROUP BY user_id
)
SELECT
    CASE
        WHEN total_amount < 100 THEN '0~100'
        WHEN total_amount < 500 THEN '100~500'
        WHEN total_amount < 2000 THEN '500~2000'
        ELSE '2000+'
    END AS bucket,
    COUNT(*) AS users
FROM total
GROUP BY bucket
ORDER BY MIN(total_amount);

考点解析 :条件分桶。陷阱:① 分桶边界注意 >=< 的取舍,避免数值落在档位缝隙;② 只统计了付费用户,若需求含"未付费用户"需把未付费的 0 元用户也并进来。


游戏-06 首次付费时长分布

题目 :表 login_log 字段 user_idlogin_date;表 pay_log 字段 user_idpay_time。请统计每个用户首次登录到首次付费间隔天数的分布。

参考答案

复制代码
WITH first_login AS (
    SELECT user_id, MIN(login_date) AS first_date
    FROM login_log
    GROUP BY user_id
),
first_pay AS (
    SELECT user_id, MIN(pay_time) AS first_pay_time
    FROM pay_log
    GROUP BY user_id
)
SELECT
    DATEDIFF(fp.first_pay_time, fl.first_date) AS days_to_first_pay,
    COUNT(*) AS users
FROM first_login fl
JOIN first_pay fp ON fl.user_id = fp.user_id
GROUP BY days_to_first_pay
ORDER BY days_to_first_pay;

考点解析 :双 MIN + 日期差。陷阱:① 需分别求首登和首付两个"最小时间"再相减;② DATEDIFF 返回天数差,同日付费为 0;③ 未付费用户不在结果中,若要看转化需另算首付率。


游戏-07 高活跃低胜率用户(流失预警)

题目 :表 battle_log 字段 user_idresultwin/lose)、battle_time。请找出近 7 天对局数 ≥ 10 且胜率 < 40% 的用户。

参考答案

复制代码
SELECT
    user_id,
    COUNT(*) AS battles,
    SUM(result = 'win') / COUNT(*) AS win_rate
FROM battle_log
WHERE battle_time >= NOW() - INTERVAL 7 DAY
GROUP BY user_id
HAVING battles >= 10 AND win_rate < 0.4;

考点解析 :条件聚合 + HAVING 复合筛选。陷阱:① 胜率是 SUM(result='win')/COUNT(*)SUM 布尔自动转 0/1;② HAVING 里用聚合别名 battles 是 MySQL 特性,标准 SQL 应写 COUNT(*);③ 分母为 0 时 win_rate 为 NULL,NULL < 0.4 不成立,天然排除无对局用户。


游戏-08 各道具消耗量排名

题目 :表 item_use_log 字段 user_iditem_iduse_time;表 items 字段 item_iditem_namecategory。请统计每个道具的消耗量,并给出按品类内的消耗量排名。

参考答案

复制代码
WITH cnt AS (
    SELECT i.item_id, i.item_name, i.category, COUNT(*) AS use_cnt
    FROM item_use_log u
    JOIN items i ON u.item_id = i.item_id
    GROUP BY i.item_id, i.item_name, i.category
)
SELECT item_id, item_name, category, use_cnt,
       RANK() OVER (PARTITION BY category ORDER BY use_cnt DESC) AS rk
FROM cnt
ORDER BY category, rk;

考点解析 :分组排名 RANK vs ROW_NUMBER。陷阱:① 消耗量是 COUNT(*) 而非 COUNT(DISTINCT user_id);② 并列时 RANK 会跳号,若需连续排名用 DENSE_RANK,若只需一条用 ROW_NUMBER


游戏-09 付费用户分层(鲸鱼/海豚/小鱼)

题目 :表 pay_log 字段 user_idamountpay_time。请按近 30 天付费金额把付费用户分为 ≥5000 鲸鱼、≥500 海豚、其余小鱼,输出各类人数与占比。

参考答案

复制代码
WITH t AS (
    SELECT user_id, SUM(amount) AS pay
    FROM pay_log
    WHERE pay_time >= NOW() - INTERVAL 30 DAY
    GROUP BY user_id
)
SELECT
    CASE WHEN pay >= 5000 THEN 'whale'
         WHEN pay >= 500 THEN 'dolphin'
         ELSE 'small_fish' END AS level,
    COUNT(*) AS users,
    ROUND(COUNT(*) / (SELECT COUNT(*) FROM t), 4) AS ratio
FROM t
GROUP BY level;

考点解析 :分层 + 占比子查询。陷阱:① 时间窗口近 30 天要先过滤再聚合;② 占比分母用子查询统计总付费用户,若直接 COUNT(*) OVER () 更简洁;③ 分层边界 >= 的归属要明确。


游戏-10 新手流失率

题目 :表 login_log 字段 user_idlogin_date。定义"新手流失"为注册后 7 天内未登录的用户。请按注册日统计新手流失率。

参考答案

复制代码
WITH first_login AS (
    SELECT user_id, MIN(login_date) AS first_date
    FROM login_log
    GROUP BY user_id
),
login_within7 AS (
    SELECT DISTINCT f.user_id, f.first_date
    FROM first_login f
    JOIN login_log l
        ON l.user_id = f.user_id
       AND l.login_date > f.first_date
       AND l.login_date <= DATE_ADD(f.first_date, INTERVAL 7 DAY)
)
SELECT
    f.first_date,
    COUNT(*) AS new_users,
    SUM(l.user_id IS NULL) AS churned,
    ROUND(SUM(l.user_id IS NULL) / COUNT(*), 4) AS churn_rate
FROM first_login f
LEFT JOIN login_within7 l ON f.user_id = l.user_id
GROUP BY f.first_date
ORDER BY f.first_date;

考点解析 :注册日以首次登录日近似。流失口径。陷阱:① "7 天内未登录"是排除首登当天、其后 7 天无登录,窗口边界 > first_date<= +7;② login_within7DISTINCT 防止多天登录导致 LEFT JOIN 重复;③ SUM(l.user_id IS NULL) 统计未匹配即流失。


游戏-11 渠道付费质量

题目 :表 users 字段 user_idregister_channelregister_date;表 pay_log 字段 user_idamountpay_time。请统计各注册渠道的注册用户数、付费用户数、人均付费。

参考答案

复制代码
SELECT
    u.register_channel,
    COUNT(DISTINCT u.user_id) AS reg_users,
    COUNT(DISTINCT p.user_id) AS pay_users,
    ROUND(SUM(p.amount) / COUNT(DISTINCT u.user_id), 4) AS arpu
FROM users u
LEFT JOIN pay_log p ON u.user_id = p.user_id
GROUP BY u.register_channel
ORDER BY arpu DESC;

考点解析 :人均付费含未付费用户,分母为注册数。渠道 ROI 基础。陷阱:① 分母是 COUNT(DISTINCT u.user_id),不能和付费用户数混淆;② 若一个用户多笔付费,LEFT JOIN 会让注册用户数被放大,故 u.user_id 必须 DISTINCT;③ 付费金额 SUM 不会被放大(金额本就该累加)。


游戏-12 DAU/MAU 粘性

题目 :表 login_log 字段 user_idlogin_date。请计算 2026-07 月的 DAU/MAU 比值。

参考答案

复制代码
SELECT
    ROUND(
        (SELECT AVG(dau) FROM (
            SELECT login_date, COUNT(DISTINCT user_id) AS dau
            FROM login_log
            WHERE login_date >= '2026-07-01' AND login_date < '2026-08-01'
            GROUP BY login_date
        ) d) /
        (SELECT COUNT(DISTINCT user_id) FROM login_log
         WHERE login_date >= '2026-07-01' AND login_date < '2026-08-01'),
        4
    ) AS dau_mau;

考点解析 :DAU/MAU = 该月日均 DAU / 该月 MAU。粘性指标。陷阱:① MAU 是该月去重用户数,DAU 是日均(先按天去重再求均值),分子分母口径必须清晰;② 时间边界 [07-01, 08-01) 左闭右开,避免漏月末或混入下月。


第三章 社交(12 题)

社交-01 粉丝数与互关数

题目 :表 follow 字段 follower_idfollowee_idfollow_time。请统计每个用户的粉丝数、关注数,以及互关数。

参考答案

复制代码
SELECT
    u.user_id,
    COUNT(DISTINCT f1.follower_id) AS fans,
    COUNT(DISTINCT f2.followee_id) AS followings,
    COUNT(DISTINCT CASE WHEN f3.follower_id IS NOT NULL THEN f2.followee_id END) AS mutual
FROM (SELECT follower_id AS user_id FROM follow UNION SELECT followee_id FROM follow) u
LEFT JOIN follow f1 ON u.user_id = f1.followee_id
LEFT JOIN follow f2 ON u.user_id = f2.follower_id
LEFT JOIN follow f3
    ON f3.follower_id = u.user_id
   AND f3.followee_id = f2.followee_id
   AND EXISTS (SELECT 1 FROM follow x WHERE x.follower_id = f2.followee_id AND x.followee_id = u.user_id)
GROUP BY u.user_id;

考点解析follower_id 为关注者、followee_id 为被关注者,互关 = 双方互相关注。自连接 + 双向关系。陷阱:① 用户全集要 UNION 关注者与被关注者,避免只出现在一侧的用户被漏掉;② 互关需双向判定;③ 三个 LEFT JOIN 叠加会产生笛卡尔放大,各字段都要 DISTINCT


社交-02 二度人脉推荐

题目 :表 follow 字段 follower_idfollowee_id。请为每个用户推荐二度人脉(其关注的人所关注的人),但要排除用户本人与已直接关注的人。

参考答案

复制代码
SELECT
    f1.follower_id AS user_id,
    f2.followee_id AS recommend_id,
    COUNT(*) AS common
FROM follow f1
JOIN follow f2 ON f1.followee_id = f2.follower_id
WHERE f1.follower_id <> f2.followee_id
  AND NOT EXISTS (
      SELECT 1 FROM follow x
      WHERE x.follower_id = f1.follower_id AND x.followee_id = f2.followee_id
  )
GROUP BY f1.follower_id, f2.followee_id
ORDER BY user_id, common DESC;

考点解析 :多跳图查询。陷阱:① 二度是"关注的关注",通过 f1.followee_id = f2.follower_id 串联;② 排除自己(<>)与已关注(NOT EXISTS)两个条件缺一不可;③ 多条路径可达同一人会产生重复,需 GROUP BY 聚合去重,COUNT(*) 可作为"共同关注数"排序依据。


社交-03 帖子点赞数与评论数

题目 :表 posts 字段 post_iduser_idcreate_time;表 likes 字段 post_iduser_id;表 comments 字段 post_iduser_id。请计算每条帖子的点赞数与评论数。

参考答案

复制代码
SELECT
    p.post_id,
    COUNT(DISTINCT l.user_id) AS like_cnt,
    COUNT(DISTINCT c.user_id) AS comment_cnt
FROM posts p
LEFT JOIN likes l ON p.post_id = l.post_id
LEFT JOIN comments c ON p.post_id = c.post_id
GROUP BY p.post_id;

考点解析 :互动率 = (点赞数 + 评论数) / 曝光数,本题以点赞数、评论数代替输出。多表关联膨胀。陷阱:① 点赞与评论两张表同时 LEFT JOIN,若帖子有 3 赞 2 评,会产生 6 行,COUNT(*) 会算成 6,必须 COUNT(DISTINCT ...);② 更稳妥的写法是分别聚合再关联,避免大表笛卡尔积的性能问题,面试可提。


社交-04 创作者综合排名

题目 :表 posts 字段 post_iduser_id;表 likes 字段 post_id;表 follow 字段 follower_idfollowee_id。请给发过帖的用户按获赞数、发帖数、粉丝数加权打分并排名。

参考答案

复制代码
WITH post_cnt AS (
    SELECT user_id, COUNT(*) AS posts
    FROM posts GROUP BY user_id
),
like_cnt AS (
    SELECT p.user_id, COUNT(*) AS likes
    FROM likes l JOIN posts p ON l.post_id = p.post_id
    GROUP BY p.user_id
),
fan_cnt AS (
    SELECT followee_id AS user_id, COUNT(*) AS fans
    FROM follow GROUP BY followee_id
)
SELECT
    pc.user_id,
    pc.posts,
    COALESCE(lc.likes, 0) AS likes,
    COALESCE(fc.fans, 0) AS fans,
    COALESCE(lc.likes, 0) * 0.5 + pc.posts * 0.2 + COALESCE(fc.fans, 0) * 0.3 AS score,
    RANK() OVER (ORDER BY COALESCE(lc.likes, 0) * 0.5 + pc.posts * 0.2 + COALESCE(fc.fans, 0) * 0.3 DESC) AS rk
FROM post_cnt pc
LEFT JOIN like_cnt lc ON pc.user_id = lc.user_id
LEFT JOIN fan_cnt fc ON pc.user_id = fc.user_id
ORDER BY rk;

考点解析 :权重(0.5/0.2/0.3)为示例,可按业务调整。多指标加权。陷阱:① 三个指标先各自聚合再关联,避免直接多表 JOIN 的膨胀;② 未获赞/无粉丝的用户用 COALESCE 补 0,否则加权得 NULL;③ 权重口径是业务决策,面试可追问如何确定权重。


社交-05 内容次日留存

题目 :表 users 字段 user_idregister_date;表 login_log 字段 user_idlogin_date。请按注册日统计次日留存率,并只保留注册用户数 ≥ 100 的日期。

参考答案

复制代码
WITH retained AS (
    SELECT
        u.register_date,
        COUNT(DISTINCT u.user_id) AS reg_users,
        COUNT(DISTINCT l.user_id) AS day1_users
    FROM users u
    LEFT JOIN login_log l
        ON l.user_id = u.user_id
       AND l.login_date = DATE_ADD(u.register_date, INTERVAL 1 DAY)
    GROUP BY u.register_date
)
SELECT register_date, reg_users, day1_users,
       ROUND(day1_users / reg_users, 4) AS day1_retention
FROM retained
WHERE reg_users >= 100
ORDER BY register_date;

考点解析 :留存率 + HAVING/WHERE 时机。陷阱:① 分母用注册表而非登录表,避免"当天注册即登录"的边界歧义;② reg_users >= 100 是对聚合结果过滤,若写进同一层 WHERE 会报错(聚合未完成),需子查询/CTE 后过滤。


社交-06 A/B 实验效果

题目 :表 experiment 字段 user_idgroup_nameassign_time;表 actions 字段 user_idaction_typeaction_time。请对比实验组与对照组在实验期间的日均人均发帖数。

参考答案

复制代码
SELECT
    e.group_name,
    COUNT(DISTINCT e.user_id) AS users,
    SUM(a.action_type = 'post') / COUNT(DISTINCT e.user_id) AS posts_per_user
FROM experiment e
LEFT JOIN actions a
    ON a.user_id = e.user_id
   AND a.action_type = 'post'
   AND a.action_time >= e.assign_time
GROUP BY e.group_name;

考点解析action_type 行为为发帖/点赞,日均人均发帖数 = 发帖总数 / 实验人数。A/B 口径。陷阱:① 行为必须发生在 assign_time 之后,否则把分流前的历史行为算入实验,会稀释或污染结果;② 分子是发帖次数(SUM),分母是实验人数(DISTINCT),两者量纲不同;③ 严格的 AB 分析还应控制分流随机性、时间对齐,面试可追问显著性检验。


社交-07 会话活跃度

题目 :表 message 字段 sender_idreceiver_idmsg_time。请统计每天的消息量、活跃用户数、人均消息量。

参考答案

复制代码
WITH daily AS (
    SELECT DATE(msg_time) AS dt,
           sender_id AS user_id
    FROM message
    UNION
    SELECT DATE(msg_time), receiver_id
    FROM message
)
SELECT
    dt,
    (SELECT COUNT(*) FROM message m WHERE DATE(m.msg_time) = dt) AS msg_cnt,
    COUNT(DISTINCT user_id) AS active_users,
    ROUND((SELECT COUNT(*) FROM message m WHERE DATE(m.msg_time) = dt) / COUNT(DISTINCT user_id), 4) AS msg_per_user
FROM daily
GROUP BY dt
ORDER BY dt;

考点解析 :活跃用户发或收都算活跃。双向关系去重。陷阱:① 活跃用户要同时含发送方和接收方,用 UNION 合并两侧再 DISTINCT;② 消息量是一对一计数(COUNT(*)),与活跃人数是两个口径,别混;③ 本答案用相关子查询取消息量,也可用 COUNT(*) OVER (PARTITION BY dt)


社交-08 最高转发帖子

题目 :表 posts 字段 post_iduser_id;表 reposts 字段 post_iduser_idrepost_time。请找出被转发次数最多的帖子及其转发次数。

参考答案

复制代码
SELECT post_id, COUNT(*) AS repost_cnt
FROM reposts
GROUP BY post_id
ORDER BY repost_cnt DESC
LIMIT 1;

考点解析reposts 记录每次转发,被转发的是原帖。简单 Top1。陷阱:① 若转发记录里含"转发的转发"(多层),post_id 可能指原始帖,口径需明确;② LIMIT 1 并列时会随机截断一条,若要求并列全取,需用 RANKWHERE rk = 1


社交-09 高价值用户

题目 :表 posts 字段 user_id;表 likes 字段 post_id;表 comments 字段 post_id。请找出发帖数 ≥ 10 且获赞 + 获评论 ≥ 100 的用户。

参考答案

复制代码
WITH engage AS (
    SELECT p.user_id, COUNT(*) AS engaged
    FROM posts p
    LEFT JOIN likes l ON p.post_id = l.post_id
    LEFT JOIN comments c ON p.post_id = c.post_id
    GROUP BY p.post_id, p.user_id
)
SELECT user_id
FROM engage
GROUP BY user_id
HAVING COUNT(*) >= 10 AND SUM(engaged) >= 100;

考点解析 :复合条件。陷阱:① 直接 LEFT JOIN 三表会因赞/评交叉放大,engage 里按 post_id 聚合时 COUNT(*) 会虚高,须先处理膨胀;② 正确做法是先分别聚合点赞数、评论数,再合并到用户层,本答案仅为示意,实际需 COUNT(DISTINCT)


社交-10 沉默用户唤醒

题目 :表 login_log 字段 user_idlogin_date。请找出连续 30 天未登录、但 30 天前曾是活跃用户的用户。

参考答案

复制代码
WITH last_login AS (
    SELECT user_id, MAX(login_date) AS last_date
    FROM login_log
    GROUP BY user_id
),
active_before AS (
    SELECT user_id
    FROM login_log
    WHERE login_date < DATE_SUB('2026-08-20', INTERVAL 30 DAY)
    GROUP BY user_id
    HAVING COUNT(DISTINCT login_date) >= 5
)
SELECT l.user_id, l.last_date
FROM last_login l
JOIN active_before a ON l.user_id = a.user_id
WHERE l.last_date < DATE_SUB('2026-08-20', INTERVAL 30 DAY);

考点解析 :活跃 = 月均登录 ≥ 5 天。沉默定义。陷阱:① "连续 30 天未登录"用"最后登录时间 < 30 天前"表达;② "曾活跃"用去重登录天数 COUNT(DISTINCT login_date) >= 5,且只统计 30 天前的历史,别把近期活跃用户也算进来;③ 时间基准 '2026-08-20' 应参数化。


社交-11 冷启动内容

题目 :表 posts 字段 post_iduser_idcreate_time;表 likes 字段 post_idcreate_time。请统计新用户发布帖子的平均获赞数,对比老用户帖子的平均获赞数。

参考答案

复制代码
WITH first_post_time AS (
    SELECT user_id, MIN(create_time) AS first_post
    FROM posts GROUP BY user_id
),
post_like AS (
    SELECT p.post_id, p.user_id, COUNT(l.post_id) AS likes
    FROM posts p
    LEFT JOIN likes l ON p.post_id = l.post_id
    GROUP BY p.post_id, p.user_id
)
SELECT
    CASE WHEN p.create_time <= DATE_ADD(f.first_post, INTERVAL 24 HOUR)
         THEN 'new_user' ELSE 'old_user' END AS segment,
    AVG(pl.likes) AS avg_likes
FROM posts p
JOIN first_post_time f ON p.user_id = f.user_id
JOIN post_like pl ON p.post_id = pl.post_id
GROUP BY segment;

考点解析 :新用户 = 注册后 24 小时内发帖,本题以"首帖后 24 小时"近似注册时间(无注册表时的折衷)。分组对比。陷阱:① 该假设应在面试中主动说明;② 获赞数要先聚合到帖子层(post_like),避免赞表直接 JOIN 的膨胀。


社交-12 首评时间差

题目 :表 posts 字段 post_idcreate_time;表 comments 字段 comment_idpost_idcreate_time。请统计每条帖子发布到收到第一条评论的耗时,并给出整体平均耗时。

参考答案

复制代码
WITH first_comment AS (
    SELECT post_id, MIN(create_time) AS first_cmt_time
    FROM comments
    GROUP BY post_id
)
SELECT
    AVG(TIMESTAMPDIFF(MINUTE, p.create_time, fc.first_cmt_time)) AS avg_minutes
FROM posts p
JOIN first_comment fc ON p.post_id = fc.post_id;

考点解析 :无评论的帖子忽略。MIN + 时间差。陷阱:① 用 JOIN 而非 LEFT JOIN,天然排除无评论帖子(若需含无评论帖子,则要 LEFT JOIN 并用 COALESCE 处理 NULL);② 时间单位 MINUTE/SECOND 按需求选,TIMESTAMPDIFFDATEDIFF 精度不同。


第四章 供应链(12 题)

供应链-01 库存周转天数

题目 :表 inventory 字段 sku_idstock_datestock_qty;表 sales 字段 sku_idqtysale_date。请计算每个 SKU 的库存周转天数。

参考答案

复制代码
WITH avg_stock AS (
    SELECT sku_id, AVG(stock_qty) AS avg_qty
    FROM inventory
    GROUP BY sku_id
),
daily_sales AS (
    SELECT sku_id, SUM(qty) / COUNT(DISTINCT sale_date) AS avg_daily_sales
    FROM sales
    GROUP BY sku_id
)
SELECT
    s.sku_id,
    ROUND(a.avg_qty / NULLIF(s.avg_daily_sales, 0), 2) AS turnover_days
FROM avg_stock a
JOIN daily_sales s ON a.sku_id = s.sku_id;

考点解析 :库存周转天数 = 平均库存 / 日均销量,inventory 为每日库存快照。陷阱:① 平均库存用 AVG(stock_qty)(时序快照均值),若只有期初期末,应用 (期初+期末)/2;② 日均销量是"总销量 / 有销量天数"还是"总销量 / 总天数"口径不同,本答案取有销量天数;③ 销量为 0 时 NULLIF 防除零。


供应链-02 缺货预警

题目 :表 inventory 字段 sku_idstock_qty;表 sales 字段 sku_idqtysale_date;表 sku_info 字段 sku_idsafe_stock。请找出当前库存低于安全库存、且近 7 天日均销量大于 0 的 SKU。

参考答案

复制代码
WITH recent_sales AS (
    SELECT sku_id, SUM(qty) / 7 AS avg_daily
    FROM sales
    WHERE sale_date >= CURDATE() - INTERVAL 7 DAY
    GROUP BY sku_id
)
SELECT i.sku_id, i.stock_qty, s.safe_stock, r.avg_daily
FROM inventory i
JOIN sku_info s ON i.sku_id = s.sku_id
JOIN recent_sales r ON i.sku_id = r.sku_id
WHERE i.stock_qty < s.safe_stock
  AND r.avg_daily > 0;

考点解析inventory.stock_qty 为当前库存,safe_stock 为安全库存。多条件预警。陷阱:① 日均销量用"近 7 天"滚动窗口,而非全历史;② "日均销量 > 0"排除零动销 SKU,避免无销量的呆滞品也触发补货;③ 补货量 = 安全库存 - 当前库存 + 在途,本答案只做预警,面试可追问完整补货公式。


供应链-03 供应商准时交付率

题目 :表 purchase_order 字段 order_idsupplier_idexpected_timeactual_time。请统计每个供应商的准时交付率。

参考答案

复制代码
SELECT
    supplier_id,
    COUNT(*) AS total_orders,
    SUM(actual_time IS NOT NULL AND actual_time <= expected_time) AS ontime_orders,
    ROUND(SUM(actual_time IS NOT NULL AND actual_time <= expected_time) / COUNT(*), 4) AS ontime_rate
FROM purchase_order
GROUP BY supplier_id;

考点解析expected_time 为预计到货、actual_time 为实际到货(未到货为 NULL),准时 = 实际到货 ≤ 预计到货。交付率口径。陷阱:① 未到货订单(actual_time IS NULL)既不算准时也不算迟,但计入分母拉低准时率,这是业务要接受的"未完成"口径;② 若需求是"已到货订单中的准时率",分母应排除 NULL,两种口径结论不同,面试应主动澄清。


供应链-04 品类采购金额排名

题目 :表 purchase_order 字段 order_idsku_idqtyunit_price;表 sku_info 字段 sku_idcategory。请按品类统计采购金额,并按金额降序排名。

参考答案

复制代码
SELECT
    s.category,
    SUM(p.qty * p.unit_price) AS purchase_amount,
    RANK() OVER (ORDER BY SUM(p.qty * p.unit_price) DESC) AS rk
FROM purchase_order p
JOIN sku_info s ON p.sku_id = s.sku_id
GROUP BY s.category
ORDER BY rk;

考点解析 :聚合 + 排名。陷阱:① 采购金额 = 数量 × 单价;② 窗口函数里可嵌套聚合函数,RANK() OVER (ORDER BY SUM(...) DESC) 合法;③ 注意退货/作废采购单是否要剔除。


供应链-05 呆滞库存

题目 :表 stock_flow 字段 sku_idflow_typeqtyflow_timeflow_type 取值 in/out。请找出最近 90 天无出库记录的 SKU。

参考答案

复制代码
SELECT sku_id
FROM stock_flow
GROUP BY sku_id
HAVING MAX(CASE WHEN flow_type = 'out' THEN flow_time END) < DATE_SUB(CURDATE(), INTERVAL 90 DAY)
    OR MAX(CASE WHEN flow_type = 'out' THEN flow_time END) IS NULL;

考点解析 :呆滞定义。陷阱:① 用"最后一次出库时间"判断,而不是统计出库次数;② 从无出库记录的 SKU,MAX 为 NULL,条件里必须显式处理 IS NULL,否则被 HAVING 排除漏掉;③ 入出库流水若含负值/冲正,需先清洗。


供应链-06 期末库存推算

题目 :表 stock_flow 字段 sku_idflow_typeqtyflow_timeflow_type 取值 in/out;表 sku_info 字段 sku_idinit_stock。请计算每个 SKU 的当前库存。

参考答案

复制代码
SELECT
    s.sku_id,
    s.init_stock
      + COALESCE(SUM(CASE WHEN f.flow_type = 'in' THEN f.qty ELSE 0 END), 0)
      - COALESCE(SUM(CASE WHEN f.flow_type = 'out' THEN f.qty ELSE 0 END), 0) AS ending_stock
FROM sku_info s
LEFT JOIN stock_flow f ON s.sku_id = f.sku_id
GROUP BY s.sku_id, s.init_stock;

考点解析init_stock 为期初库存,期末库存 = 期初 + 入库 - 出库。流水汇总。陷阱:① flow_type 条件聚合;② 无流水的 SKU 用 LEFT JOIN + COALESCE 保留并返回期初值;③ 若有盘点调整/盘盈盘亏类型,公式需扩展。


供应链-07 履约时效分布

题目 :表 orders 字段 order_idcreate_timesign_time。请统计下单到签收的耗时分布,按天分桶(0~1 天、1~3 天、3~5 天、5 天以上)。

参考答案

复制代码
SELECT
    CASE
        WHEN diff <= 1 THEN '0~1天'
        WHEN diff <= 3 THEN '1~3天'
        WHEN diff <= 5 THEN '3~5天'
        ELSE '5天以上'
    END AS bucket,
    COUNT(*) AS orders
FROM (
    SELECT DATEDIFF(sign_time, create_time) AS diff
    FROM orders
    WHERE sign_time IS NOT NULL
) t
GROUP BY bucket
ORDER BY MIN(diff);

考点解析sign_time 为签收时间(未签收为 NULL)。时效分桶。陷阱:① 未签收订单(sign_time IS NULL)应排除,否则 DATEDIFF(NULL, ...) 为 NULL 会落入 ELSE 污染"5 天以上"桶;② 分桶边界 <= 的归属要明确;③ 若需含在途订单,应单列"未完成"桶。


供应链-08 同商品多供应商比价

题目 :表 purchase_order 字段 supplier_idsku_idunit_priceorder_time。请找出每个 SKU 的最低价供应商及对应单价。

参考答案

复制代码
WITH ranked AS (
    SELECT sku_id, supplier_id, unit_price,
        ROW_NUMBER() OVER (PARTITION BY sku_id ORDER BY unit_price ASC) AS rn
    FROM purchase_order
)
SELECT sku_id, supplier_id, unit_price
FROM ranked
WHERE rn = 1;

考点解析 :分组取最小值。陷阱:① 同一 SKU 同一供应商可能多笔订单,取最低价时按"最低单价"而非"最低总价",本答案按单价;② 若多个供应商并列最低价,ROW_NUMBER 只取一条,需全取用 RANK;③ 是否只统计近 N 期价格(历史价可能失效),面试可追问。


供应链-09 安全库存测算

题目 :表 sales 字段 sku_idqtysale_date。请估算每个 SKU 的建议安全库存。

参考答案

复制代码
SELECT
    sku_id,
    SUM(qty) / 30 AS avg_daily,
    STDDEV(qty) AS std_daily,
    ROUND(SUM(qty) / 30 * 7 + COALESCE(STDDEV(qty), 0) * 1.64, 2) AS safety_stock
FROM sales
WHERE sale_date >= CURDATE() - INTERVAL 30 DAY
GROUP BY sku_id;

考点解析 :建议安全库存 = 近 30 天日均销量 × 7 + 日均销量标准差 × 1.64。统计函数应用。陷阱:① STDDEV 是总体标准差(样本用 STDDEV_SAMP),口径差异影响缓冲量;② 日均销量"除以 30"是固定天数,若 SKU 只有几天有销量,用 COUNT(DISTINCT sale_date) 更合理,面试可讨论;③ 无销量 SKU 的标准差为 NULL,COALESCE 兜底。


供应链-10 仓库利用率

题目 :表 warehouse 字段 warehouse_idcapacity;表 inventory 字段 sku_idwarehouse_idstock_qty。请计算每个仓库的库存量与库容利用率。

参考答案

复制代码
SELECT
    w.warehouse_id,
    w.capacity,
    COALESCE(SUM(i.stock_qty), 0) AS total_stock,
    ROUND(COALESCE(SUM(i.stock_qty), 0) / w.capacity, 4) AS utilization
FROM warehouse w
LEFT JOIN inventory i ON w.warehouse_id = i.warehouse_id
GROUP BY w.warehouse_id, w.capacity
ORDER BY utilization DESC;

考点解析 :利用率 = 库存量 / 库容。陷阱:① 库存量是所有 SKU 之和,注意不同 SKU 体积不同,纯按数量求和是简化(真实应按体积/托盘);② 空仓库用 LEFT JOIN + COALESCE 保留,利用率为 0;③ capacity 为 0 时除零,需 NULLIF


供应链-11 库存 ABC 分类

题目 :表 sales 字段 sku_idqtyunit_price。请按累计销售金额占比做 ABC 分类:A 类(累计占比 ≤ 70%)、B 类(≤ 90%)、C 类(其余)。

参考答案

复制代码
WITH sku_sales AS (
    SELECT sku_id, SUM(qty * unit_price) AS amount
    FROM sales
    GROUP BY sku_id
),
cum AS (
    SELECT sku_id, amount,
        SUM(amount) OVER (ORDER BY amount DESC) AS cum_amount,
        SUM(amount) OVER () AS total_amount
    FROM sku_sales
)
SELECT
    sku_id,
    amount,
    cum_amount / total_amount AS cum_ratio,
    CASE
        WHEN cum_amount / total_amount <= 0.7 THEN 'A'
        WHEN cum_amount / total_amount <= 0.9 THEN 'B'
        ELSE 'C'
    END AS abc
FROM cum
ORDER BY amount DESC;

考点解析 :累计占比窗口。陷阱:① 累计占比 = SUM(amount) OVER (ORDER BY amount DESC) / 总额;② 总额用 SUM(amount) OVER () 窗口避免子查询;③ 边界归属:恰好 70% 的 SKU 归 A,<= 的取舍要与业务对齐。


供应链-12 销量移动平均预测

题目 :表 sales 字段 sku_idqtysale_date。请按周统计每个 SKU 的销量,并计算近 3 周销量移动平均作为下一周需求预测。

参考答案

复制代码
WITH weekly AS (
    SELECT
        sku_id,
        DATE_FORMAT(sale_date, '%Y-%u') AS wk,
        SUM(qty) AS weekly_qty
    FROM sales
    GROUP BY sku_id, DATE_FORMAT(sale_date, '%Y-%u')
),
mv AS (
    SELECT sku_id, wk, weekly_qty,
        AVG(weekly_qty) OVER (
            PARTITION BY sku_id
            ORDER BY wk
            ROWS BETWEEN 2 PRECEDING AND CURRENT ROW
        ) AS ma3
    FROM weekly
)
SELECT sku_id, wk, weekly_qty,
       ROUND(ma3, 2) AS next_week_forecast
FROM mv
ORDER BY sku_id, wk;

考点解析 :移动平均窗口。陷阱:① 先按周聚合,再用 ROWS BETWEEN 2 PRECEDING AND CURRENT ROW 做 3 周移动平均;② 前两周数据不足 3 期时,移动平均只对已有窗口计算(结果偏小),需在业务上说明"首两期预测不可靠";③ %u(周序号)跨年边界需注意,%Y-%u 组合避免跨年错序。


附:高频追问与踩坑清单(速查)

维度 常见陷阱
去重 多表 JOIN 导致行膨胀,聚合前先确认是否需要 DISTINCT 或先聚合再关联
口径 分母是"下单"还是"支付"、是"用户数"还是"次数"、是"注册日"还是"首单日"
时间 "近 N 天"是否含当天、自然月 vs 滚动 30 天、左闭右开区间
窗口 ROW_NUMBER/RANK/DENSE_RANK 并列差异、LAG 缺天错位
条件 WHERE vs HAVING 时机、LEFT JOIN 后用 WHERE 过滤右表退化为内连接
边界 除零 NULLIF、空值 COALESCE、状态字段与时间字段双口径
相关推荐
破土士V1 小时前
MySQL基础知识集合
数据库·mysql
润乾软件2 小时前
CodeBuddy+SQLazy 编写复杂 SQL 详解
数据库·sql·codebuddy·sqlazy
360智汇云3 小时前
MySQL-DTS数据同步工具:告别手动拼方案,迁移同步一站完成
数据库·mysql
kisshyshy3 小时前
掘金同款社区数据库设计:9张表带你吃透建表、索引与约束
后端·sql·markdown
这个DBA有点耶3 小时前
SQL优化进阶:读懂“索引下推”,告别回表噩梦
数据库·sql·性能优化
宠友信息5 小时前
内容社区源码开发实践解析,用Spring Boot打造1:1仿小红书源码平台
java·spring boot·redis·websocket·mysql·spring·uni-app
λqaq76 小时前
MySQL 数据库基础(2)约束、用户权限、远程连接、外键与 TCP/UDP 理解
linux·数据库·python·tcp/ip·mysql
NineData7 小时前
DTCC 2026 NineData 叶正盛:如何统一管理人与 AI Agent 的数据访问行为
数据库·人工智能·sql·oracle·agent·ninedata·dtcc
叫我:松哥8 小时前
基于flask图书数据管理系统,技术栈flask+MySQL+boostrap
python·mysql·flask