Настройка рекомендаций товаров на основе истории покупок 1С-Битрикс

Наша компания занимается разработкой, поддержкой и обслуживанием решений на Битрикс и Битрикс24 любой сложности. От простых одностраничных сайтов до сложных интернет магазинов, CRM систем с интеграцией 1С и телефонии. Опыт разработчиков подтвержден сертификатами от вендора.
Предлагаемые услуги
Показано 1 из 1 услугВсе 1626 услуг
Настройка рекомендаций товаров на основе истории покупок 1С-Битрикс
Простая
~1 рабочий день
Часто задаваемые вопросы
Наши компетенции:
Этапы разработки
Последние работы
  • image_website-b2b-advance_0.png
    Разработка сайта компании B2B ADVANCE
    1181
  • image_bitrix-bitrix-24-1c_fixper_448_0.png
    Разработка веб-сайта для компании ФИКСПЕР
    813
  • image_bitrix-bitrix-24-1c_development_of_an_online_appointment_booking_widget_for_a_medical_center_594_0.webp
    Разработка на базе Битрикс, Битрикс24, 1С для компании Development of an Online Appointment Booking Widget for a Medical Center
    564
  • image_bitrix-bitrix-24-1c_mirsanbel_458_0.webp
    Разработка на базе 1С Предприятие для компании МИРСАНБЕЛ
    747
  • image_crm_dolbimby_434_0.webp
    Разработка сайта на CRM Битрикс24 для компании DOLBIMBY
    655
  • image_crm_technotorgcomplex_453_0.webp
    Разработка на базе Битрикс24 для компании ТЕХНОТОРГКОМПЛЕКС
    976

Настройка рекомендаций товаров на основе истории покупок 1С-Битрикс

История покупок — самый сильный поведенческий сигнал. Пользователь уже заплатил деньги — это не просмотр, не клик, это подтверждённый интерес. Рекомендации на основе истории покупок работают по двум паттернам: «покупатели этого товара также купили» (item-based) и «ваши прошлые покупки похожи на покупки пользователей X, они взяли ещё Y» (user-based). Оба паттерна реализуются прямо в Битриксе без внешних ML-сервисов.

Таблицы с данными о покупках

Вся история заказов в Битриксе — три ключевые таблицы:

  • b_sale_order — заказы: поля USER_ID, CANCELED, STATUS_ID, PRICE
  • b_sale_order_basket — состав заказов: ORDER_ID, PRODUCT_ID, QUANTITY, PRICE
  • b_catalog_product — наличие товара: QUANTITY, AVAILABLE

Для рекомендаций используем только незаменённые заказы (CANCELED = 'N') в финальных статусах. Статус F (Finished) — стандартный финальный, но на многих проектах используются кастомные статусы.

Item-based: «с этим часто покупают»

Основной паттерн — блок «Вместе с этим товаром покупают» на карточке товара:

SELECT
    ob2.PRODUCT_ID,
    COUNT(DISTINCT ob1.ORDER_ID) AS co_purchase_count,
    SUM(ob2.QUANTITY)            AS total_qty
FROM b_sale_order_basket ob1
JOIN b_sale_order_basket ob2
    ON ob1.ORDER_ID = ob2.ORDER_ID
    AND ob2.PRODUCT_ID != ob1.PRODUCT_ID
JOIN b_sale_order o
    ON o.ID = ob1.ORDER_ID
    AND o.CANCELED = 'N'
    AND o.DATE_INSERT > NOW() - INTERVAL '90 days'
WHERE ob1.PRODUCT_ID = :target_product_id
GROUP BY ob2.PRODUCT_ID
ORDER BY co_purchase_count DESC
LIMIT 20;

Этот запрос выполняется офлайн через агент Битрикса — раз в 4 часа. Результат пишется в таблицу:

CREATE TABLE b_product_cross_sell (
    SOURCE_ID        INT NOT NULL,
    RECOMMENDED_ID   INT NOT NULL,
    SCORE            INT NOT NULL,
    UPDATED_AT       TIMESTAMP DEFAULT NOW(),
    PRIMARY KEY (SOURCE_ID, RECOMMENDED_ID)
);
CREATE INDEX idx_cross_sell_source ON b_product_cross_sell(SOURCE_ID, SCORE DESC);

Индекс (PRODUCT_ID, ORDER_ID) на b_sale_order_basket критически важен — без него JOIN на больших магазинах (100k+ заказов) будет выполняться секундами.

User-based: персональные рекомендации для авторизованного пользователя

Для конкретного пользователя строится список товаров, которые купили «похожие» покупатели. «Похожесть» — пересечение истории покупок.

function getUserBasedRecs(int $userId, int $limit = 8): array {
    // 1. История покупок текущего пользователя
    $myOrderIds = array_column(
        \Bitrix\Sale\OrderTable::getList([
            'filter' => ['USER_ID' => $userId, 'CANCELED' => 'N'],
            'select' => ['ID'],
        ])->fetchAll(),
        'ID'
    );

    if (empty($myOrderIds)) return getPopularItems($limit);

    $myProductIds = array_column(
        \Bitrix\Sale\Internals\BasketTable::getList([
            'filter' => ['ORDER_ID' => $myOrderIds],
            'select' => ['PRODUCT_ID'],
        ])->fetchAll(),
        'PRODUCT_ID'
    );

    // 2. Пользователи, купившие те же товары
    // 3. Товары этих пользователей, которых у нас нет
    $res = $GLOBALS['DB']->Query("
        SELECT ob2.PRODUCT_ID, COUNT(DISTINCT o2.USER_ID) AS score
        FROM b_sale_order_basket ob1
        JOIN b_sale_order o1 ON o1.ID = ob1.ORDER_ID AND o1.USER_ID = {$userId}
        JOIN b_sale_order_basket ob2 ON ob2.ORDER_ID IN (
            SELECT DISTINCT o3.ID FROM b_sale_order o3
            JOIN b_sale_order_basket ob3 ON ob3.ORDER_ID = o3.ID
                AND ob3.PRODUCT_ID IN (" . implode(',', array_map('intval', $myProductIds)) . ")
            WHERE o3.USER_ID != {$userId} AND o3.CANCELED = 'N'
        )
        WHERE ob2.PRODUCT_ID NOT IN (" . implode(',', array_map('intval', $myProductIds)) . ")
        GROUP BY ob2.PRODUCT_ID
        ORDER BY score DESC
        LIMIT {$limit}
    ");

    $ids = [];
    while ($row = $res->Fetch()) $ids[] = (int)$row['PRODUCT_ID'];
    return $ids;
}

Фильтрация рекомендованных товаров

Рекомендованные ID передаются в финальный фильтр перед отображением — убрать неактивные, снятые с продажи, с нулевым остатком:

$availableIds = \CIBlockElement::GetList(
    ['SORT' => 'ASC'],
    [
        'ID'        => $recommendedIds,
        'ACTIVE'    => 'Y',
        'IBLOCK_ID' => CATALOG_IBLOCK_ID,
        '>CATALOG_QUANTITY' => 0,
    ],
    false,
    ['nTopCount' => 8],
    ['ID']
)->fetchAll();

Кеш и инвалидация

Кеш item-based рекомендаций: по PRODUCT_ID, TTL = 4 часа (синхронно с агентом обновления). Кеш user-based: по USER_ID, TTL = 30 минут — короче, потому что история пользователя меняется. Инвалидация: при сохранении нового заказа (OnSaleOrderSaved) сбрасывать кеш для всех товаров из заказа через тег product_recs_{id}.