大厂真题 / 拼多多

拼多多 7.2 笔试真题 - 数据岗(SQL)

本场考试概述

考试时间:2026年7月2日 考试岗位:数据岗(数据分析/数据开发) 难度评级:简单到中等

考点分析

  1. 第一题:各品类商品定价区间统计——LEFT JOIN 连表(难度简单)
  2. 第二题:新商家前3单广告激励金统计——ROW_NUMBER 分组 TopN(难度中等)
  3. 第三题:用户会员等级升级日期与天数统计——窗口函数累计求和(难度中等)

建议策略

  1. 三题都围绕「主表要全保留 + 分组聚合」,LEFT JOIN 保空组、COUNT(列名) 数真实值是通用套路
  2. 第二三题用窗口函数:ROW_NUMBER() 分组叫号取 TopN、SUM() OVER() 累计求首次达标日,把 PARTITION BY / ORDER BY 填空模板练熟即可
  3. 注意空组处理:没有商品的品类用 COUNT(列名) 得 0,不要用 COUNT(*);不够 3 单的商家跨越天数填 NULL

第 1 题:各品类商品定价区间统计

题目描述

统计每个品类的商品总数、最高售价、最低售价、平均售价。没有商品的品类也要展示(商品总数为 0,售价字段为 NULL)。平均售价四舍五入保留 2 位小数,结果按品类名称字典序升序排列。

表结构

  • c21_pdd_category:品类表(category_id, category_name)
  • c21_pdd_goods:商品表(goods_id, goods_name, category_id, price)

样例

输入

-- 品类:数码、服饰、图书、家居(家居无商品)
-- 商品:手机3999、蓝牙耳机299、数据线19.9、纯棉T恤89、连帽卫衣219、SQL入门45.5

输出

category_name | goods_count | max_price | min_price | avg_price
图书          | 1           | 45.50     | 45.50     | 45.50
家居          | 0           | NULL      | NULL      | NULL
数码          | 3           | 3999.00   | 19.90     | 1439.30
服饰          | 2           | 219.00    | 89.00     | 154.00

思路分析

第一步:为什么用 LEFT JOIN

如果用普通 JOIN,”家居”这个没有商品的品类在商品表里找不到对应行,整行被丢掉——结果里看不到它。改用 LEFT JOIN,品类表每行都保留,商品表没对应的填 NULL。

第二步:COUNT(列名) vs COUNT(*)

家居那行虽然商品是空的,行却还在。COUNT(*) 会把它数成 1;而 COUNT(g.goods_id) 只数真实存在的值,家居正好得 0。

第三步:聚合函数遇到全 NULL

MAX、MIN、AVG 对全 NULL 的分组自动返回 NULL,无需额外处理。

题解代码

SELECT c.category_name,
       COUNT(g.goods_id)      AS goods_count,
       MAX(g.price)           AS max_price,
       MIN(g.price)           AS min_price,
       ROUND(AVG(g.price), 2) AS avg_price
FROM c21_pdd_category c
LEFT JOIN c21_pdd_goods g ON c.category_id = g.category_id
GROUP BY c.category_id, c.category_name
ORDER BY c.category_name;

复杂度分析

时间复杂度:$O(n)$,$n$ 为商品总数,一次扫描即可完成连接与聚合。 空间复杂度:$O(m)$,$m$ 为品类数,存储分组结果。


第 2 题:新商家前3单广告激励金统计

题目描述

“新商家前 3 单广告激励金”规则:第 1 单奖 100 元、第 2 单奖 80 元、第 3 单奖 50 元。退款订单(status=’refunded’)不计入、不参与排序。统计每个商家的激励金总额和从第 1 单到第 3 单的跨越天数。

  • 只统计至少有 1 单有效订单的商家
  • 有效订单不足 3 单时跨越天数为 NULL
  • 结果按激励金降序,总额相同按商家名称字典序升序

表结构

  • c21_pdd_seller:商家表(seller_id, seller_name)
  • c21_pdd_seller_order:订单表(order_id, seller_id, order_time, amount, status)

样例

输入

-- 优选生鲜:5单有效+1单退款,取前3单有效
-- 百味小吃:3单有效(含同天2单)
-- 潮流服饰:2单有效+1单退款
-- 数码优品:1单有效
-- 居家优选:全退款

输出

seller_name | total_bonus | span_days
优选生鲜    | 230         | 5
百味小吃    | 230         | 3
潮流服饰    | 180         | NULL
数码优品    | 100         | NULL

思路分析

第一步:先剔除退款,再叫号

退款单不参与排序——必须在 ROW_NUMBER() 之前用 WHERE status='completed' 过滤。如果退款单参与叫号,它会占掉号码,”第 1 单”可能变成退款单。

第二步:用 ROW_NUMBER 给有效订单编号

按商家分组,按下单时间排序,每个商家的有效订单从 1 开始编号。同一天有多单时补个 order_id 作为第二排序键,避免名次飘忽。

第三步:取前 3 号,名次换钱

CASE rn WHEN 1 THEN 100 WHEN 2 THEN 80 WHEN 3 THEN 50 END 再 SUM,就是激励金总额。跨越天数用 TIMESTAMPDIFF(DAY, 第1单时间, 第3单时间),不够 3 单给 NULL。

题解代码

WITH ranked AS (
    SELECT s.seller_id, s.seller_name, o.order_time,
           ROW_NUMBER() OVER (PARTITION BY o.seller_id
                              ORDER BY o.order_time, o.order_id) AS rn
    FROM c21_pdd_seller s
    JOIN c21_pdd_seller_order o ON s.seller_id = o.seller_id
    WHERE o.status = 'completed'
)
SELECT seller_name,
       SUM(CASE rn WHEN 1 THEN 100 WHEN 2 THEN 80 WHEN 3 THEN 50 END) AS total_bonus,
       CASE WHEN COUNT(*) = 3
            THEN TIMESTAMPDIFF(DAY,
                     MIN(CASE WHEN rn = 1 THEN order_time END),
                     MAX(CASE WHEN rn = 3 THEN order_time END))
            ELSE NULL END AS span_days
FROM ranked
WHERE rn <= 3
GROUP BY seller_id, seller_name
ORDER BY total_bonus DESC, seller_name;

复杂度分析

时间复杂度:$O(n \log n)$,窗口函数需要按商家分组排序。 空间复杂度:$O(n)$,存储中间排序结果。


第 3 题:用户会员等级升级日期与天数统计

题目描述

会员等级由累计消费决定:累计 1000 元升银卡、5000 元升金卡、20000 元升钻石。累计额首次达到门槛的当天即升级。统计每位用户达到各等级的日期,以及注册→银卡、银卡→金卡、金卡→钻石各阶段天数。

  • 未达门槛的日期和天数为 NULL
  • 每位用户都要输出(无订单也要展示)
  • 按用户 ID 升序排列

表结构

  • c21_pdd_member:用户表(user_id, register_date)
  • c21_pdd_member_order:订单表(order_id, user_id, order_date, amount)

样例

输入

-- U01:升到钻石(1/5→600, 1/10→1100, 2/1→5100, 3/1→20100)
-- U02:升到金卡(2/10→1200, 2/20→5200, 3/15→8200)
-- U03:累计不足1000(300+400=700)
-- U04:无订单

输出

user_id | silver_date | gold_date  | diamond_date | days_reg_to_silver | days_silver_to_gold | days_gold_to_diamond
U01     | 2026-01-10  | 2026-02-01 | 2026-03-01   | 9                  | 22                  | 28
U02     | 2026-02-10  | 2026-02-20 | NULL         | 9                  | 10                  | NULL
U03     | NULL        | NULL       | NULL         | NULL               | NULL                | NULL
U04     | NULL        | NULL       | NULL         | NULL               | NULL                | NULL

思路分析

第一步:按天累加消费

同一天可能多笔订单,先按 (user_id, order_date) 合并成日级汇总,再用窗口函数 SUM(day_amt) OVER (PARTITION BY user_id ORDER BY order_date) 做累计求和。累计只增不减(消费不可能为负),这是后续逻辑成立的前提。

第二步:取首次达标日

因为累计只增不减,一旦够线后面天天都够。所以 MIN(CASE WHEN running_total >= 门槛 THEN order_date END) 就能挑出满足条件的最早日期。

第三步:LEFT JOIN 保留无订单用户

U04 一单都没下,前面几步根本没有它的行。以用户表为主做 LEFT JOIN 把它捞回来,整行自动填 NULL。天数用 DATEDIFF,遇到 NULL 自动返回 NULL。

题解代码

WITH daily AS (
    SELECT user_id, order_date, SUM(amount) AS day_amt
    FROM c21_pdd_member_order
    GROUP BY user_id, order_date
),
cum AS (
    SELECT user_id, order_date,
           SUM(day_amt) OVER (PARTITION BY user_id ORDER BY order_date) AS running_total
    FROM daily
),
lvl AS (
    SELECT user_id,
           MIN(CASE WHEN running_total >= 1000  THEN order_date END) AS silver_date,
           MIN(CASE WHEN running_total >= 5000  THEN order_date END) AS gold_date,
           MIN(CASE WHEN running_total >= 20000 THEN order_date END) AS diamond_date
    FROM cum
    GROUP BY user_id
)
SELECT u.user_id, l.silver_date, l.gold_date, l.diamond_date,
       DATEDIFF(l.silver_date,  u.register_date) AS days_reg_to_silver,
       DATEDIFF(l.gold_date,    l.silver_date)   AS days_silver_to_gold,
       DATEDIFF(l.diamond_date, l.gold_date)     AS days_gold_to_diamond
FROM c21_pdd_member u
LEFT JOIN lvl l ON u.user_id = l.user_id
ORDER BY u.user_id;

复杂度分析

时间复杂度:$O(n \log n)$,窗口函数累计求和需排序。 空间复杂度:$O(n)$,存储日级汇总和累计结果。


小结

  • 第一题是 LEFT JOIN 的经典应用,核心是 COUNT(列名) 区分空组和 COUNT(*)
  • 第二题考查 ROW_NUMBER 分组 TopN 模式,关键是退款要在叫号之前过滤,排序键要唯一
  • 第三题用窗口函数做累计求和,再利用”累计只增不减”的性质用 MIN + CASE WHEN 取首次达标日