Исторически метод модуля iblockCIBlockElement::GetList может работать с данными товара (при наличии модуля catalog). Это подробно описано в документации и активно используется как в публичных компонентах, так и на административных страницах и скриптах. Однако архитектурные особенности реализации, равно как и неразумное (чаще всего) использование этих возможностей, приводят к резкому падению производительности. [spoiler]
SELECT BE.ID as ID,BE.NAME as NAME,BE.IBLOCK_ID as IBLOCK_ID
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
WH ERE 1=1
AND (
((((BE.IBLOCK_ID = '2'))))
)
AND (((BE.WF_STATUS_ID=1 AND BE.WF_PARENT_ELEMENT_ID IS NULL)))
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
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_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):
SEL ECT 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 необходимо избегать категорически (особенно при наличии сортировки - неважно по каким полям). Эти данные нужно получать отдельными вызовами api. К слову сказать, в штатных компонентах это было сделано еще в версии 17.0. Остается фильтрация и сортировка по полям товара, ценам, складам. Обращение к полям товара по ключам CATALOG_, дает дополнительный join 3-х таблиц. Обращение к 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 все вышеописанное относилось, в том числе, к штатным компонентам и админским спискам модуля iblock (особенно в режиме совместного просмотра элементов и разделов, но об этом в конце). Связка сейчас находится на тестировании, после ее выхода в CIBlockElement::GetList появятся новые возможности работы с товарами.
Поля товара:
TYPE
тип товара
AVAILABLE
доступность
BUNDLE
наличие набора
QUANTITY
доступное количество
QUANTITY_RESERVED
зарезервированное количество
QUANTITY_TRACE
включен количественный учет (с учетом значения "по умолчанию" в настройках модуля) - Y/N
SEL ECT BE.ID as ID,BE.NAME as NAME,BE.IBLOCK_ID as IBLOCK_ID
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_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)))
т.е. в результирующем кортеже только то, что просили. Да и join только один.
Выборка размеров и веса доступных простых товаров:
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
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_product as PRD on (PRD.ID = BE.ID)
WH ERE 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)))
Поля цен (без привязки к конкретному типу цены):
PRICE_TYPE
ID типа цены
фильтр
PRICE
цена
фильтр
CURRENCY
валюта
фильтр
QUANTITY_TO
минимальное количество товара
фильтр
QUANTITY_FROM
максимальное количество товара
фильтр
EXTRA_ID
ID наценки
фильтр
SCALED_PRICE
цена в базовой валюте
фильтр
QUANTITY_RANGE_FILTER
количество товара, для которого искать цены
только фильтр, вспомогательное
CURRENCY_FOR_SCALE
фильтрация по цене с указанием валюты (т.е. когда необходимо отфильтровать цены в любых валютах с учетом курса)
только фильтр, вспомогательное
Поля цен (с указанием типа цены):
PRICE_ИДцены
цена
фильтр, сортировка, выборка
CURRENCY_ИДцены
валюта
фильтр, сортировка, выборка
QUANTITY_TO_ИДцены
минимальное количество товара
фильтр, сортировка, выборка
QUANTITY_FROM_ИДцены
максимальное количество товара
фильтр, сортировка, выборка
EXTRA_ID_ИДцены
ID наценки
фильтр, сортировка, выборка
SCALED_PRICE_ИДцены
цена в базовой валюте
фильтр, сортировка, выборка
QUANTITY_RANGE_FILTER_ИДцены
количество товара, для которого искать цены
только фильтр, вспомогательное
CURRENCY_FOR_SCALE_ИДцены
фильтрация по цене с указанием валюты (т.е. когда необходимо отфильтровать цены в любых валютах с учетом курса)
Сейчас обновления на тестировании, после их выхода настоятельно рекомендуется перевести свои компоненты и скрипты на новые ключи. Увеличение производительности прямо пропорционально числу товаров в каталоге. Так, при тестах на разделе из 1,5 тыс. товаров прирост скорости выполнения составил порядка 30%. Штатные компоненты (catalog.section, catalog.element, catalog.top), а так же все компоненты наследники \Bitrix\Iblock\Component\Base переведены на новые фильтры.
Что касается вышеупомянутого совместного режима просмотра элементов и разделов в админке... Ранее для всех больших инфоблоков мы рекомендовали его не использовать по причине большого расхода памяти. С выходом iblock 18.5.5 эта проблема решена - инфоблок с 100 тыс элементов спокойно выводится в админке в этом режиме (расход памяти снижен примерно в 20 раз).
Upd. Обновления доступны клиентам (как стабильные).
Хотя бы потому, что там нет возможности проверки прав доступа и прав бизнес-процессов. А так же многих фильтров, доступных в CIBlockElement::GetList - об этом чуть позже в 3-й части. Как только реализуем все требуемое - так и перейдем.
Просто от фильтрации по цене как в примерах из статьи толку нет т.к. цена по сути не актуальная. Нужна фильтрация по актуальной цене, т.е. с учетом примененных скидок и наценок. Это гораздо чаще используемый случай применения подобной фильтрации.
И какой будет в итоге запрос? Решение давно придумано - отдельное свойство с ценой и сортировать по нему, естественно значения свойства обновлять при изменении цены/скидок.
Сейчас есть давняя ошибка связанная с фильтрацией в каталоге (обращение было в январе 2018 и до сих пор не исправлено). При фильтрации по значению свойства торгового предложения не учитывается доступность к покупке (наличие на складах). На демо версии магазина это воспроизводится при фильтрации по размеру. Связка обновлений catalog 18.6.100 + iblock 18.6.200 исправит эту проблему ?
Добрый день! Скажите, пожалуйста, можно ли по ключу PRICE в $arSelect получить цену товара? Фильтрация по этому ключу работает, а получить значение цены у выбранных товаров не получается.
Подскажите пожалуйста как реализовать следующую задачу:
У товара есть свойство «признак (LABEL)» типа список с множественным выбором (Новинка, Топ, Акция). Необходимо создать скрипт, который можно повесить на cron (сделать периодической операцией), который:
1) Очищает у товаров признак «Топ»;
2) Выбирает топ 50 самых просматриваемых товаров, присваивает им признак «Топ».
Позитивная статья - очень хорошо что занялись инфоблоками. На особо крупных проектах приходится уходить от использования стандартного API даже для компонента catalog.section. Создавать ORM таблицу которая выполняет роль индекса в котором собираются используемые поля инфоблоков необходимые для запросов и те поля по которым будет происходить сортировка. Наполнять таблицу на кроне или событиях и делать повторение логики CIBlockElement::GetList в том числе поддержку умного фильтра, получать нужные ID с учетом постраничной навигации и скармливать уже полученные ID товаров компоненту catalog.section. А причина всего велосипеда - это то что стандартное API не вывозит каталоги 370+ тыс товаров. Время генерации страницы в разделе с 90+ тыс товаров более 6 секунд. На решении описанном в комментарии это 0,4-0,7 сек при сброшенном кеше.
Инфоблоками мы занимаемся все время, в т.ч. оптимизацией.
На решении описанном в комментарии это 0,4-0,7 сек при сброшенном кеше
Я правильно понимаю, что вы просто выкинули все поля, кроме ID, из запроса и получаете их для уже итоговой выборки? Если да - решение логичное, нопонятно только, зачем еще требуется своя таблица. Таким же способом в одном из последних обновлений был ускорен административный список элементов в совместном режиме.
Представим что все данные уже есть и они лежат в свойствах или полях зарезервированных системой.
В запросе нужно получить товары из раздела с ID 4, активные с ценой больше 5000, размером скидки (свойство) более 30% затем применить 3 сортировки. 1 отсортировать по наличию, товары которые есть в наличии идут первыми, вторыми идут товары под заказ и в конце которых нет в наличии. Вторая сортировка должна быть по наличию фото, и финальная сортировка по размеру скидки плюс должна быть возможность сортировать по цене, рейтингу (который рассчитывается особым алгоритмом пусть это будет тоже свойство).
Суть логики максимально упростил.
Теперь считаем сколько JOINов нужно сделать что бы получить данный список через runtime
Таблица b_iblock_element - поля ID, ACTIVE, DETAIL_PICTURE Таблица b_iblock_1_index - Из фасетного индекса поля SECTION_ID, ELEMENT_ID (при этом сущность нужно еще создать) Таблица b_catalog_price - CATALOG_GROUP_ID, PRICE, PRODUCT_ID Таблица b_catalog_product - ID CATALOG_AVAILABLE Таблица b_iblock_element_property - IBLOCK_PROPERTY_ID IBLOCK_ELEMENT_ID VALUE свойство для получения скидки. (для инфоблоков 1.0)
Не учитывая права доступа. Получаем минимум 4 JOIN к основной таблице на ровном месте. А ведь еще даже к умному фильтру не подобрались
Были попытки сделать через стандартные сущности D7 используя runtime. Все упиралось в фасетный индекс - фильтрация по таблице с 30 млн записей быстро не проходит.
Поле CATALOG_AVAILABLE не используется. А вот из таблицы b_catalog_price тянем данные классом \Bitrix\Catalog\PriceTable. Но проект пока сидит на версии с инфоблоками ниже 18.5.5. Посмотрю что там нового в обновлениях и потом примем решение что делать дальше)
По поводу цен хотелось бы спросить. Опишу кратко ситуацию. Есть товары, к ним подключается инфоблок услуги через свойство - список связанных элементов. В карточке товаров необходимо вывести содержимое связанных элементов. С этим проблем нет, в доках описано как это сделать, но список полей для вывода ограничен. Например необходимо вывести цену связанного элемента. К примеру, вывести поле "CATALOG_GROUP_1", возможно ли это сделать? Вариант "PROPERTY_SERVICE_PROD.CATALOG_GROUP_1" уже не работает, Ниже пример сортировки для наглядности :
Если речь о работе с товаром НЕ в карточке - я бы ориентировался на старое. Есть ряд нерешенных проблем. По срокам не могу сказать даже приблизительно.
При фильтрации по значению свойства торгового предложения не учитывается доступность к покупке (наличие на складах). На демо версии магазина это воспроизводится при фильтрации по размеру.
Связка обновлений catalog 18.6.100 + iblock 18.6.200 исправит эту проблему ?
У товара есть свойство «признак (LABEL)» типа список с множественным выбором (Новинка, Топ, Акция). Необходимо создать скрипт, который можно повесить на cron (сделать периодической операцией), который:
1) Очищает у товаров признак «Топ»;
2) Выбирает топ 50 самых просматриваемых товаров, присваивает им признак «Топ».
Представим что все данные уже есть и они лежат в свойствах или полях зарезервированных системой.
В запросе нужно получить товары из раздела с ID 4, активные с ценой больше 5000, размером скидки (свойство) более 30%
затем применить 3 сортировки. 1 отсортировать по наличию, товары которые есть в наличии идут первыми, вторыми идут товары под заказ и в конце
которых нет в наличии. Вторая сортировка должна быть по наличию фото, и финальная сортировка по размеру скидки плюс
должна быть возможность сортировать по цене, рейтингу (который рассчитывается особым алгоритмом пусть это будет тоже свойство).
Суть логики максимально упростил.
Теперь считаем сколько JOINов нужно сделать что бы получить данный список через runtime
Таблица b_iblock_element - поля ID, ACTIVE, DETAIL_PICTURE
Таблица b_iblock_1_index - Из фасетного индекса поля SECTION_ID, ELEMENT_ID (при этом сущность нужно еще создать)
Таблица b_catalog_price - CATALOG_GROUP_ID, PRICE, PRODUCT_ID
Таблица b_catalog_product - ID CATALOG_AVAILABLE
Таблица b_iblock_element_property - IBLOCK_PROPERTY_ID IBLOCK_ELEMENT_ID VALUE свойство для получения скидки. (для инфоблоков 1.0)
Не учитывая права доступа. Получаем минимум 4 JOIN к основной таблице на ровном месте. А ведь еще даже к умному фильтру не подобрались
Были попытки сделать через стандартные сущности D7 используя runtime. Все упиралось в фасетный индекс - фильтрация по
таблице с 30 млн записей быстро не проходит.
Таблица b_catalog_product - ID CATALOG_AVAILABLE
$arSelect = Array("ID", "IBLOCK_ID", "DATE_ACTIVE_FROM",
"PROPERTY_SERVICE_PROD.PREVIEW_TEXT",
"PROPERTY_SERVICE_PROD.NAME",
);
$arFilter = Array("IBLOCK_ID"=>2,
"ACTIVE_DATE"=>"Y",
"ACTIVE"=>"Y" ,
"ID" => $arResult["ID"]);
$res = CIBlockElement::GetList(
Array(),
$arFilter,
false,
Array("nPageSize"=>20),
$arSelect
);
Поиск товара/вариации по полям.
Создание товара/вариации
Чтение/изменение товара/вариации
Чтение/установка цен.
Почти уже все что нужно нашел в новом апи.