Сколько элементов в разделе инфоблока в D7: SectionElementTable и агент с UF-счётчиками
Зачем SectionElementTable
Элемент может быть привязан к нескольким разделам. Таблица \Bitrix\Iblock\SectionElementTable отражает именно связку «раздел ↔ элемент»: одна строка на пару. Агрегат COUNT(*) по фильтру по разделу даёт число активных элементов с учётом множественных привязок — без двойного подсчёта «по элементу», если нужна логика на уровне связей раздел–элемент.
Перед кодом всегда подключают модуль: \Bitrix\Main\Loader::includeModule('iblock');
Разовый подсчёт для одного раздела
Ниже — вымышленные идентификаторы: инфоблок $catalogPk = 10, раздел $leafSectionPk = 989. В фильтре оставлены только активные элементы этого инфоблока:
use Bitrix\Main\Loader;
use Bitrix\Main\ORM\Fields\ExpressionField;
use Bitrix\Iblock\SectionElementTable;
Loader::includeModule('iblock');
$catalogPk = 10;
$leafSectionPk = 989;
$aggregate = SectionElementTable::getList([
'runtime' => [
new ExpressionField('TALLY', 'COUNT(*)'),
],
'filter' => [
'=IBLOCK_SECTION_ID' => $leafSectionPk,
'IBLOCK_ELEMENT.ACTIVE' => 'Y',
'=IBLOCK_ELEMENT.IBLOCK_ID' => $catalogPk,
],
'select' => ['TALLY'],
]);
$directQty = 0;
if ($row = $aggregate->fetch()) {
$directQty = (int)$row['TALLY'];
}Смысл runtime — объявить псевдополе с агрегатной функцией, которое затем можно вывести в select.
Хранить числа на разделе: задача для агента
Иногда в шаблоне каталога не хочется каждый раз гонять COUNT: проще два пользовательских поля раздела — сколько активных элементов «здесь» и сколько суммарно «здесь + подразделы». Названия полей в примере: UF_ELEMENT_COUNT и UF_ELEMENT_RECURSIVE_COUNT; их нужно завести в настройках инфоблока для разделов.
Идея обхода: сортировка разделов по DEPTH_LEVEL DESC, чтобы дочерние обрабатывались раньше родителей. Для каждого раздела считают прямой $directQty, кладут в стек по ID, затем один проход суммирует вклад каждого раздела в цепочку родителей. В конце через CIBlockSection::Update записывают только изменённые значения.
Полностью класс агента (исправление логики сравнения)
В распространённом варианте исходного сниппета при сравнении для рекурсивного счётчика ошибочно сравнивают с тем же свойствем, что и для прямого. Ниже в условии обновления рекурсии используют именно UF_ELEMENT_RECURSIVE_COUNT. Пространство имён и номер инфоблока подставьте свои:
namespace Partner\IblockAssist;
use Bitrix\Main\Loader;
use Bitrix\Main\ORM\Fields\ExpressionField;
use Bitrix\Iblock\SectionElementTable;
use Bitrix\Iblock\Model\Section;
use CIBlockSection;
class SectionLeafAndSubtreeCounters
{
public static function runAgent(): string
{
if (!Loader::includeModule('iblock')) {
return '\\'.__METHOD__.'();';
}
$catalogPk = 10;
/** @var class-string|\Bitrix\Main\ORM\Data\DataManager $entity */
$entity = Section::compileEntityByIblock($catalogPk);
$walker = $entity::getList([
'order' => ['DEPTH_LEVEL' => 'DESC'],
'filter' => [
'IBLOCK_ID' => $catalogPk,
'ACTIVE' => 'Y',
],
'select' => [
'ID',
'DEPTH_LEVEL',
'IBLOCK_SECTION_ID',
'UF_ELEMENT_COUNT',
'UF_ELEMENT_RECURSIVE_COUNT',
],
]);
$stack = [];
while ($sectionPayload = $walker->fetch()) {
$aggregate = SectionElementTable::getList([
'runtime' => [
new ExpressionField('TALLY', 'COUNT(*)'),
],
'filter' => [
'=IBLOCK_SECTION_ID' => $sectionPayload['ID'],
'IBLOCK_ELEMENT.ACTIVE' => 'Y',
'=IBLOCK_ELEMENT.IBLOCK_ID' => $catalogPk,
],
'select' => ['TALLY'],
]);
$directQty = 0;
if ($countRow = $aggregate->fetch()) {
$directQty = (int)$countRow['TALLY'];
}
$parentEdge = (int)$sectionPayload['IBLOCK_SECTION_ID'];
$selfEdge = (int)$sectionPayload['ID'];
foreach ([$parentEdge, $selfEdge] as $edgeId) {
if ($edgeId <= 0) {
continue;
}
if (!isset($stack[$edgeId])) {
$stack[$edgeId] = [
'directQty' => 0,
'subtreeQty' => 0,
'nestedChildren' => 0,
];
}
}
if ($parentEdge > 0) {
$stack[$parentEdge]['nestedChildren']++;
}
$stack[$selfEdge]['directQty'] = $directQty;
$stack[$selfEdge]['section'] = $sectionPayload;
}
foreach ($stack as $accumulatorPayload) {
if (!isset($accumulatorPayload['section'])) {
continue;
}
$trail = [];
$cursor = (int)$accumulatorPayload['section']['IBLOCK_SECTION_ID'];
$guard = 0;
while (
$cursor > 0
&& isset($stack[$cursor]['section'])
&& ++$guard <= 500
) {
$trail[] = $cursor;
$cursor = (int)$stack[$cursor]['section']['IBLOCK_SECTION_ID'];
}
$selfPk = (int)$accumulatorPayload['section']['ID'];
$qty = $accumulatorPayload['directQty'];
$stack[$selfPk]['subtreeQty'] += $qty;
foreach ($trail as $ancestorPk) {
$stack[$ancestorPk]['subtreeQty'] += $qty;
}
}
foreach ($stack as $slicePk => $accumulatorPayload) {
if (!isset($accumulatorPayload['section'])) {
continue;
}
$fields = [];
$storedDirect = (int)$accumulatorPayload['section']['UF_ELEMENT_COUNT'];
$storedSubtree = (int)$accumulatorPayload['section']['UF_ELEMENT_RECURSIVE_COUNT'];
if ($accumulatorPayload['directQty'] !== $storedDirect) {
$fields['UF_ELEMENT_COUNT'] = $accumulatorPayload['directQty'];
}
if ($accumulatorPayload['subtreeQty'] !== $storedSubtree) {
$fields['UF_ELEMENT_RECURSIVE_COUNT'] = $accumulatorPayload['subtreeQty'];
}
if (!$fields) {
continue;
}
$broker = new CIBlockSection();
$broker->Update((int)$slicePk, $fields);
}
return '\\'.__METHOD__.'();';
}
}Строка для записи агента: \Partner\IblockAssist\SectionLeafAndSubtreeCounters::runAgent();
Важные оговорки
- Убедитесь, что торговые и основные инфоблоки вашей схемы не смешиваются в одном
$catalogPk, если SKU вынесены отдельно. - Для огромных деревьев ночной cron предпочтительнее триггеров на каждом сохранении элемента — иначе пишущие транзакции будут упираться в пересчёт очередности.
- Если в админке привязки меняются часто, добавьте обновление счётчиков в обработчики событий инфоблока или смешайте агент с инкрементальной логикой.
Итог
SectionElementTable + ExpressionField дают короткий ORM-подсчёт по связи раздела с элементами. Агент с обходом снизу вверх и двумя UF-полями закрывает вывод счётчиков в меню и карточках разделов без повторных тяжёлых запросов на каждый хит.
Не хотите копаться сами?
Починю за 1-3 дня. Без предоплаты — оплата по результату.
15+ лет опыта с 1С-Битрикс · Без предоплаты · 7 дней гарантии