Ответ
Это классическая задача анализа воронки. Я решал ее с помощью SQL, соединяя таблицы событий. Ключевой момент — использовать LEFT JOIN для поиска отсутствующих действий, а не NOT IN, так как это обычно эффективнее на больших объемах данных.
Базовый SQL-запрос:
SELECT
DISTINCT pv.user_id
FROM product_page_views pv
LEFT JOIN cart_additions ca
ON pv.user_id = ca.user_id
AND pv.product_id = ca.product_id -- Важно: связывать по конкретному товару
AND ca.event_time >= pv.event_time -- Ищем добавления после просмотра
AND ca.event_time <= pv.event_time + INTERVAL '1 HOUR' -- В рамках сессии
WHERE ca.user_id IS NULL; -- Находим только тех, у кого НЕТ события добавления
Почему такой подход?
- Связь по
product_id: Пользователь мог просмотреть iPhone, но добавить в корзину AirPods. Нас интересуют именно те же товары. - Временное окно: Мы ограничиваем анализ разумным временным интервалом (например, 1 час), чтобы не учитывать просмотр и добавление, разделенные днями, как часть одной сессии.
LEFT JOIN ... IS NULL: Этот паттерн идеально подходит для поиска «отсутствующих» записей и обычно оптимизируется лучше, чемNOT INилиNOT EXISTSв современных СУБД.
Для более глубокого анализа я бы расширил запрос, чтобы получить метрики:
SELECT
pv.product_id,
COUNT(DISTINCT pv.user_id) AS total_viewers,
COUNT(DISTINCT ca.user_id) AS users_who_added,
COUNT(DISTINCT pv.user_id) - COUNT(DISTINCT ca.user_id) AS users_who_did_not_add,
ROUND(
COUNT(DISTINCT ca.user_id) * 100.0 / NULLIF(COUNT(DISTINCT pv.user_id), 0),
2
) AS view_to_cart_conversion_rate
FROM product_page_views pv
LEFT JOIN cart_additions ca
ON pv.user_id = ca.user_id AND pv.product_id = ca.product_id
AND ca.event_time BETWEEN pv.event_time AND pv.event_time + INTERVAL '1 HOUR'
GROUP BY pv.product_id
ORDER BY users_who_did_not_add DESC;
Этот запрос покажет по каждому товару, сколько пользователей «отвалилось» на этом шаге, и поможет выявить проблемные позиции с низкой конверсией в корзину.