Количество товаров в разделе - подсчёт, вложенность, кэш
Показываем число товаров рядом с разделом каталога: штатный счётчик, учёт подразделов и наличия, кэш результата и пересчёт после обмена.
Что нужно знать заранее
Выборка разделов умеет считать элементы сама, без дополнительных запросов. Отдельный запрос на каждый раздел не нужен, и это первое, что стоит проверить в чужом коде меню каталога.
Штатный счётчик считает только элементы самого раздела, без его подразделов. Товары вложенных разделов в него не попадают, и для дерева это почти всегда не то, что нужно.
Подсчёт элементов заметно дороже обычной выборки разделов каталога. На каталоге с сотней разделов и десятками тысяч товаров он заметен в замерах, поэтому результат кэшируют.
Шаги
- Решить, что считать: элементы раздела или всю его ветку с подразделами.
- Учесть в подсчёте активность, даты показа и наличие, если это важно витрине.
- Взять счётчик штатной выборкой, а не запросом на каждый раздел в цикле.
- Обернуть результат подсчёта кэшем и сбрасывать его вместе с кэшем каталога.
- Пересчитывать счётчики после обмена данными, а не при каждом заходе посетителя.
Решение
Берём счётчик штатной выборкой:
$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));// обмен меняет товары пачками, и счётчики после него устаревают разомТипичные проблемы
Рядом с разделами показываются нули.
В выборке разделов не включён подсчёт элементов третьим аргументом. Поле с количеством элементов появляется только при включённом подсчёте.
В родительском разделе меньше товаров, чем в подразделах.
Штатный счётчик считает только элементы самого раздела без всех вложенных. Для целой ветки считают отдельной выборкой элементов с учётом подразделов.
Меню каталога стало открываться заметно дольше.
Число товаров считается отдельным запросом на каждый раздел прямо в шаблоне. Счётчики берут одной общей выборкой и кэшируют полученный результат целиком.
Числа не совпадают с выдачей раздела.
В подсчёте не учтены те же условия, что и на витрине: наличие, даты, свойства. Условия подсчёта и условия отбора на витрине держат в одном месте кода.
После обмена счётчики неделю показывают старое.
Кэш счётчиков не привязан к тегу инфоблока и живёт до истечения срока. Кэш счётчиков помечают тегом каталога и сбрасывают по завершении обмена.
Частые вопросы
Почему количество не приходит в выборке?
Подсчёт включается отдельным аргументом выборки разделов. Без него поле с числом элементов в ответе отсутствует.
Как посчитать товары всей ветки?
Выборкой элементов с указанием раздела и признака учёта подразделов. Штатный счётчик разделов считает только сам раздел.
Считать все товары или только в наличии?
Покупателю честнее показывать доступные к покупке. Общее число полезно менеджеру, и его показывают в административной части.
Нужно ли хранить счётчик в поле раздела?
На большом каталоге - да, вместе с пересчётом по расписанию. На среднем хватает кэша с тегом инфоблока.
Совпадут ли числа с умным фильтром?
Нет, фильтр считает по своему индексу и с учётом выбранных значений. Это разные механизмы, и одинаковых чисел от них ждать не стоит.
Смежное
- Разделы инфоблока - оглавление подтемы
- Вывод разделов: дерево, подразделы, адреса - как выводят само дерево
- Раздел не выводится: разбор причин - когда раздела нет в списке
- Количество товаров в фильтре: фасетный индекс и пустые значения - числа в умном фильтре
- Кэширование своей выборки: ключ, теги, сброс - устройство кэша с тегами
- Тегированный кэш: сброс по изменению данных, а не по расписанию - как работает сброс по тегу
- Кэш меню: разрастание каталога, тяжёлые запросы, настройка - где такие счётчики обычно и живут
- Инфоблоки - устройство инфоблоков целиком