题目重点:按餐厅统计完成订单,明确比值、占比、零分母和无订单餐厅。
题意与假设
这道练习题保留了面经中值得训练的核心约束,并将需要澄清的条件单独列出。现场拿到变体时,先和面试官确认这些条件。
- 训练orders一行一订单,restaurant一行一餐厅;只算completed的pickup/delivery。pickup/delivery ratio与pickup/(pickup+delivery) share不同,示例都返回。
- 无delivery的ratio为NULL,无任何两类订单的share为NULL,不是0;真实NULL表示未定义。DDL与status值为示例合同。
解题路线
- 从restaurant出发LEFT JOIN订单,在ON中筛状态和履约类型,保留没有订单的餐厅。若WHERE筛o.status,会把空右表行去掉,意外退化inner join。
- CASE WHEN分别累加pickup与delivery,COUNT(o.order_id)得到匹配订单总数;不要COUNT(*),left join空订单仍有一行会污染分母。
- 用1.0保证数值除法,NULLIF(denominator,0)保护未定义比值。输出数量便于对账和识别低样本ratio波动。
- 如果order_items也join,先按order聚合或按orderId去重;一单多菜行会把订单计数放大,不能因为原题只有两表就忽略扩展join粒度。
手动走一遍
pickup3、delivery1,ratio3而share0.75。pickup2、delivery0,ratioNULL而share1;两类订单都0,则ratio/share都NULL,无订单餐厅仍显示0/0计数。
SQL 参考实现
下面的代码按上文假设实现,适合本地运行并追加自己的测试用例。
代码区可横向滚动;使用键盘时,先聚焦代码区再按方向键查看。
-- Training: orders(order_id PK, restaurant_id, fulfillment, net_revenue, status)
-- restaurant(restaurant_id PK, restaurant_type). Completed delivery/pickup only.
-- Groups with no delivery get NULL ratio; pickup_share counts pickup/(pickup+delivery).
SELECT r.restaurant_id,
SUM(CASE WHEN o.fulfillment='pickup' THEN 1 ELSE 0 END) AS pickup_orders,
SUM(CASE WHEN o.fulfillment='delivery' THEN 1 ELSE 0 END) AS delivery_orders,
1.0 * SUM(CASE WHEN o.fulfillment='pickup' THEN 1 ELSE 0 END)
/ NULLIF(SUM(CASE WHEN o.fulfillment='delivery' THEN 1 ELSE 0 END),0) AS pickup_delivery_ratio,
1.0 * SUM(CASE WHEN o.fulfillment='pickup' THEN 1 ELSE 0 END)
/ NULLIF(COUNT(o.order_id),0) AS pickup_share
FROM restaurant r
LEFT JOIN orders o ON o.restaurant_id=r.restaurant_id
AND o.status='completed' AND o.fulfillment IN ('pickup','delivery')
GROUP BY r.restaurant_id
ORDER BY r.restaurant_id;
复杂度
扫描订单与餐厅并按主键hash/索引join,聚合按餐厅数状态;平均约O(O+R),具体由查询计划决定。按日期切片需在ON中过滤并明确时区,不加未指定时间条件。
易错点与追问
容易失分的边界
- 把ratio误写成share,数字看似合理但回答对象错误。
- LEFT JOIN之后WHERE右表条件漏掉无订单餐厅。
继续深挖
- 要求ratio>1的餐厅比例时,应基于餐厅聚合再做第二层比例,不能直接把订单当餐厅分母。
- 取消订单和退款算已完成需求还是排除,需与指标目标一致。
