Дата последнего изменения: 08.11.2023
При работе с отношениями 1:N и N:M в общем случае и с множественными свойствами инфоблока в частности можно столкнуться с двумя проблемами.
Интуитивное ожидание логики работы LIMIT:
$iblockEntity = IblockTable::compileEntity(…); $query = $iblockEntity->getDataClass()::query() ->addSelect('NAME') ->addSelect('MULTI_PROP_1') ->setLimit(5); $elements = $query->fetchCollection();
Ожидать в данном примере выборку 5 элементов будет ошибкой. Лимит указывается не на уровне объектов, а на уровне SQL запроса:
SELECT ... FROM `b_iblock_element` LEFT JOIN `b_iblock_element_property` ... LIMIT 5
Фактически будет выбрано 5 значений свойств с соответствующими элементами. Поэтому в выборке может оказаться менее 5 элементов, или вовсе 1 элемент с не полностью выбранными значениями свойства.
Если выбирать несколько полей отношений в одном запросе, то результатом будет декартово произведение всех записей. Например:
$iblockEntity = IblockTable::compileEntity(…); $query = $iblockEntity->getDataClass()::query() ->addSelect('NAME') ->addSelect('MULTI_PROP_1') ->addSelect('MULTI_PROP_2') ->addSelect('MULTI_PROP_3'); $elements = $query->fetchCollection();
Выполнится запрос вида:
SELECT ... FROM `b_iblock_element` LEFT JOIN `b_iblock_element_property` ... // 15 значений свойства LEFT JOIN `b_iblock_element_property` ... // 7 значений свойства LEFT JOIN `b_iblock_element_property` ... // 11 значений свойства
И если интуитивно кажется, что будет выбрано 15 + 7 + 11 = 33 строки, то фактически будет выбрано 15 * 7 * 11 = 1155 строк. Если свойств или значений в запросе еще больше, то счет может идти на миллионы результирующих записей, и как следствие - о нехватке памяти в приложении для получения всего результата.
Для обхода этих проблем был добавлен класс Bitrix\Main\ORM\Query\QueryHelper с универсальным методом decompose:
/** ** Декомпозиция запросов с 1:N и N:M отношениями ** ** @param Query $query ** @param bool $fairLimit При установке этой опции сначала выбираются ID объектов, а следующим запросом остальные данные с фильтром по ID ** @param bool $separateRelations При установке этой опции каждое 1:N или N:M отношение выбирается отдельным запросом ** @return Collection **/ public static function decompose(Query $query, $fairLimit = true, $separateRelations = true)
Параметр fairLimit приводит к двум запросам: сначала выбирается primary записей с заданным Limit / Offset в запросе, и после этого для всех primary одним запросом выбираются все отношения.
Дополнительный параметр separateRelations позволяет выполнить отдельный запрос на каждое отношение, чтобы не возникало декартова произведения всех записей.
В качестве результата будет возвращена готовая коллекция объектов с уже объединенными данными.
Сортировка применяется при первичном выборе primary и в дальнейшем приводится в соответствии с этими primary. Что касается объектов отношений внутри объектов верхнего уровня, то там сортировка как правило не настолько важна, как при работе с массивами.