1С-Битрикс. Постраничная навигация для Bitrix\Main\Entity\Query в ORM D7
Пока нет официальной поддержки в ядре я придумал 2 варианта реализации: с опцией SQL_CALC_FOUND_ROWS, с отдельным запросом.
Выбирать данные для примера мы будем конечно из таблицы b_iblock_element (\Bitrix\Iblock\ElementTable);
У меня есть инфоблок с id 24 с 4062 активными элементами. Над ним и будем проводить эксперименты, какой способ быстрее.
Вариант №1 с использованием опции SQL_CALC_FOUND_ROWS
Всё сводится к использованию опции SQL_CALC_FOUND_ROWS в секции SELECT запроса. Говорят, что опция довольно спорная и проседает в скорости, когда элементов много. Если есть подходящие индексы для WHERE и ORDER запроса, то возможно решение с двумя отдельными запросами, будет быстрее, чем один с SQL_CALC_FOUND_ROWS.
Поехали:
use Bitrix\Main; use Bitrix\Iblock; Main\Loader::includeModule('iblock'); $params = array( '=IBLOCK_ID' => 24, '=ACTIVE' => 'Y', ); // Подготовим параметры для постраничной навигации $navParams = array( 'NAV_NUM' => 1, 'PAGE_NUMBER' => 1, 'PAGE_ELEMENT_COUNT' => 30, ); $navParams['PAGE_NUMBER'] = isset($_GET['PAGEN_' . $navParams['NAV_NUM']]) && intval($_GET['PAGEN_' . $navParams['NAV_NUM']]) ? intval($_GET['PAGEN_' . $navParams['NAV_NUM']]) : 1; // Инициируем запрос $query = new Main\Entity\Query(Iblock\ElementTable::getEntity()); // Формируем запрос. Устанавливаем фильры, сортировку, лимит. // Через ExpressionField-то и добавляем опцию SQL_CALC_FOUND_ROWS (важно её первой ставить в select) $query ->setSelect(array( new Main\Entity\ExpressionField('FOUND_ROWS', 'SQL_CALC_FOUND_ROWS %s', 'ID'), 'ID', 'NAME', 'SORT', 'CODE', 'PREVIEW_PICTURE', 'DETAIL_PICTURE', 'IBLOCK_SECTION_ID', )) ->setFilter($params) ->setOrder(array('ID' => 'DESC')) ->setOffset(($navParams['PAGE_NUMBER'] - 1) * $navParams['PAGE_ELEMENT_COUNT']) ->setLimit($navParams['PAGE_ELEMENT_COUNT']);; $t = microtime(true); // Выполняем основной запрос $resultItems = $query->exec(); // Вторым запросом и получаем количество элементов в предыдущем запросе с установленной опцией SQL_CALC_FOUND_ROWS $total = 0; if ($resultTotal = Main\Application::getConnection()->queryScalar('SELECT FOUND_ROWS() as TOTAL')) { $total = $resultTotal; } $t = sprintf('<strong>Время выполнения 2 sql запросов: %.05f s.</strong>', microtime(true) - $t); $elements = array(); while($item = $resultItems->fetch()) { $elements[] = sprintf('%s [%d]', $item['NAME'], $item['ID']); } // Сформируем html для вывода постранички $navResult = ''; if ($total > 0) { $dbResult = new CDBResult(); $dbResult->NavPageCount = ceil($total / $navParams['PAGE_ELEMENT_COUNT']); $dbResult->NavPageNomer = $navParams['PAGE_NUMBER']; $dbResult->NavNum = $navParams['NAV_NUM']; $dbResult->NavPageSize = $navParams['PAGE_ELEMENT_COUNT']; $dbResult->NavRecordCount = $total; ob_start(); $APPLICATION->IncludeComponent('bitrix:system.pagenavigation', '', array( 'NAV_RESULT' => $dbResult, )); $navResult = @ob_get_clean(); } echo $t, '<br><br>', implode('<br>', $elements), '<br><br>', $navResult;
Время выполнения 2 sql запросов: 0.02865 s.
Вариант №2 с использованием отдельного запроса с помощью COUNT
Хочу отметить: данный способ поддерживает и группировку.
Перепишем код:
use Bitrix\Main; use Bitrix\Iblock; Main\Loader::includeModule('iblock'); $params = array( '=IBLOCK_ID' => 24, '=ACTIVE' => 'Y', ); // Подготовим параметры для постраничной навигации $navParams = array( 'NAV_NUM' => 1, 'PAGE_NUMBER' => 1, 'PAGE_ELEMENT_COUNT' => 30, ); $navParams['PAGE_NUMBER'] = isset($_GET['PAGEN_' . $navParams['NAV_NUM']]) && intval($_GET['PAGEN_' . $navParams['NAV_NUM']]) ? intval($_GET['PAGEN_' . $navParams['NAV_NUM']]) : 1; // Инициируем запрос $query = new Main\Entity\Query(Iblock\ElementTable::getEntity()); // Формируем запрос. Устанавливаем фильры, сортировку, $query ->setSelect(array( 'ID', 'NAME', 'SORT', 'CODE', 'PREVIEW_PICTURE', 'DETAIL_PICTURE', 'IBLOCK_SECTION_ID', )) ->setFilter($params) ->setOrder(array('ID' => 'DESC')) ; // Сохраним sql запрос, не выполняя его, он нам ещё пригодится $sql = $query->getQuery(); // Добавляем в запрос $query ->setOffset(($navParams['PAGE_NUMBER'] - 1) * $navParams['PAGE_ELEMENT_COUNT']) ->setLimit($navParams['PAGE_ELEMENT_COUNT']) ; $t = microtime(true); // Выполняем основной запрос $resultItems = $query->exec(); // Отдельным запросом получаем количество элементов $total = 0; if ($resultTotal = Main\Application::getConnection()->queryScalar(sprintf('SELECT count(*) FROM (%s) as TOTAL', $sql))) { $total = $resultTotal; } $t = sprintf('<strong>Время выполнения 2 sql запросов: %.05f s.</strong>', microtime(true) - $t); $elements = array(); while($item = $resultItems->fetch()) { $elements[] = sprintf('%s [%d]', $item['NAME'], $item['ID']); } // Сформируем html для вывода постранички $navResult = ''; if ($total > 0) { $dbResult = new CDBResult(); $dbResult->NavPageCount = ceil($total / $navParams['PAGE_ELEMENT_COUNT']); $dbResult->NavPageNomer = $navParams['PAGE_NUMBER']; $dbResult->NavNum = $navParams['NAV_NUM']; $dbResult->NavPageSize = $navParams['PAGE_ELEMENT_COUNT']; $dbResult->NavRecordCount = $total; ob_start(); $APPLICATION->IncludeComponent('bitrix:system.pagenavigation', '', array( 'NAV_RESULT' => $dbResult, )); $navResult = @ob_get_clean(); } echo $t, '<br><br>', implode('<br>', $elements), '<br><br>', $navResult;
Время выполнения 2 sql запросов: 0.06029 s.
Выводы
Как видите, в моём случае вариант с опцией SQL_CALC_FOUND_ROWS оказался в 2 раза быстрее. Поэтому выбирайте вариант в зависимости от вашей ситуации.
P.S. Для настройки постраничной навигации придётся поизучать свойства класса CDBResult (/bitrix/modules/main/classes/mysql/database_mysql.php и в официальной документации).
3 комментария
Теперь можно — https://dev.1c-bitrix.ru/learning/course/?COURSE_ID=43&LESSON_ID=2741
А ещё можно не выводить количество и избавиться от второго запроса (по ссылке раздел «Постраничная навигация без COUNT»)
За SQL_CALC_FOUND_ROWS спасибо! Гораздо быстрее работает чем штатный подсчет в D7...
добавляю в параметры "=IBLOCK_SECTION_ID" => $SID и работать перестаёт :(
почему нельзя фильтровать по секции?