Условие
В Учи.ру дети решают задания, доступ платный. Учитель может включить функцию «Урок», и в течение часа все задания становятся бесплатными.
Посчитать количество учеников, у которых доля заданий, решённых во время функции «Урок», составляет более 50% от общего числа решённых ими заданий.
Таблицы:
sessions(card_id, session_time, user_id, teacher_id)— решённые задания.lessons(teacher_id, lesson_start, lesson_end)— окна включённой функции.
Решение
Подход
- Для каждого решённого задания узнать: было ли это в окне
lesson_start..lesson_endпо тому жеteacher_id. - На уровне ученика посчитать долю:
n_lesson / n_total. - Отфильтровать долю > 50%.
- Подсчитать число учеников.
SQL
WITH flagged AS (
SELECT
s.user_id,
s.card_id,
CASE WHEN EXISTS (
SELECT 1 FROM lessons l
WHERE l.teacher_id = s.teacher_id
AND s.session_time >= l.lesson_start
AND s.session_time < l.lesson_end
) THEN 1 ELSE 0 END AS in_lesson
FROM sessions s
),
agg AS (
SELECT
user_id,
SUM(in_lesson) AS in_lesson_cnt,
COUNT(*) AS total_cnt,
1.0 * SUM(in_lesson) / COUNT(*) AS share
FROM flagged
GROUP BY user_id
)
SELECT COUNT(*) AS n_users
FROM agg
WHERE share > 0.5;Альтернатива через JOIN
WITH flagged AS (
SELECT
s.user_id, s.card_id,
MAX(CASE WHEN l.teacher_id IS NOT NULL THEN 1 ELSE 0 END) AS in_lesson
FROM sessions s
LEFT JOIN lessons l
ON l.teacher_id = s.teacher_id
AND s.session_time >= l.lesson_start
AND s.session_time < l.lesson_end
GROUP BY s.user_id, s.card_id
)
...Подводные камни
- У ученика может быть несколько учителей в разное время — учитывать
teacher_idсессии, а не «последнего учителя». - Если окно
lesson_end < lesson_start(бракованные данные) —WHERE l.lesson_end > l.lesson_startдля безопасности. - Совмещение:
EXISTSбыстрее, чемLEFT JOIN ... GROUP BYс дублями (если у учителя несколько lessons, одно задание попадёт в несколько строк). > 50%строго или>= 50%? Уточнять.- На больших данных индекс по
(teacher_id, lesson_start)критичен.
Эталонный ответ
Помечаем in_lesson через EXISTS по teacher_id и временному диапазону, считаем долю на ученика, фильтруем > 0.5, считаем количество.