Перейти к содержимому

Количество товаров в разделе - подсчёт, вложенность, кэш

Показываем число товаров рядом с разделом каталога: штатный счётчик, учёт подразделов и наличия, кэш результата и пересчёт после обмена.

Что нужно знать заранее

Выборка разделов умеет считать элементы сама, без дополнительных запросов. Отдельный запрос на каждый раздел не нужен, и это первое, что стоит проверить в чужом коде меню каталога.

Штатный счётчик считает только элементы самого раздела, без его подразделов. Товары вложенных разделов в него не попадают, и для дерева это почти всегда не то, что нужно.

Подсчёт элементов заметно дороже обычной выборки разделов каталога. На каталоге с сотней разделов и десятками тысяч товаров он заметен в замерах, поэтому результат кэшируют.

Шаги

  1. Решить, что считать: элементы раздела или всю его ветку с подразделами.
  2. Учесть в подсчёте активность, даты показа и наличие, если это важно витрине.
  3. Взять счётчик штатной выборкой, а не запросом на каждый раздел в цикле.
  4. Обернуть результат подсчёта кэшем и сбрасывать его вместе с кэшем каталога.
  5. Пересчитывать счётчики после обмена данными, а не при каждом заходе посетителя.

Решение

Берём счётчик штатной выборкой:

$rs = \CIBlockSection::GetList(['SORT' => 'ASC'],
['IBLOCK_ID' => $iblockId, 'ACTIVE' => 'Y', 'CNT_ACTIVE' => 'Y'],
true, // третий аргумент включает подсчёт
['ID', 'NAME', 'ELEMENT_CNT']);
while ($row = $rs->Fetch()) { printf("%-30s %d\n", $row['NAME'], $row['ELEMENT_CNT']); }

Третий аргумент выборки и включает счётчик. Без него поля с количеством в ответе просто не будет, и это самая частая причина пустого числа рядом с названием раздела.

Считаем ветку вместе с подразделами:

$cnt = \CIBlockElement::GetList([], ['IBLOCK_ID' => $iblockId, 'ACTIVE' => 'Y',
'SECTION_ID' => $sectionId, 'INCLUDE_SUBSECTIONS' => 'Y'], []);
printf("в ветке: %d\n", $cnt);
// пустой третий аргумент означает «посчитай, но не выбирай данные»

Учитываем наличие товара:

$cnt = \CIBlockElement::GetList([], ['IBLOCK_ID' => $iblockId, 'ACTIVE' => 'Y',
'SECTION_ID' => $sectionId, 'INCLUDE_SUBSECTIONS' => 'Y',
'>CATALOG_QUANTITY' => 0], []);
// покупателю показывают число доступных товаров, а не всех заведённых

Число «в наличии» честнее общего количества. Раздел с тысячей позиций и нулём доступных раздражает покупателя сильнее, чем честная надпись о временном отсутствии товара.

Кэшируем результат подсчёта:

$cache = \Bitrix\Main\Data\Cache::createInstance();
if ($cache->initCache(3600, 'sect_cnt_' . $iblockId, '/vendor/catalog')) {
$counts = $cache->getVars();
} elseif ($cache->startDataCache()) {
$counts = collectSectionCounts($iblockId); // тяжёлая часть внутри кэша
$taggedCache = \Bitrix\Main\Application::getInstance()->getTaggedCache();
$taggedCache->registerTag('iblock_id_' . $iblockId);
$cache->endDataCache($counts);
}

Тег инфоблока сбрасывает счётчики разделов вместе с кэшем всего каталога. Тогда после правки товара числа обновляются сами, а посетитель не ждёт пересчёта на своей странице.

Пересчитываем после обмена:

\Bitrix\Main\EventManager::getInstance()->addEventHandler('catalog',
'OnSuccessCatalogImport1C', static fn() =>
\Bitrix\Main\Application::getInstance()->getTaggedCache()
->clearByTag('iblock_id_' . IBLOCK_ID));
// обмен меняет товары пачками, и счётчики после него устаревают разом

Типичные проблемы

Рядом с разделами показываются нули.

В выборке разделов не включён подсчёт элементов третьим аргументом. Поле с количеством элементов появляется только при включённом подсчёте.

В родительском разделе меньше товаров, чем в подразделах.

Штатный счётчик считает только элементы самого раздела без всех вложенных. Для целой ветки считают отдельной выборкой элементов с учётом подразделов.

Меню каталога стало открываться заметно дольше.

Число товаров считается отдельным запросом на каждый раздел прямо в шаблоне. Счётчики берут одной общей выборкой и кэшируют полученный результат целиком.

Числа не совпадают с выдачей раздела.

В подсчёте не учтены те же условия, что и на витрине: наличие, даты, свойства. Условия подсчёта и условия отбора на витрине держат в одном месте кода.

После обмена счётчики неделю показывают старое.

Кэш счётчиков не привязан к тегу инфоблока и живёт до истечения срока. Кэш счётчиков помечают тегом каталога и сбрасывают по завершении обмена.

Частые вопросы

Почему количество не приходит в выборке?

Подсчёт включается отдельным аргументом выборки разделов. Без него поле с числом элементов в ответе отсутствует.

Как посчитать товары всей ветки?

Выборкой элементов с указанием раздела и признака учёта подразделов. Штатный счётчик разделов считает только сам раздел.

Считать все товары или только в наличии?

Покупателю честнее показывать доступные к покупке. Общее число полезно менеджеру, и его показывают в административной части.

Нужно ли хранить счётчик в поле раздела?

На большом каталоге - да, вместе с пересчётом по расписанию. На среднем хватает кэша с тегом инфоблока.

Совпадут ли числа с умным фильтром?

Нет, фильтр считает по своему индексу и с учётом выбранных значений. Это разные механизмы, и одинаковых чисел от них ждать не стоит.

Смежное

Первоисточник