グループ内最小値抽出 + 条件集計 による O(N log N) 実装
食品配達サービスにおいて、各顧客の最初の注文が即日配達(order_date = customer_pref_delivery_date)だった割合を求めます。 即日配達の場合は「immediate」、予約配達の場合は「scheduled」として分類されます。
Delivery table:
+-------------+-------------+------------+-----------------------------+
| delivery_id | customer_id | order_date | customer_pref_delivery_date |
+-------------+-------------+------------+-----------------------------+
| 1 | 1 | 2019-08-01 | 2019-08-02 |
| 2 | 2 | 2019-08-02 | 2019-08-02 |
| 3 | 1 | 2019-08-11 | 2019-08-12 |
| 4 | 3 | 2019-08-24 | 2019-08-24 |
| 5 | 3 | 2019-08-21 | 2019-08-22 |
| 6 | 2 | 2019-08-11 | 2019-08-13 |
| 7 | 4 | 2019-08-09 | 2019-08-09 |
+-------------+-------------+------------+-----------------------------+
+----------------------+
| immediate_percentage |
+----------------------+
| 50.00 |
+----------------------+
WITH first_orders AS (
SELECT
customer_id,
order_date,
customer_pref_delivery_date,
ROW_NUMBER() OVER (
PARTITION BY customer_id
ORDER BY order_date
) AS rn
FROM Delivery
)
SELECT
ROUND(
100.0 * SUM(CASE WHEN order_date = customer_pref_delivery_date THEN 1 ELSE 0 END)
/ COUNT(*),
2
) AS immediate_percentage
FROM first_orders
WHERE rn = 1;
import pandas as pd
def immediate_food_delivery(delivery: pd.DataFrame) -> pd.DataFrame:
"""
各顧客の最初の注文における即日配達の割合を計算
Args:
delivery: 配達情報 (delivery_id, customer_id, order_date, customer_pref_delivery_date)
Returns:
pd.DataFrame: 列名は ['immediate_percentage']、1行のみ
"""
# 各顧客の最初の注文(order_dateが最小)のインデックスを取得
first_order_idx = delivery.groupby('customer_id')['order_date'].idxmin()
# 最初の注文のみを抽出
first_orders = delivery.loc[first_order_idx, ['order_date', 'customer_pref_delivery_date']]
# 即日配達判定(order_date == customer_pref_delivery_date)
is_immediate = (first_orders['order_date'] == first_orders['customer_pref_delivery_date'])
# 割合を計算(パーセンテージ、小数点2桁)
percentage = round(100.0 * is_immediate.sum() / len(is_immediate), 2)
return pd.DataFrame({'immediate_percentage': [percentage]})
フローの説明:
1.
入力: Deliveryテーブルから全配達記録を読み込み
2.
グループ化: customer_idでグループを作成(PARTITION BY)
3.
順位付け:
各グループ内でorder_dateの昇順にROW_NUMBERを付与
4.
抽出: rn = 1(最初の注文)のみをフィルタ
5.
条件判定: order_date = customer_pref_delivery_date
か確認
6a.
はい → 即日配達カウントに加算
6b.
いいえ → 予約配達としてスキップ
7.
集計: 即日配達の件数を総数で割って100倍
8.
丸め: ROUND(..., 2) で小数点2桁に丸めて出力
| 項目 | 本実装(Window Function) | 代替案(Subquery) |
|---|---|---|
| 時間計算量 | O(N log N) | O(N × 顧客数) |
| 空間計算量 | O(顧客数) | O(N) |
| データベーススキャン | 1回 | 顧客数分 |
| 実装の簡潔さ | ★★★★★ | ★★★☆☆ |
| インデックス活用 | 効率的 | 非効率 |