大厂真题 / 拼多多
拼多多 7.2 笔试真题 - 数据岗(SQL)
本场考试概述
考试时间:2026年7月2日 考试岗位:数据岗(数据分析/数据开发) 难度评级:简单到中等
考点分析:
- 第一题:各品类商品定价区间统计——LEFT JOIN 连表(难度简单)
- 第二题:新商家前3单广告激励金统计——ROW_NUMBER 分组 TopN(难度中等)
- 第三题:用户会员等级升级日期与天数统计——窗口函数累计求和(难度中等)
建议策略:
- 三题都围绕「主表要全保留 + 分组聚合」,
LEFT JOIN保空组、COUNT(列名)数真实值是通用套路 - 第二三题用窗口函数:
ROW_NUMBER()分组叫号取 TopN、SUM() OVER()累计求首次达标日,把PARTITION BY / ORDER BY填空模板练熟即可 - 注意空组处理:没有商品的品类用
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 取首次达标日