Розробка блоку "з цим товаром купують" 1С-Бітрікс
«З цим товаром купують» — блок, який показує товари, що реально придбавалися разом із поточним у рамках одного замовлення. Це не «схожі товари» і не «раніше переглянуті» — це статистика спільних покупок. Правильно реалізований блок збільшує середній чек, адже пропонує те, що дійсно потрібно покупцеві в контексті обраного товару.
Джерело даних: таблиці замовлень Бітрікс
Дані беремо з реальних замовлень. Ключові таблиці:
-
b_sale_order— замовлення (статус, дата, користувач). -
b_sale_basket— позиції в замовленнях (товар, кількість, ціна).
Логіка: знаходимо всі замовлення, в яких було куплено товар A, потім дивимося, які інші товари зустрічалися в цих самих замовленнях. Чим частіше — тим вище в рекомендаціях.
SELECT
b2.product_id AS recommended_id,
COUNT(DISTINCT b2.order_id) AS co_purchase_count
FROM b_sale_basket b1
JOIN b_sale_order o ON b1.order_id = o.id
AND o.canceled = 'N'
AND o.status_id NOT IN ('F') -- виключаємо скасовані статуси
JOIN b_sale_basket b2 ON b1.order_id = b2.order_id
AND b2.product_id != b1.product_id
AND b2.product_id IS NOT NULL
WHERE
b1.product_id = :productId
AND o.date_insert >= DATE_SUB(NOW(), INTERVAL 90 DAY)
GROUP BY b2.product_id
HAVING co_purchase_count >= 3 -- мінімальний поріг для надійності
ORDER BY co_purchase_count DESC
LIMIT 20;
Обмеження вибірки 90 днями — щоб асортиментні зміни відображалися в рекомендаціях швидше.
Попередній розрахунок і кеш
Запускати цей SQL при кожному відкритті картки товару не можна — надто важко для активного каталогу. Попередньо розраховуємо для топ-N товарів і зберігаємо результат.
CREATE TABLE custom_co_purchases (
product_id INT NOT NULL,
recommended_id INT NOT NULL,
score INT NOT NULL,
calculated_at DATETIME DEFAULT NOW(),
PRIMARY KEY (product_id, recommended_id),
INDEX idx_product (product_id)
);
Агент Бітрікс запускається раз на добу в нічний час, перераховує таблицю для товарів, що продавалися за останні 90 днів.
// Агент у local/php_interface/init.php
function RecalcCoPurchasesAgent(): string {
$topProducts = getTopSellingProducts(500); // топ-500 товарів
foreach ($topProducts as $productId) {
$recs = calcCoPurchases($productId);
saveToCoPurchases($productId, $recs);
}
return 'RecalcCoPurchasesAgent();'; // агент продовжує
}
Компонент відображення
Створюємо компонент company:catalog.co_purchases у local/components/:
// component.php
if (!\Bitrix\Main\Loader::includeModule('iblock') || !\Bitrix\Main\Loader::includeModule('catalog')) {
return;
}
$productId = (int)$arParams['PRODUCT_ID'];
$limit = (int)($arParams['LIMIT'] ?? 8);
// Читаємо з кешу
$cache = \Bitrix\Main\Data\Cache::createInstance();
if ($cache->initCache(3600, "co_purchases_{$productId}_{$limit}", '/co_purchases')) {
$arResult = $cache->getVars();
} elseif ($cache->startDataCache()) {
$recommendedIds = getFromCoPurchasesTable($productId, $limit);
$arResult = getProductsByIds($recommendedIds);
$cache->endDataCache($arResult);
}
$this->IncludeComponentTemplate();
Фільтрація та валідність рекомендацій
Перед відображенням фільтруємо рекомендовані товари:
function filterValidRecommendations(array $productIds): array {
if (empty($productIds)) return [];
// Лише активні товари з ненульовим залишком
$filter = [
'ID' => $productIds,
'ACTIVE' => 'Y',
'>CATALOG_QUANTITY' => 0,
];
$result = [];
$res = \CIBlockElement::GetList([], $filter, false, false, ['ID']);
while ($row = $res->Fetch()) {
$result[] = $row['ID'];
}
return $result;
}
Якщо у товару недостатньо спільних покупок (наприклад, новий товар) — показуємо товари з тієї самої категорії як fallback.
Холодний старт
Для нових товарів або магазинів з малою історією замовлень — використовуємо content-based fallback:
function getRecommendations(int $productId, int $limit): array {
$coPurchases = getFromCoPurchasesTable($productId, $limit);
if (count($coPurchases) >= $limit) {
return $coPurchases;
}
// Доповнюємо товарами з тієї самої категорії
$needed = $limit - count($coPurchases);
$exclude = array_merge([$productId], $coPurchases);
$categoryFill = getSameCategoryProducts($productId, $needed, $exclude);
return array_merge($coPurchases, $categoryFill);
}
Відображення в кошику
Блок працює і в кошику — «до товарів у вашому кошику часто купують». Логіка: беремо всі товари з кошика, об'єднуємо їхні рекомендації, ранжуємо за сумарним score, виключаємо вже додані в кошик.
$basketItems = \Bitrix\Sale\Basket::loadItemsForFUser(\Bitrix\Sale\Fuser::getId());
$basketIds = [];
foreach ($basketItems as $item) {
$basketIds[] = $item->getProductId();
}
$allRecs = [];
foreach ($basketIds as $id) {
$recs = getFromCoPurchasesTable($id, 20);
foreach ($recs as $rec) {
$allRecs[$rec['recommended_id']] = ($allRecs[$rec['recommended_id']] ?? 0) + $rec['score'];
}
}
// Виключаємо товари з кошика
foreach ($basketIds as $id) unset($allRecs[$id]);
arsort($allRecs);
$topRecs = array_slice(array_keys($allRecs), 0, 8);
Терміни
| Етап | Термін |
|---|---|
| SQL розрахунку спільних покупок + таблиця | 1–2 дні |
| Агент попереднього розрахунку | 1–2 дні |
| Компонент із кешуванням та fallback | 2–3 дні |
| Блок у кошику | 1–2 дні |
| Тестування та тюнінг порогів | 1–2 дні |
Разом: 1–1.5 тижні.







