Как найти пользователей, которые просмотрели товар, но не добавили его в корзину?

«Как найти пользователей, которые просмотрели товар, но не добавили его в корзину?» — вопрос из категории Аналитика и метрики, который задают на 33% собеседований Data Инженер. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

Это классическая задача анализа воронки. Я решал ее с помощью 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; -- Находим только тех, у кого НЕТ события добавления

Почему такой подход?

  1. Связь по product_id: Пользователь мог просмотреть iPhone, но добавить в корзину AirPods. Нас интересуют именно те же товары.
  2. Временное окно: Мы ограничиваем анализ разумным временным интервалом (например, 1 час), чтобы не учитывать просмотр и добавление, разделенные днями, как часть одной сессии.
  3. 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;

Этот запрос покажет по каждому товару, сколько пользователей «отвалилось» на этом шаге, и поможет выявить проблемные позиции с низкой конверсией в корзину.