259  /  381
Справочник

Товары и CIBlockElement::GetList

Просмотров: 60072
Дата последнего изменения: 09.04.2021
Татьяна Старкова
Сложность урока:
3 уровень - средняя сложность. Необходимо внимание и немного подумать.
1
2
3
4
5
Недоступно в лицензиях:
Старт, Стандарт

CIBlockElement::GetList

Как вы знаете, метод CIBlockElement::GetList модуля Информационные блоки может работать с данными товара (при наличии модуля Торговый каталог). Это подробно описано в документации и активно используется как в публичных компонентах, так и на административных страницах и скриптах. Однако архитектурные особенности реализации, равно как и неправильное использование этих возможностей, приводят к резкому падению производительности.

Давайте сделаем разные вызовы CIBlockElement::GetList и посмотрим, какие запросы будут выполнены в итоге.

  1. Сначала сделаем простую выборку товаров из инфоблока с ID = 2:
    $iterator = \CIBlockElement::GetList(
       array(),
       array('IBLOCK_ID' => 2),
       false,
       false,
       array('ID', 'NAME', 'IBLOCK_ID')
    );
    

    Запрос, который пойдет в базу:

    SELECT  BE.ID as ID,BE.NAME as NAME,BE.IBLOCK_ID as IBLOCK_ID
    FROM b_iblock B
    INNER JOIN b_lang L ON B.LID=L.LID
    INNER JOIN b_iblock_element BE ON BE.IBLOCK_ID = B.ID
    WHERE 1=1 
       AND (
          ((((BE.IBLOCK_ID = '2'))))
       )
       AND (((BE.WF_STATUS_ID=1 AND BE.WF_PARENT_ELEMENT_ID IS NULL)))
    
  2. Теперь в выборку добавим фильтрацию по доступности товара:
    $iterator = \CIBlockElement::GetList(
       array(),
       array('IBLOCK_ID' => 2, 'CATALOG_AVAILABLE' => 'Y'),
       false,
       false,
       array('ID', 'NAME', 'IBLOCK_ID')
    );
    

    Это приводит к запросу:

    SELECT  BE.ID as ID,BE.NAME as NAME,BE.IBLOCK_ID as IBLOCK_ID ,CAT_PR.QUANTITY as CATALOG_QUANTITY,
    IF (CAT_PR.QUANTITY_TRACE = 'D', 'Y', CAT_PR.QUANTITY_TRACE) as CATALOG_QUANTITY_TRACE,
    CAT_PR.QUANTITY_TRACE as CATALOG_QUANTITY_TRACE_ORIG, CAT_PR.WEIGHT as CATALOG_WEIGHT,
    CAT_PR.VAT_ID as CATALOG_VAT_ID, CAT_PR.VAT_INCLUDED as CATALOG_VAT_INCLUDED,
    IF (CAT_PR.CAN_BUY_ZERO = 'D', 'N', CAT_PR.CAN_BUY_ZERO) as CATALOG_CAN_BUY_ZERO,
    CAT_PR.CAN_BUY_ZERO as CATALOG_CAN_BUY_ZERO_ORIG,
    CAT_PR.PURCHASING_PRICE as CATALOG_PURCHASING_PRICE, CAT_PR.PURCHASING_CURRENCY as CATALOG_PURCHASING_CURRENCY,
    CAT_PR.QUANTITY_RESERVED as CATALOG_QUANTITY_RESERVED,
    IF (CAT_PR.SUBSCRIBE = 'D', 'Y', CAT_PR.SUBSCRIBE) as CATALOG_SUBSCRIBE, CAT_PR.SUBSCRIBE as CATALOG_SUBSCRIBE_ORIG,
    CAT_PR.WIDTH as CATALOG_WIDTH, CAT_PR.LENGTH as CATALOG_LENGTH, CAT_PR.HEIGHT as CATALOG_HEIGHT,
    CAT_PR.MEASURE as CATALOG_MEASURE, CAT_PR.TYPE as CATALOG_TYPE, CAT_PR.AVAILABLE as CATALOG_AVAILABLE,
    CAT_PR.BUNDLE as CATALOG_BUNDLE, CAT_PR.PRICE_TYPE as CATALOG_PRICE_TYPE,
    CAT_PR.RECUR_SCHEME_LENGTH as CATALOG_RECUR_SCHEME_LENGTH, CAT_PR.RECUR_SCHEME_TYPE as CATALOG_RECUR_SCHEME_TYPE,
    CAT_PR.TRIAL_PRICE_ID as CATALOG_TRIAL_PRICE_ID, CAT_PR.WITHOUT_ORDER as CATALOG_WITHOUT_ORDER,
    CAT_PR.SELECT_BEST_PRICE as CATALOG_SELECT_BEST_PRICE,
    IF (CAT_PR.NEGATIVE_AMOUNT_TRACE = 'D', 'N', CAT_PR.NEGATIVE_AMOUNT_TRACE) as CATALOG_NEGATIVE_AMOUNT_TRACE,
    CAT_PR.NEGATIVE_AMOUNT_TRACE as CATALOG_NEGATIVE_AMOUNT_TRACE_ORIG, CAT_VAT.RATE as CATALOG_VAT
    
       FROM b_iblock B
       INNER JOIN b_lang L ON B.LID=L.LID
       INNER JOIN b_iblock_element BE ON BE.IBLOCK_ID = B.ID
    
       left join b_catalog_product as CAT_PR on (CAT_PR.ID = BE.ID)
       left join b_catalog_iblock as CAT_IB on ((CAT_PR.VAT_ID IS NULL or CAT_PR.VAT_ID = 0) and CAT_IB.IBLOCK_ID = BE.IBLOCK_ID)
       left join b_catalog_vat as CAT_VAT on
             (CAT_VAT.ID = IF((CAT_PR.VAT_ID IS NULL OR CAT_PR.VAT_ID = 0), CAT_IB.VAT_ID, CAT_PR.VAT_ID))
    
       WHERE 1=1 
       AND (
          ((((BE.IBLOCK_ID = '2'))))
          AND ((((CAT_PR.AVAILABLE='Y'))))
       )
       AND (((BE.WF_STATUS_ID=1 AND BE.WF_PARENT_ELEMENT_ID IS NULL)))
    

    Таким образом, любое обращение к данным товара (фильтрация, сортировка, выборка одного из полей) приводит к join трех таблиц и выборке всех полей товара.
  3. Уберем фильтрацию, но будем выбирать цену одного из типов (ID типа цены - 1):
    $iterator = \CIBlockElement::GetList(
       array(),
       array('IBLOCK_ID' => 2),
       false,
       false,
       array('ID', 'NAME', 'IBLOCK_ID', 'CATALOG_CATALOG_GROUP_ID_1')
    );
    

    Видим, что стало еще хуже:

    SELECT BE.ID as ID,BE.NAME as NAME,BE.IBLOCK_ID as IBLOCK_ID,
    CAT_P1.CATALOG_GROUP_ID as CATALOG_GROUP_ID_1, CAT_P1.ID as CATALOG_PRICE_ID_1,
    CAT_P1.PRICE as CATALOG_PRICE_1, CAT_P1.CURRENCY as CATALOG_CURRENCY_1,
    CAT_P1.QUANTITY_FROM as CATALOG_QUANTITY_FROM_1, CAT_P1.QUANTITY_TO as CATALOG_QUANTITY_TO_1,
    CAT_P1.EXTRA_ID as CATALOG_EXTRA_ID_1,
    'Базовая цена' as CATALOG_GROUP_NAME_1, 'Y' as CATALOG_CAN_ACCESS_1, 'Y' as CATALOG_CAN_BUY_1,
    CAT_PR.QUANTITY as CATALOG_QUANTITY, IF (CAT_PR.QUANTITY_TRACE = 'D', 'Y', CAT_PR.QUANTITY_TRACE) as CATALOG_QUANTITY_TRACE,
    CAT_PR.QUANTITY_TRACE as CATALOG_QUANTITY_TRACE_ORIG, CAT_PR.WEIGHT as CATALOG_WEIGHT,
    CAT_PR.VAT_ID as CATALOG_VAT_ID, CAT_PR.VAT_INCLUDED as CATALOG_VAT_INCLUDED,
    IF (CAT_PR.CAN_BUY_ZERO = 'D', 'N', CAT_PR.CAN_BUY_ZERO) as CATALOG_CAN_BUY_ZERO,
    CAT_PR.CAN_BUY_ZERO as CATALOG_CAN_BUY_ZERO_ORIG, CAT_PR.PURCHASING_PRICE as CATALOG_PURCHASING_PRICE,
    CAT_PR.PURCHASING_CURRENCY as CATALOG_PURCHASING_CURRENCY, CAT_PR.QUANTITY_RESERVED as CATALOG_QUANTITY_RESERVED,
    IF (CAT_PR.SUBSCRIBE = 'D', 'Y', CAT_PR.SUBSCRIBE) as CATALOG_SUBSCRIBE, CAT_PR.SUBSCRIBE as CATALOG_SUBSCRIBE_ORIG,
    CAT_PR.WIDTH as CATALOG_WIDTH, CAT_PR.LENGTH as CATALOG_LENGTH, CAT_PR.HEIGHT as CATALOG_HEIGHT,
    CAT_PR.MEASURE as CATALOG_MEASURE, CAT_PR.TYPE as CATALOG_TYPE, CAT_PR.AVAILABLE as CATALOG_AVAILABLE,
    CAT_PR.BUNDLE as CATALOG_BUNDLE, CAT_PR.PRICE_TYPE as CATALOG_PRICE_TYPE,
    CAT_PR.RECUR_SCHEME_LENGTH as CATALOG_RECUR_SCHEME_LENGTH, CAT_PR.RECUR_SCHEME_TYPE as CATALOG_RECUR_SCHEME_TYPE,
    CAT_PR.TRIAL_PRICE_ID as CATALOG_TRIAL_PRICE_ID, CAT_PR.WITHOUT_ORDER as CATALOG_WITHOUT_ORDER,
    CAT_PR.SELECT_BEST_PRICE as CATALOG_SELECT_BEST_PRICE,
    IF (CAT_PR.NEGATIVE_AMOUNT_TRACE = 'D', 'N', CAT_PR.NEGATIVE_AMOUNT_TRACE) as CATALOG_NEGATIVE_AMOUNT_TRACE,
    CAT_PR.NEGATIVE_AMOUNT_TRACE as CATALOG_NEGATIVE_AMOUNT_TRACE_ORIG, CAT_VAT.RATE as CATALOG_VAT
    FR OM b_iblock B
    INNER JOIN b_lang L ON B.LID=L.LID
    INNER JOIN b_iblock_element BE ON BE.IBLOCK_ID = B.ID
    left join b_catalog_price as CAT_P1 on (CAT_P1.PRODUCT_ID = BE.ID and CAT_P1.CATALOG_GROUP_ID = 1)
    left join b_catalog_product as CAT_PR on (CAT_PR.ID = BE.ID)
    left join b_catalog_iblock as CAT_IB on ((CAT_PR.VAT_ID IS NULL or CAT_PR.VAT_ID = 0) and CAT_IB.IBLOCK_ID = BE.IBLOCK_ID)
    left join b_catalog_vat as CAT_VAT on (CAT_VAT.ID = IF((CAT_PR.VAT_ID IS NULL OR CAT_PR.VAT_ID = 0), CAT_IB.VAT_ID, CAT_PR.VAT_ID))
    WH ERE 1=1
       AND (
          ((((BE.IBLOCK_ID = '2'))))
       )
       AND (((BE.WF_STATUS_ID=1 AND BE.WF_PARENT_ELEMENT_ID IS NULL)))
    

    Если мы попробуем выбрать еще и данные по складам, то ситуация будет аналогичной.

Подведем промежуточные итоги:

  • Выборки цен, количества на складах в CIBlockElement::GetList необходимо избегать категорически (особенно при наличии сортировки и неважно по каким полям). Эти данные нужно получать отдельными вызовами АПИ. К слову сказать, в штатных компонентах это было сделано еще в версии 17.0.
  • Остается фильтрация и сортировка по полям товара, ценам, складам. Обращение к полям товара по ключам CATALOG_ дает дополнительный join трех таблиц. Обращение к N типам цен или складов - join N+3 таблиц. Мало того, что время запроса увеличивается, так еще можно получить ошибку MySql "Too many tables; MySQL can only use 61 tables in a join".

До выпуска связки обновлений catalog 18.6.100 + iblock 18.6.200 все вышеописанное относится, в том числе, к штатным компонентам и административным спискам модуля Информационные блоки (особенно в режиме совместного просмотра элементов и разделов, смотрите примечание ниже). После выхода данных обновлений в CIBlockElement::GetList доступны новые возможности работы с товарами.

Примечание: ранее для всех больших инфоблоков мы рекомендовали не использовать режим совместного просмотра по причине большого расхода памяти. С выходом версии 18.5.5 модуля Информационные блоки эта проблема решена - инфоблок со 100 тысячами элементов спокойно выводится в административном разделе в этом режиме (расход памяти снижен примерно в 20 раз).

  С версий catalog 18.6.100 + iblock 18.6.200

С версии iblock 18.6.200 изменяются ключи метода. По всем ключам возможна фильтрация, сортировка, выборка.

Поля товара


С версии catalog 20.0.200 добавились поля товара

Теперь вызов метода с фильтрацией по доступности выглядит так:

$iterator = \CIBlockElement::GetList(
   array(),
   array('IBLOCK_ID' => 2, '=AVAILABLE' => 'Y'),
   false,
   false,
   array('ID', 'NAME', 'IBLOCK_ID')
);

а в запросе только то, что просили, и join только один:

   SELECT  BE.ID as ID,BE.NAME as NAME,BE.IBLOCK_ID as IBLOCK_ID

   FROM b_iblock B
   INNER JOIN b_lang L ON B.LID=L.LID
   INNER JOIN b_iblock_element BE ON BE.IBLOCK_ID = B.ID
   left join b_catalog_product as PRD on (PRD.ID = BE.ID)

   WHERE 1=1 
   AND (
      ((((BE.IBLOCK_ID = '2'))))
      AND ((((PRD.AVAILABLE='Y'))))
   )
   AND (((BE.WF_STATUS_ID=1 AND BE.WF_PARENT_ELEMENT_ID IS NULL)))

Сделаем выборку размеров и веса доступных простых товаров:

$iterator = \CIBlockElement::GetList(
   array(),
   array('IBLOCK_ID' => 2, '=AVAILABLE' => 'Y', '=TYPE' => 1),
   false,
   false,
   array('ID', 'NAME', 'IBLOCK_ID', 'WEIGHT', 'WIDTH', 'HEIGHT', 'LENGTH')
);

Запрос получается следующим:

   SELECT  BE.ID as ID,BE.NAME as NAME,BE.IBLOCK_ID as IBLOCK_ID ,
   PRD.WEIGHT as WEIGHT, PRD.WIDTH as WIDTH, PRD.HEIGHT as HEIGHT, PRD.LENGTH as LENGTH

   FROM b_iblock B
   INNER JOIN b_lang L ON B.LID=L.LID
   INNER JOIN b_iblock_element BE ON BE.IBLOCK_ID = B.ID
   left join b_catalog_product as PRD on (PRD.ID = BE.ID)

   WHERE 1=1 
   AND (
      ((((BE.IBLOCK_ID = '2'))))
      AND ((((PRD.AVAILABLE='Y'))))
      AND ((((PRD.TYPE = '1'))))
   )
   AND (((BE.WF_STATUS_ID=1 AND BE.WF_PARENT_ELEMENT_ID IS NULL)))
Поля цен (без привязки к конкретному типу цены)


Поля цен (с указанием типа цены)


Поля складов

  Примеры выборок

Фильтрация по цене любого типа:

$iterator = \CIBlockElement::GetList(
   array(),
   array('IBLOCK_ID' => 2, '=AVAILABLE' => 'Y', '=TYPE' => 1, '>PRICE' => 500),
   false,
   false,
   array('ID', 'NAME', 'IBLOCK_ID', 'WEIGHT', 'WIDTH', 'HEIGHT', 'LENGTH')
);

Фильтрация для типа цены с кодом 1 (обычно это базовая цена):

$iterator = \CIBlockElement::GetList(
   array(),
   array('IBLOCK_ID' => 2, '=AVAILABLE' => 'Y', '=TYPE' => 1, '>PRICE' => 500, '=PRICE_TYPE' => 1),
   false,
   false,
   array('ID', 'NAME', 'IBLOCK_ID', 'WEIGHT', 'WIDTH', 'HEIGHT', 'LENGTH')
);

Или в таком варианте:

$iterator = \CIBlockElement::GetList(
   array(),
   array('IBLOCK_ID' => 2, '=AVAILABLE' => 'Y', '=TYPE' => 1, '>PRICE_1' => 500),
   false,
   false,
   array('ID', 'NAME', 'IBLOCK_ID', 'WEIGHT', 'WIDTH', 'HEIGHT', 'LENGTH')
);

Теперь можно делать фильтры, ранее недоступные. Выбрать все товары, имеющие цены любого типа от 500 до 1000:

$iterator = \CIBlockElement::GetList(
   array(),
   array('IBLOCK_ID' => 2, '=AVAILABLE' => 'Y', '=TYPE' => 1, '>=PRICE' => 500, '<=PRICE' => 1000),
   false,
   false,
   array('ID', 'NAME', 'IBLOCK_ID')
);

Или только цены типов с кодом 1,4,5:

$iterator = \CIBlockElement::GetList(
   array(),
   array('IBLOCK_ID' => 2, '=AVAILABLE' => 'Y', '=TYPE' => 1, '>=PRICE' => 500, '<=PRICE' => 1000, '@PRICE_TYPE' => [1,4,5]),
   false,
   false,
   array('ID', 'NAME', 'IBLOCK_ID')
);

Выбрать все товары с ценами в любой валюте, эквивалентными диапазону от 100 до 200 USD:

$iterator = \CIBlockElement::GetList(
   array(),
   array('IBLOCK_ID' => 2, '=AVAILABLE' => 'Y', '=TYPE' => 1, '>=PRICE' => 100, '<=PRICE' => 200, 'CURRENCY_FOR_SCALE' => 'USD'),
   false,
   false,
   array('ID', 'NAME', 'IBLOCK_ID')
);

Выбрать товары, которых на любом складе не больше 3:

$iterator = \CIBlockElement::GetList(
   array(),
   array('IBLOCK_ID' => 2, '<=STORE_AMOUNT' => 3),
   false,
   false,
   array('ID', 'NAME', 'IBLOCK_ID')
);

Выбрать товары, которых на 17-м складе от 5 до 7:

$iterator = \CIBlockElement::GetList(
   array(),
   array('IBLOCK_ID' => 2, '<=STORE_AMOUNT' => 7, '>=STORE_AMOUNT' => 5, 'STORE_NUMBER' => 17),
   false,
   false,
   array('ID', 'NAME', 'IBLOCK_ID')
);

Либо в таком виде:

$iterator = \CIBlockElement::GetList(
   array(),
   array('IBLOCK_ID' => 2, '<=STORE_AMOUNT_17' => 7, '>=STORE_AMOUNT_17' => 5),
   false,
   false,
   array('ID', 'NAME', 'IBLOCK_ID')
);

Фильтрация товаров по наличию на определенных складах:

if(!empty($arParams['STORES'])){ 
    $GLOBALS[$arParams['FILTER_NAME']]['@STORE_NUMBER'] => $arParams['STORES'];
   $GLOBALS[$arParams['FILTER_NAME']]['>STORE_AMOUNT'] = 0;
}

Другой пример фильтрации товаров по наличию на определенных складах:

if(!empty($arParams['STORES'])){

$storesFilter = [
                     'LOGIC'=>'OR'
            ];
            foreach ($arParams['STORES'] as $store_id){
                $storesFilter[] = ['STORE_NUMBER' => intval($store_id),'>STORE_AMOUNT'=>0];
            }

  $GLOBALS[$arParams['FILTER_NAME']][] = $storesFilter;
        }

Заключение

После установки обновлений catalog 18.6.100 + iblock 18.6.200 настоятельно рекомендуется перевести свои компоненты и скрипты на новые ключи. Увеличение производительности прямо пропорционально числу товаров в каталоге. Так, при тестах на разделе из 1,5 тысяч товаров прирост скорости выполнения составил порядка 30%. Штатные компоненты ( catalog.section Компонент выводит список элементов раздела с указанным набором свойств.

Описание компонента «Элементы раздела» в пользовательской документации.
, catalog.element Компонент выводит детальную информацию по элементу каталога.

Описание компонента «Элемент каталога детально» в пользовательской документации.
, catalog.top Компонент выводит в таблице top элементов из всех разделов в соответствии с заданной сортировкой (используется как правило на главной странице сайта).

Описание компонента «top элементов каталога» в пользовательской документации.
), а также все компоненты наследники \Bitrix\Iblock\Component\Base переведены на новые фильтры.



Курсы разработаны в компании «1С-Битрикс»

Если вы нашли неточность в тексте, непонятное объяснение, пожалуйста, сообщите нам об этом в комментариях.
Развернуть комментарии