Условие
Таблицы:
friend_requests(sender_id, receiver_id, sent_date)friend_accepts(sender_id, receiver_id, accepted_date)
Для каждого месяца отправки верните acceptance rate: доля заявок, принятых в принципе (когда-либо), относительно отправленных в этот месяц.
Решение
WITH sent AS (
SELECT
DATE_TRUNC('month', sent_date)::DATE AS month,
sender_id, receiver_id
FROM friend_requests
),
joined AS (
SELECT
s.month,
s.sender_id, s.receiver_id,
CASE WHEN a.accepted_date IS NOT NULL THEN 1 ELSE 0 END AS accepted
FROM sent s
LEFT JOIN friend_accepts a
ON a.sender_id = s.sender_id
AND a.receiver_id = s.receiver_id
)
SELECT
month,
ROUND(AVG(accepted)::NUMERIC, 4) AS acceptance_rate
FROM joined
GROUP BY month
ORDER BY month;Тонкость с «когда-либо принято»
LEFT JOIN без фильтра по accepted_date — у заявки от мая 2024 может быть accept в июне 2025. Это всё ещё «принято». Если же хотим «принято в течение N дней» — нужен дополнительный фильтр в ON.
Дубли
Если на одну пару (sender, receiver) пришло несколько accepts (например, после повторной отправки) — LEFT JOIN задвоит. Защита:
LEFT JOIN (SELECT DISTINCT sender_id, receiver_id FROM friend_accepts) a
ON ...или EXISTS:
SELECT
DATE_TRUNC('month', sent_date)::DATE AS month,
ROUND(
AVG(CASE WHEN EXISTS (
SELECT 1 FROM friend_accepts a
WHERE a.sender_id = r.sender_id AND a.receiver_id = r.receiver_id
) THEN 1.0 ELSE 0 END), 4
) AS acceptance_rate
FROM friend_requests r
GROUP BY 1
ORDER BY 1;Подводные камни
- Перепутать sender/receiver. В оригинале одного направления:
(sender, receiver)— accept должен быть тот же ключ. LEFT JOINбез DISTINCT в правой. Задвоение accept-ов искажает знаменатель/числитель.- Заявки без accept остаются. Это правильно — они в знаменателе, но не в числителе.
- NULL acceptance. В первый месяц со 100% непринятыми —
AVG = 0, не NULL. С пустыми месяцами —0/0отсутствует.
Эталонный ответ
LEFT JOIN или EXISTS, AVG(accepted) по месяцу отправки. С защитой от дублей в accepts.