Блог

Сергей Сенаторов

Name: Sergey Senatorov

FullStack developer

Убираем подсчет элементов 1С-Битрикс в отдельную таблицу средствами MYSQL

В компоненте каталога 1С-битрикс есть параметр - COUNT_ELEMENTS, который подсчитывает количество элементов в таблице. Если у вас таблица содержит достаточно большое количество значений, выполнения запроса может занимать до нескольких секунд. Как один из вариантов решения можно отключить в компоненте данную опцию и реализовать ее посредствам создания отдельной таблицы, в которой будет храниться информация о количестве элементов в разделах. А обновлять таблицу можно на событиях Битрикс, обновлять скриптом по крону или через триггеры базы данных. Разберем последний. Чтобы произвести подсчет Битрикс делает запрос примерно такого вида:

SELECT DISTINCT BS.*, B.LIST_PAGE_URL, B.SECTION_PAGE_URL, B.IBLOCK_TYPE_ID, B.CODE as IBLOCK_CODE, B.XML_ID as IBLOCK_EXTERNAL_ID, BS.XML_ID as EXTERNAL_ID, DATE_FORMAT(BS.TIMESTAMP_X, '%d.%m.%Y %H:%i:%s') as TIMESTAMP_X, DATE_FORMAT(BS.DATE_CREATE, '%d.%m.%Y %H:%i:%s') as DATE_CREATE ,COUNT(DISTINCT BE.ID) as ELEMENT_CNT FROM b_iblock_section BS INNER JOIN b_iblock B ON BS.IBLOCK_ID = B.ID INNER JOIN b_iblock_section BSTEMP ON BSTEMP.IBLOCK_ID = BS.IBLOCK_ID LEFT JOIN b_iblock_section_element BSE ON BSE.IBLOCK_SECTION_ID=BSTEMP.ID LEFT JOIN b_iblock_element BE ON (BSE.IBLOCK_ELEMENT_ID=BE.ID AND ((BE.WF_STATUS_ID=1 AND BE.WF_PARENT_ELEMENT_ID IS NULL ) AND BE.IBLOCK_ID = BS.IBLOCK_ID ) AND BE.ACTIVE='Y' AND (BE.ACTIVE_TO >= now() OR BE.ACTIVE_TO IS NULL) AND (BE.ACTIVE_FROM <= now() OR BE.ACTIVE_FROM IS NULL)) WHERE 1=1 AND BSTEMP.IBLOCK_ID = BS.IBLOCK_ID AND BSTEMP.LEFT_MARGIN >= BS.LEFT_MARGIN AND BSTEMP.RIGHT_MARGIN <= BS.RIGHT_MARGIN AND BSTEMP.GLOBAL_ACTIVE = 'Y' AND ((((BS.ACTIVE='Y')))) AND ((((BS.GLOBAL_ACTIVE='Y')))) AND ((((BS.IBLOCK_ID = '1')))) GROUP BY BS.ID, B.ID

Здесь все прелести и DISTINCT и JOINы с разными условиями. Попробуем создать свою табличку для хранения данных о количестве элементов. В mysql это будет выглядеть примерно так:

CREATE TABLE `book_counter` (
  `IBLOCK_SECTION_ID` int(11) NOT NULL,
  `COUNT_ELEMENTS` int(11) NOT NULL
) 

Из запроса следует, что в Битриксе нас интересуют две таблицы:

  • b_iblock_section - таблица разделов,
  • b_iblock_element - таблица элементов.

Функцию COUNT в данном случае мы не будем использовать совсем. А будем брать из созданной таблички. Напишем триггеры на обновление, удаление и перемещение для таблички b_iblock_element примерно так:
#Добавление

CREATE TRIGGER `add_element` AFTER INSERT ON ` b_iblock_element ` 
FOR EACH ROW UPDATE book_counter 
SET COUNT_ELEMENTS=COUNT_ELEMENTS+1 WHERE IBLOCK_SECTION_ID=NEW.IBLOCK_SECTION_ID

#Удаление

CREATE TRIGGER `delete_element` AFTER DELETE ON ` b_iblock_element ` 
FOR EACH ROW 
UPDATE book_counter SET COUNT_ELEMENTS=COUNT_ELEMENTS-1 WHERE IBLOCK_SECTION_ID=OLD.IBLOCK_SECTION_ID

#Перемещение

CREATE TRIGGER `move_element` AFTER UPDATE ON ` b_iblock_element ` 
FOR EACH ROW IF (NEW.IBLOCK_SECTION_ID!=OLD.IBLOCK_SECTION_ID) THEN
UPDATE book_counter SET COUNT_ELEMENTS=COUNT_ELEMENTS+1 WHERE IBLOCK_SECTION_ID=NEW.IBLOCK_SECTION_ID;
UPDATE book_counter SET COUNT_ELEMENTS=COUNT_ELEMENTS-1 WHERE IBLOCK_SECTION_ID=OLD.IBLOCK_SECTION_ID;
END IF

И еще пару событий на b_iblock_section добавление/удаление разделов. Чтобы было куда писать значения новых разделов. Примерно так:

#Удаление

CREATE TRIGGER `delete_counter` AFTER DELETE ON `section` FOR EACH ROW DELETE FROM `book_counter` WHERE IBLOCK_SECTION_ID = OLD.ID

#Добавление

CREATE TRIGGER `new_section` AFTER INSERT ON `section` FOR EACH ROW INSERT INTO book_counter (`IBLOCK_SECTION_ID`, `COUNT_ELEMENTS`)
  VALUES (NEW.ID, 0)

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

Если нужно учитывать активность элементов можно, добавить еще один триггер на BEFORE UPDATE:

IF (NEW.ACTIVE!="Y") THEN 
UPDATE book_counter SET COUNT_ELEMENTS=COUNT_ELEMENTS-1 WHERE IBLOCK_SECTION_ID=NEW.IBLOCK_SECTION_ID;
ELSE
UPDATE book_counter SET COUNT_ELEMENTS=COUNT_ELEMENTS+1 WHERE IBLOCK_SECTION_ID=NEW.IBLOCK_SECTION_ID;
 END IF

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